Coverage Report

Created: 2026-06-16 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/sequences/track_visual.cc
Line
Count
Source
1
/*
2
 * HEIF image base codec.
3
 * Copyright (c) 2025 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
21
#include "track_visual.h"
22
23
#include <memory>
24
#include <utility>
25
#include "codecs/decoder.h"
26
#include "codecs/encoder.h"
27
#include "chunk.h"
28
#include "image/pixelimage.h"
29
#include "context.h"
30
#include "api_structs.h"
31
#include "codecs/hevc_boxes.h"
32
#include "codecs/uncompressed/unc_boxes.h"
33
34
35
Track_Visual::Track_Visual(HeifContext* ctx)
36
331
  : Track(ctx)
37
331
{
38
331
}
39
40
41
Track_Visual::~Track_Visual()
42
331
{
43
331
  for (auto& user_data : m_frame_user_data) {
44
0
    user_data.second.release();
45
0
  }
46
331
}
47
48
49
Error Track_Visual::load(const std::shared_ptr<Box_trak>& trak)
50
331
{
51
331
  Error parentLoadError = Track::load(trak);
52
331
  if (parentLoadError) {
53
273
    return parentLoadError;
54
273
  }
55
56
58
  const std::vector<uint32_t>& chunk_offsets = m_stco->get_offsets();
57
58
  // Find sequence resolution
59
60
58
  if (!chunk_offsets.empty()) {
61
58
    auto* s2c = m_stsc->get_chunk(1);
62
58
    if (!s2c) {
63
0
      return {
64
0
        heif_error_Invalid_input,
65
0
        heif_suberror_Unspecified,
66
0
        "Visual track has no chunk 1"
67
0
      };
68
0
    }
69
70
58
    Box_stsc::SampleToChunk sampleToChunk = *s2c;
71
72
58
    auto sample_description = m_stsd->get_sample_entry(sampleToChunk.sample_description_index - 1);
73
58
    if (!sample_description) {
74
0
      return {
75
0
        heif_error_Invalid_input,
76
0
        heif_suberror_Unspecified,
77
0
        "Visual track has sample description"
78
0
      };
79
0
    }
80
81
58
    auto visual_sample_description = std::dynamic_pointer_cast<const Box_VisualSampleEntry>(sample_description);
82
58
    if (!visual_sample_description) {
83
10
      return {
84
10
        heif_error_Invalid_input,
85
10
        heif_suberror_Unspecified,
86
10
        "Visual track sample description does not match visual track."
87
10
      };
88
10
    }
89
90
48
    m_width = visual_sample_description->get_VisualSampleEntry_const().width;
91
48
    m_height = visual_sample_description->get_VisualSampleEntry_const().height;
92
48
  }
93
94
48
  return {};
95
58
}
96
97
98
Error Track_Visual::initialize_after_parsing(HeifContext* ctx, const std::vector<std::shared_ptr<Track> >& all_tracks)
99
46
{
100
  // --- check whether there is an auxiliary alpha track assigned to this track
101
102
  // Only assign to image-sequence tracks (TODO: are there also alpha tracks allowed for video tracks 'heif_track_type_video'?)
103
104
46
  if (get_handler() == heif_track_type_image_sequence) {
105
1
    for (auto track : all_tracks) {
106
      // skip ourselves
107
1
      if (track->get_id() != get_id()) {
108
        // Is this an aux alpha track?
109
0
        auto h = fourcc_to_string(track->get_handler());
110
0
        if (track->get_handler() == heif_track_type_auxiliary &&
111
0
            track->get_auxiliary_info_type() == heif_auxiliary_track_info_type_alpha) {
112
          // Is it assigned to the current track
113
0
          auto tref = track->get_tref_box();
114
0
          if (!tref) {
115
0
            return {
116
0
              heif_error_Invalid_input,
117
0
              heif_suberror_Unspecified,
118
0
              "Auxiliary track without 'tref'"
119
0
            };
120
0
          }
121
122
0
          auto references = tref->get_references(fourcc("auxl"));
123
0
          if (std::any_of(references.begin(), references.end(), [this](uint32_t id) { return id == get_id(); })) {
124
            // Assign it
125
126
0
            m_aux_alpha_track = std::dynamic_pointer_cast<Track_Visual>(track);
127
0
          }
128
0
        }
129
0
      }
130
1
    }
131
1
  }
132
133
46
  return {};
134
46
}
135
136
137
Track_Visual::Track_Visual(HeifContext* ctx, uint32_t track_id, uint16_t width, uint16_t height,
138
                           const TrackOptions* options, uint32_t handler_type)
139
0
  : Track(ctx, track_id, options, handler_type)
140
0
{
141
0
  m_tkhd->set_resolution(width, height);
142
  //m_hdlr->set_handler_type(handler_type);  already done in Track()
143
144
0
  auto vmhd = std::make_shared<Box_vmhd>();
145
0
  m_minf->append_child_box(vmhd);
146
0
}
147
148
149
bool Track_Visual::has_alpha_channel() const
150
0
{
151
0
  if (m_aux_alpha_track != nullptr) {
152
0
    return true;
153
0
  }
154
155
  // --- special case: 'uncv' with alpha component
156
0
#if WITH_UNCOMPRESSED_CODEC
157
0
  if (m_stsd) {
158
0
    auto sampleEntry = m_stsd->get_sample_entry(0);
159
0
    if (sampleEntry) {
160
0
      if (auto box_uncv = std::dynamic_pointer_cast<const Box_uncv>(sampleEntry)) {
161
0
        if (auto cmpd = box_uncv->get_child_box<const Box_cmpd>()) {
162
0
          if (cmpd->has_component(heif_cmpd_component_type_alpha)) {
163
0
            return true;
164
0
          }
165
0
        }
166
0
      }
167
0
    }
168
0
  }
169
0
#endif
170
171
0
  return false;
172
0
}
173
174
175
Result<std::shared_ptr<HeifPixelImage> > Track_Visual::decode_next_image_sample(const heif_decoding_options& options)
176
0
{
177
  // --- If we ignore the editlist, we stop when we reached the end of the original samples.
178
179
0
  uint64_t num_output_samples = m_num_output_samples;
180
0
  if (options.ignore_sequence_editlist) {
181
0
    num_output_samples = m_num_samples;
182
0
  }
183
184
  // --- Did we reach the end of the sequence?
185
186
0
  if (m_next_sample_to_be_output >= num_output_samples) {
187
0
    return Error{
188
0
      heif_error_End_of_sequence,
189
0
      heif_suberror_Unspecified,
190
0
      "End of sequence"
191
0
    };
192
0
  }
193
194
195
0
  std::shared_ptr<HeifPixelImage> image;
196
197
0
  uint32_t sample_idx_in_chunk;
198
0
  uintptr_t decoded_sample_idx = 0; // TODO: map this to sample idx + chunk
199
200
0
  for (;;) {
201
0
    const SampleTiming& sampleTiming = m_presentation_timeline[m_next_sample_to_be_decoded % m_presentation_timeline.size()];
202
0
    sample_idx_in_chunk = sampleTiming.sampleIdx;
203
0
    uint32_t chunk_idx = sampleTiming.chunkIdx;
204
205
0
    const std::shared_ptr<Chunk>& chunk = m_chunks[chunk_idx];
206
207
0
    auto decoder = chunk->get_decoder();
208
0
    assert(decoder);
209
210
    // avoid calling get_decoded_frame() before starting the decoder.
211
0
    if (m_next_sample_to_be_decoded != 0) {
212
0
      Result<std::shared_ptr<HeifPixelImage> > getFrameResult = decoder->get_decoded_frame(options,
213
0
                                                                                           &decoded_sample_idx,
214
0
                                                                                           m_heif_context->get_security_limits());
215
0
      if (getFrameResult.error()) {
216
0
        return getFrameResult.error();
217
0
      }
218
219
220
      // We received a decoded frame. Exit "push data" / "decode" loop.
221
222
0
      if (*getFrameResult != nullptr) {
223
0
        image = *getFrameResult;
224
225
        // If this was the last frame in the EditList segment, reset the 'flushed' flag
226
        // in case we have to restart the decoder for another repetition of the segment.
227
0
        if ((m_next_sample_to_be_output + 1) % m_presentation_timeline.size() == 0) {
228
0
          m_decoder_is_flushed = false;
229
0
        }
230
231
0
        break;
232
0
      }
233
234
      // If the sequence has ended and the decoder was flushed, but we still did not receive
235
      // the image, we are waiting for, this is an error.
236
0
      if (m_decoder_is_flushed) {
237
0
        return Error(heif_error_Decoder_plugin_error,
238
0
                     heif_suberror_Unspecified,
239
0
                     "Did not decode all frames");
240
0
      }
241
0
    }
242
243
244
    // --- Push more data into the decoder (or send end-of-sequence).
245
246
0
    if (m_next_sample_to_be_decoded < m_num_output_samples) {
247
248
      // --- Find the data extent that stores the compressed frame data.
249
250
0
      DataExtent extent = chunk->get_data_extent_for_sample(sample_idx_in_chunk);
251
0
      decoder->set_data_extent(std::move(extent));
252
253
      // std::cout << "PUSH chunk " << chunk_idx << " sample " << sample_idx << " (" << extent.m_size << " bytes)\n";
254
255
      // advance decoding index to next in segment
256
0
      m_next_sample_to_be_decoded++;
257
258
      // Send the decoder configuration when we send the first sample of the chunk.
259
      // The configuration NALs might change for each chunk.
260
0
      const bool is_first_sample = (sample_idx_in_chunk == 0);
261
262
      // --- Push data into the decoder.
263
0
      Error decodingError = decoder->decode_sequence_frame_from_compressed_data(is_first_sample,
264
0
                                                                                options,
265
0
                                                                                sample_idx_in_chunk, // user data
266
0
                                                                                m_heif_context->get_security_limits());
267
0
      if (decodingError) {
268
0
        return decodingError;
269
0
      }
270
0
    }
271
0
    else {
272
      // --- End of sequence (Editlist segment) reached.
273
274
      // std::cout << "FLUSH\n";
275
0
      Error flushError = decoder->flush_decoder();
276
0
      if (flushError) {
277
0
        return flushError;
278
0
      }
279
280
0
      m_decoder_is_flushed = true;
281
0
    }
282
0
  }
283
284
285
  // --- We have received a new decoded image.
286
  //     Postprocess decoded image, attach metadata.
287
288
0
  if (m_stts) {
289
0
    image->set_sample_duration(m_stts->get_sample_duration(sample_idx_in_chunk));
290
0
  }
291
292
  // --- assign alpha if we have an assigned alpha track
293
294
0
  if (m_aux_alpha_track) {
295
0
    auto alphaResult = m_aux_alpha_track->decode_next_image_sample(options);
296
0
    if (!alphaResult) {
297
0
      return alphaResult.error();
298
0
    }
299
300
0
    auto alphaImage = *alphaResult;
301
0
    image->transfer_channel_from_image_as(alphaImage, heif_channel_Y, heif_channel_Alpha);
302
0
  }
303
304
305
  // --- read sample auxiliary data
306
307
0
  if (m_aux_reader_content_ids) {
308
0
    auto readResult = m_aux_reader_content_ids->get_sample_info(get_file().get(), (uint32_t)decoded_sample_idx);
309
0
    if (!readResult) {
310
0
      return readResult.error();
311
0
    }
312
313
0
    Result<std::string> convResult = vector_to_string(*readResult);
314
0
    if (!convResult) {
315
0
      return convResult.error();
316
0
    }
317
318
0
    image->set_gimi_sample_content_id(*convResult);
319
0
  }
320
321
0
  if (m_aux_reader_tai_timestamps) {
322
0
    auto readResult = m_aux_reader_tai_timestamps->get_sample_info(get_file().get(), (uint32_t)decoded_sample_idx);
323
0
    if (!readResult) {
324
0
      return readResult.error();
325
0
    }
326
327
0
    std::vector<uint8_t>& tai_data = *readResult;
328
0
    if (!tai_data.empty()) {
329
0
      auto resultTai = Box_itai::decode_tai_from_vector(tai_data);
330
0
      if (!resultTai) {
331
0
        return resultTai.error();
332
0
      }
333
334
0
      image->set_tai_timestamp(&*resultTai);
335
0
    }
336
0
  }
337
338
0
  m_next_sample_to_be_output++;
339
340
0
  return image;
341
0
}
342
343
344
Error Track_Visual::encode_end_of_sequence(heif_encoder* h_encoder)
345
0
{
346
0
  auto encoder = m_chunks.back()->get_encoder();
347
348
0
  for (;;) {
349
0
    Error err = encoder->encode_sequence_flush(h_encoder);
350
0
    if (err) {
351
0
      return err;
352
0
    }
353
354
0
    Result<bool> processingResult = process_encoded_data(h_encoder);
355
0
    if (processingResult.is_error()) {
356
0
      return processingResult.error();
357
0
    }
358
359
0
    if (!*processingResult) {
360
0
      break;
361
0
    }
362
0
  }
363
364
365
  // --- also end alpha track
366
367
0
  if (m_aux_alpha_track) {
368
0
    auto err = m_aux_alpha_track->encode_end_of_sequence(m_alpha_track_encoder.get());
369
0
    if (err) {
370
0
      return err;
371
0
    }
372
0
  }
373
374
0
  return {};
375
0
}
376
377
378
Error Track_Visual::encode_image(std::shared_ptr<HeifPixelImage> image,
379
                                 heif_encoder* h_encoder,
380
                                 const heif_sequence_encoding_options* in_options,
381
                                 heif_image_input_class input_class)
382
0
{
383
0
  if (image->get_width() > 0xFFFF ||
384
0
      image->get_height() > 0xFFFF) {
385
0
    return {
386
0
      heif_error_Invalid_input,
387
0
      heif_suberror_Unspecified,
388
0
      "Input image resolution too high"
389
0
    };
390
0
  }
391
392
0
  m_image_class = input_class;
393
394
395
  // --- If input has an alpha channel, add an alpha auxiliary track.
396
397
0
  if (in_options->save_alpha_channel && image->has_alpha() && !m_aux_alpha_track &&
398
0
      h_encoder->plugin->compression_format != heif_compression_uncompressed) { // TODO: ask plugin
399
0
    if (m_active_encoder) {
400
0
      return {
401
0
        heif_error_Usage_error,
402
0
        heif_suberror_Unspecified,
403
0
        "Input images must all either have an alpha channel or none of them."
404
0
      };
405
0
    }
406
0
    else {
407
      // alpha track uses default options, same timescale as color track
408
409
0
      TrackOptions alphaOptions;
410
0
      alphaOptions.track_timescale = m_track_info.track_timescale;
411
412
0
      auto newAlphaTrackResult = m_heif_context->add_visual_sequence_track(&alphaOptions,
413
0
                                                                           heif_track_type_auxiliary,
414
0
                                                                           static_cast<uint16_t>(image->get_width()),
415
0
                                                                           static_cast<uint16_t>(image->get_height()));
416
417
0
      if (auto err = newAlphaTrackResult.error()) {
418
0
        return err;
419
0
      }
420
421
      // add a reference to the color track
422
423
0
      m_aux_alpha_track = *newAlphaTrackResult;
424
0
      m_aux_alpha_track->add_reference_to_track(fourcc("auxl"), m_id);
425
426
      // make a copy of the encoder from the color track for encoding the alpha track
427
428
0
      m_alpha_track_encoder = std::make_unique<heif_encoder>(h_encoder->plugin);
429
0
      heif_error err = m_alpha_track_encoder->alloc();
430
0
      if (err.code) {
431
0
        return {err.code, err.subcode, err.message};
432
0
      }
433
0
      m_alpha_track_encoder->copy_parameters_from(*h_encoder);
434
0
    }
435
0
  }
436
437
438
0
  if (!m_active_encoder) {
439
0
    m_active_encoder = h_encoder;
440
0
  }
441
0
  else if (m_active_encoder != h_encoder) {
442
0
    return {
443
0
      heif_error_Usage_error,
444
0
      heif_suberror_Unspecified,
445
0
      "You may not switch the heif_encoder while encoding a sequence."
446
0
    };
447
0
  }
448
449
0
  if (h_encoder->plugin->plugin_api_version < 4) {
450
0
    return Error{
451
0
      heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed,
452
0
      "Encoder plugin needs to be at least version 4."
453
0
    };
454
0
  }
455
456
  // === generate compressed image bitstream
457
458
  // generate new chunk for first image or when compression formats don't match
459
460
0
  if (m_chunks.empty() || m_chunks.back()->get_compression_format() != h_encoder->plugin->compression_format) {
461
0
    add_chunk(h_encoder->plugin->compression_format);
462
0
  }
463
464
  // --- check whether we have to convert the image color space
465
466
  // The reason for doing the color conversion here is that the input might be an RGBA image and the color conversion
467
  // will extract the alpha plane anyway. We can reuse that plane below instead of having to do a new conversion.
468
469
0
  auto encoder = m_chunks.back()->get_encoder();
470
471
0
  const heif_color_profile_nclx* output_nclx;
472
0
  heif_color_profile_nclx nclx;
473
474
0
  if (const auto* image_nclx = encoder->get_forced_output_nclx()) {
475
0
    output_nclx = const_cast<heif_color_profile_nclx*>(image_nclx);
476
0
  }
477
0
  else if (in_options) {
478
0
    output_nclx = in_options->output_nclx_profile;
479
0
  }
480
0
  else {
481
0
    if (image->has_nclx_color_profile()) {
482
0
      nclx_profile input_nclx = image->get_color_profile_nclx();
483
484
0
      nclx.version = 1;
485
0
      nclx.color_primaries = (enum heif_color_primaries) input_nclx.get_colour_primaries();
486
0
      nclx.transfer_characteristics = (enum heif_transfer_characteristics) input_nclx.get_transfer_characteristics();
487
0
      nclx.matrix_coefficients = (enum heif_matrix_coefficients) input_nclx.get_matrix_coefficients();
488
0
      nclx.full_range_flag = input_nclx.get_full_range_flag();
489
490
0
      output_nclx = &nclx;
491
0
    }
492
0
    else {
493
0
      nclx_profile undefined_nclx;
494
0
      undefined_nclx.copy_to_heif_color_profile_nclx(&nclx);
495
496
0
      output_nclx = &nclx;
497
0
    }
498
0
  }
499
500
0
  Result<std::shared_ptr<HeifPixelImage> > srcImageResult = encoder->convert_colorspace_for_encoding(image,
501
0
                                                                                                     h_encoder,
502
0
                                                                                                     output_nclx,
503
0
                                                                                                     in_options ? &in_options->color_conversion_options : nullptr,
504
0
                                                                                                     m_heif_context->get_security_limits());
505
0
  if (!srcImageResult) {
506
0
    return srcImageResult.error();
507
0
  }
508
509
0
  std::shared_ptr<HeifPixelImage> colorConvertedImage = *srcImageResult;
510
511
  // integer range is checked at beginning of function.
512
0
  assert(colorConvertedImage->get_width() == image->get_width());
513
0
  assert(colorConvertedImage->get_height() == image->get_height());
514
0
  m_width = static_cast<uint16_t>(colorConvertedImage->get_width());
515
0
  m_height = static_cast<uint16_t>(colorConvertedImage->get_height());
516
517
  // --- encode image
518
519
  // Build an effective options struct so libheif can resolve "auto" fields
520
  // (e.g. content_kind) to concrete values before passing them to the encoder
521
  // plugin. heif_sequence_encoding_options_copy is version-aware, so callers
522
  // built against older headers won't be read past their actual allocation.
523
524
0
  std::unique_ptr<heif_sequence_encoding_options, void(*)(heif_sequence_encoding_options*)>
525
0
      effective_options(heif_sequence_encoding_options_alloc(),
526
0
                        heif_sequence_encoding_options_release);
527
0
  heif_sequence_encoding_options_copy(effective_options.get(), in_options);
528
529
  // Resolve content_kind=auto based on this track's handler type. For auxiliary
530
  // tracks (e.g. alpha) the parent track resolves the value before recursing in,
531
  // so we never reach this branch with handler=auxv.
532
  // TODO: in the future, "auto" could also factor in input frame rate or
533
  // frame-to-frame similarity.
534
0
  if (effective_options->content_kind == heif_sequence_content_kind_auto) {
535
0
    switch (get_handler()) {
536
0
      case heif_track_type_video:
537
0
        effective_options->content_kind = heif_sequence_content_kind_video;
538
0
        break;
539
0
      case heif_track_type_image_sequence:
540
0
      default:
541
0
        effective_options->content_kind = heif_sequence_content_kind_image_sequence;
542
0
        break;
543
0
    }
544
0
  }
545
546
0
  Error encodeError = encoder->encode_sequence_frame(colorConvertedImage, h_encoder,
547
0
                                                     *effective_options,
548
0
                                                     input_class,
549
0
                                                     colorConvertedImage->get_sample_duration(), get_timescale(),
550
0
                                                     m_current_frame_nr);
551
552
0
  if (encodeError) {
553
0
    return encodeError;
554
0
  }
555
556
0
  m_sample_duration = colorConvertedImage->get_sample_duration();
557
  // TODO heif_tai_timestamp_packet* tai = image->get_tai_timestamp();
558
  // TODO image->has_gimi_sample_content_id() ? image->get_gimi_sample_content_id() : std::string{});
559
560
561
  // store frame user data
562
563
0
  FrameUserData userData;
564
0
  userData.sample_duration = colorConvertedImage->get_sample_duration();
565
0
  if (image->has_gimi_sample_content_id()) {
566
0
    userData.gimi_content_id = image->get_gimi_sample_content_id();
567
0
  }
568
569
0
  if (const auto* tai = image->get_tai_timestamp()) {
570
0
    userData.tai_timestamp = heif_tai_timestamp_packet_alloc();
571
0
    heif_tai_timestamp_packet_copy(userData.tai_timestamp, tai);
572
0
  }
573
574
0
  m_frame_user_data[m_current_frame_nr] = userData;
575
576
0
  m_current_frame_nr++;
577
578
  // --- get compressed data from encoder
579
580
0
  Result<bool> processingResult = process_encoded_data(h_encoder);
581
0
  if (auto err = processingResult.error()) {
582
0
    return err;
583
0
  }
584
585
586
  // --- encode alpha channel into auxiliary track
587
588
0
  if (m_aux_alpha_track) {
589
0
    auto alphaImageResult = create_alpha_image_from_image_alpha_channel(colorConvertedImage,
590
0
                                                                        m_heif_context->get_security_limits());
591
0
    if (auto err = alphaImageResult.error()) {
592
0
      return err;
593
0
    }
594
595
0
    (*alphaImageResult)->set_sample_duration(colorConvertedImage->get_sample_duration());
596
597
    // Pass the resolved options (not the caller's in_options) so the alpha aux
598
    // track inherits the color track's content_kind instead of re-resolving
599
    // from its own 'auxv' handler.
600
0
    auto err = m_aux_alpha_track->encode_image(*alphaImageResult,
601
0
                                               m_alpha_track_encoder.get(),
602
0
                                               effective_options.get(),
603
0
                                               heif_image_input_class_alpha);
604
0
    if (err) {
605
0
      return err;
606
0
    }
607
0
  }
608
609
0
  return {};
610
0
}
611
612
613
Result<bool> Track_Visual::process_encoded_data(heif_encoder* h_encoder)
614
0
{
615
0
  auto encoder = m_chunks.back()->get_encoder();
616
617
0
  std::optional<Encoder::CodedImageData> encodingResult = encoder->encode_sequence_get_data();
618
0
  if (!encodingResult) {
619
0
    return {};
620
0
  }
621
622
0
  const Encoder::CodedImageData& data = *encodingResult;
623
624
625
0
  if (data.bitstream.empty() &&
626
0
      data.properties.empty()) {
627
0
    return {false};
628
0
  }
629
630
  // --- generate SampleDescriptionBox
631
632
0
  if (!m_generated_sample_description_box) {
633
0
    auto sample_description_box = encoder->get_sample_description_box(data);
634
0
    if (sample_description_box) {
635
0
      VisualSampleEntry& visualSampleEntry = sample_description_box->get_VisualSampleEntry();
636
0
      visualSampleEntry.width = m_width;
637
0
      visualSampleEntry.height = m_height;
638
639
      // add Coding-Constraints box (ccst) only if we are generating an image sequence
640
641
      // TODO: does the alpha track also need a ccst box?
642
      //       ComplianceWarden says so (and it makes sense), but HEIF says that 'ccst' shall be present
643
      //       if the handler is 'pict'. However, the alpha track is 'auxv'.
644
0
      if (true) { // m_hdlr->get_handler_type() == heif_track_type_image_sequence) {
645
0
        auto ccst = std::make_shared<Box_ccst>();
646
0
        ccst->set_coding_constraints(data.codingConstraints);
647
0
        sample_description_box->append_child_box(ccst);
648
0
      }
649
650
0
      if (m_image_class == heif_image_input_class_alpha) {
651
0
        auto auxi_box = std::make_shared<Box_auxi>();
652
0
        auxi_box->set_aux_track_type_urn(get_track_auxiliary_info_type(h_encoder->plugin->compression_format));
653
0
        sample_description_box->append_child_box(auxi_box);
654
0
      }
655
656
0
      set_sample_description_box(sample_description_box);
657
0
      m_generated_sample_description_box = true;
658
0
    }
659
0
  }
660
661
0
  if (!data.bitstream.empty()) {
662
0
    uintptr_t frame_number = data.frame_nr;
663
664
0
    auto& user_data = m_frame_user_data[frame_number];
665
666
0
    int32_t decoding_time = static_cast<int32_t>(m_stsz->num_samples()) * m_sample_duration;
667
0
    int32_t composition_time = static_cast<int32_t>(frame_number) * m_sample_duration;
668
669
0
    Error err = write_sample_data(data.bitstream,
670
0
                                  user_data.sample_duration,
671
0
                                  composition_time - decoding_time,
672
0
                                  data.is_sync_frame,
673
0
                                  user_data.tai_timestamp,
674
0
                                  user_data.gimi_content_id);
675
676
0
    user_data.release();
677
0
    m_frame_user_data.erase(frame_number);
678
679
0
    if (err) {
680
0
      return err;
681
0
    }
682
0
  }
683
684
0
  return {true};
685
0
}
686
687
688
Error Track_Visual::finalize_track()
689
0
{
690
0
  if (m_active_encoder) {
691
0
    Error err = encode_end_of_sequence(m_active_encoder);
692
0
    if (err) {
693
0
      return err;
694
0
    }
695
0
  }
696
697
0
  return Track::finalize_track();
698
0
}
699
700
701
heif_brand2 Track_Visual::get_compatible_brand() const
702
0
{
703
0
  if (m_stsd->get_num_sample_entries() == 0) {
704
0
    return 0; // TODO: error ? Or can we assume at this point that there is at least one sample entry?
705
0
  }
706
707
0
  auto sampleEntry = m_stsd->get_sample_entry(0);
708
709
0
  uint32_t sample_entry_type = sampleEntry->get_short_type();
710
0
  switch (sample_entry_type) {
711
0
    case fourcc("hvc1"): {
712
0
      auto hvcC = sampleEntry->get_child_box<Box_hvcC>();
713
0
      if (!hvcC) { return 0; }
714
715
0
      const auto& config = hvcC->get_configuration();
716
0
      if (config.is_profile_compatible(HEVCDecoderConfigurationRecord::Profile_Main) ||
717
0
          config.is_profile_compatible(HEVCDecoderConfigurationRecord::Profile_MainStillPicture)) {
718
0
        return heif_brand2_hevc;
719
0
      }
720
0
      else {
721
0
        return heif_brand2_hevx;
722
0
      }
723
0
    }
724
725
0
    case fourcc("avc1"):
726
0
      return heif_brand2_avcs;
727
728
0
    case fourcc("av01"):
729
0
      return heif_brand2_avis;
730
731
0
    case fourcc("j2ki"):
732
0
      return heif_brand2_j2is;
733
734
0
    case fourcc("mjpg"):
735
0
      return heif_brand2_jpgs;
736
737
0
    case fourcc("vvc1"):
738
0
      return heif_brand2_vvis;
739
740
0
    default:
741
0
      return 0;
742
0
  }
743
0
}