/src/libheif/libheif/sequences/track.cc
Line | Count | Source |
1 | | /* |
2 | | * HEIF image base codec. |
3 | | * Copyright (c) 2024 Dirk Farin <dirk.farin@gmail.com> |
4 | | * |
5 | | * This file is part of libheif. |
6 | | * |
7 | | * libheif is free software: you can redistribute it and/or modify |
8 | | * it under the terms of the GNU Lesser General Public License as |
9 | | * published by the Free Software Foundation, either version 3 of |
10 | | * the License, or (at your option) any later version. |
11 | | * |
12 | | * libheif is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU Lesser General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU Lesser General Public License |
18 | | * along with libheif. If not, see <http://www.gnu.org/licenses/>. |
19 | | */ |
20 | | |
21 | | #include <cstring> |
22 | | #include "track.h" |
23 | | #include "context.h" |
24 | | #include "sequences/seq_boxes.h" |
25 | | #include "sequences/chunk.h" |
26 | | #include "sequences/track_visual.h" |
27 | | #include "sequences/track_metadata.h" |
28 | | #include "api_structs.h" |
29 | | #include <limits> |
30 | | |
31 | | |
32 | | TrackOptions& TrackOptions::operator=(const TrackOptions& src) |
33 | 0 | { |
34 | 0 | if (&src == this) { |
35 | 0 | return *this; |
36 | 0 | } |
37 | | |
38 | 0 | this->track_timescale = src.track_timescale; |
39 | 0 | this->write_sample_aux_infos_interleaved = src.write_sample_aux_infos_interleaved; |
40 | 0 | this->with_sample_tai_timestamps = src.with_sample_tai_timestamps; |
41 | |
|
42 | 0 | if (src.tai_clock_info) { |
43 | 0 | this->tai_clock_info = heif_tai_clock_info_alloc(); |
44 | 0 | heif_tai_clock_info_copy(this->tai_clock_info, src.tai_clock_info); |
45 | 0 | } |
46 | 0 | else { |
47 | 0 | this->tai_clock_info = nullptr; |
48 | 0 | } |
49 | |
|
50 | 0 | this->with_sample_content_ids = src.with_sample_content_ids; |
51 | 0 | this->gimi_track_content_id = src.gimi_track_content_id; |
52 | |
|
53 | 0 | return *this; |
54 | 0 | } |
55 | | |
56 | | |
57 | | SampleAuxInfoHelper::SampleAuxInfoHelper(bool interleaved) |
58 | 0 | : m_interleaved(interleaved) |
59 | 0 | { |
60 | 0 | m_saiz = std::make_shared<Box_saiz>(); |
61 | 0 | m_saio = std::make_shared<Box_saio>(); |
62 | 0 | } |
63 | | |
64 | | |
65 | | void SampleAuxInfoHelper::set_aux_info_type(uint32_t aux_info_type, uint32_t aux_info_type_parameter) |
66 | 0 | { |
67 | 0 | m_saiz->set_aux_info_type(aux_info_type, aux_info_type_parameter); |
68 | 0 | m_saio->set_aux_info_type(aux_info_type, aux_info_type_parameter); |
69 | 0 | } |
70 | | |
71 | | Error SampleAuxInfoHelper::add_sample_info(const std::vector<uint8_t>& data) |
72 | 0 | { |
73 | 0 | if (data.size() > 0xFF) { |
74 | 0 | return {heif_error_Encoding_error, |
75 | 0 | heif_suberror_Unspecified, |
76 | 0 | "Encoded sample auxiliary information exceeds maximum size"}; |
77 | 0 | } |
78 | | |
79 | 0 | m_saiz->add_sample_size(static_cast<uint8_t>(data.size())); |
80 | |
|
81 | 0 | m_data.insert(m_data.end(), data.begin(), data.end()); |
82 | |
|
83 | 0 | return Error::Ok; |
84 | 0 | } |
85 | | |
86 | | void SampleAuxInfoHelper::add_nonpresent_sample() |
87 | 0 | { |
88 | 0 | m_saiz->add_nonpresent_sample(); |
89 | 0 | } |
90 | | |
91 | | |
92 | | void SampleAuxInfoHelper::write_interleaved(const std::shared_ptr<HeifFile>& file) |
93 | 0 | { |
94 | 0 | if (m_interleaved && !m_data.empty()) { |
95 | 0 | uint64_t pos = file->append_mdat_data(m_data); |
96 | 0 | m_saio->add_sample_offset(pos); |
97 | |
|
98 | 0 | m_data.clear(); |
99 | 0 | } |
100 | 0 | } |
101 | | |
102 | | void SampleAuxInfoHelper::write_all(const std::shared_ptr<Box>& parent, const std::shared_ptr<HeifFile>& file) |
103 | 0 | { |
104 | 0 | parent->append_child_box(m_saiz); |
105 | 0 | parent->append_child_box(m_saio); |
106 | |
|
107 | 0 | if (!m_data.empty()) { |
108 | 0 | uint64_t pos = file->append_mdat_data(m_data); |
109 | 0 | m_saio->add_sample_offset(pos); |
110 | 0 | } |
111 | 0 | } |
112 | | |
113 | | |
114 | | SampleAuxInfoReader::SampleAuxInfoReader(std::shared_ptr<Box_saiz> saiz, |
115 | | std::shared_ptr<Box_saio> saio) |
116 | 0 | { |
117 | 0 | m_saiz = saiz; |
118 | 0 | m_saio = saio; |
119 | |
|
120 | 0 | m_contiguous = (saio->get_num_samples() == 1); |
121 | 0 | if (m_contiguous) { |
122 | 0 | uint64_t offset = saio->get_sample_offset(0); |
123 | 0 | auto nSamples = saiz->get_num_samples(); |
124 | |
|
125 | 0 | for (uint32_t i=0;i<nSamples;i++) { |
126 | 0 | m_contiguous_offsets.push_back(offset); |
127 | 0 | offset += saiz->get_sample_size(i); |
128 | 0 | } |
129 | | |
130 | | // TODO: we could add a special case for contiguous data with constant size |
131 | 0 | } |
132 | 0 | } |
133 | | |
134 | | |
135 | | heif_sample_aux_info_type SampleAuxInfoReader::get_type() const |
136 | 0 | { |
137 | 0 | heif_sample_aux_info_type type; |
138 | 0 | type.type = m_saiz->get_aux_info_type(); |
139 | 0 | type.parameter = m_saiz->get_aux_info_type_parameter(); |
140 | 0 | return type; |
141 | 0 | } |
142 | | |
143 | | |
144 | | Result<std::vector<uint8_t>> SampleAuxInfoReader::get_sample_info(const HeifFile* file, uint32_t idx) |
145 | 0 | { |
146 | 0 | uint64_t offset; |
147 | 0 | if (m_contiguous) { |
148 | 0 | offset = m_contiguous_offsets[idx]; |
149 | 0 | } |
150 | 0 | else { |
151 | 0 | offset = m_saio->get_sample_offset(idx); |
152 | 0 | } |
153 | |
|
154 | 0 | uint8_t size = m_saiz->get_sample_size(idx); |
155 | |
|
156 | 0 | std::vector<uint8_t> data; |
157 | 0 | Error err = file->append_data_from_file_range(data, offset, size); |
158 | 0 | if (err) { |
159 | 0 | return err; |
160 | 0 | } |
161 | | |
162 | 0 | return data; |
163 | 0 | } |
164 | | |
165 | | |
166 | | std::shared_ptr<HeifFile> Track::get_file() const |
167 | 0 | { |
168 | 0 | return m_heif_context->get_heif_file(); |
169 | 0 | } |
170 | | |
171 | | |
172 | | Track::Track(HeifContext* ctx) |
173 | 0 | { |
174 | 0 | m_heif_context = ctx; |
175 | 0 | } |
176 | | |
177 | | |
178 | | Error Track::load(const std::shared_ptr<Box_trak>& trak_box) |
179 | 0 | { |
180 | 0 | m_trak = trak_box; |
181 | |
|
182 | 0 | auto tkhd = trak_box->get_child_box<Box_tkhd>(); |
183 | 0 | if (!tkhd) { |
184 | 0 | return { |
185 | 0 | heif_error_Invalid_input, |
186 | 0 | heif_suberror_Unspecified, |
187 | 0 | "Track has no 'tkhd' box." |
188 | 0 | }; |
189 | 0 | } |
190 | | |
191 | 0 | m_id = tkhd->get_track_id(); |
192 | |
|
193 | 0 | auto edts = trak_box->get_child_box<Box_edts>(); |
194 | 0 | if (edts) { |
195 | 0 | m_elst = edts->get_child_box<Box_elst>(); |
196 | 0 | } |
197 | |
|
198 | 0 | auto mdia = trak_box->get_child_box<Box_mdia>(); |
199 | 0 | if (!mdia) { |
200 | 0 | return { |
201 | 0 | heif_error_Invalid_input, |
202 | 0 | heif_suberror_Unspecified, |
203 | 0 | "Track has no 'mdia' box." |
204 | 0 | }; |
205 | 0 | } |
206 | | |
207 | 0 | m_tref = trak_box->get_child_box<Box_tref>(); |
208 | |
|
209 | 0 | auto hdlr = mdia->get_child_box<Box_hdlr>(); |
210 | 0 | if (!hdlr) { |
211 | 0 | return { |
212 | 0 | heif_error_Invalid_input, |
213 | 0 | heif_suberror_Unspecified, |
214 | 0 | "Track has no 'hdlr' box." |
215 | 0 | }; |
216 | 0 | } |
217 | | |
218 | 0 | m_handler_type = hdlr->get_handler_type(); |
219 | |
|
220 | 0 | m_minf = mdia->get_child_box<Box_minf>(); |
221 | 0 | if (!m_minf) { |
222 | 0 | return { |
223 | 0 | heif_error_Invalid_input, |
224 | 0 | heif_suberror_Unspecified, |
225 | 0 | "Track has no 'minf' box." |
226 | 0 | }; |
227 | 0 | } |
228 | | |
229 | 0 | m_mdhd = mdia->get_child_box<Box_mdhd>(); |
230 | 0 | if (!m_mdhd) { |
231 | 0 | return { |
232 | 0 | heif_error_Invalid_input, |
233 | 0 | heif_suberror_Unspecified, |
234 | 0 | "Track has no 'mdhd' box." |
235 | 0 | }; |
236 | 0 | } |
237 | | |
238 | 0 | auto stbl = m_minf->get_child_box<Box_stbl>(); |
239 | 0 | if (!stbl) { |
240 | 0 | return { |
241 | 0 | heif_error_Invalid_input, |
242 | 0 | heif_suberror_Unspecified, |
243 | 0 | "Track has no 'stbl' box." |
244 | 0 | }; |
245 | 0 | } |
246 | | |
247 | 0 | m_stsd = stbl->get_child_box<Box_stsd>(); |
248 | 0 | if (!m_stsd) { |
249 | 0 | return { |
250 | 0 | heif_error_Invalid_input, |
251 | 0 | heif_suberror_Unspecified, |
252 | 0 | "Track has no 'stsd' box." |
253 | 0 | }; |
254 | 0 | } |
255 | | |
256 | 0 | m_stsc = stbl->get_child_box<Box_stsc>(); |
257 | 0 | if (!m_stsc) { |
258 | 0 | return { |
259 | 0 | heif_error_Invalid_input, |
260 | 0 | heif_suberror_Unspecified, |
261 | 0 | "Track has no 'stsc' box." |
262 | 0 | }; |
263 | 0 | } |
264 | | |
265 | 0 | m_stco = stbl->get_child_box<Box_stco>(); |
266 | 0 | if (!m_stco) { |
267 | 0 | return { |
268 | 0 | heif_error_Invalid_input, |
269 | 0 | heif_suberror_Unspecified, |
270 | 0 | "Track has no 'stco' box." |
271 | 0 | }; |
272 | 0 | } |
273 | | |
274 | 0 | m_stsz = stbl->get_child_box<Box_stsz>(); |
275 | 0 | if (!m_stsz) { |
276 | 0 | return { |
277 | 0 | heif_error_Invalid_input, |
278 | 0 | heif_suberror_Unspecified, |
279 | 0 | "Track has no 'stsz' box." |
280 | 0 | }; |
281 | 0 | } |
282 | | |
283 | 0 | m_stts = stbl->get_child_box<Box_stts>(); |
284 | |
|
285 | 0 | const std::vector<uint32_t>& chunk_offsets = m_stco->get_offsets(); |
286 | 0 | assert(chunk_offsets.size() <= (size_t) std::numeric_limits<uint32_t>::max()); // There cannot be more than uint32_t chunks. |
287 | |
|
288 | 0 | uint32_t current_sample_idx = 0; |
289 | 0 | int32_t previous_sample_description_index = -1; |
290 | |
|
291 | 0 | for (size_t chunk_idx = 0; chunk_idx < chunk_offsets.size(); chunk_idx++) { |
292 | 0 | auto* s2c = m_stsc->get_chunk(static_cast<uint32_t>(chunk_idx + 1)); |
293 | 0 | if (!s2c) { |
294 | 0 | return { |
295 | 0 | heif_error_Invalid_input, |
296 | 0 | heif_suberror_Unspecified, |
297 | 0 | "'stco' box references a non-existing chunk." |
298 | 0 | }; |
299 | 0 | } |
300 | | |
301 | 0 | Box_stsc::SampleToChunk sampleToChunk = *s2c; |
302 | |
|
303 | 0 | auto sample_description = m_stsd->get_sample_entry(sampleToChunk.sample_description_index - 1); |
304 | 0 | if (!sample_description) { |
305 | 0 | return { |
306 | 0 | heif_error_Invalid_input, |
307 | 0 | heif_suberror_Unspecified, |
308 | 0 | "Track references a non-existing sample description." |
309 | 0 | }; |
310 | 0 | } |
311 | | |
312 | 0 | if (auto auxi = sample_description->get_child_box<Box_auxi>()) { |
313 | 0 | m_auxiliary_info_type = auxi->get_aux_track_type_urn(); |
314 | 0 | } |
315 | |
|
316 | 0 | if (m_first_taic == nullptr) { |
317 | 0 | auto taic = sample_description->get_child_box<Box_taic>(); |
318 | 0 | if (taic) { |
319 | 0 | m_first_taic = taic; |
320 | 0 | } |
321 | 0 | } |
322 | |
|
323 | 0 | auto chunk = std::make_shared<Chunk>(m_heif_context, m_id, |
324 | 0 | current_sample_idx, sampleToChunk.samples_per_chunk, |
325 | 0 | m_stco->get_offsets()[chunk_idx], |
326 | 0 | m_stsz); |
327 | |
|
328 | 0 | if (auto visualSampleDescription = std::dynamic_pointer_cast<const Box_VisualSampleEntry>(sample_description)) { |
329 | 0 | if (chunk_idx > 0 && (int32_t) sampleToChunk.sample_description_index == previous_sample_description_index) { |
330 | | // reuse decoder from previous chunk if it uses the sample sample_description_index |
331 | 0 | chunk->set_decoder(m_chunks[chunk_idx - 1]->get_decoder()); |
332 | 0 | } |
333 | 0 | else { |
334 | | // use a new decoder |
335 | 0 | chunk->set_decoder(Decoder::alloc_for_sequence_sample_description_box(visualSampleDescription)); |
336 | 0 | } |
337 | 0 | } |
338 | |
|
339 | 0 | m_chunks.push_back(chunk); |
340 | |
|
341 | 0 | current_sample_idx += sampleToChunk.samples_per_chunk; |
342 | 0 | previous_sample_description_index = sampleToChunk.sample_description_index; |
343 | 0 | } |
344 | | |
345 | | // --- read sample auxiliary information boxes |
346 | | |
347 | 0 | std::vector<std::shared_ptr<Box_saiz>> saiz_boxes = stbl->get_child_boxes<Box_saiz>(); |
348 | 0 | std::vector<std::shared_ptr<Box_saio>> saio_boxes = stbl->get_child_boxes<Box_saio>(); |
349 | |
|
350 | 0 | for (const auto& saiz : saiz_boxes) { |
351 | 0 | uint32_t aux_info_type = saiz->get_aux_info_type(); |
352 | 0 | uint32_t aux_info_type_parameter = saiz->get_aux_info_type_parameter(); |
353 | | |
354 | | // find the corresponding saio box |
355 | |
|
356 | 0 | std::shared_ptr<Box_saio> saio; |
357 | 0 | for (const auto& candidate : saio_boxes) { |
358 | 0 | if (candidate->get_aux_info_type() == aux_info_type && |
359 | 0 | candidate->get_aux_info_type_parameter() == aux_info_type_parameter) { |
360 | 0 | saio = candidate; |
361 | 0 | break; |
362 | 0 | } |
363 | 0 | } |
364 | |
|
365 | 0 | if (saio) { |
366 | 0 | if (aux_info_type == fourcc("suid")) { |
367 | 0 | m_aux_reader_content_ids = std::make_unique<SampleAuxInfoReader>(saiz, saio); |
368 | 0 | } |
369 | |
|
370 | 0 | if (aux_info_type == fourcc("stai")) { |
371 | 0 | m_aux_reader_tai_timestamps = std::make_unique<SampleAuxInfoReader>(saiz, saio); |
372 | 0 | } |
373 | 0 | } |
374 | 0 | } |
375 | | |
376 | | // --- read track properties |
377 | |
|
378 | 0 | if (auto meta = trak_box->get_child_box<Box_meta>()) { |
379 | 0 | auto iloc = meta->get_child_box<Box_iloc>(); |
380 | 0 | auto idat = meta->get_child_box<Box_idat>(); |
381 | |
|
382 | 0 | auto iinf = meta->get_child_box<Box_iinf>(); |
383 | 0 | if (iinf) { |
384 | 0 | auto infe_boxes = iinf->get_child_boxes<Box_infe>(); |
385 | 0 | for (const auto& box : infe_boxes) { |
386 | 0 | if (box->get_item_type_4cc() == fourcc("uri ") && |
387 | 0 | box->get_item_uri_type() == "urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73") { |
388 | 0 | heif_item_id id = box->get_item_ID(); |
389 | |
|
390 | 0 | std::vector<uint8_t> data; |
391 | 0 | Error err = iloc->read_data(id, m_heif_context->get_heif_file()->get_reader(), idat, &data, m_heif_context->get_security_limits()); |
392 | 0 | if (err) { |
393 | | // TODO |
394 | 0 | } |
395 | |
|
396 | 0 | Result contentIdResult = vector_to_string(data); |
397 | 0 | if (!contentIdResult) { |
398 | | // TODO |
399 | 0 | } |
400 | |
|
401 | 0 | m_track_info.gimi_track_content_id = *contentIdResult; |
402 | 0 | } |
403 | 0 | } |
404 | 0 | } |
405 | 0 | } |
406 | |
|
407 | 0 | init_sample_timing_table(); |
408 | |
|
409 | 0 | return {}; |
410 | 0 | } |
411 | | |
412 | | |
413 | | Track::Track(HeifContext* ctx, uint32_t track_id, const TrackOptions* options, uint32_t handler_type) |
414 | 0 | { |
415 | 0 | m_heif_context = ctx; |
416 | |
|
417 | 0 | m_moov = ctx->get_heif_file()->get_moov_box(); |
418 | 0 | assert(m_moov); |
419 | | |
420 | | // --- find next free track ID |
421 | |
|
422 | 0 | if (track_id == 0) { |
423 | 0 | track_id = 1; // minimum track ID |
424 | |
|
425 | 0 | for (const auto& track : m_moov->get_child_boxes<Box_trak>()) { |
426 | 0 | auto tkhd = track->get_child_box<Box_tkhd>(); |
427 | |
|
428 | 0 | if (tkhd->get_track_id() >= track_id) { |
429 | 0 | track_id = tkhd->get_track_id() + 1; |
430 | 0 | } |
431 | 0 | } |
432 | |
|
433 | 0 | auto mvhd = m_moov->get_child_box<Box_mvhd>(); |
434 | 0 | mvhd->set_next_track_id(track_id + 1); |
435 | |
|
436 | 0 | m_id = track_id; |
437 | 0 | } |
438 | |
|
439 | 0 | m_trak = std::make_shared<Box_trak>(); |
440 | 0 | m_moov->append_child_box(m_trak); |
441 | |
|
442 | 0 | m_tkhd = std::make_shared<Box_tkhd>(); |
443 | 0 | m_trak->append_child_box(m_tkhd); |
444 | 0 | m_tkhd->set_track_id(track_id); |
445 | |
|
446 | 0 | auto mdia = std::make_shared<Box_mdia>(); |
447 | 0 | m_trak->append_child_box(mdia); |
448 | |
|
449 | 0 | m_mdhd = std::make_shared<Box_mdhd>(); |
450 | 0 | m_mdhd->set_timescale(options ? options->track_timescale : 90000); |
451 | 0 | mdia->append_child_box(m_mdhd); |
452 | |
|
453 | 0 | m_hdlr = std::make_shared<Box_hdlr>(); |
454 | 0 | mdia->append_child_box(m_hdlr); |
455 | 0 | m_hdlr->set_handler_type(handler_type); |
456 | |
|
457 | 0 | m_minf = std::make_shared<Box_minf>(); |
458 | 0 | mdia->append_child_box(m_minf); |
459 | | |
460 | | // vmhd is added in Track_Visual |
461 | |
|
462 | 0 | m_stbl = std::make_shared<Box_stbl>(); |
463 | 0 | m_minf->append_child_box(m_stbl); |
464 | |
|
465 | 0 | m_stsd = std::make_shared<Box_stsd>(); |
466 | 0 | m_stbl->append_child_box(m_stsd); |
467 | |
|
468 | 0 | m_stts = std::make_shared<Box_stts>(); |
469 | 0 | m_stbl->append_child_box(m_stts); |
470 | |
|
471 | 0 | m_stsc = std::make_shared<Box_stsc>(); |
472 | 0 | m_stbl->append_child_box(m_stsc); |
473 | |
|
474 | 0 | m_stsz = std::make_shared<Box_stsz>(); |
475 | 0 | m_stbl->append_child_box(m_stsz); |
476 | |
|
477 | 0 | m_stco = std::make_shared<Box_stco>(); |
478 | 0 | m_stbl->append_child_box(m_stco); |
479 | |
|
480 | 0 | m_stss = std::make_shared<Box_stss>(); |
481 | 0 | m_stbl->append_child_box(m_stss); |
482 | |
|
483 | 0 | if (options) { |
484 | 0 | m_track_info = *options; |
485 | |
|
486 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
487 | 0 | m_aux_helper_tai_timestamps = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved); |
488 | 0 | m_aux_helper_tai_timestamps->set_aux_info_type(fourcc("stai")); |
489 | 0 | } |
490 | |
|
491 | 0 | if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) { |
492 | 0 | m_aux_helper_content_ids = std::make_unique<SampleAuxInfoHelper>(m_track_info.write_sample_aux_infos_interleaved); |
493 | 0 | m_aux_helper_content_ids->set_aux_info_type(fourcc("suid")); |
494 | 0 | } |
495 | |
|
496 | 0 | if (!options->gimi_track_content_id.empty()) { |
497 | 0 | auto hdlr_box = std::make_shared<Box_hdlr>(); |
498 | 0 | hdlr_box->set_handler_type(fourcc("meta")); |
499 | |
|
500 | 0 | auto uuid_box = std::make_shared<Box_infe>(); |
501 | 0 | uuid_box->set_item_type_4cc(fourcc("uri ")); |
502 | 0 | uuid_box->set_item_uri_type("urn:uuid:15beb8e4-944d-5fc6-a3dd-cb5a7e655c73"); |
503 | 0 | uuid_box->set_item_ID(1); |
504 | |
|
505 | 0 | auto iinf_box = std::make_shared<Box_iinf>(); |
506 | 0 | iinf_box->append_child_box(uuid_box); |
507 | |
|
508 | 0 | std::vector<uint8_t> track_uuid_vector; |
509 | 0 | track_uuid_vector.insert(track_uuid_vector.begin(), |
510 | 0 | options->gimi_track_content_id.c_str(), |
511 | 0 | options->gimi_track_content_id.c_str() + options->gimi_track_content_id.length() + 1); |
512 | |
|
513 | 0 | auto iloc_box = std::make_shared<Box_iloc>(); |
514 | 0 | iloc_box->append_data(1, track_uuid_vector, 1); |
515 | |
|
516 | 0 | auto meta_box = std::make_shared<Box_meta>(); |
517 | 0 | meta_box->append_child_box(hdlr_box); |
518 | 0 | meta_box->append_child_box(iinf_box); |
519 | 0 | meta_box->append_child_box(iloc_box); |
520 | |
|
521 | 0 | m_trak->append_child_box(meta_box); |
522 | 0 | } |
523 | 0 | } |
524 | 0 | } |
525 | | |
526 | | |
527 | | Result<std::shared_ptr<Track>> Track::alloc_track(HeifContext* ctx, const std::shared_ptr<Box_trak>& trak) |
528 | 0 | { |
529 | 0 | auto mdia = trak->get_child_box<Box_mdia>(); |
530 | 0 | if (!mdia) { |
531 | 0 | return Error{ |
532 | 0 | heif_error_Invalid_input, |
533 | 0 | heif_suberror_Unspecified, |
534 | 0 | "Track has no 'mdia' box." |
535 | 0 | }; |
536 | 0 | } |
537 | | |
538 | 0 | auto hdlr = mdia->get_child_box<Box_hdlr>(); |
539 | 0 | if (!hdlr) { |
540 | 0 | return Error{ |
541 | 0 | heif_error_Invalid_input, |
542 | 0 | heif_suberror_Unspecified, |
543 | 0 | "Track has no 'hdlr' box." |
544 | 0 | }; |
545 | 0 | } |
546 | | |
547 | 0 | std::shared_ptr<Track> track; |
548 | |
|
549 | 0 | switch (hdlr->get_handler_type()) { |
550 | 0 | case fourcc("pict"): |
551 | 0 | case fourcc("vide"): |
552 | 0 | case fourcc("auxv"): |
553 | 0 | track = std::make_shared<Track_Visual>(ctx); |
554 | 0 | break; |
555 | 0 | case fourcc("meta"): |
556 | 0 | track = std::make_shared<Track_Metadata>(ctx); |
557 | 0 | default: { |
558 | 0 | std::stringstream sstr; |
559 | 0 | sstr << "Track with unsupported handler type '" << fourcc_to_string(hdlr->get_handler_type()) << "'."; |
560 | 0 | return Error{ |
561 | 0 | heif_error_Unsupported_filetype, |
562 | 0 | heif_suberror_Unspecified, |
563 | 0 | sstr.str() |
564 | 0 | }; |
565 | 0 | } |
566 | 0 | } |
567 | | |
568 | 0 | assert(track); |
569 | 0 | Error loadError = track->load(trak); |
570 | 0 | if (loadError) { |
571 | 0 | return loadError; |
572 | 0 | } |
573 | | |
574 | 0 | return {track}; |
575 | 0 | } |
576 | | |
577 | | |
578 | | bool Track::is_visual_track() const |
579 | 0 | { |
580 | 0 | return (m_handler_type == fourcc("pict") || |
581 | 0 | m_handler_type == fourcc("vide")); |
582 | 0 | } |
583 | | |
584 | | |
585 | | static const char* cAuxType_alpha_miaf = "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; |
586 | | static const char* cAuxType_alpha_hevc = "urn:mpeg:hevc:2015:auxid:1"; |
587 | | static const char* cAuxType_alpha_avc = "urn:mpeg:avc:2015:auxid:1"; |
588 | | |
589 | | heif_auxiliary_track_info_type Track::get_auxiliary_info_type() const |
590 | 0 | { |
591 | 0 | if (m_auxiliary_info_type == cAuxType_alpha_miaf || |
592 | 0 | m_auxiliary_info_type == cAuxType_alpha_hevc || |
593 | 0 | m_auxiliary_info_type == cAuxType_alpha_avc) { |
594 | 0 | return heif_auxiliary_track_info_type_alpha; |
595 | 0 | } |
596 | 0 | else { |
597 | 0 | return heif_auxiliary_track_info_type_unknown; |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | |
602 | | void Track::set_auxiliary_info_type(heif_auxiliary_track_info_type type) |
603 | 0 | { |
604 | 0 | switch (type) { |
605 | 0 | case heif_auxiliary_track_info_type_alpha: |
606 | 0 | m_auxiliary_info_type = cAuxType_alpha_miaf; |
607 | 0 | break; |
608 | 0 | default: |
609 | 0 | m_auxiliary_info_type.clear(); |
610 | 0 | break; |
611 | 0 | } |
612 | 0 | } |
613 | | |
614 | | |
615 | | uint32_t Track::get_first_cluster_sample_entry_type() const |
616 | 0 | { |
617 | 0 | if (m_stsd->get_num_sample_entries() == 0) { |
618 | 0 | return 0; // TODO: error ? Or can we assume at this point that there is at least one sample entry? |
619 | 0 | } |
620 | | |
621 | 0 | return m_stsd->get_sample_entry(0)->get_short_type(); |
622 | 0 | } |
623 | | |
624 | | |
625 | | Result<std::string> Track::get_first_cluster_urim_uri() const |
626 | 0 | { |
627 | 0 | if (m_stsd->get_num_sample_entries() == 0) { |
628 | 0 | return Error{heif_error_Invalid_input, |
629 | 0 | heif_suberror_Unspecified, |
630 | 0 | "This track has no sample entries."}; |
631 | 0 | } |
632 | | |
633 | 0 | std::shared_ptr<const Box> sampleEntry = m_stsd->get_sample_entry(0); |
634 | 0 | auto urim = std::dynamic_pointer_cast<const Box_URIMetaSampleEntry>(sampleEntry); |
635 | 0 | if (!urim) { |
636 | 0 | return Error{heif_error_Usage_error, |
637 | 0 | heif_suberror_Unspecified, |
638 | 0 | "This cluster is no 'urim' sample entry."}; |
639 | 0 | } |
640 | | |
641 | 0 | std::shared_ptr<const Box_uri> uri = urim->get_child_box<const Box_uri>(); |
642 | 0 | if (!uri) { |
643 | 0 | return Error{heif_error_Invalid_input, |
644 | 0 | heif_suberror_Unspecified, |
645 | 0 | "The 'urim' box has no 'uri' child box."}; |
646 | 0 | } |
647 | | |
648 | 0 | return uri->get_uri(); |
649 | 0 | } |
650 | | |
651 | | |
652 | | bool Track::end_of_sequence_reached() const |
653 | 0 | { |
654 | | //return (m_next_sample_to_be_processed > m_chunks.back()->last_sample_number()); |
655 | 0 | return m_next_sample_to_be_processed >= m_num_output_samples; |
656 | 0 | } |
657 | | |
658 | | |
659 | | void Track::finalize_track() |
660 | 0 | { |
661 | 0 | if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_all(m_stbl, get_file()); |
662 | 0 | if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_all(m_stbl, get_file()); |
663 | |
|
664 | 0 | uint64_t duration = m_stts->get_total_duration(true); |
665 | 0 | m_mdhd->set_duration(duration); |
666 | 0 | } |
667 | | |
668 | | |
669 | | uint64_t Track::get_duration_in_media_units() const |
670 | 0 | { |
671 | 0 | return m_mdhd->get_duration(); |
672 | 0 | } |
673 | | |
674 | | |
675 | | uint32_t Track::get_timescale() const |
676 | 0 | { |
677 | 0 | return m_mdhd->get_timescale(); |
678 | 0 | } |
679 | | |
680 | | |
681 | | void Track::set_track_duration_in_movie_units(uint64_t total_duration) |
682 | 0 | { |
683 | 0 | m_tkhd->set_duration(total_duration); |
684 | |
|
685 | 0 | if (m_elst) { |
686 | 0 | Box_elst::Entry entry; |
687 | 0 | entry.segment_duration = total_duration; |
688 | |
|
689 | 0 | m_elst->add_entry(entry); |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | | |
694 | | void Track::enable_edit_list_repeat_mode(bool enable) |
695 | 0 | { |
696 | 0 | if (!m_elst) { |
697 | 0 | if (!enable) { |
698 | 0 | return; |
699 | 0 | } |
700 | | |
701 | 0 | auto edts = std::make_shared<Box_edts>(); |
702 | 0 | m_trak->append_child_box(edts); |
703 | |
|
704 | 0 | m_elst = std::make_shared<Box_elst>(); |
705 | 0 | edts->append_child_box(m_elst); |
706 | |
|
707 | 0 | m_elst->enable_repeat_mode(enable); |
708 | 0 | } |
709 | 0 | } |
710 | | |
711 | | |
712 | | void Track::add_chunk(heif_compression_format format) |
713 | 0 | { |
714 | 0 | auto chunk = std::make_shared<Chunk>(m_heif_context, m_id, format); |
715 | 0 | m_chunks.push_back(chunk); |
716 | |
|
717 | 0 | int chunkIdx = (uint32_t) m_chunks.size(); |
718 | 0 | m_stsc->add_chunk(chunkIdx); |
719 | 0 | } |
720 | | |
721 | | void Track::set_sample_description_box(std::shared_ptr<Box> sample_description_box) |
722 | 0 | { |
723 | | // --- add 'taic' when we store timestamps as sample auxiliary information |
724 | |
|
725 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
726 | 0 | auto taic = std::make_shared<Box_taic>(); |
727 | 0 | taic->set_from_tai_clock_info(m_track_info.tai_clock_info); |
728 | 0 | sample_description_box->append_child_box(taic); |
729 | 0 | } |
730 | |
|
731 | 0 | m_stsd->add_sample_entry(sample_description_box); |
732 | 0 | } |
733 | | |
734 | | |
735 | | Error Track::write_sample_data(const std::vector<uint8_t>& raw_data, uint32_t sample_duration, bool is_sync_sample, |
736 | | const heif_tai_timestamp_packet* tai, const std::string& gimi_contentID) |
737 | 0 | { |
738 | 0 | size_t data_start = m_heif_context->get_heif_file()->append_mdat_data(raw_data); |
739 | | |
740 | | // first sample in chunk? -> write chunk offset |
741 | |
|
742 | 0 | if (m_stsc->last_chunk_empty()) { |
743 | | // if auxiliary data is interleaved, write it between the chunks |
744 | 0 | if (m_aux_helper_tai_timestamps) m_aux_helper_tai_timestamps->write_interleaved(get_file()); |
745 | 0 | if (m_aux_helper_content_ids) m_aux_helper_content_ids->write_interleaved(get_file()); |
746 | | |
747 | | // TODO |
748 | 0 | assert(data_start < 0xFF000000); // add some headroom for header data |
749 | 0 | m_stco->add_chunk_offset(static_cast<uint32_t>(data_start)); |
750 | 0 | } |
751 | |
|
752 | 0 | m_stsc->increase_samples_in_chunk(1); |
753 | |
|
754 | 0 | m_stsz->append_sample_size((uint32_t)raw_data.size()); |
755 | |
|
756 | 0 | if (is_sync_sample) { |
757 | 0 | m_stss->add_sync_sample(m_next_sample_to_be_processed + 1); |
758 | 0 | } |
759 | |
|
760 | 0 | if (sample_duration == 0) { |
761 | 0 | return {heif_error_Usage_error, |
762 | 0 | heif_suberror_Unspecified, |
763 | 0 | "Sample duration may not be 0"}; |
764 | 0 | } |
765 | | |
766 | 0 | m_stts->append_sample_duration(sample_duration); |
767 | | |
768 | | |
769 | | // --- sample timestamp |
770 | |
|
771 | 0 | if (m_track_info.with_sample_tai_timestamps != heif_sample_aux_info_presence_none) { |
772 | 0 | if (tai) { |
773 | 0 | std::vector<uint8_t> tai_data = Box_itai::encode_tai_to_bitstream(tai); |
774 | 0 | auto err = m_aux_helper_tai_timestamps->add_sample_info(tai_data); |
775 | 0 | if (err) { |
776 | 0 | return err; |
777 | 0 | } |
778 | 0 | } |
779 | 0 | else if (m_track_info.with_sample_tai_timestamps == heif_sample_aux_info_presence_optional) { |
780 | 0 | m_aux_helper_tai_timestamps->add_nonpresent_sample(); |
781 | 0 | } |
782 | 0 | else { |
783 | 0 | return {heif_error_Encoding_error, |
784 | 0 | heif_suberror_Unspecified, |
785 | 0 | "Mandatory TAI timestamp missing"}; |
786 | 0 | } |
787 | 0 | } |
788 | | |
789 | | // --- sample content id |
790 | | |
791 | 0 | if (m_track_info.with_sample_content_ids != heif_sample_aux_info_presence_none) { |
792 | 0 | if (!gimi_contentID.empty()) { |
793 | 0 | auto id = gimi_contentID; |
794 | 0 | const char* id_str = id.c_str(); |
795 | 0 | std::vector<uint8_t> id_vector; |
796 | 0 | id_vector.insert(id_vector.begin(), id_str, id_str + id.length() + 1); |
797 | 0 | auto err = m_aux_helper_content_ids->add_sample_info(id_vector); |
798 | 0 | if (err) { |
799 | 0 | return err; |
800 | 0 | } |
801 | 0 | } else if (m_track_info.with_sample_content_ids == heif_sample_aux_info_presence_optional) { |
802 | 0 | m_aux_helper_content_ids->add_nonpresent_sample(); |
803 | 0 | } else { |
804 | 0 | return {heif_error_Encoding_error, |
805 | 0 | heif_suberror_Unspecified, |
806 | 0 | "Mandatory ContentID missing"}; |
807 | 0 | } |
808 | 0 | } |
809 | | |
810 | 0 | m_next_sample_to_be_processed++; |
811 | |
|
812 | 0 | return Error::Ok; |
813 | 0 | } |
814 | | |
815 | | |
816 | | void Track::add_reference_to_track(uint32_t referenceType, uint32_t to_track_id) |
817 | 0 | { |
818 | 0 | if (!m_tref) { |
819 | 0 | m_tref = std::make_shared<Box_tref>(); |
820 | 0 | m_trak->append_child_box(m_tref); |
821 | 0 | } |
822 | |
|
823 | 0 | m_tref->add_references(to_track_id, referenceType); |
824 | 0 | } |
825 | | |
826 | | |
827 | | void Track::init_sample_timing_table() |
828 | 0 | { |
829 | 0 | m_num_samples = m_stsz->num_samples(); |
830 | | |
831 | | // --- build media timeline |
832 | |
|
833 | 0 | std::vector<SampleTiming> media_timeline; |
834 | |
|
835 | 0 | uint64_t current_decoding_time = 0; |
836 | 0 | uint32_t current_chunk = 0; |
837 | |
|
838 | 0 | for (uint32_t i = 0; i<m_num_samples; i++) { |
839 | 0 | SampleTiming timing; |
840 | 0 | timing.sampleIdx = i; |
841 | 0 | timing.media_decoding_time = current_decoding_time; |
842 | 0 | timing.sample_duration_media_time = m_stts->get_sample_duration(i); |
843 | 0 | current_decoding_time += timing.sample_duration_media_time; |
844 | |
|
845 | 0 | while (i > m_chunks[current_chunk]->last_sample_number()) { |
846 | 0 | current_chunk++; |
847 | |
|
848 | 0 | if (current_chunk > m_chunks.size()) { |
849 | 0 | timing.chunkIdx = 0; // TODO: error |
850 | 0 | } |
851 | 0 | } |
852 | |
|
853 | 0 | timing.chunkIdx = current_chunk; |
854 | |
|
855 | 0 | media_timeline.push_back(timing); |
856 | 0 | } |
857 | | |
858 | | // --- build presentation timeline from editlist |
859 | |
|
860 | 0 | bool fallback = false; |
861 | |
|
862 | 0 | if (m_heif_context->get_sequence_timescale() != get_timescale()) { |
863 | 0 | fallback = true; |
864 | 0 | } |
865 | 0 | else if (m_elst && |
866 | 0 | m_elst->num_entries() == 1 && |
867 | 0 | m_elst->get_entry(0).media_time == 0 && |
868 | 0 | m_elst->get_entry(0).segment_duration == m_mdhd->get_duration() && |
869 | 0 | m_elst->is_repeat_mode()) { |
870 | 0 | m_presentation_timeline = media_timeline; |
871 | 0 | m_num_output_samples = m_heif_context->get_sequence_duration() / get_duration_in_media_units() * media_timeline.size(); |
872 | 0 | } |
873 | 0 | else { |
874 | 0 | fallback = true; |
875 | 0 | } |
876 | | |
877 | | // Fallback: just play the media timeline |
878 | 0 | if (fallback) { |
879 | 0 | m_presentation_timeline = media_timeline; |
880 | 0 | m_num_output_samples = media_timeline.size(); |
881 | 0 | } |
882 | 0 | } |
883 | | |
884 | | |
885 | | Result<heif_raw_sequence_sample*> Track::get_next_sample_raw_data(const heif_decoding_options* options) |
886 | 0 | { |
887 | 0 | uint64_t num_output_samples = m_num_output_samples; |
888 | 0 | if (options && options->ignore_sequence_editlist) { |
889 | 0 | num_output_samples = m_num_samples; |
890 | 0 | } |
891 | |
|
892 | 0 | if (m_next_sample_to_be_processed >= num_output_samples) { |
893 | 0 | return Error{heif_error_End_of_sequence, |
894 | 0 | heif_suberror_Unspecified, |
895 | 0 | "End of sequence"}; |
896 | 0 | } |
897 | | |
898 | 0 | const auto& sampleTiming = m_presentation_timeline[m_next_sample_to_be_processed % m_presentation_timeline.size()]; |
899 | 0 | uint32_t sample_idx = sampleTiming.sampleIdx; |
900 | 0 | uint32_t chunk_idx = sampleTiming.chunkIdx; |
901 | |
|
902 | 0 | const std::shared_ptr<Chunk>& chunk = m_chunks[chunk_idx]; |
903 | |
|
904 | 0 | DataExtent extent = chunk->get_data_extent_for_sample(sample_idx); |
905 | 0 | auto readResult = extent.read_data(); |
906 | 0 | if (!readResult) { |
907 | 0 | return readResult.error(); |
908 | 0 | } |
909 | | |
910 | 0 | heif_raw_sequence_sample* sample = new heif_raw_sequence_sample(); |
911 | 0 | sample->data = **readResult; |
912 | | |
913 | | // read sample duration |
914 | |
|
915 | 0 | if (m_stts) { |
916 | 0 | sample->duration = m_stts->get_sample_duration(sample_idx); |
917 | 0 | } |
918 | | |
919 | | // --- read sample auxiliary data |
920 | |
|
921 | 0 | if (m_aux_reader_content_ids) { |
922 | 0 | auto readResult = m_aux_reader_content_ids->get_sample_info(get_file().get(), sample_idx); |
923 | 0 | if (!readResult) { |
924 | 0 | return readResult.error(); |
925 | 0 | } |
926 | | |
927 | 0 | if (!readResult->empty()) { |
928 | 0 | Result<std::string> convResult = vector_to_string(*readResult); |
929 | 0 | if (!convResult) { |
930 | 0 | return convResult.error(); |
931 | 0 | } |
932 | | |
933 | 0 | sample->gimi_sample_content_id = *convResult; |
934 | 0 | } |
935 | 0 | } |
936 | | |
937 | 0 | if (m_aux_reader_tai_timestamps) { |
938 | 0 | auto readResult = m_aux_reader_tai_timestamps->get_sample_info(get_file().get(), sample_idx); |
939 | 0 | if (!readResult) { |
940 | 0 | return readResult.error(); |
941 | 0 | } |
942 | | |
943 | 0 | if (!readResult->empty()) { |
944 | 0 | auto resultTai = Box_itai::decode_tai_from_vector(*readResult); |
945 | 0 | if (!resultTai) { |
946 | 0 | return resultTai.error(); |
947 | 0 | } |
948 | | |
949 | 0 | sample->timestamp = heif_tai_timestamp_packet_alloc(); |
950 | 0 | heif_tai_timestamp_packet_copy(sample->timestamp, &*resultTai); |
951 | 0 | } |
952 | 0 | } |
953 | | |
954 | 0 | m_next_sample_to_be_processed++; |
955 | |
|
956 | 0 | return sample; |
957 | 0 | } |
958 | | |
959 | | |
960 | | std::vector<heif_sample_aux_info_type> Track::get_sample_aux_info_types() const |
961 | 0 | { |
962 | 0 | std::vector<heif_sample_aux_info_type> types; |
963 | |
|
964 | 0 | if (m_aux_reader_tai_timestamps) types.emplace_back(m_aux_reader_tai_timestamps->get_type()); |
965 | 0 | if (m_aux_reader_content_ids) types.emplace_back(m_aux_reader_content_ids->get_type()); |
966 | |
|
967 | 0 | return types; |
968 | 0 | } |