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