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 {
366 int64_t seek_offset = 0;
370
371 if (track_resource->
ctx) {
374 return 0;
375 }
376
378 if (!track_resource->
ctx)
380
382 #if FF_API_AVFORMAT_IO_CLOSE
384 track_resource->
ctx->io_close =
s->io_close;
386 #endif
389
392
395
398
407 }
409
410 /* make sure there is only one stream in the file */
411
415 }
416
418
419 /* Determine the seek offset into the Track File, taking into account:
420 * - the current timestamp within the virtual track
421 * - the entry point of the resource
422 */
429
430 if (seek_offset) {
437 "Could not seek at %" PRId64 "on %s: %s\n",
438 seek_offset,
443 }
444 }
445
446 return 0;
447
453 }
454
458 {
462
464 if (!asset_locator) {
468 }
469
475
487
490
491 vt_ctx.
locator = asset_locator;
492 vt_ctx.
resource = track_file_resource;
504 }
505
506 return 0;
507 }
508
510 {
513
515 }
516
520 {
525
531
543 goto clean_up;
544 }
545 }
546
548
549 if (
c->track_count == UINT32_MAX) {
551 goto clean_up;
552 }
556 goto clean_up;
557 }
559 c->tracks[
c->track_count++] = track;
560
561 return 0;
562
563 clean_up:
567 }
568
570 {
573
574 for (uint32_t
i = 0;
i <
c->track_count;
i++) {
577
578 /* Open the first resource of the track to get stream information */
582 first_resource_stream =
c->tracks[
i]->resources[0].ctx->streams[0];
584
586 if (!asset_stream) {
589 }
590
591 asset_stream->
id =
i;
599 }
600
601 return 0;
602 }
603
605 {
609
610 if (
c->cpl->main_image_2d_track) {
613 AV_UUID_ARG(
c->cpl->main_image_2d_track->base.id_uuid));
615 }
616 }
617
618 for (uint32_t
i = 0;
i <
c->cpl->main_audio_track_count;
i++) {
623 }
624 }
625
627 }
628
630 {
632 char *asset_map_path;
633 char *tmp_str;
637
638 c->interrupt_callback = &
s->interrupt_callback;
640 if (!tmp_str)
646
649
651
654
656 if (!tcr &&
c->cpl->tc) {
662 }
663
668
669 if (!
c->asset_map_paths) {
671 if (!
c->asset_map_paths) {
674 }
676 }
677
678 /* Parse each asset map XML file */
680 asset_map_path =
av_strtok(
c->asset_map_paths,
",", &tmp_str);
681 while (asset_map_path !=
NULL) {
683
686
688 }
689
691
694
696
697 return 0;
698 }
699
701 {
705
708
709 for (uint32_t
i =
c->track_count;
i > 0;
i--) {
716
717 if (
av_cmp_q(
c->tracks[
i - 1]->current_timestamp, minimum_timestamp) <= 0) {
718 track =
c->tracks[
i - 1];
720 }
721 }
722
723 return track;
724 }
725
727 {
729
733 }
734
737 "Looking for track %d resource for timestamp = %lf / %lf\n",
742
750
753
756
763 }
764
766 return 0;
767 }
768 }
769
772 }
773
775 {
779 int64_t delta_ts;
782
784
785 if (!track) {
788 }
789
792
796
800
802 ", duration=%" PRId64 ", stream_index=%d, pos=%" PRId64
805
806 /* IMF resources contain only one stream */
807
811
813
814 /* adjust the packet PTS and DTS based on the temporal position of the resource within the timeline */
815
817
823 } else {
828 }
829
830 /* advance the track timestamp by the packet duration */
831
834
835 /* if necessary, clamp the next timestamp to the end of the current resource */
836
838
839 int64_t new_pkt_dur;
840
841 /* shrink the packet duration */
842
846
849 else
851
852 /* shrink the packet itself for audio essence */
853
855
857 /* AV_CODEC_ID_PCM_S24LE is the only PCM format supported in IMF */
858 /* in this case, explicitly shrink the packet */
859
865
866 } else {
867 /* in all other cases, use side data to skip samples */
868 int64_t skip_samples;
869
873
874 if (
ret || skip_samples < 0 || skip_samples > UINT32_MAX) {
876 } else {
878 if (!side_data)
880
881 AV_WL32(side_data + 4, skip_samples);
/* skip from end of this packet */
882 side_data[6] = 1; /* reason for end is convergence */
883 }
884 }
885
886 next_timestamp = resource->
end_time;
887
888 } else {
890 }
891 }
892
894
895 return 0;
896 }
897
899 {
901
907
908 for (uint32_t
i = 0;
i <
c->track_count;
i++) {
911 }
912
914
915 return 0;
916 }
917
919 {
920 if (!strstr(p->
buf,
"<CompositionPlaylist"))
921 return 0;
922
923 /* check for a ContentTitle element without including ContentTitleText,
924 * which is used by the D-Cinema CPL.
925 */
926 if (!strstr(p->
buf,
"ContentTitle>"))
927 return 0;
928
930 }
931
933 {
934 int dst_num;
935 int dst_den;
937
939 in_tb.
den * out_tb.
num, INT64_MAX);
940 if (!
ret || dst_den != 1)
941 return 0;
942
943 return 1;
944 }
945
947 int64_t ts, int64_t max_ts,
int flags)
948 {
951
954
955 /* rescale timestamps to Composition edit units */
956 if (stream_index < 0)
958 av_make_q(
c->cpl->edit_rate.den,
c->cpl->edit_rate.num),
959 &min_ts, &ts, &max_ts);
960 else
962 av_make_q(
c->cpl->edit_rate.den,
c->cpl->edit_rate.num),
963 &min_ts, &ts, &max_ts);
964
965 /* requested timestamp bounds are too close */
966 if (max_ts < min_ts)
967 return -1;
968
969 /* clamp requested timestamp to provided bounds */
971
973
974 /* set the dts of each stream and temporal offset of each track */
975 for (
i = 0;
i <
c->track_count;
i++) {
978 int64_t dts;
979
983
987
990
995 }
996 }
997
998 return 0;
999 }
1000
1002 {
1003 .
name =
"assetmaps",
1004 .help = "Comma-separated paths to ASSETMAP files."
1005 "If not specified, the `ASSETMAP.xml` file in the same "
1006 "directory as the CPL is used.",
1007 .offset = offsetof(
IMFContext, asset_map_paths),
1009 .default_val = {.str =
NULL},
1011 },
1013 };
1014
1020 };
1021
1034 };