Coverage Report

Created: 2026-02-14 07:11

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