1 /*
2 * This file is part of FFmpeg.
3 *
4 * FFmpeg is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * FFmpeg is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with FFmpeg; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 */
18
19 /*
20 *
21 * Copyright (c) Sandflow Consulting LLC
22 *
23 * Redistribution and use in source and binary forms, with or without
24 * modification, are permitted provided that the following conditions are met:
25 *
26 * * Redistributions of source code must retain the above copyright notice, this
27 * list of conditions and the following disclaimer.
28 * * Redistributions in binary form must reproduce the above copyright notice,
29 * this list of conditions and the following disclaimer in the documentation
30 * and/or other materials provided with the distribution.
31 *
32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
33 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
34 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
36 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
37 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
38 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
39 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
40 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
42 * POSSIBILITY OF SUCH DAMAGE.
43 */
44
45 /**
46 * Demuxes an IMF Composition
47 *
48 * References
49 * OV 2067-0:2018 - SMPTE Overview Document - Interoperable Master Format
50 * ST 2067-2:2020 - SMPTE Standard - Interoperable Master Format — Core Constraints
51 * ST 2067-3:2020 - SMPTE Standard - Interoperable Master Format — Composition Playlist
52 * ST 2067-5:2020 - SMPTE Standard - Interoperable Master Format — Essence Component
53 * ST 2067-20:2016 - SMPTE Standard - Interoperable Master Format — Application #2
54 * ST 2067-21:2020 - SMPTE Standard - Interoperable Master Format — Application #2 Extended
55 * ST 2067-102:2017 - SMPTE Standard - Interoperable Master Format — Common Image Pixel Color Schemes
56 * ST 429-9:2007 - SMPTE Standard - D-Cinema Packaging — Asset Mapping and File Segmentation
57 *
58 * @author Marc-Antoine Arnaud
59 * @author Valentin Noel
60 * @author Nicholas Vanderzwet
61 * @file
62 * @ingroup lavu_imf
63 */
64
75 #include <inttypes.h>
76 #include <libxml/parser.h>
77
78 #define AVRATIONAL_FORMAT "%d/%d"
79 #define AVRATIONAL_ARG(rational) rational.num, rational.den
80
81 /**
82 * IMF Asset locator
83 */
88
89 /**
90 * IMF Asset locator map
91 * Results from the parsing of one or more ASSETMAP XML files
92 */
97
106
115 or < 0 if a current resource has yet to be selected */
117
129
131 {
132 return strstr(
string,
"://") !=
NULL;
133 }
134
136 {
137 return string[0] == '/';
138 }
139
141 {
142 /* Absolute path case: `C:\path\to\somwhere` */
143 if (string[1] == ':' && string[2] == '\\')
144 return 1;
145
146 /* Absolute path case: `C:/path/to/somwhere` */
147 if (string[1] == ':' && string[2] == '/')
148 return 1;
149
150 /* Network path case: `\\path\to\somwhere` */
151 if (string[0] == '\\' && string[1] == '\\')
152 return 1;
153
154 return 0;
155 }
156
158 {
159 int dst_num;
160 int dst_den;
162
164
165 if ((
av_reduce(&dst_num, &dst_den,
r.num,
r.den, INT64_MAX) != 1))
166 return 1;
167
168 if (dst_den != 1)
169 return 1;
170
171 *ts = dst_num;
172
173 return 0;
174 }
175
176 /**
177 * Parse a ASSETMAP XML file to extract the UUID-URI mapping of assets.
178 * @param s the current format context, if any (can be NULL).
179 * @param doc the XML document to be parsed.
180 * @param asset_map pointer on the IMFAssetLocatorMap to fill.
181 * @param base_url the url of the asset map XML file, if any (can be NULL).
182 * @return a negative value in case of error, 0 otherwise.
183 */
185 xmlDocPtr doc,
187 const char *base_url)
188 {
189 xmlNodePtr asset_map_element =
NULL;
190 xmlNodePtr node =
NULL;
191 xmlNodePtr asset_element =
NULL;
192 unsigned long elem_count;
193 char *uri;
197
198 asset_map_element = xmlDocGetRootElement(doc);
199
200 if (!asset_map_element) {
203 }
204
205 if (asset_map_element->type != XML_ELEMENT_NODE ||
av_strcasecmp(asset_map_element->name,
"AssetMap")) {
206 av_log(
s,
AV_LOG_ERROR,
"Unable to parse asset map XML - wrong root node name[%s] type[%d]\n",
207 asset_map_element->name, (int)asset_map_element->type);
209 }
210
211 /* parse asset locators */
215 }
216 elem_count = xmlChildElementCount(node);
217 if (elem_count > UINT32_MAX
218 || asset_map->
asset_count > UINT32_MAX - elem_count)
226 }
228
229 asset_element = xmlFirstElementChild(node);
230 while (asset_element) {
232 continue;
233
235
239 }
240
244 }
245
247
251 }
252
256 }
257
261 else
263 xmlFree(uri);
266
268
270 asset_element = xmlNextElementSibling(asset_element);
271 }
272
274 }
275
276 /**
277 * Initializes an IMFAssetLocatorMap structure.
278 */
280 {
283 }
284
285 /**
286 * Free a IMFAssetLocatorMap pointer.
287 */
289 {
292
294 }
295
297 {
300 struct AVBPrint buf;
303 const char *base_url;
304 char *tmp_str =
NULL;
306
308
314
315 av_bprint_init(&buf, 0, INT_MAX);
// xmlReadMemory uses integer length
316
322 goto clean_up;
323 }
324
325 LIBXML_TEST_VERSION
326
328 if (!tmp_str) {
330 goto clean_up;
331 }
333
334 doc = xmlReadMemory(buf.str, buf.len, url,
NULL, 0);
335
339 c->asset_locator_map.asset_count, url);
340
341 xmlFreeDoc(doc);
342
343 clean_up:
344 if (tmp_str)
349 }
350
352 {
354 if (memcmp(asset_map->
assets[
i].
uuid, uuid, 16) == 0)
355 return &(asset_map->
assets[
i]);
356 }
358 }
359
363 {
370
371 if (track_resource->
ctx) {
374 return 0;
375 }
376
378 if (!track_resource->
ctx)
380
384
387
390
393
402 }
404
405 /* make sure there is only one stream in the file */
406
410 }
411
413
414 /* Determine the seek offset into the Track File, taking into account:
415 * - the current timestamp within the virtual track
416 * - the entry point of the resource
417 */
424
425 if (seek_offset) {
432 "Could not seek at %" PRId64 "on %s: %s\n",
433 seek_offset,
438 }
439 }
440
441 return 0;
442
448 }
449
453 {
457
459 if (!asset_locator) {
463 }
464
470
482
485
486 vt_ctx.
locator = asset_locator;
487 vt_ctx.
resource = track_file_resource;
499 }
500
501 return 0;
502 }
503
505 {
508
510 }
511
515 {
520
526
538 goto clean_up;
539 }
540 }
541
543
544 if (
c->track_count == UINT32_MAX) {
546 goto clean_up;
547 }
551 goto clean_up;
552 }
554 c->tracks[
c->track_count++] = track;
555
556 return 0;
557
558 clean_up:
562 }
563
565 {
568
569 for (uint32_t
i = 0;
i <
c->track_count;
i++) {
572
573 /* Open the first resource of the track to get stream information */
577 first_resource_stream =
c->tracks[
i]->resources[0].ctx->streams[0];
579
581 if (!asset_stream) {
584 }
585
586 asset_stream->
id =
i;
594 }
595
596 return 0;
597 }
598
600 {
604
605 if (
c->cpl->main_image_2d_track) {
608 AV_UUID_ARG(
c->cpl->main_image_2d_track->base.id_uuid));
610 }
611 }
612
613 for (uint32_t
i = 0;
i <
c->cpl->main_audio_track_count;
i++) {
618 }
619 }
620
622 }
623
625 {
627 char *asset_map_path;
628 char *tmp_str;
632
633 c->interrupt_callback = &
s->interrupt_callback;
635 if (!tmp_str)
641
644
646
649
651 if (!tcr &&
c->cpl->tc) {
657 }
658
663
664 if (!
c->asset_map_paths) {
666 if (!
c->asset_map_paths) {
669 }
671 }
672
673 /* Parse each asset map XML file */
675 asset_map_path =
av_strtok(
c->asset_map_paths,
",", &tmp_str);
676 while (asset_map_path !=
NULL) {
678
681
683 }
684
686
689
691
692 return 0;
693 }
694
696 {
700
701 for (uint32_t
i =
c->track_count;
i > 0;
i--) {
708
709 if (
av_cmp_q(
c->tracks[
i - 1]->current_timestamp, minimum_timestamp) <= 0) {
710 track =
c->tracks[
i - 1];
712 }
713 }
714
715 return track;
716 }
717
719 {
721
725 }
726
729 "Looking for track %d resource for timestamp = %lf / %lf\n",
734
742
745
748
755 }
756
758 return 0;
759 }
760 }
761
764 }
765
767 {
774
776
777 if (!track) {
780 }
781
784
788
792
794 ", duration=%" PRId64 ", stream_index=%d, pos=%" PRId64
797
798 /* IMF resources contain only one stream */
799
803
805
806 /* adjust the packet PTS and DTS based on the temporal position of the resource within the timeline */
807
809
815 } else {
820 }
821
822 /* advance the track timestamp by the packet duration */
823
826
827 /* if necessary, clamp the next timestamp to the end of the current resource */
828
830
832
833 /* shrink the packet duration */
834
838
841 else
843
844 /* shrink the packet itself for audio essence */
845
847
849 /* AV_CODEC_ID_PCM_S24LE is the only PCM format supported in IMF */
850 /* in this case, explicitly shrink the packet */
851
857
858 } else {
859 /* in all other cases, use side data to skip samples */
861
865
866 if (
ret || skip_samples < 0 || skip_samples > UINT32_MAX) {
868 } else {
870 if (!side_data)
872
873 AV_WL32(side_data + 4, skip_samples);
/* skip from end of this packet */
874 side_data[6] = 1; /* reason for end is convergence */
875 }
876 }
877
878 next_timestamp = resource->
end_time;
879
880 } else {
882 }
883 }
884
886
887 return 0;
888 }
889
891 {
893
899
900 for (uint32_t
i = 0;
i <
c->track_count;
i++) {
903 }
904
906
907 return 0;
908 }
909
911 {
912 if (!strstr(p->
buf,
"<CompositionPlaylist"))
913 return 0;
914
915 /* check for a ContentTitle element without including ContentTitleText,
916 * which is used by the D-Cinema CPL.
917 */
918 if (!strstr(p->
buf,
"ContentTitle>"))
919 return 0;
920
922 }
923
925 {
926 int dst_num;
927 int dst_den;
929
931 in_tb.
den * out_tb.
num, INT64_MAX);
932 if (!
ret || dst_den != 1)
933 return 0;
934
935 return 1;
936 }
937
940 {
943
946
947 /* rescale timestamps to Composition edit units */
948 if (stream_index < 0)
950 av_make_q(
c->cpl->edit_rate.den,
c->cpl->edit_rate.num),
951 &min_ts, &ts, &max_ts);
952 else
954 av_make_q(
c->cpl->edit_rate.den,
c->cpl->edit_rate.num),
955 &min_ts, &ts, &max_ts);
956
957 /* requested timestamp bounds are too close */
958 if (max_ts < min_ts)
959 return -1;
960
961 /* clamp requested timestamp to provided bounds */
963
965
966 /* set the dts of each stream and temporal offset of each track */
967 for (
i = 0;
i <
c->track_count;
i++) {
971
975
979
982
987 }
988 }
989
990 return 0;
991 }
992
994 {
996 .help = "Comma-separated paths to ASSETMAP files."
997 "If not specified, the `ASSETMAP.xml` file in the same "
998 "directory as the CPL is used.",
999 .offset = offsetof(
IMFContext, asset_map_paths),
1001 .default_val = {.str =
NULL},
1003 },
1005 };
1006
1012 };
1013
1026 };