/src/libheif/libheif/sequences/track.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF image base codec. |
3 | | * Copyright (c) 2024 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 <cstring> |
22 | | #include "track.h" |
23 | | #include "context.h" |
24 | | #include "sequences/seq_boxes.h" |
25 | | #include "sequences/chunk.h" |
26 | | #include "sequences/track_visual.h" |
27 | | #include "sequences/track_metadata.h" |
28 | | #include "api_structs.h" |
29 | | #include <limits> |
30 | | |
31 | | |
32 | | TrackOptions& TrackOptions::operator=(const TrackOptions& src) |
33 | 0 | { |
34 | 0 | if (&src == this) { |
35 | 0 | return *this; |
36 | 0 | } |
37 | | |
38 | 0 | this->track_timescale = src.track_timescale; |
39 | 0 | this->write_sample_aux_infos_interleaved = src.write_sample_aux_infos_interleaved; |
40 | 0 | this->with_sample_tai_timestamps = src.with_sample_tai_timestamps; |
41 | |
|
42 | 0 | if (src.tai_clock_info) { |
43 | 0 | this->tai_clock_info = heif_tai_clock_info_alloc(); |
44 | 0 | heif_tai_clock_info_copy(this->tai_clock_info, src.tai_clock_info); |
45 | 0 | } |
46 | 0 | else { |
47 | 0 | this->tai_clock_info = nullptr; |
48 | 0 | } |
49 | |
|
50 | 0 | this->with_sample_content_ids = src.with_sample_content_ids; |
51 | 0 | this->gimi_track_content_id = src.gimi_track_content_id; |
52 | |
|
53 | 0 | return *this; |
54 | 0 | } |
55 | | |
56 | | |
57 | | SampleAuxInfoHelper::SampleAuxInfoHelper(bool interleaved) |
58 | 0 | : m_interleaved(interleaved) |
59 | 0 | { |
60 | 0 | m_saiz = std::make_shared<Box_saiz>(); |
61 | 0 | m_saio = std::make_shared<Box_saio>(); |
62 | 0 | } |
63 | | |
64 | | |
65 | | void SampleAuxInfoHelper::set_aux_info_type(uint32_t aux_info_type, uint32_t aux_info_type_parameter) |
66 | 0 | { |
67 | 0 | m_saiz->set_aux_info_type(aux_info_type, aux_info_type_parameter); |
68 | 0 | m_saio->set_aux_info_type(aux_info_type, aux_info_type_parameter); |
69 | 0 | } |
70 | | |
71 | | Error SampleAuxInfoHelper::add_sample_info(const std::vector<uint8_t>& data) |
72 | 0 | { |
73 | 0 | if (data.size() > 0xFF) { |
74 | 0 | return { |
75 | 0 | heif_error_Encoding_error, |
76 | 0 | heif_suberror_Unspecified, |
77 | 0 | "Encoded sample auxiliary information exceeds maximum size" |
78 | 0 | }; |
79 | 0 | } |
80 | | |
81 | 0 | m_saiz->add_sample_size(static_cast<uint8_t>(data.size())); |
82 | |
|
83 | 0 | m_data.insert(m_data.end(), data.begin(), data.end()); |
84 | |
|
85 | 0 | return Error::Ok; |
86 | 0 | } |
87 | | |
88 | | void SampleAuxInfoHelper::add_nonpresent_sample() |
89 | 0 | { |
90 | 0 | m_saiz->add_nonpresent_sample(); |
91 | 0 | } |
92 | | |
93 | | |
94 | | void SampleAuxInfoHelper::write_interleaved(const std::shared_ptr<HeifFile>& file) |
95 | 0 | { |
96 | 0 | if (m_interleaved && !m_data.empty()) { |
97 | | // TODO: I think this does not work because the image data does not know that there is SAI in-between |
98 | 0 | uint64_t pos = file->append_mdat_data(m_data); |
99 | 0 | m_saio->add_chunk_offset(pos); |
100 | |
|
101 | 0 | m_data.clear(); |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | void SampleAuxInfoHelper::write_all(const std::shared_ptr<Box>& parent, const std::shared_ptr<HeifFile>& file) |
106 | 0 | { |
107 | 0 | parent->append_child_box(m_saiz); |
108 | 0 | parent->append_child_box(m_saio); |
109 | |
|
110 | 0 | if (!m_data.empty()) { |
111 | 0 | uint64_t pos = file->append_mdat_data(m_data); |
112 | 0 | m_saio->add_chunk_offset(pos); |
113 | 0 | } |
114 | 0 | } |
115 | | |
116 | | |
117 | | SampleAuxInfoReader::SampleAuxInfoReader(std::shared_ptr<Box_saiz> saiz, |
118 | | std::shared_ptr<Box_saio> saio, |
119 | | const std::vector<std::shared_ptr<Chunk>>& chunks) |
120 | 0 | { |
121 | 0 | m_saiz = saiz; |
122 | 0 | m_saio = saio; |
123 | |
|
124 | 0 | bool oneChunk = (saio->get_num_chunks() == 1); |
125 | |
|
126 | 0 | uint32_t current_chunk = 0; |
127 | 0 | uint64_t offset = saio->get_chunk_offset(0); |
128 | 0 | uint32_t nSamples = saiz->get_num_samples(); |
129 | |
|
130 | 0 | m_contiguous_and_constant_size = (oneChunk && m_saiz->have_samples_constant_size()); |
131 | |
|
132 | 0 | if (m_contiguous_and_constant_size) { |
133 | 0 | m_singleChunk_offset = offset; |
134 | 0 | } |
135 | 0 | else { |
136 | 0 | m_sample_offsets.resize(nSamples); |
137 | |
|
138 | 0 | for (uint32_t i = 0; i < nSamples; i++) { |
139 | 0 | if (!oneChunk && i > chunks[current_chunk]->last_sample_number()) { |
140 | 0 | current_chunk++; |
141 | 0 | if (current_chunk >= chunks.size()) { |
142 | 0 | break; |
143 | 0 | } |
144 | 0 | offset = saio->get_chunk_offset(current_chunk); |
145 | 0 | } |
146 | | |
147 | 0 | m_sample_offsets[i] = offset; |
148 | 0 | offset += saiz->get_sample_size(i); |
149 | 0 | } |
150 | 0 | } |
151 | 0 | } |
152 | | |
153 | | |
154 | | heif_sample_aux_info_type SampleAuxInfoReader::get_type() const |
155 | 0 | { |
156 | 0 | heif_sample_aux_info_type type; |
157 | 0 | type.type = m_saiz->get_aux_info_type(); |
158 | 0 | type.parameter = m_saiz->get_aux_info_type_parameter(); |
159 | 0 | return type; |
160 | 0 | } |
161 | | |
162 | | |
163 | | Result<std::vector<uint8_t> > SampleAuxInfoReader::get_sample_info(const HeifFile* file, uint32_t sample_idx) |
164 | 0 | { |
165 | 0 | uint64_t offset; |
166 | 0 | uint8_t size; |
167 | |
|
168 | 0 | if (m_contiguous_and_constant_size) { |
169 | 0 | size = m_saiz->get_sample_size(0); |
170 | 0 | offset = m_singleChunk_offset + uint64_t{sample_idx} * size; |
171 | 0 | } |
172 | 0 | else { |
173 | 0 | size = m_saiz->get_sample_size(sample_idx); |
174 | 0 | if (size > 0) { |
175 | 0 | if (sample_idx >= m_sample_offsets.size()) { |
176 | 0 | return {}; |
177 | 0 | } |
178 | | |
179 | 0 | offset = m_sample_offsets[sample_idx]; |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | 0 | std::vector<uint8_t> data; |
184 | |
|
185 | 0 | if (size > 0) { |
186 | 0 | Error err = file->append_data_from_file_range(data, offset, size); |
187 | 0 | if (err) { |
188 | 0 | return err; |
189 | 0 | } |
190 | 0 | } |
191 | | |
192 | 0 | return data; |
193 | 0 | } |
194 | | |
195 | | |
196 | | std::shared_ptr<HeifFile> Track::get_file() const |
197 | 0 | { |
198 | 0 | return m_heif_context->get_heif_file(); |
199 | 0 | } |
200 | | |
201 | | |
202 | | Track::Track(HeifContext* ctx) |
203 | 7 | { |
204 | 7 | m_heif_context = ctx; |
205 | 7 | } |
206 | | |
207 | | |
208 | | Error Track::load(const std::shared_ptr<Box_trak>& trak_box) |
209 | 7 | { |
210 | 7 | m_trak = trak_box; |
211 | | |
212 | 7 | auto tkhd = trak_box->get_child_box<Box_tkhd>(); |
213 | 7 | if (!tkhd) { |
214 | 7 | return { |
215 | 7 | heif_error_Invalid_input, |
216 | 7 | heif_suberror_Unspecified, |
217 | 7 | "Track has no 'tkhd' box." |
218 | 7 | }; |
219 | 7 | } |
220 | | |
221 | 0 | m_tkhd = tkhd; |
222 | |
|
223 | 0 | m_id = tkhd->get_track_id(); |
224 | |
|
225 | 0 | auto edts = trak_box->get_child_box<Box_edts>(); |
226 | 0 | if (edts) { |
227 | 0 | m_elst = edts->get_child_box<Box_elst>(); |
228 | 0 | } |
229 | |
|
230 | 0 | auto mdia = trak_box->get_child_box<Box_mdia>(); |
231 | 0 | if (!mdia) { |
232 | 0 | return { |
233 | 0 | heif_error_Invalid_input, |
234 | 0 | heif_suberror_Unspecified, |
235 | 0 | "Track has no 'mdia' box." |
236 | 0 | }; |
237 | 0 | } |
238 | | |
239 | 0 | m_tref = trak_box->get_child_box<Box_tref>(); |
240 | |
|
241 | 0 | auto hdlr = mdia->get_child_box<Box_hdlr>(); |
242 | 0 | if (!hdlr) { |
243 | 0 | return { |
244 | 0 | heif_error_Invalid_input, |
245 | 0 | heif_suberror_Unspecified, |
246 | 0 | "Track has no 'hdlr' box." |
247 | 0 | }; |
248 | 0 | } |
249 | | |
250 | 0 | m_handler_type = hdlr->get_handler_type(); |
251 | |
|
252 | 0 | m_minf = mdia->get_child_box<Box_minf>(); |
253 | 0 | if (!m_minf) { |
254 | 0 | return { |
255 | 0 | heif_error_Invalid_input, |
256 | 0 | heif_suberror_Unspecified, |
257 | 0 | "Track has no 'minf' box." |
258 | 0 | }; |
259 | 0 | } |
260 | | |
261 | 0 | m_mdhd = mdia->get_child_box<Box_mdhd>(); |
262 | 0 | if (!m_mdhd) { |
263 | 0 | return { |
264 | 0 | heif_error_Invalid_input, |
265 | 0 | heif_suberror_Unspecified, |
266 | 0 | "Track has no 'mdhd' box." |
267 | 0 | }; |
268 | 0 | } |
269 | | |
270 | 0 | auto stbl = m_minf->get_child_box<Box_stbl>(); |
271 | 0 | if (!stbl) { |
272 | 0 | return { |
273 | 0 | heif_error_Invalid_input, |
274 | 0 | heif_suberror_Unspecified, |
275 | 0 | "Track has no 'stbl' box." |
276 | 0 | }; |
277 | 0 | } |
278 | | |
279 | 0 | m_stsd = stbl->get_child_box<Box_stsd>(); |
280 | 0 | if (!m_stsd) { |
281 | 0 | return { |
282 | 0 | heif_error_Invalid_input, |
283 | 0 | heif_suberror_Unspecified, |
284 | 0 | "Track has no 'stsd' box." |
285 | 0 | }; |
286 | 0 | } |
287 | | |
288 | 0 | m_stsc = stbl->get_child_box<Box_stsc>(); |
289 | 0 | if (!m_stsc) { |
290 | 0 | return { |
291 | 0 | heif_error_Invalid_input, |
292 | 0 | heif_suberror_Unspecified, |
293 | 0 | "Track has no 'stsc' box." |
294 | 0 | }; |
295 | 0 | } |
296 | | |
297 | 0 | m_stco = stbl->get_child_box<Box_stco>(); |
298 | 0 | if (!m_stco) { |
299 | 0 | return { |
300 | 0 | heif_error_Invalid_input, |
301 | 0 | heif_suberror_Unspecified, |
302 | 0 | "Track has no 'stco' box." |
303 | 0 | }; |
304 | 0 | } |
305 | | |
306 | 0 | m_stsz = stbl->get_child_box<Box_stsz>(); |
307 | 0 | if (!m_stsz) { |
308 | 0 | return { |
309 | 0 | heif_error_Invalid_input, |
310 | 0 | heif_suberror_Unspecified, |
311 | 0 | "Track has no 'stsz' box." |
312 | 0 | }; |
313 | 0 | } |
314 | | |
315 | | // Enforce the sequence-frame limit before allocating any per-chunk state below. |
316 | | // Box_stsz::parse already applies this when parsing from a file, but check again |
317 | | // here so the invariant holds even if m_stsz was built another way. |
318 | 0 | const auto* limits = m_heif_context->get_security_limits(); |
319 | 0 | if (limits->max_sequence_frames > 0 && |
320 | 0 | m_stsz->num_samples() > limits->max_sequence_frames) { |
321 | 0 | return { |
322 | 0 | heif_error_Memory_allocation_error, |
323 | 0 | heif_suberror_Security_limit_exceeded, |
324 | 0 | "Security limit for maximum number of sequence frames exceeded" |
325 | 0 | }; |
326 | 0 | } |
327 | | |
328 | 0 | m_stts = stbl->get_child_box<Box_stts>(); |
329 | 0 | if (!m_stts) { |
330 | 0 | return { |
331 | 0 | heif_error_Invalid_input, |
332 | 0 | heif_suberror_Unspecified, |
333 | 0 | "Track has no 'stts' box." |
334 | 0 | }; |
335 | 0 | } |
336 | | |
337 | 0 | m_ctts = stbl->get_child_box<Box_ctts>(); |
338 | | |
339 | | // --- check that number of samples in various boxes are consistent |
340 | |
|
341 | 0 | if (m_stts->get_number_of_samples() != m_stsz->num_samples()) { |
342 | 0 | return { |
343 | 0 | heif_error_Invalid_input, |
344 | 0 | heif_suberror_Unspecified, |
345 | 0 | "Number of samples in 'stts' and 'stsz' is inconsistent." |
346 | 0 | }; |
347 | 0 | } |
348 | | |
349 | 0 | if (m_ctts && m_ctts->get_number_of_samples() != m_stsz->num_samples()) { |
350 | 0 | return { |
351 | 0 | heif_error_Invalid_input, |
352 | 0 | heif_suberror_Unspecified, |
353 | 0 | "Number of samples in 'ctts' and 'stsz' is inconsistent." |
354 | 0 | }; |
355 | 0 | } |
356 | | |
357 | 0 | const std::vector<uint32_t>& chunk_offsets = m_stco->get_offsets(); |
358 | 0 | assert(chunk_offsets.size() <= (size_t) std::numeric_limits<uint32_t>::max()); // There cannot be more than uint32_t chunks. |
359 | |
|
360 | 0 | uint32_t current_sample_idx = 0; |
361 | 0 | int32_t previous_sample_description_index = -1; |
362 | |
|
363 | 0 | for (size_t chunk_idx = 0; chunk_idx < chunk_offsets.size(); chunk_idx++) { |
364 | 0 | auto* s2c = m_stsc->get_chunk(static_cast<uint32_t>(chunk_idx + 1)); |
365 | 0 | if (!s2c) { |
366 | 0 | return { |
367 | 0 | heif_error_Invalid_input, |
368 | 0 | heif_suberror_Unspecified, |
369 | 0 | "'stco' box references a non-existing chunk." |
370 | 0 | }; |
371 | 0 | } |
372 | | |
373 | 0 | Box_stsc::SampleToChunk sampleToChunk = *s2c; |
374 | |
|
375 | 0 | auto sample_description = m_stsd->get_sample_entry(sampleToChunk.sample_description_index - 1); |
376 | 0 | if (!sample_description) { |
377 | 0 | return { |
378 | 0 | heif_error_Invalid_input, |
379 | 0 | heif_suberror_Unspecified, |
380 | 0 | "Track references a non-existing sample description." |
381 | 0 | }; |
382 | 0 | } |
383 | | |
384 | 0 | if (auto auxi = sample_description->get_child_box<Box_auxi>()) { |
385 | 0 | m_auxiliary_info_type = auxi->get_aux_track_type_urn(); |
386 | 0 | } |
387 | |
|
388 | 0 | if (m_first_taic == nullptr) { |
389 | 0 | auto taic = sample_description->get_child_box<Box_taic>(); |
390 | 0 | if (taic) { |
391 | 0 | m_first_taic = taic; |
392 | 0 | } |
393 | 0 | } |
394 | |
|
395 | 0 | if (static_cast<uint64_t>(current_sample_idx) + sampleToChunk.samples_per_chunk > m_stsz->num_samples()) { |
396 | 0 | return { |
397 | 0 | heif_error_Invalid_input, |
398 | 0 | heif_suberror_Unspecified, |
399 | 0 | "Number of samples in 'stsc' box exceeds sample sizes in 'stsz' box." |
400 | 0 | }; |
401 | 0 | } |
402 | | |
403 | 0 | auto chunk = Chunk::create(m_heif_context, m_id, |
404 | 0 | current_sample_idx, sampleToChunk.samples_per_chunk, |
405 | 0 | m_stco->get_offsets()[chunk_idx], |
406 | 0 | m_stsz); |
407 | |
|
408 | 0 | if (!chunk) { |
409 | 0 | return { |
410 | 0 | heif_error_Invalid_input, |
411 | 0 | heif_suberror_Unspecified, |
412 | 0 | "Chunk file offset overflows 64-bit range." |
413 | 0 | }; |
414 | 0 | } |
415 | | |
416 | 0 | if (auto visualSampleDescription = std::dynamic_pointer_cast<const Box_VisualSampleEntry>(sample_description)) { |
417 | 0 | if (chunk_idx > 0 && (int32_t) sampleToChunk.sample_description_index == previous_sample_description_index) { |
418 | | // reuse decoder from previous chunk if it uses the sample sample_description_index |
419 | 0 | chunk->set_decoder(m_chunks[chunk_idx - 1]->get_decoder()); |
420 | 0 | } |
421 | 0 | else { |
422 | | // use a new decoder |
423 | 0 | auto decoder = Decoder::alloc_for_sequence_sample_description_box(visualSampleDescription); |
424 | 0 | if (!decoder) { |
425 | 0 | return { |
426 | 0 | heif_error_Invalid_input, |
427 | 0 | heif_suberror_Unspecified, |
428 | 0 | "Sample description has unsupported codec or is missing the codec configuration box." |
429 | 0 | }; |
430 | 0 | } |
431 | 0 | chunk->set_decoder(decoder); |
432 | 0 | } |
433 | 0 | } |
434 | | |
435 | 0 | m_chunks.push_back(chunk); |
436 | |
|
437 | 0 | current_sample_idx += sampleToChunk.samples_per_chunk; |
438 | 0 | previous_sample_description_index = sampleToChunk.sample_description_index; |
439 | 0 | } |
440 | | |
441 | 0 | if (current_sample_idx != m_stsz->num_samples()) { |
442 | 0 | return { |
443 | 0 | heif_error_Invalid_input, |
444 | 0 | heif_suberror_Unspecified, |
445 | 0 | "Number of samples covered by 'stsc' does not match 'stsz'/'stts'." |
446 | 0 | }; |
447 | 0 | } |
448 | | |
449 | | // --- read sample auxiliary information boxes |
450 | | |
451 | 0 | std::vector<std::shared_ptr<Box_saiz> > saiz_boxes = stbl->get_child_boxes<Box_saiz>(); |
452 | 0 | std::vector<std::shared_ptr<Box_saio> > saio_boxes = stbl->get_child_boxes<Box_saio>(); |
453 | |
|
454 | 0 | if (saio_boxes.size() != saiz_boxes.size()) { |
455 | 0 | return Error{ |
456 | 0 | heif_error_Invalid_input, |
457 | 0 | heif_suberror_Unspecified, |
458 | 0 | "Boxes 'saiz' and `saio` must come in pairs." |
459 | 0 | }; |
460 | 0 | } |
461 | | |
462 | 0 | for (const auto& saiz : saiz_boxes) { |
463 | 0 | uint32_t aux_info_type = saiz->get_aux_info_type(); |
464 | 0 | uint32_t aux_info_type_parameter = saiz->get_aux_info_type_parameter(); |
465 | | |
466 | | // find the corresponding saio box |
467 | |
|
468 | 0 | std::shared_ptr<Box_saio> saio; |
469 | 0 | for (const auto& candidate : saio_boxes) { |
470 | 0 | if (candidate->get_aux_info_type() == aux_info_type && |
471 | 0 | candidate->get_aux_info_type_parameter() == aux_info_type_parameter) { |
472 | 0 | saio = candidate; |
473 | 0 | break; |
474 | 0 | } |
475 | 0 | } |
476 | |
|
477 | 0 | if (saio) { |
478 | 0 | if (saio->get_num_chunks() != 1 && |
479 | 0 | saio->get_num_chunks() != m_stco->get_number_of_chunks()) { |
480 | 0 | return Error{ |
481 | 0 | heif_error_Invalid_input, |
482 | 0 | heif_suberror_Unspecified, |
483 | 0 | "Invalid number of chunks in 'saio' box." |
484 | 0 | }; |
485 | 0 | } |
486 | | |
487 | 0 | if (saio->get_num_chunks() != 1 && m_chunks.empty() && saiz->get_num_samples() > 0) { |
488 | 0 | return Error{ |
489 | 0 | heif_error_Invalid_input, |
490 | 0 | heif_suberror_Unspecified, |
491 | 0 | "'saiz' box references samples but no chunks exist." |
492 | 0 | }; |
493 | 0 | } |
494 | | |
495 | 0 | if (saiz->get_num_samples() > m_stsz->num_samples()) { |
496 | 0 | return Error{ |
497 | 0 | heif_error_Invalid_input, |
498 | 0 | heif_suberror_Unspecified, |
499 | 0 | "Number of samples in 'saiz' box exceeds actual number of samples." |
500 | 0 | }; |
501 | 0 | } |
502 | | |
503 | 0 | if (aux_info_type == fourcc("suid")) { |
504 | 0 | m_aux_reader_content_ids = std::make_unique<SampleAuxInfoReader>(saiz, saio, m_chunks); |
505 | 0 | } |
506 | |
|
507 | 0 | if (aux_info_type == fourcc("stai")) { |
508 | 0 | m_aux_reader_tai_timestamps = std::make_unique<SampleAuxInfoReader>(saiz, saio, m_chunks); |
509 | 0 | } |
510 | 0 | } |
511 | 0 | else { |
512 | 0 | return Error{ |
513 | 0 | heif_error_Invalid_input, |
514 | 0 | heif_suberror_Unspecified, |
515 | 0 | "'saiz' box without matching 'saio' box." |
516 | 0 | }; |
517 | 0 | } |
518 | 0 | } |
519 | | |
520 | | // --- read track properties |
521 | | |
522 | 0 | if (auto meta = trak_box->get_child_box<Box_meta>()) { |
523 | 0 | auto iloc = meta->get_child_box<Box_iloc>(); |
524 | 0 | auto idat = meta->get_child_box<Box_idat>(); |
525 | |
|
526 | 0 | auto iinf = meta->get_child_box<Box_iinf>(); |
527 | 0 | if (iinf) { |
528 | 0 | auto infe_boxes = iinf->get_child_boxes<Box_infe>(); |
529 | 0 | for (const auto& box : infe_boxes) { |
530 | 0 | if (box->get_item_type_4cc() == fourcc("uri ") && |
531 | 0 | box->get_item_uri_type() == "urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73") { |
532 | 0 | heif_item_id id = box->get_item_ID(); |
533 | |
|
534 | 0 | if (!iloc) { |
535 | 0 | return { |
536 | 0 | heif_error_Invalid_input, |
537 | 0 | heif_suberror_Unspecified, |
538 | 0 | "Track meta references item content-id but file has no 'iloc' box." |
539 | 0 | }; |
540 | 0 | } |
541 | | |
542 | 0 | std::vector<uint8_t> data; |
543 | 0 | Error err = iloc->read_data(id, m_heif_context->get_heif_file()->get_reader(), idat, &data, m_heif_context->get_security_limits()); |
544 | 0 | if (err) { |
545 | 0 | return err; |
546 | 0 | } |
547 | | |
548 | 0 | Result<std::string> contentIdResult = vector_to_string(data); |
549 | 0 | if (!contentIdResult) { |
550 | 0 | return contentIdResult.error(); |
551 | 0 | } |
552 | | |
553 | 0 | m_track_info.gimi_track_content_id = *contentIdResult; |
554 | 0 | } |
555 | 0 | } |
556 | 0 | } |
557 | 0 | } |
558 | | |
559 | | |
560 | | // --- initialize track tables |
561 | | |
562 | 0 | Error err = init_sample_timing_table(); |
563 | 0 | if (err) { |
564 | 0 | return err; |
565 | 0 | } |
566 | | |
567 | 0 | return {}; |
568 | 0 | } |
569 | | |
570 | | |
571 | | Track::Track(HeifContext* ctx, uint32_t track_id, const TrackOptions* options, uint32_t handler_type) |
572 | 0 | { |
573 | 0 | m_heif_context = ctx; |
574 | |
|
575 | 0 | m_moov = ctx->get_heif_file()->get_moov_box(); |
576 | 0 | assert(m_moov); |
577 | | |
578 | | // --- find next free track ID |
579 | |
|
580 | 0 | if (track_id == 0) { |
581 | 0 | auto idResult = ctx->get_id_creator().get_new_id(IDCreator::Namespace::track); |
582 | | // Track constructor cannot return errors; assert on overflow (extremely unlikely). |
583 | 0 | assert(idResult); |
584 | 0 | track_id = *idResult; |
585 | |
|
586 | 0 | auto mvhd = m_moov->get_child_box<Box_mvhd>(); |
587 | 0 | mvhd->set_next_track_id(track_id + 1); |
588 | |
|
589 | 0 | m_id = track_id; |
590 | 0 | } |
591 | |
|
592 | 0 | m_trak = std::make_shared<Box_trak>(); |
593 | 0 | m_moov->append_child_box(m_trak); |
594 | |
|
595 | 0 | m_tkhd = std::make_shared<Box_tkhd>(); |
596 | 0 | m_trak->append_child_box(m_tkhd); |
597 | 0 | m_tkhd->set_track_id(track_id); |
598 | |
|
599 | 0 | auto mdia = std::make_shared<Box_mdia>(); |
600 | 0 | m_trak->append_child_box(mdia); |
601 | |
|
602 | 0 | m_mdhd = std::make_shared<Box_mdhd>(); |
603 | 0 | m_mdhd->set_timescale(options ? options->track_timescale : 90000); |
604 | 0 | mdia->append_child_box(m_mdhd); |
605 | |
|
606 | 0 | m_hdlr = std::make_shared<Box_hdlr>(); |
607 | 0 | mdia->append_child_box(m_hdlr); |
608 | 0 | m_hdlr->set_handler_type(handler_type); |
609 | |
|
610 | 0 | m_minf = std::make_shared<Box_minf>(); |
611 | 0 | mdia->append_child_box(m_minf); |
612 | | |
613 | | // add (unused) 'dinf' |
614 | |
|
615 | 0 | auto dinf = std::make_shared<Box_dinf>(); |
616 | 0 | auto dref = std::make_shared<Box_dref>(); |
617 | 0 | auto url = std::make_shared<Box_url>(); |
618 | 0 | m_minf->append_child_box(dinf); |
619 | 0 | dinf->append_child_box(dref); |
620 | 0 | dref->append_child_box(url); |
621 | | |
622 | | // vmhd is added in Track_Visual |
623 | |
|
624 | 0 | m_stbl = std::make_shared<Box_stbl>(); |
625 | 0 | m_minf->append_child_box(m_stbl); |
626 | |
|
627 | 0 | m_stsd = std::make_shared<Box_stsd>(); |
628 | 0 | m_stbl->append_child_box(m_stsd); |
629 | |
|
630 | 0 | m_stts = std::make_shared<Box_stts>(); |
631 | 0 | m_stbl->append_child_box(m_stts); |
632 | |
|
633 | 0 | m_ctts = std::make_shared<Box_ctts>(); |
634 | | // The ctts box will be added in finalize_track(), but only there is frame-reordering. |
635 | |
|
636 | 0 | m_stsc = std::make_shared<Box_stsc>(); |
637 | 0 | m_stbl->append_child_box(m_stsc); |
638 | |
|
639 | 0 | m_stsz = std::make_shared<Box_stsz>(); |
640 | 0 | m_stbl->append_child_box(m_stsz); |
641 | |
|
642 | 0 | m_stco = std::make_shared<Box_stco>(); |
643 | 0 | m_stbl->append_child_box(m_stco); |
644 | |
|
645 | 0 | m_stss = std::make_shared<Box_stss>(); |
646 | 0 | m_stbl->append_child_box(m_stss); |
647 | |
|
648 | 0 | if (options) { |
649 | 0 | m_track_info = *options; |
650 | |
|
651 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
652 | 0 | m_aux_helper_tai_timestamps = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved); |
653 | 0 | m_aux_helper_tai_timestamps->set_aux_info_type(fourcc("stai")); |
654 | 0 | } |
655 | |
|
656 | 0 | if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) { |
657 | 0 | m_aux_helper_content_ids = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved); |
658 | 0 | m_aux_helper_content_ids->set_aux_info_type(fourcc("suid")); |
659 | 0 | } |
660 | |
|
661 | 0 | if (!options->gimi_track_content_id.empty()) { |
662 | 0 | auto hdlr_box = std::make_shared<Box_hdlr>(); |
663 | 0 | hdlr_box->set_handler_type(fourcc("meta")); |
664 | |
|
665 | 0 | auto uuid_box = std::make_shared<Box_infe>(); |
666 | 0 | uuid_box->set_item_type_4cc(fourcc("uri ")); |
667 | 0 | uuid_box->set_item_uri_type("urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73"); |
668 | 0 | uuid_box->set_item_ID(1); |
669 | |
|
670 | 0 | auto iinf_box = std::make_shared<Box_iinf>(); |
671 | 0 | iinf_box->append_child_box(uuid_box); |
672 | |
|
673 | 0 | std::vector<uint8_t> track_uuid_vector; |
674 | 0 | track_uuid_vector.insert(track_uuid_vector.begin(), |
675 | 0 | options->gimi_track_content_id.c_str(), |
676 | 0 | options->gimi_track_content_id.c_str() + options->gimi_track_content_id.length() + 1); |
677 | |
|
678 | 0 | auto iloc_box = std::make_shared<Box_iloc>(); |
679 | 0 | iloc_box->append_data(1, track_uuid_vector, 1); |
680 | |
|
681 | 0 | auto meta_box = std::make_shared<Box_meta>(); |
682 | 0 | meta_box->append_child_box(hdlr_box); |
683 | 0 | meta_box->append_child_box(iinf_box); |
684 | 0 | meta_box->append_child_box(iloc_box); |
685 | |
|
686 | 0 | m_trak->append_child_box(meta_box); |
687 | 0 | } |
688 | 0 | } |
689 | 0 | } |
690 | | |
691 | | |
692 | | Result<std::shared_ptr<Track> > Track::alloc_track(HeifContext* ctx, const std::shared_ptr<Box_trak>& trak) |
693 | 41 | { |
694 | 41 | auto mdia = trak->get_child_box<Box_mdia>(); |
695 | 41 | if (!mdia) { |
696 | 9 | return Error{ |
697 | 9 | heif_error_Invalid_input, |
698 | 9 | heif_suberror_Unspecified, |
699 | 9 | "Track has no 'mdia' box." |
700 | 9 | }; |
701 | 9 | } |
702 | | |
703 | 32 | auto hdlr = mdia->get_child_box<Box_hdlr>(); |
704 | 32 | if (!hdlr) { |
705 | 8 | return Error{ |
706 | 8 | heif_error_Invalid_input, |
707 | 8 | heif_suberror_Unspecified, |
708 | 8 | "Track has no 'hdlr' box." |
709 | 8 | }; |
710 | 8 | } |
711 | | |
712 | 24 | std::shared_ptr<Track> track; |
713 | | |
714 | 24 | switch (hdlr->get_handler_type()) { |
715 | 6 | case fourcc("pict"): |
716 | 7 | case fourcc("vide"): |
717 | 7 | case fourcc("auxv"): |
718 | 7 | track = std::make_shared<Track_Visual>(ctx); |
719 | 7 | break; |
720 | 0 | case fourcc("meta"): |
721 | 0 | track = std::make_shared<Track_Metadata>(ctx); |
722 | 0 | break; |
723 | 17 | default: { |
724 | 17 | std::stringstream sstr; |
725 | 17 | sstr << "Track with unsupported handler type '" << fourcc_to_string(hdlr->get_handler_type()) << "'."; |
726 | 17 | return Error{ |
727 | 17 | heif_error_Unsupported_feature, |
728 | 17 | heif_suberror_Unsupported_track_type, |
729 | 17 | sstr.str() |
730 | 17 | }; |
731 | 7 | } |
732 | 24 | } |
733 | | |
734 | 24 | assert(track); |
735 | 7 | Error loadError = track->load(trak); |
736 | 7 | if (loadError) { |
737 | 7 | return loadError; |
738 | 7 | } |
739 | | |
740 | 0 | return {track}; |
741 | 7 | } |
742 | | |
743 | | |
744 | | bool Track::is_visual_track() const |
745 | 0 | { |
746 | 0 | return (m_handler_type == fourcc("pict") || |
747 | 0 | m_handler_type == fourcc("vide")); |
748 | 0 | } |
749 | | |
750 | | |
751 | | static const char* cAuxType_alpha_miaf = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; |
752 | | static const char* cAuxType_alpha_hevc = "urn:mpeg:hevc:2015:auxid:1"; |
753 | | static const char* cAuxType_alpha_avc = "urn:mpeg:avc:2015:auxid:1"; |
754 | | |
755 | | heif_auxiliary_track_info_type Track::get_auxiliary_info_type() const |
756 | 0 | { |
757 | 0 | if (m_auxiliary_info_type == cAuxType_alpha_miaf || |
758 | 0 | m_auxiliary_info_type == cAuxType_alpha_hevc || |
759 | 0 | m_auxiliary_info_type == cAuxType_alpha_avc) { |
760 | 0 | return heif_auxiliary_track_info_type_alpha; |
761 | 0 | } |
762 | 0 | else { |
763 | 0 | return heif_auxiliary_track_info_type_unknown; |
764 | 0 | } |
765 | 0 | } |
766 | | |
767 | | const char* get_track_auxiliary_info_type(heif_compression_format format) |
768 | 0 | { |
769 | 0 | switch (format) { |
770 | 0 | case heif_compression_HEVC: |
771 | 0 | return cAuxType_alpha_hevc; |
772 | 0 | case heif_compression_AVC: |
773 | 0 | return cAuxType_alpha_avc; |
774 | 0 | case heif_compression_AV1: |
775 | 0 | return cAuxType_alpha_miaf; |
776 | 0 | default: |
777 | 0 | return cAuxType_alpha_miaf; // TODO: is this correct for all remaining compression types ? |
778 | 0 | } |
779 | 0 | } |
780 | | |
781 | | // TODO: is this correct or should we set the aux_info_type depending on the compression format? |
782 | | void Track::set_auxiliary_info_type(heif_auxiliary_track_info_type type) |
783 | 0 | { |
784 | 0 | switch (type) { |
785 | 0 | case heif_auxiliary_track_info_type_alpha: |
786 | 0 | m_auxiliary_info_type = cAuxType_alpha_miaf; |
787 | 0 | break; |
788 | 0 | default: |
789 | 0 | m_auxiliary_info_type.clear(); |
790 | 0 | break; |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | | |
795 | | uint32_t Track::get_first_cluster_sample_entry_type() const |
796 | 0 | { |
797 | 0 | if (m_stsd->get_num_sample_entries() == 0) { |
798 | 0 | return 0; // TODO: error ? Or can we assume at this point that there is at least one sample entry? |
799 | 0 | } |
800 | | |
801 | 0 | return m_stsd->get_sample_entry(0)->get_short_type(); |
802 | 0 | } |
803 | | |
804 | | |
805 | | Result<std::string> Track::get_first_cluster_urim_uri() const |
806 | 0 | { |
807 | 0 | if (m_stsd->get_num_sample_entries() == 0) { |
808 | 0 | return Error{ |
809 | 0 | heif_error_Invalid_input, |
810 | 0 | heif_suberror_Unspecified, |
811 | 0 | "This track has no sample entries." |
812 | 0 | }; |
813 | 0 | } |
814 | | |
815 | 0 | std::shared_ptr<const Box> sampleEntry = m_stsd->get_sample_entry(0); |
816 | 0 | auto urim = std::dynamic_pointer_cast<const Box_URIMetaSampleEntry>(sampleEntry); |
817 | 0 | if (!urim) { |
818 | 0 | return Error{ |
819 | 0 | heif_error_Usage_error, |
820 | 0 | heif_suberror_Unspecified, |
821 | 0 | "This cluster is no 'urim' sample entry." |
822 | 0 | }; |
823 | 0 | } |
824 | | |
825 | 0 | std::shared_ptr<const Box_uri> uri = urim->get_child_box<const Box_uri>(); |
826 | 0 | if (!uri) { |
827 | 0 | return Error{ |
828 | 0 | heif_error_Invalid_input, |
829 | 0 | heif_suberror_Unspecified, |
830 | 0 | "The 'urim' box has no 'uri' child box." |
831 | 0 | }; |
832 | 0 | } |
833 | | |
834 | 0 | return uri->get_uri(); |
835 | 0 | } |
836 | | |
837 | | |
838 | | bool Track::end_of_sequence_reached() const |
839 | 0 | { |
840 | | //return (m_next_sample_to_be_processed > m_chunks.back()->last_sample_number()); |
841 | 0 | return m_next_sample_to_be_output >= m_num_output_samples; |
842 | 0 | } |
843 | | |
844 | | |
845 | | Error Track::finalize_track() |
846 | 0 | { |
847 | | // --- write active chunk data |
848 | |
|
849 | 0 | size_t data_start = m_heif_context->get_heif_file()->append_mdat_data(m_chunk_data); |
850 | 0 | m_chunk_data.clear(); |
851 | | |
852 | | // first sample in chunk? -> write chunk offset |
853 | |
|
854 | 0 | if (true) { |
855 | | // m_stsc->last_chunk_empty()) { |
856 | | // TODO: we will have to call this at the end of a chunk to dump the current SAI queue |
857 | | |
858 | | // if auxiliary data is interleaved, write it between the chunks |
859 | 0 | if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_interleaved(get_file()); |
860 | 0 | if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_interleaved(get_file()); |
861 | | |
862 | | // TODO |
863 | 0 | assert(data_start < 0xFF000000); // add some headroom for header data |
864 | 0 | m_stco->add_chunk_offset(static_cast<uint32_t>(data_start)); |
865 | 0 | } |
866 | | |
867 | | |
868 | | // --- write rest of data |
869 | |
|
870 | 0 | if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_all(m_stbl, get_file()); |
871 | 0 | if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_all(m_stbl, get_file()); |
872 | |
|
873 | 0 | uint64_t duration = m_stts->get_total_duration(true); |
874 | 0 | m_mdhd->set_duration(duration); |
875 | |
|
876 | 0 | m_stss->set_total_number_of_samples(m_stsz->num_samples()); |
877 | | |
878 | | // only add ctts box if we use frame-reordering |
879 | 0 | if (!m_ctts->is_constant_offset()) { |
880 | 0 | m_stbl->append_child_box(m_ctts); |
881 | 0 | } |
882 | |
|
883 | 0 | return {}; |
884 | 0 | } |
885 | | |
886 | | |
887 | | uint64_t Track::get_duration_in_media_units() const |
888 | 0 | { |
889 | 0 | return m_mdhd->get_duration(); |
890 | 0 | } |
891 | | |
892 | | |
893 | | uint32_t Track::get_timescale() const |
894 | 0 | { |
895 | 0 | return m_mdhd->get_timescale(); |
896 | 0 | } |
897 | | |
898 | | |
899 | | void Track::set_track_duration_in_movie_units(uint64_t total_duration, uint64_t segment_duration) |
900 | 0 | { |
901 | 0 | m_tkhd->set_duration(total_duration); |
902 | |
|
903 | 0 | if (m_elst) { |
904 | 0 | Box_elst::Entry entry; |
905 | 0 | entry.segment_duration = segment_duration; |
906 | |
|
907 | 0 | m_elst->add_entry(entry); |
908 | 0 | } |
909 | 0 | } |
910 | | |
911 | | |
912 | | void Track::enable_edit_list_repeat_mode(bool enable) |
913 | 0 | { |
914 | 0 | if (!m_elst) { |
915 | 0 | if (!enable) { |
916 | 0 | return; |
917 | 0 | } |
918 | | |
919 | 0 | auto edts = std::make_shared<Box_edts>(); |
920 | 0 | m_trak->append_child_box(edts); |
921 | |
|
922 | 0 | m_elst = std::make_shared<Box_elst>(); |
923 | 0 | edts->append_child_box(m_elst); |
924 | |
|
925 | 0 | m_elst->enable_repeat_mode(enable); |
926 | 0 | } |
927 | 0 | } |
928 | | |
929 | | |
930 | | void Track::add_chunk(heif_compression_format format) |
931 | 0 | { |
932 | 0 | auto chunk = std::make_shared<Chunk>(m_heif_context, m_id, format); |
933 | 0 | m_chunks.push_back(chunk); |
934 | |
|
935 | 0 | int chunkIdx = (uint32_t) m_chunks.size(); |
936 | 0 | m_stsc->add_chunk(chunkIdx); |
937 | 0 | } |
938 | | |
939 | | void Track::set_sample_description_box(std::shared_ptr<Box> sample_description_box) |
940 | 0 | { |
941 | | // --- add 'taic' when we store timestamps as sample auxiliary information |
942 | |
|
943 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
944 | 0 | auto taic = std::make_shared<Box_taic>(); |
945 | 0 | taic->set_from_tai_clock_info(m_track_info.tai_clock_info); |
946 | 0 | sample_description_box->append_child_box(taic); |
947 | 0 | } |
948 | |
|
949 | 0 | m_stsd->add_sample_entry(sample_description_box); |
950 | 0 | } |
951 | | |
952 | | |
953 | | Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sample_duration, |
954 | | int32_t composition_time_offset, |
955 | | bool is_sync_sample, |
956 | | const heif_tai_timestamp_packet* tai, const std::optional<std::string>& gimi_contentID) |
957 | 0 | { |
958 | 0 | m_chunk_data.insert(m_chunk_data.end(), raw_data.begin(), raw_data.end()); |
959 | |
|
960 | 0 | m_stsc->increase_samples_in_chunk(1); |
961 | |
|
962 | 0 | m_stsz->append_sample_size((uint32_t) raw_data.size()); |
963 | |
|
964 | 0 | if (is_sync_sample) { |
965 | 0 | m_stss->add_sync_sample(m_next_sample_to_be_output + 1); |
966 | 0 | } |
967 | |
|
968 | 0 | if (sample_duration == 0) { |
969 | 0 | return { |
970 | 0 | heif_error_Usage_error, |
971 | 0 | heif_suberror_Unspecified, |
972 | 0 | "Sample duration may not be 0" |
973 | 0 | }; |
974 | 0 | } |
975 | | |
976 | 0 | m_stts->append_sample_duration(sample_duration); |
977 | 0 | m_ctts->append_sample_offset(composition_time_offset); |
978 | | |
979 | | |
980 | | // --- sample timestamp |
981 | |
|
982 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
983 | 0 | if (tai) { |
984 | 0 | std::vector<uint8_t> tai_data = Box_itai::encode_tai_to_bitstream(tai); |
985 | 0 | auto err = m_aux_helper_tai_timestamps->add_sample_info(tai_data); |
986 | 0 | if (err) { |
987 | 0 | return err; |
988 | 0 | } |
989 | 0 | } |
990 | 0 | else if (m_track_info.with_sample_tai_timestamps == heif_sample_aux_info_presence_optional) { |
991 | 0 | m_aux_helper_tai_timestamps->add_nonpresent_sample(); |
992 | 0 | } |
993 | 0 | else { |
994 | 0 | return { |
995 | 0 | heif_error_Encoding_error, |
996 | 0 | heif_suberror_Unspecified, |
997 | 0 | "Mandatory TAI timestamp missing" |
998 | 0 | }; |
999 | 0 | } |
1000 | 0 | } |
1001 | | |
1002 | | // --- sample content id |
1003 | | |
1004 | 0 | if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) { |
1005 | 0 | if (gimi_contentID) { |
1006 | 0 | auto id = *gimi_contentID; |
1007 | 0 | const char* id_str = id.c_str(); |
1008 | 0 | std::vector<uint8_t> id_vector; |
1009 | 0 | id_vector.insert(id_vector.begin(), id_str, id_str + id.length() + 1); |
1010 | 0 | auto err = m_aux_helper_content_ids->add_sample_info(id_vector); |
1011 | 0 | if (err) { |
1012 | 0 | return err; |
1013 | 0 | } |
1014 | 0 | } |
1015 | 0 | else if (m_track_info.with_sample_content_ids == heif_sample_aux_info_presence_optional) { |
1016 | 0 | m_aux_helper_content_ids->add_nonpresent_sample(); |
1017 | 0 | } |
1018 | 0 | else { |
1019 | 0 | return { |
1020 | 0 | heif_error_Encoding_error, |
1021 | 0 | heif_suberror_Unspecified, |
1022 | 0 | "Mandatory ContentID missing" |
1023 | 0 | }; |
1024 | 0 | } |
1025 | 0 | } |
1026 | | |
1027 | 0 | m_next_sample_to_be_output++; |
1028 | |
|
1029 | 0 | return Error::Ok; |
1030 | 0 | } |
1031 | | |
1032 | | |
1033 | | void Track::add_reference_to_track(uint32_t referenceType, uint32_t to_track_id) |
1034 | 0 | { |
1035 | 0 | if (!m_tref) { |
1036 | 0 | m_tref = std::make_shared<Box_tref>(); |
1037 | 0 | m_trak->append_child_box(m_tref); |
1038 | 0 | } |
1039 | |
|
1040 | 0 | m_tref->add_references(to_track_id, referenceType); |
1041 | 0 | } |
1042 | | |
1043 | | |
1044 | | Error Track::init_sample_timing_table() |
1045 | 0 | { |
1046 | 0 | m_num_samples = m_stsz->num_samples(); |
1047 | | |
1048 | | // --- build media timeline |
1049 | |
|
1050 | 0 | std::vector<SampleTiming> media_timeline; |
1051 | |
|
1052 | 0 | uint64_t current_decoding_time = 0; |
1053 | 0 | uint32_t current_chunk = 0; |
1054 | 0 | uint32_t current_sample_in_chunk_idx = 0; |
1055 | |
|
1056 | 0 | for (uint32_t i = 0; i < m_num_samples; i++) { |
1057 | 0 | SampleTiming timing; |
1058 | 0 | timing.sampleIdx = i; |
1059 | 0 | timing.sampleInChunkIdx = current_sample_in_chunk_idx; |
1060 | 0 | timing.media_decoding_time = current_decoding_time; |
1061 | 0 | timing.sample_duration_media_time = m_stts->get_sample_duration(i); |
1062 | 0 | current_decoding_time += timing.sample_duration_media_time; |
1063 | 0 | current_sample_in_chunk_idx++; |
1064 | |
|
1065 | 0 | while (current_chunk < m_chunks.size() && |
1066 | 0 | i > m_chunks[current_chunk]->last_sample_number()) { |
1067 | 0 | current_chunk++; |
1068 | 0 | current_sample_in_chunk_idx = 0; |
1069 | 0 | } |
1070 | |
|
1071 | 0 | if (current_chunk >= m_chunks.size()) { |
1072 | 0 | return { |
1073 | 0 | heif_error_Invalid_input, |
1074 | 0 | heif_suberror_Unspecified, |
1075 | 0 | "Sample not covered by any chunk." |
1076 | 0 | }; |
1077 | 0 | } |
1078 | | |
1079 | 0 | timing.chunkIdx = current_chunk; |
1080 | |
|
1081 | 0 | media_timeline.push_back(timing); |
1082 | 0 | } |
1083 | | |
1084 | | // --- build presentation timeline from editlist |
1085 | | |
1086 | 0 | bool fallback = false; |
1087 | |
|
1088 | 0 | if (m_heif_context->get_sequence_timescale() != get_timescale()) { |
1089 | 0 | fallback = true; |
1090 | 0 | } |
1091 | 0 | else if (m_elst && |
1092 | 0 | m_elst->num_entries() == 1 && |
1093 | 0 | m_elst->get_entry(0).media_time == 0 && |
1094 | 0 | m_elst->get_entry(0).segment_duration == m_mdhd->get_duration() && |
1095 | 0 | m_elst->is_repeat_mode()) { |
1096 | 0 | m_presentation_timeline = media_timeline; |
1097 | |
|
1098 | 0 | uint64_t duration_media_units = get_duration_in_media_units(); |
1099 | 0 | if (duration_media_units == 0) { |
1100 | 0 | return { |
1101 | 0 | heif_error_Invalid_input, |
1102 | 0 | heif_suberror_Unspecified, |
1103 | 0 | "Track duration is zero." |
1104 | 0 | }; |
1105 | 0 | } |
1106 | | |
1107 | 0 | uint64_t multiplier = m_heif_context->get_sequence_duration() / get_duration_in_media_units(); |
1108 | 0 | m_num_output_samples = multiplier * media_timeline.size(); |
1109 | |
|
1110 | 0 | if (m_heif_context->is_sequence_duration_indefinite()) { |
1111 | | // mvhd carries the all-1s sentinel -> editlist repeats forever. |
1112 | 0 | m_num_repetitions = std::numeric_limits<uint32_t>::max(); |
1113 | 0 | } |
1114 | 0 | else if (multiplier >= std::numeric_limits<uint32_t>::max()) { |
1115 | | // Doesn't fit in the API's uint32_t; treat as effectively infinite. |
1116 | 0 | m_num_repetitions = std::numeric_limits<uint32_t>::max(); |
1117 | 0 | } |
1118 | 0 | else { |
1119 | 0 | m_num_repetitions = static_cast<uint32_t>(multiplier); |
1120 | 0 | } |
1121 | 0 | } |
1122 | 0 | else { |
1123 | 0 | fallback = true; |
1124 | 0 | } |
1125 | | |
1126 | | // Fallback: just play the media timeline |
1127 | 0 | if (fallback) { |
1128 | 0 | m_presentation_timeline = media_timeline; |
1129 | 0 | m_num_output_samples = media_timeline.size(); |
1130 | | // No editlist box at all: the media plays exactly once. |
1131 | | // Editlist box present but its pattern isn't one libheif interprets: report |
1132 | | // 0 so callers know they should not rely on a repetition count. |
1133 | 0 | m_num_repetitions = m_elst ? 0 : 1; |
1134 | 0 | } |
1135 | |
|
1136 | 0 | return {}; |
1137 | 0 | } |
1138 | | |
1139 | | |
1140 | | Result<heif_raw_sequence_sample*> Track::get_next_sample_raw_data(const heif_decoding_options* options) |
1141 | 0 | { |
1142 | 0 | uint64_t num_output_samples = m_num_output_samples; |
1143 | 0 | if (options && options->ignore_sequence_editlist) { |
1144 | 0 | num_output_samples = m_num_samples; |
1145 | 0 | } |
1146 | |
|
1147 | 0 | if (m_next_sample_to_be_output >= num_output_samples) { |
1148 | 0 | return Error{ |
1149 | 0 | heif_error_End_of_sequence, |
1150 | 0 | heif_suberror_Unspecified, |
1151 | 0 | "End of sequence" |
1152 | 0 | }; |
1153 | 0 | } |
1154 | | |
1155 | 0 | const auto& sampleTiming = m_presentation_timeline[m_next_sample_to_be_output % m_presentation_timeline.size()]; |
1156 | 0 | uint32_t sample_idx = sampleTiming.sampleIdx; |
1157 | 0 | uint32_t chunk_idx = sampleTiming.chunkIdx; |
1158 | |
|
1159 | 0 | const std::shared_ptr<Chunk>& chunk = m_chunks[chunk_idx]; |
1160 | |
|
1161 | 0 | DataExtent extent = chunk->get_data_extent_for_sample(sample_idx); |
1162 | 0 | auto readResult = extent.read_data(); |
1163 | 0 | if (!readResult) { |
1164 | 0 | return readResult.error(); |
1165 | 0 | } |
1166 | | |
1167 | 0 | heif_raw_sequence_sample* sample = new heif_raw_sequence_sample(); |
1168 | 0 | sample->data = **readResult; |
1169 | | |
1170 | | // read sample duration |
1171 | |
|
1172 | 0 | if (m_stts) { |
1173 | 0 | sample->duration = m_stts->get_sample_duration(sample_idx); |
1174 | 0 | } |
1175 | | |
1176 | | // --- read sample auxiliary data |
1177 | |
|
1178 | 0 | if (m_aux_reader_content_ids) { |
1179 | 0 | auto readResult = m_aux_reader_content_ids->get_sample_info(get_file().get(), sample_idx); |
1180 | 0 | if (!readResult) { |
1181 | 0 | return readResult.error(); |
1182 | 0 | } |
1183 | | |
1184 | 0 | if (!readResult->empty()) { |
1185 | 0 | Result<std::string> convResult = vector_to_string(*readResult); |
1186 | 0 | if (!convResult) { |
1187 | 0 | return convResult.error(); |
1188 | 0 | } |
1189 | | |
1190 | 0 | sample->gimi_sample_content_id = *convResult; |
1191 | 0 | } |
1192 | 0 | } |
1193 | | |
1194 | 0 | if (m_aux_reader_tai_timestamps) { |
1195 | 0 | auto readResult = m_aux_reader_tai_timestamps->get_sample_info(get_file().get(), sample_idx); |
1196 | 0 | if (!readResult) { |
1197 | 0 | return readResult.error(); |
1198 | 0 | } |
1199 | | |
1200 | 0 | if (!readResult->empty()) { |
1201 | 0 | auto resultTai = Box_itai::decode_tai_from_vector(*readResult); |
1202 | 0 | if (!resultTai) { |
1203 | 0 | return resultTai.error(); |
1204 | 0 | } |
1205 | | |
1206 | 0 | sample->timestamp = heif_tai_timestamp_packet_alloc(); |
1207 | 0 | heif_tai_timestamp_packet_copy(sample->timestamp, &*resultTai); |
1208 | 0 | } |
1209 | 0 | } |
1210 | | |
1211 | 0 | m_next_sample_to_be_output++; |
1212 | |
|
1213 | 0 | return sample; |
1214 | 0 | } |
1215 | | |
1216 | | |
1217 | | std::vector<heif_sample_aux_info_type> Track::get_sample_aux_info_types() const |
1218 | 0 | { |
1219 | 0 | std::vector<heif_sample_aux_info_type> types; |
1220 | |
|
1221 | 0 | if (m_aux_reader_tai_timestamps) types.emplace_back(m_aux_reader_tai_timestamps->get_type()); |
1222 | 0 | if (m_aux_reader_content_ids) types.emplace_back(m_aux_reader_content_ids->get_type()); |
1223 | |
|
1224 | 0 | return types; |
1225 | 0 | } |