/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 | } |