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