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