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
65 #include "avio_internal.h"
66 #include "demux.h"
67 #include "imf.h"
68 #include "internal.h"
69 #include "libavcodec/packet.h"
70 #include "libavutil/avstring.h"
71 #include "libavutil/bprint.h"
72 #include "libavutil/intreadwrite.h"
73 #include "libavutil/opt.h"
74 #include "mxf.h"
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 */
84 typedef struct IMFAssetLocator {
85 AVUUID uuid;
86 char *absolute_uri;
87 } IMFAssetLocator;
88
89 /**
90 * IMF Asset locator map
91 * Results from the parsing of one or more ASSETMAP XML files
92 */
93 typedef struct IMFAssetLocatorMap {
94 uint32_t asset_count;
95 IMFAssetLocator *assets;
96 } IMFAssetLocatorMap;
97
98 typedef struct IMFVirtualTrackResourcePlaybackCtx {
99 IMFAssetLocator *locator; /**< Location of the resource */
100 FFIMFTrackFileResource *resource; /**< Underlying IMF CPL resource */
101 AVFormatContext *ctx; /**< Context associated with the resource */
102 AVRational start_time; /**< inclusive start time of the resource on the CPL timeline (s) */
103 AVRational end_time; /**< exclusive end time of the resource on the CPL timeline (s) */
104 AVRational ts_offset; /**< start_time minus the entry point into the resource (s) */
105 } IMFVirtualTrackResourcePlaybackCtx;
106
107 typedef struct IMFVirtualTrackPlaybackCtx {
108 int32_t index; /**< Track index in playlist */
109 AVRational current_timestamp; /**< Current temporal position */
110 AVRational duration; /**< Overall duration */
111 uint32_t resource_count; /**< Number of resources (<= INT32_MAX) */
112 unsigned int resources_alloc_sz; /**< Size of the buffer holding the resource */
113 IMFVirtualTrackResourcePlaybackCtx *resources; /**< Buffer holding the resources */
114 int32_t current_resource_index; /**< Index of the current resource in resources,
115 or < 0 if a current resource has yet to be selected */
116 } IMFVirtualTrackPlaybackCtx;
117
118 typedef struct IMFContext {
119 const AVClass *class;
120 const char *base_url;
121 char *asset_map_paths;
122 AVIOInterruptCB *interrupt_callback;
123 AVDictionary *avio_opts;
124 FFIMFCPL *cpl;
125 IMFAssetLocatorMap asset_locator_map;
126 uint32_t track_count;
127 IMFVirtualTrackPlaybackCtx **tracks;
128 } IMFContext;
129
imf_uri_is_url(const char * string)130 static int imf_uri_is_url(const char *string)
131 {
132 return strstr(string, "://") != NULL;
133 }
134
imf_uri_is_unix_abs_path(const char * string)135 static int imf_uri_is_unix_abs_path(const char *string)
136 {
137 return string[0] == '/';
138 }
139
imf_uri_is_dos_abs_path(const char * string)140 static int imf_uri_is_dos_abs_path(const char *string)
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
imf_time_to_ts(int64_t * ts,AVRational t,AVRational time_base)157 static int imf_time_to_ts(int64_t *ts, AVRational t, AVRational time_base)
158 {
159 int dst_num;
160 int dst_den;
161 AVRational r;
162
163 r = av_div_q(t, time_base);
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 */
parse_imf_asset_map_from_xml_dom(AVFormatContext * s,xmlDocPtr doc,IMFAssetLocatorMap * asset_map,const char * base_url)184 static int parse_imf_asset_map_from_xml_dom(AVFormatContext *s,
185 xmlDocPtr doc,
186 IMFAssetLocatorMap *asset_map,
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;
194 int ret = 0;
195 IMFAssetLocator *asset = NULL;
196 void *tmp;
197
198 asset_map_element = xmlDocGetRootElement(doc);
199
200 if (!asset_map_element) {
201 av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - missing root node\n");
202 return AVERROR_INVALIDDATA;
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);
208 return AVERROR_INVALIDDATA;
209 }
210
211 /* parse asset locators */
212 if (!(node = ff_imf_xml_get_child_element_by_name(asset_map_element, "AssetList"))) {
213 av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - missing AssetList node\n");
214 return AVERROR_INVALIDDATA;
215 }
216 elem_count = xmlChildElementCount(node);
217 if (elem_count > UINT32_MAX
218 || asset_map->asset_count > UINT32_MAX - elem_count)
219 return AVERROR(ENOMEM);
220 tmp = av_realloc_array(asset_map->assets,
221 elem_count + asset_map->asset_count,
222 sizeof(IMFAssetLocator));
223 if (!tmp) {
224 av_log(s, AV_LOG_ERROR, "Cannot allocate IMF asset locators\n");
225 return AVERROR(ENOMEM);
226 }
227 asset_map->assets = tmp;
228
229 asset_element = xmlFirstElementChild(node);
230 while (asset_element) {
231 if (av_strcasecmp(asset_element->name, "Asset") != 0)
232 continue;
233
234 asset = &(asset_map->assets[asset_map->asset_count]);
235
236 if (ff_imf_xml_read_uuid(ff_imf_xml_get_child_element_by_name(asset_element, "Id"), asset->uuid)) {
237 av_log(s, AV_LOG_ERROR, "Could not parse UUID from asset in asset map.\n");
238 return AVERROR_INVALIDDATA;
239 }
240
241 av_log(s, AV_LOG_DEBUG, "Found asset id: " AV_PRI_URN_UUID "\n", AV_UUID_ARG(asset->uuid));
242
243 if (!(node = ff_imf_xml_get_child_element_by_name(asset_element, "ChunkList"))) {
244 av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - missing ChunkList node\n");
245 return AVERROR_INVALIDDATA;
246 }
247
248 if (!(node = ff_imf_xml_get_child_element_by_name(node, "Chunk"))) {
249 av_log(s, AV_LOG_ERROR, "Unable to parse asset map XML - missing Chunk node\n");
250 return AVERROR_INVALIDDATA;
251 }
252
253 uri = xmlNodeGetContent(ff_imf_xml_get_child_element_by_name(node, "Path"));
254 if (!imf_uri_is_url(uri) && !imf_uri_is_unix_abs_path(uri) && !imf_uri_is_dos_abs_path(uri))
255 asset->absolute_uri = av_append_path_component(base_url, uri);
256 else
257 asset->absolute_uri = av_strdup(uri);
258 xmlFree(uri);
259 if (!asset->absolute_uri)
260 return AVERROR(ENOMEM);
261
262 av_log(s, AV_LOG_DEBUG, "Found asset absolute URI: %s\n", asset->absolute_uri);
263
264 asset_map->asset_count++;
265 asset_element = xmlNextElementSibling(asset_element);
266 }
267
268 return ret;
269 }
270
271 /**
272 * Initializes an IMFAssetLocatorMap structure.
273 */
imf_asset_locator_map_init(IMFAssetLocatorMap * asset_map)274 static void imf_asset_locator_map_init(IMFAssetLocatorMap *asset_map)
275 {
276 asset_map->assets = NULL;
277 asset_map->asset_count = 0;
278 }
279
280 /**
281 * Free a IMFAssetLocatorMap pointer.
282 */
imf_asset_locator_map_deinit(IMFAssetLocatorMap * asset_map)283 static void imf_asset_locator_map_deinit(IMFAssetLocatorMap *asset_map)
284 {
285 for (uint32_t i = 0; i < asset_map->asset_count; i++)
286 av_freep(&asset_map->assets[i].absolute_uri);
287
288 av_freep(&asset_map->assets);
289 }
290
parse_assetmap(AVFormatContext * s,const char * url)291 static int parse_assetmap(AVFormatContext *s, const char *url)
292 {
293 IMFContext *c = s->priv_data;
294 AVIOContext *in = NULL;
295 struct AVBPrint buf;
296 AVDictionary *opts = NULL;
297 xmlDoc *doc = NULL;
298 const char *base_url;
299 char *tmp_str = NULL;
300 int ret;
301
302 av_log(s, AV_LOG_DEBUG, "Asset Map URL: %s\n", url);
303
304 av_dict_copy(&opts, c->avio_opts, 0);
305 ret = s->io_open(s, &in, url, AVIO_FLAG_READ, &opts);
306 av_dict_free(&opts);
307 if (ret < 0)
308 return ret;
309
310 av_bprint_init(&buf, 0, INT_MAX); // xmlReadMemory uses integer length
311
312 ret = avio_read_to_bprint(in, &buf, SIZE_MAX);
313 if (ret < 0 || !avio_feof(in)) {
314 av_log(s, AV_LOG_ERROR, "Unable to read to asset map '%s'\n", url);
315 if (ret == 0)
316 ret = AVERROR_INVALIDDATA;
317 goto clean_up;
318 }
319
320 LIBXML_TEST_VERSION
321
322 tmp_str = av_strdup(url);
323 if (!tmp_str) {
324 ret = AVERROR(ENOMEM);
325 goto clean_up;
326 }
327 base_url = av_dirname(tmp_str);
328
329 doc = xmlReadMemory(buf.str, buf.len, url, NULL, 0);
330
331 ret = parse_imf_asset_map_from_xml_dom(s, doc, &c->asset_locator_map, base_url);
332 if (!ret)
333 av_log(s, AV_LOG_DEBUG, "Found %d assets from %s\n",
334 c->asset_locator_map.asset_count, url);
335
336 xmlFreeDoc(doc);
337
338 clean_up:
339 if (tmp_str)
340 av_freep(&tmp_str);
341 ff_format_io_close(s, &in);
342 av_bprint_finalize(&buf, NULL);
343 return ret;
344 }
345
find_asset_map_locator(IMFAssetLocatorMap * asset_map,AVUUID uuid)346 static IMFAssetLocator *find_asset_map_locator(IMFAssetLocatorMap *asset_map, AVUUID uuid)
347 {
348 for (uint32_t i = 0; i < asset_map->asset_count; i++) {
349 if (memcmp(asset_map->assets[i].uuid, uuid, 16) == 0)
350 return &(asset_map->assets[i]);
351 }
352 return NULL;
353 }
354
open_track_resource_context(AVFormatContext * s,IMFVirtualTrackPlaybackCtx * track,int32_t resource_index)355 static int open_track_resource_context(AVFormatContext *s,
356 IMFVirtualTrackPlaybackCtx *track,
357 int32_t resource_index)
358 {
359 IMFContext *c = s->priv_data;
360 int ret = 0;
361 int64_t seek_offset = 0;
362 AVDictionary *opts = NULL;
363 AVStream *st;
364 IMFVirtualTrackResourcePlaybackCtx *track_resource = track->resources + resource_index;
365
366 if (track_resource->ctx) {
367 av_log(s, AV_LOG_DEBUG, "Input context already opened for %s.\n",
368 track_resource->locator->absolute_uri);
369 return 0;
370 }
371
372 track_resource->ctx = avformat_alloc_context();
373 if (!track_resource->ctx)
374 return AVERROR(ENOMEM);
375
376 track_resource->ctx->io_open = s->io_open;
377 track_resource->ctx->io_close = s->io_close;
378 track_resource->ctx->io_close2 = s->io_close2;
379 track_resource->ctx->flags |= s->flags & ~AVFMT_FLAG_CUSTOM_IO;
380
381 if ((ret = ff_copy_whiteblacklists(track_resource->ctx, s)) < 0)
382 goto cleanup;
383
384 if ((ret = av_opt_set(track_resource->ctx, "format_whitelist", "mxf", 0)))
385 goto cleanup;
386
387 if ((ret = av_dict_copy(&opts, c->avio_opts, 0)) < 0)
388 goto cleanup;
389
390 ret = avformat_open_input(&track_resource->ctx,
391 track_resource->locator->absolute_uri,
392 NULL,
393 &opts);
394 if (ret < 0) {
395 av_log(s, AV_LOG_ERROR, "Could not open %s input context: %s\n",
396 track_resource->locator->absolute_uri, av_err2str(ret));
397 goto cleanup;
398 }
399 av_dict_free(&opts);
400
401 /* make sure there is only one stream in the file */
402
403 if (track_resource->ctx->nb_streams != 1) {
404 ret = AVERROR_INVALIDDATA;
405 goto cleanup;
406 }
407
408 st = track_resource->ctx->streams[0];
409
410 /* Determine the seek offset into the Track File, taking into account:
411 * - the current timestamp within the virtual track
412 * - the entry point of the resource
413 */
414 if (imf_time_to_ts(&seek_offset,
415 av_sub_q(track->current_timestamp, track_resource->ts_offset),
416 st->time_base))
417 av_log(s, AV_LOG_WARNING, "Incoherent stream timebase " AVRATIONAL_FORMAT
418 "and composition timeline position: " AVRATIONAL_FORMAT "\n",
419 AVRATIONAL_ARG(st->time_base), AVRATIONAL_ARG(track->current_timestamp));
420
421 if (seek_offset) {
422 av_log(s, AV_LOG_DEBUG, "Seek at resource %s entry point: %" PRIi64 "\n",
423 track_resource->locator->absolute_uri, seek_offset);
424 ret = avformat_seek_file(track_resource->ctx, 0, seek_offset, seek_offset, seek_offset, 0);
425 if (ret < 0) {
426 av_log(s,
427 AV_LOG_ERROR,
428 "Could not seek at %" PRId64 "on %s: %s\n",
429 seek_offset,
430 track_resource->locator->absolute_uri,
431 av_err2str(ret));
432 avformat_close_input(&track_resource->ctx);
433 return ret;
434 }
435 }
436
437 return 0;
438
439 cleanup:
440 av_dict_free(&opts);
441 avformat_free_context(track_resource->ctx);
442 track_resource->ctx = NULL;
443 return ret;
444 }
445
open_track_file_resource(AVFormatContext * s,FFIMFTrackFileResource * track_file_resource,IMFVirtualTrackPlaybackCtx * track)446 static int open_track_file_resource(AVFormatContext *s,
447 FFIMFTrackFileResource *track_file_resource,
448 IMFVirtualTrackPlaybackCtx *track)
449 {
450 IMFContext *c = s->priv_data;
451 IMFAssetLocator *asset_locator;
452 void *tmp;
453
454 asset_locator = find_asset_map_locator(&c->asset_locator_map, track_file_resource->track_file_uuid);
455 if (!asset_locator) {
456 av_log(s, AV_LOG_ERROR, "Could not find asset locator for UUID: " AV_PRI_URN_UUID "\n",
457 AV_UUID_ARG(track_file_resource->track_file_uuid));
458 return AVERROR_INVALIDDATA;
459 }
460
461 av_log(s,
462 AV_LOG_DEBUG,
463 "Found locator for " AV_PRI_URN_UUID ": %s\n",
464 AV_UUID_ARG(asset_locator->uuid),
465 asset_locator->absolute_uri);
466
467 if (track->resource_count > INT32_MAX - track_file_resource->base.repeat_count
468 || (track->resource_count + track_file_resource->base.repeat_count)
469 > INT_MAX / sizeof(IMFVirtualTrackResourcePlaybackCtx))
470 return AVERROR(ENOMEM);
471 tmp = av_fast_realloc(track->resources,
472 &track->resources_alloc_sz,
473 (track->resource_count + track_file_resource->base.repeat_count)
474 * sizeof(IMFVirtualTrackResourcePlaybackCtx));
475 if (!tmp)
476 return AVERROR(ENOMEM);
477 track->resources = tmp;
478
479 for (uint32_t i = 0; i < track_file_resource->base.repeat_count; i++) {
480 IMFVirtualTrackResourcePlaybackCtx vt_ctx;
481
482 vt_ctx.locator = asset_locator;
483 vt_ctx.resource = track_file_resource;
484 vt_ctx.ctx = NULL;
485 vt_ctx.start_time = track->duration;
486 vt_ctx.ts_offset = av_sub_q(vt_ctx.start_time,
487 av_div_q(av_make_q((int)track_file_resource->base.entry_point, 1),
488 track_file_resource->base.edit_rate));
489 vt_ctx.end_time = av_add_q(track->duration,
490 av_make_q((int)track_file_resource->base.duration
491 * track_file_resource->base.edit_rate.den,
492 track_file_resource->base.edit_rate.num));
493 track->resources[track->resource_count++] = vt_ctx;
494 track->duration = vt_ctx.end_time;
495 }
496
497 return 0;
498 }
499
imf_virtual_track_playback_context_deinit(IMFVirtualTrackPlaybackCtx * track)500 static void imf_virtual_track_playback_context_deinit(IMFVirtualTrackPlaybackCtx *track)
501 {
502 for (uint32_t i = 0; i < track->resource_count; i++)
503 avformat_close_input(&track->resources[i].ctx);
504
505 av_freep(&track->resources);
506 }
507
open_virtual_track(AVFormatContext * s,FFIMFTrackFileVirtualTrack * virtual_track,int32_t track_index)508 static int open_virtual_track(AVFormatContext *s,
509 FFIMFTrackFileVirtualTrack *virtual_track,
510 int32_t track_index)
511 {
512 IMFContext *c = s->priv_data;
513 IMFVirtualTrackPlaybackCtx *track = NULL;
514 void *tmp;
515 int ret = 0;
516
517 if (!(track = av_mallocz(sizeof(IMFVirtualTrackPlaybackCtx))))
518 return AVERROR(ENOMEM);
519 track->current_resource_index = -1;
520 track->index = track_index;
521 track->duration = av_make_q(0, 1);
522
523 for (uint32_t i = 0; i < virtual_track->resource_count; i++) {
524 av_log(s,
525 AV_LOG_DEBUG,
526 "Open stream from file " AV_PRI_URN_UUID ", stream %d\n",
527 AV_UUID_ARG(virtual_track->resources[i].track_file_uuid),
528 i);
529 if ((ret = open_track_file_resource(s, &virtual_track->resources[i], track)) != 0) {
530 av_log(s,
531 AV_LOG_ERROR,
532 "Could not open image track resource " AV_PRI_URN_UUID "\n",
533 AV_UUID_ARG(virtual_track->resources[i].track_file_uuid));
534 goto clean_up;
535 }
536 }
537
538 track->current_timestamp = av_make_q(0, track->duration.den);
539
540 if (c->track_count == UINT32_MAX) {
541 ret = AVERROR(ENOMEM);
542 goto clean_up;
543 }
544 tmp = av_realloc_array(c->tracks, c->track_count + 1, sizeof(IMFVirtualTrackPlaybackCtx *));
545 if (!tmp) {
546 ret = AVERROR(ENOMEM);
547 goto clean_up;
548 }
549 c->tracks = tmp;
550 c->tracks[c->track_count++] = track;
551
552 return 0;
553
554 clean_up:
555 imf_virtual_track_playback_context_deinit(track);
556 av_free(track);
557 return ret;
558 }
559
set_context_streams_from_tracks(AVFormatContext * s)560 static int set_context_streams_from_tracks(AVFormatContext *s)
561 {
562 IMFContext *c = s->priv_data;
563 int ret = 0;
564
565 for (uint32_t i = 0; i < c->track_count; i++) {
566 AVStream *asset_stream;
567 AVStream *first_resource_stream;
568
569 /* Open the first resource of the track to get stream information */
570 ret = open_track_resource_context(s, c->tracks[i], 0);
571 if (ret)
572 return ret;
573 first_resource_stream = c->tracks[i]->resources[0].ctx->streams[0];
574 av_log(s, AV_LOG_DEBUG, "Open the first resource of track %d\n", c->tracks[i]->index);
575
576 /* Copy stream information */
577 asset_stream = avformat_new_stream(s, NULL);
578 if (!asset_stream) {
579 av_log(s, AV_LOG_ERROR, "Could not create stream\n");
580 return AVERROR(ENOMEM);
581 }
582 asset_stream->id = i;
583 ret = avcodec_parameters_copy(asset_stream->codecpar, first_resource_stream->codecpar);
584 if (ret < 0) {
585 av_log(s, AV_LOG_ERROR, "Could not copy stream parameters\n");
586 return ret;
587 }
588 avpriv_set_pts_info(asset_stream,
589 first_resource_stream->pts_wrap_bits,
590 first_resource_stream->time_base.num,
591 first_resource_stream->time_base.den);
592 asset_stream->duration = (int64_t)av_q2d(av_mul_q(c->tracks[i]->duration,
593 av_inv_q(asset_stream->time_base)));
594 }
595
596 return 0;
597 }
598
open_cpl_tracks(AVFormatContext * s)599 static int open_cpl_tracks(AVFormatContext *s)
600 {
601 IMFContext *c = s->priv_data;
602 int32_t track_index = 0;
603 int ret;
604
605 if (c->cpl->main_image_2d_track) {
606 if ((ret = open_virtual_track(s, c->cpl->main_image_2d_track, track_index++)) != 0) {
607 av_log(s, AV_LOG_ERROR, "Could not open image track " AV_PRI_URN_UUID "\n",
608 AV_UUID_ARG(c->cpl->main_image_2d_track->base.id_uuid));
609 return ret;
610 }
611 }
612
613 for (uint32_t i = 0; i < c->cpl->main_audio_track_count; i++) {
614 if ((ret = open_virtual_track(s, &c->cpl->main_audio_tracks[i], track_index++)) != 0) {
615 av_log(s, AV_LOG_ERROR, "Could not open audio track " AV_PRI_URN_UUID "\n",
616 AV_UUID_ARG(c->cpl->main_audio_tracks[i].base.id_uuid));
617 return ret;
618 }
619 }
620
621 return set_context_streams_from_tracks(s);
622 }
623
imf_read_header(AVFormatContext * s)624 static int imf_read_header(AVFormatContext *s)
625 {
626 IMFContext *c = s->priv_data;
627 char *asset_map_path;
628 char *tmp_str;
629 int ret = 0;
630
631 c->interrupt_callback = &s->interrupt_callback;
632 tmp_str = av_strdup(s->url);
633 if (!tmp_str)
634 return AVERROR(ENOMEM);
635 c->base_url = av_strdup(av_dirname(tmp_str));
636 av_freep(&tmp_str);
637 if (!c->base_url)
638 return AVERROR(ENOMEM);
639
640 if ((ret = ffio_copy_url_options(s->pb, &c->avio_opts)) < 0)
641 return ret;
642
643 av_log(s, AV_LOG_DEBUG, "start parsing IMF CPL: %s\n", s->url);
644
645 if ((ret = ff_imf_parse_cpl(s->pb, &c->cpl)) < 0)
646 return ret;
647
648 av_log(s,
649 AV_LOG_DEBUG,
650 "parsed IMF CPL: " AV_PRI_URN_UUID "\n",
651 AV_UUID_ARG(c->cpl->id_uuid));
652
653 if (!c->asset_map_paths) {
654 c->asset_map_paths = av_append_path_component(c->base_url, "ASSETMAP.xml");
655 if (!c->asset_map_paths) {
656 ret = AVERROR(ENOMEM);
657 return ret;
658 }
659 av_log(s, AV_LOG_DEBUG, "No asset maps provided, using the default ASSETMAP.xml\n");
660 }
661
662 /* Parse each asset map XML file */
663 imf_asset_locator_map_init(&c->asset_locator_map);
664 asset_map_path = av_strtok(c->asset_map_paths, ",", &tmp_str);
665 while (asset_map_path != NULL) {
666 av_log(s, AV_LOG_DEBUG, "start parsing IMF Asset Map: %s\n", asset_map_path);
667
668 if ((ret = parse_assetmap(s, asset_map_path)))
669 return ret;
670
671 asset_map_path = av_strtok(NULL, ",", &tmp_str);
672 }
673
674 av_log(s, AV_LOG_DEBUG, "parsed IMF Asset Maps\n");
675
676 if ((ret = open_cpl_tracks(s)))
677 return ret;
678
679 av_log(s, AV_LOG_DEBUG, "parsed IMF package\n");
680
681 return 0;
682 }
683
get_next_track_with_minimum_timestamp(AVFormatContext * s)684 static IMFVirtualTrackPlaybackCtx *get_next_track_with_minimum_timestamp(AVFormatContext *s)
685 {
686 IMFContext *c = s->priv_data;
687 IMFVirtualTrackPlaybackCtx *track;
688
689 AVRational minimum_timestamp = av_make_q(INT32_MAX, 1);
690 for (uint32_t i = c->track_count; i > 0; i--) {
691 av_log(s, AV_LOG_TRACE, "Compare track %d timestamp " AVRATIONAL_FORMAT
692 " to minimum " AVRATIONAL_FORMAT
693 " (over duration: " AVRATIONAL_FORMAT ")\n", i,
694 AVRATIONAL_ARG(c->tracks[i - 1]->current_timestamp),
695 AVRATIONAL_ARG(minimum_timestamp),
696 AVRATIONAL_ARG(c->tracks[i - 1]->duration));
697
698 if (av_cmp_q(c->tracks[i - 1]->current_timestamp, minimum_timestamp) <= 0) {
699 track = c->tracks[i - 1];
700 minimum_timestamp = track->current_timestamp;
701 }
702 }
703
704 av_log(s, AV_LOG_DEBUG, "Found next track to read: %d (timestamp: %lf / %lf)\n",
705 track->index, av_q2d(track->current_timestamp), av_q2d(minimum_timestamp));
706 return track;
707 }
708
get_resource_context_for_timestamp(AVFormatContext * s,IMFVirtualTrackPlaybackCtx * track,IMFVirtualTrackResourcePlaybackCtx ** resource)709 static int get_resource_context_for_timestamp(AVFormatContext *s, IMFVirtualTrackPlaybackCtx *track, IMFVirtualTrackResourcePlaybackCtx **resource)
710 {
711 *resource = NULL;
712
713 if (av_cmp_q(track->current_timestamp, track->duration) >= 0) {
714 av_log(s, AV_LOG_DEBUG, "Reached the end of the virtual track\n");
715 return AVERROR_EOF;
716 }
717
718 av_log(s,
719 AV_LOG_TRACE,
720 "Looking for track %d resource for timestamp = %lf / %lf\n",
721 track->index,
722 av_q2d(track->current_timestamp),
723 av_q2d(track->duration));
724 for (uint32_t i = 0; i < track->resource_count; i++) {
725
726 if (av_cmp_q(track->resources[i].end_time, track->current_timestamp) > 0) {
727 av_log(s, AV_LOG_DEBUG, "Found resource %d in track %d to read at timestamp %lf: "
728 "entry=%" PRIu32 ", duration=%" PRIu32 ", editrate=" AVRATIONAL_FORMAT "\n",
729 i, track->index, av_q2d(track->current_timestamp),
730 track->resources[i].resource->base.entry_point,
731 track->resources[i].resource->base.duration,
732 AVRATIONAL_ARG(track->resources[i].resource->base.edit_rate));
733
734 if (track->current_resource_index != i) {
735 int ret;
736
737 av_log(s, AV_LOG_TRACE, "Switch resource on track %d: re-open context\n",
738 track->index);
739
740 ret = open_track_resource_context(s, track, i);
741 if (ret != 0)
742 return ret;
743 if (track->current_resource_index > 0)
744 avformat_close_input(&track->resources[track->current_resource_index].ctx);
745 track->current_resource_index = i;
746 }
747
748 *resource = track->resources + track->current_resource_index;
749 return 0;
750 }
751 }
752
753 av_log(s, AV_LOG_ERROR, "Could not find IMF track resource to read\n");
754 return AVERROR_STREAM_NOT_FOUND;
755 }
756
imf_read_packet(AVFormatContext * s,AVPacket * pkt)757 static int imf_read_packet(AVFormatContext *s, AVPacket *pkt)
758 {
759 IMFVirtualTrackResourcePlaybackCtx *resource = NULL;
760 int ret = 0;
761 IMFVirtualTrackPlaybackCtx *track;
762 int64_t delta_ts;
763 AVStream *st;
764 AVRational next_timestamp;
765
766 track = get_next_track_with_minimum_timestamp(s);
767
768 ret = get_resource_context_for_timestamp(s, track, &resource);
769 if (ret)
770 return ret;
771
772 ret = av_read_frame(resource->ctx, pkt);
773 if (ret)
774 return ret;
775
776 av_log(s, AV_LOG_DEBUG, "Got packet: pts=%" PRId64 ", dts=%" PRId64
777 ", duration=%" PRId64 ", stream_index=%d, pos=%" PRId64
778 ", time_base=" AVRATIONAL_FORMAT "\n", pkt->pts, pkt->dts, pkt->duration,
779 pkt->stream_index, pkt->pos, AVRATIONAL_ARG(pkt->time_base));
780
781 /* IMF resources contain only one stream */
782
783 if (pkt->stream_index != 0)
784 return AVERROR_INVALIDDATA;
785 st = resource->ctx->streams[0];
786
787 pkt->stream_index = track->index;
788
789 /* adjust the packet PTS and DTS based on the temporal position of the resource within the timeline */
790
791 ret = imf_time_to_ts(&delta_ts, resource->ts_offset, st->time_base);
792
793 if (!ret) {
794 if (pkt->pts != AV_NOPTS_VALUE)
795 pkt->pts += delta_ts;
796 if (pkt->dts != AV_NOPTS_VALUE)
797 pkt->dts += delta_ts;
798 } else {
799 av_log(s, AV_LOG_WARNING, "Incoherent time stamp " AVRATIONAL_FORMAT
800 " for time base " AVRATIONAL_FORMAT,
801 AVRATIONAL_ARG(resource->ts_offset),
802 AVRATIONAL_ARG(pkt->time_base));
803 }
804
805 /* advance the track timestamp by the packet duration */
806
807 next_timestamp = av_add_q(track->current_timestamp,
808 av_mul_q(av_make_q((int)pkt->duration, 1), st->time_base));
809
810 /* if necessary, clamp the next timestamp to the end of the current resource */
811
812 if (av_cmp_q(next_timestamp, resource->end_time) > 0) {
813
814 int64_t new_pkt_dur;
815
816 /* shrink the packet duration */
817
818 ret = imf_time_to_ts(&new_pkt_dur,
819 av_sub_q(resource->end_time, track->current_timestamp),
820 st->time_base);
821
822 if (!ret)
823 pkt->duration = new_pkt_dur;
824 else
825 av_log(s, AV_LOG_WARNING, "Incoherent time base in packet duration calculation\n");
826
827 /* shrink the packet itself for audio essence */
828
829 if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
830
831 if (st->codecpar->codec_id == AV_CODEC_ID_PCM_S24LE) {
832 /* AV_CODEC_ID_PCM_S24LE is the only PCM format supported in IMF */
833 /* in this case, explicitly shrink the packet */
834
835 int bytes_per_sample = av_get_exact_bits_per_sample(st->codecpar->codec_id) >> 3;
836 int64_t nbsamples = av_rescale_q(pkt->duration,
837 st->time_base,
838 av_make_q(1, st->codecpar->sample_rate));
839 av_shrink_packet(pkt, nbsamples * st->codecpar->ch_layout.nb_channels * bytes_per_sample);
840
841 } else {
842 /* in all other cases, use side data to skip samples */
843 int64_t skip_samples;
844
845 ret = imf_time_to_ts(&skip_samples,
846 av_sub_q(next_timestamp, resource->end_time),
847 av_make_q(1, st->codecpar->sample_rate));
848
849 if (ret || skip_samples < 0 || skip_samples > UINT32_MAX) {
850 av_log(s, AV_LOG_WARNING, "Cannot skip audio samples\n");
851 } else {
852 uint8_t *side_data = av_packet_new_side_data(pkt, AV_PKT_DATA_SKIP_SAMPLES, 10);
853 if (!side_data)
854 return AVERROR(ENOMEM);
855
856 AV_WL32(side_data + 4, skip_samples); /* skip from end of this packet */
857 side_data[6] = 1; /* reason for end is convergence */
858 }
859 }
860
861 next_timestamp = resource->end_time;
862
863 } else {
864 av_log(s, AV_LOG_WARNING, "Non-audio packet duration reduced\n");
865 }
866 }
867
868 track->current_timestamp = next_timestamp;
869
870 return 0;
871 }
872
imf_close(AVFormatContext * s)873 static int imf_close(AVFormatContext *s)
874 {
875 IMFContext *c = s->priv_data;
876
877 av_log(s, AV_LOG_DEBUG, "Close IMF package\n");
878 av_dict_free(&c->avio_opts);
879 av_freep(&c->base_url);
880 imf_asset_locator_map_deinit(&c->asset_locator_map);
881 ff_imf_cpl_free(c->cpl);
882
883 for (uint32_t i = 0; i < c->track_count; i++) {
884 imf_virtual_track_playback_context_deinit(c->tracks[i]);
885 av_freep(&c->tracks[i]);
886 }
887
888 av_freep(&c->tracks);
889
890 return 0;
891 }
892
imf_probe(const AVProbeData * p)893 static int imf_probe(const AVProbeData *p)
894 {
895 if (!strstr(p->buf, "<CompositionPlaylist"))
896 return 0;
897
898 /* check for a ContentTitle element without including ContentTitleText,
899 * which is used by the D-Cinema CPL.
900 */
901 if (!strstr(p->buf, "ContentTitle>"))
902 return 0;
903
904 return AVPROBE_SCORE_MAX;
905 }
906
coherent_ts(int64_t ts,AVRational in_tb,AVRational out_tb)907 static int coherent_ts(int64_t ts, AVRational in_tb, AVRational out_tb)
908 {
909 int dst_num;
910 int dst_den;
911 int ret;
912
913 ret = av_reduce(&dst_num, &dst_den, ts * in_tb.num * out_tb.den,
914 in_tb.den * out_tb.num, INT64_MAX);
915 if (!ret || dst_den != 1)
916 return 0;
917
918 return 1;
919 }
920
imf_seek(AVFormatContext * s,int stream_index,int64_t min_ts,int64_t ts,int64_t max_ts,int flags)921 static int imf_seek(AVFormatContext *s, int stream_index, int64_t min_ts,
922 int64_t ts, int64_t max_ts, int flags)
923 {
924 IMFContext *c = s->priv_data;
925 uint32_t i;
926
927 if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
928 return AVERROR(ENOSYS);
929
930 /* rescale timestamps to Composition edit units */
931 if (stream_index < 0)
932 ff_rescale_interval(AV_TIME_BASE_Q,
933 av_make_q(c->cpl->edit_rate.den, c->cpl->edit_rate.num),
934 &min_ts, &ts, &max_ts);
935 else
936 ff_rescale_interval(s->streams[stream_index]->time_base,
937 av_make_q(c->cpl->edit_rate.den, c->cpl->edit_rate.num),
938 &min_ts, &ts, &max_ts);
939
940 /* requested timestamp bounds are too close */
941 if (max_ts < min_ts)
942 return -1;
943
944 /* clamp requested timestamp to provided bounds */
945 ts = FFMAX(FFMIN(ts, max_ts), min_ts);
946
947 av_log(s, AV_LOG_DEBUG, "Seeking to Composition Playlist edit unit %" PRIi64 "\n", ts);
948
949 /* set the dts of each stream and temporal offset of each track */
950 for (i = 0; i < c->track_count; i++) {
951 AVStream *st = s->streams[i];
952 IMFVirtualTrackPlaybackCtx *t = c->tracks[i];
953 int64_t dts;
954
955 if (!coherent_ts(ts, av_make_q(c->cpl->edit_rate.den, c->cpl->edit_rate.num),
956 st->time_base))
957 av_log(s, AV_LOG_WARNING, "Seek position is not coherent across tracks\n");
958
959 dts = av_rescale(ts,
960 st->time_base.den * c->cpl->edit_rate.den,
961 st->time_base.num * c->cpl->edit_rate.num);
962
963 av_log(s, AV_LOG_DEBUG, "Seeking to dts=%" PRId64 " on stream_index=%d\n",
964 dts, i);
965
966 t->current_timestamp = av_mul_q(av_make_q(dts, 1), st->time_base);
967 if (t->current_resource_index >= 0) {
968 avformat_close_input(&t->resources[t->current_resource_index].ctx);
969 t->current_resource_index = -1;
970 }
971 }
972
973 return 0;
974 }
975
976 static const AVOption imf_options[] = {
977 {
978 .name = "assetmaps",
979 .help = "Comma-separated paths to ASSETMAP files."
980 "If not specified, the `ASSETMAP.xml` file in the same "
981 "directory as the CPL is used.",
982 .offset = offsetof(IMFContext, asset_map_paths),
983 .type = AV_OPT_TYPE_STRING,
984 .default_val = {.str = NULL},
985 .flags = AV_OPT_FLAG_DECODING_PARAM,
986 },
987 {NULL},
988 };
989
990 static const AVClass imf_class = {
991 .class_name = "imf",
992 .item_name = av_default_item_name,
993 .option = imf_options,
994 .version = LIBAVUTIL_VERSION_INT,
995 };
996
997 const AVInputFormat ff_imf_demuxer = {
998 .name = "imf",
999 .long_name = NULL_IF_CONFIG_SMALL("IMF (Interoperable Master Format)"),
1000 .flags = AVFMT_EXPERIMENTAL | AVFMT_NO_BYTE_SEEK,
1001 .flags_internal = FF_FMT_INIT_CLEANUP,
1002 .priv_class = &imf_class,
1003 .priv_data_size = sizeof(IMFContext),
1004 .read_probe = imf_probe,
1005 .read_header = imf_read_header,
1006 .read_packet = imf_read_packet,
1007 .read_close = imf_close,
1008 .read_seek2 = imf_seek,
1009 };
1010