FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/concatdec.c
Date: 2024-03-29 11:55:30
Exec Total Coverage
Lines: 306 533 57.4%
Functions: 17 21 81.0%
Branches: 158 365 43.3%

Line Branch Exec Source
1 /*
2 * Copyright (c) 2012 Nicolas George
3 *
4 * This file is part of FFmpeg.
5 *
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public License
8 * as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "libavutil/avstring.h"
22 #include "libavutil/avassert.h"
23 #include "libavutil/bprint.h"
24 #include "libavutil/intreadwrite.h"
25 #include "libavutil/opt.h"
26 #include "libavutil/parseutils.h"
27 #include "libavutil/timestamp.h"
28 #include "libavcodec/codec_desc.h"
29 #include "libavcodec/bsf.h"
30 #include "avformat.h"
31 #include "avio_internal.h"
32 #include "demux.h"
33 #include "internal.h"
34 #include "url.h"
35
36 typedef enum ConcatMatchMode {
37 MATCH_ONE_TO_ONE,
38 MATCH_EXACT_ID,
39 } ConcatMatchMode;
40
41 typedef struct ConcatStream {
42 AVBSFContext *bsf;
43 int out_stream_index;
44 } ConcatStream;
45
46 typedef struct {
47 char *url;
48 int64_t start_time;
49 int64_t file_start_time;
50 int64_t file_inpoint;
51 int64_t duration;
52 int64_t user_duration;
53 int64_t next_dts;
54 ConcatStream *streams;
55 int64_t inpoint;
56 int64_t outpoint;
57 AVDictionary *metadata;
58 AVDictionary *options;
59 int nb_streams;
60 } ConcatFile;
61
62 typedef struct {
63 AVClass *class;
64 ConcatFile *files;
65 ConcatFile *cur_file;
66 unsigned nb_files;
67 AVFormatContext *avf;
68 int safe;
69 int seekable;
70 int eof;
71 ConcatMatchMode stream_match_mode;
72 unsigned auto_convert;
73 int segment_time_metadata;
74 } ConcatContext;
75
76 7124 static int concat_probe(const AVProbeData *probe)
77 {
78 7124 return memcmp(probe->buf, "ffconcat version 1.0", 20) ?
79
2/2
✓ Branch 0 taken 7119 times.
✓ Branch 1 taken 5 times.
7124 0 : AVPROBE_SCORE_MAX;
80 }
81
82 407 static char *get_keyword(uint8_t **cursor)
83 {
84 407 char *ret = *cursor += strspn(*cursor, SPACE_CHARS);
85 407 *cursor += strcspn(*cursor, SPACE_CHARS);
86
2/2
✓ Branch 0 taken 210 times.
✓ Branch 1 taken 197 times.
407 if (**cursor) {
87 210 *((*cursor)++) = 0;
88 210 *cursor += strspn(*cursor, SPACE_CHARS);
89 }
90 407 return ret;
91 }
92
93 static int safe_filename(const char *f)
94 {
95 const char *start = f;
96
97 for (; *f; f++) {
98 /* A-Za-z0-9_- */
99 if (!((unsigned)((*f | 32) - 'a') < 26 ||
100 (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) {
101 if (f == start)
102 return 0;
103 else if (*f == '/')
104 start = f + 1;
105 else if (*f != '.')
106 return 0;
107 }
108 }
109 return 1;
110 }
111
112 #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0)
113
114 68 static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile,
115 unsigned *nb_files_alloc)
116 {
117 68 ConcatContext *cat = avf->priv_data;
118 ConcatFile *file;
119 68 char *url = NULL;
120 const char *proto;
121 const char *ptr;
122 size_t url_len;
123 int ret;
124
125
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
68 if (cat->safe && !safe_filename(filename)) {
126 av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename);
127 FAIL(AVERROR(EPERM));
128 }
129
130 68 proto = avio_find_protocol_name(filename);
131
2/4
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
68 if (proto && av_strstart(filename, proto, &ptr) &&
132 (*ptr == ':' || *ptr == ',')) {
133 url = filename;
134 filename = NULL;
135 } else {
136 68 url_len = strlen(avf->url) + strlen(filename) + 16;
137
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if (!(url = av_malloc(url_len)))
138 FAIL(AVERROR(ENOMEM));
139 68 ff_make_absolute_url(url, url_len, avf->url, filename);
140 68 av_freep(&filename);
141 }
142
143
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 61 times.
68 if (cat->nb_files >= *nb_files_alloc) {
144 7 size_t n = FFMAX(*nb_files_alloc * 2, 16);
145 ConcatFile *new_files;
146
3/6
✓ Branch 0 taken 7 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 7 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 7 times.
14 if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) ||
147 7 !(new_files = av_realloc(cat->files, n * sizeof(*cat->files))))
148 FAIL(AVERROR(ENOMEM));
149 7 cat->files = new_files;
150 7 *nb_files_alloc = n;
151 }
152
153 68 file = &cat->files[cat->nb_files++];
154 68 memset(file, 0, sizeof(*file));
155 68 *rfile = file;
156
157 68 file->url = url;
158 68 file->start_time = AV_NOPTS_VALUE;
159 68 file->duration = AV_NOPTS_VALUE;
160 68 file->next_dts = AV_NOPTS_VALUE;
161 68 file->inpoint = AV_NOPTS_VALUE;
162 68 file->outpoint = AV_NOPTS_VALUE;
163 68 file->user_duration = AV_NOPTS_VALUE;
164
165 68 return 0;
166
167 fail:
168 av_free(url);
169 av_free(filename);
170 return ret;
171 }
172
173 136 static int copy_stream_props(AVStream *st, AVStream *source_st)
174 {
175 int ret;
176
177
3/4
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 126 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10 times.
136 if (st->codecpar->codec_id || !source_st->codecpar->codec_id) {
178
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 126 times.
126 if (st->codecpar->extradata_size < source_st->codecpar->extradata_size) {
179 ret = ff_alloc_extradata(st->codecpar,
180 source_st->codecpar->extradata_size);
181 if (ret < 0)
182 return ret;
183 }
184
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 63 times.
126 if (source_st->codecpar->extradata_size)
185 63 memcpy(st->codecpar->extradata, source_st->codecpar->extradata,
186 63 source_st->codecpar->extradata_size);
187 126 return 0;
188 }
189
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if ((ret = avcodec_parameters_copy(st->codecpar, source_st->codecpar)) < 0)
190 return ret;
191 10 st->r_frame_rate = source_st->r_frame_rate;
192 10 st->avg_frame_rate = source_st->avg_frame_rate;
193 10 st->sample_aspect_ratio = source_st->sample_aspect_ratio;
194 10 avpriv_set_pts_info(st, 64, source_st->time_base.num, source_st->time_base.den);
195
196 10 av_dict_copy(&st->metadata, source_st->metadata, 0);
197 10 return 0;
198 }
199
200 136 static int detect_stream_specific(AVFormatContext *avf, int idx)
201 {
202 136 ConcatContext *cat = avf->priv_data;
203 136 AVStream *st = cat->avf->streams[idx];
204 136 ConcatStream *cs = &cat->cur_file->streams[idx];
205 const AVBitStreamFilter *filter;
206 AVBSFContext *bsf;
207 int ret;
208
209
2/4
✓ Branch 0 taken 136 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 136 times.
136 if (cat->auto_convert && st->codecpar->codec_id == AV_CODEC_ID_H264) {
210 if (!st->codecpar->extradata_size ||
211 (st->codecpar->extradata_size >= 3 && AV_RB24(st->codecpar->extradata) == 1) ||
212 (st->codecpar->extradata_size >= 4 && AV_RB32(st->codecpar->extradata) == 1))
213 return 0;
214 av_log(cat->avf, AV_LOG_INFO,
215 "Auto-inserting h264_mp4toannexb bitstream filter\n");
216 filter = av_bsf_get_by_name("h264_mp4toannexb");
217 if (!filter) {
218 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter "
219 "required for H.264 streams\n");
220 return AVERROR_BSF_NOT_FOUND;
221 }
222 ret = av_bsf_alloc(filter, &bsf);
223 if (ret < 0)
224 return ret;
225 cs->bsf = bsf;
226
227 ret = avcodec_parameters_copy(bsf->par_in, st->codecpar);
228 if (ret < 0)
229 return ret;
230
231 ret = av_bsf_init(bsf);
232 if (ret < 0)
233 return ret;
234
235 ret = avcodec_parameters_copy(st->codecpar, bsf->par_out);
236 if (ret < 0)
237 return ret;
238 }
239 136 return 0;
240 }
241
242 64 static int match_streams_one_to_one(AVFormatContext *avf)
243 {
244 64 ConcatContext *cat = avf->priv_data;
245 AVStream *st;
246 int i, ret;
247
248
2/2
✓ Branch 0 taken 128 times.
✓ Branch 1 taken 64 times.
192 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
249
2/2
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 8 times.
128 if (i < avf->nb_streams) {
250 120 st = avf->streams[i];
251 } else {
252
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if (!(st = avformat_new_stream(avf, NULL)))
253 return AVERROR(ENOMEM);
254 }
255
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 128 times.
128 if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0)
256 return ret;
257 128 cat->cur_file->streams[i].out_stream_index = i;
258 }
259 64 return 0;
260 }
261
262 4 static int match_streams_exact_id(AVFormatContext *avf)
263 {
264 4 ConcatContext *cat = avf->priv_data;
265 AVStream *st;
266 int i, j, ret;
267
268
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 4 times.
12 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
269 8 st = cat->avf->streams[i];
270
2/2
✓ Branch 0 taken 16 times.
✓ Branch 1 taken 8 times.
24 for (j = 0; j < avf->nb_streams; j++) {
271
2/2
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 8 times.
16 if (avf->streams[j]->id == st->id) {
272 8 av_log(avf, AV_LOG_VERBOSE,
273 "Match slave stream #%d with stream #%d id 0x%x\n",
274 i, j, st->id);
275
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 8 times.
8 if ((ret = copy_stream_props(avf->streams[j], st)) < 0)
276 return ret;
277 8 cat->cur_file->streams[i].out_stream_index = j;
278 }
279 }
280 }
281 4 return 0;
282 }
283
284 1202 static int match_streams(AVFormatContext *avf)
285 {
286 1202 ConcatContext *cat = avf->priv_data;
287 ConcatStream *map;
288 int i, ret;
289
290
2/2
✓ Branch 0 taken 1134 times.
✓ Branch 1 taken 68 times.
1202 if (cat->cur_file->nb_streams >= cat->avf->nb_streams)
291 1134 return 0;
292 68 map = av_realloc(cat->cur_file->streams,
293 68 cat->avf->nb_streams * sizeof(*map));
294
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (!map)
295 return AVERROR(ENOMEM);
296 68 cat->cur_file->streams = map;
297 68 memset(map + cat->cur_file->nb_streams, 0,
298 68 (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map));
299
300
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 68 times.
204 for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) {
301 136 map[i].out_stream_index = -1;
302
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 136 times.
136 if ((ret = detect_stream_specific(avf, i)) < 0)
303 return ret;
304 }
305
2/3
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
68 switch (cat->stream_match_mode) {
306 64 case MATCH_ONE_TO_ONE:
307 64 ret = match_streams_one_to_one(avf);
308 64 break;
309 4 case MATCH_EXACT_ID:
310 4 ret = match_streams_exact_id(avf);
311 4 break;
312 default:
313 ret = AVERROR_BUG;
314 }
315
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
316 return ret;
317 68 cat->cur_file->nb_streams = cat->avf->nb_streams;
318 68 return 0;
319 }
320
321 136 static int64_t get_best_effort_duration(ConcatFile *file, AVFormatContext *avf)
322 {
323
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 130 times.
136 if (file->user_duration != AV_NOPTS_VALUE)
324 6 return file->user_duration;
325
2/2
✓ Branch 0 taken 110 times.
✓ Branch 1 taken 20 times.
130 if (file->outpoint != AV_NOPTS_VALUE)
326 110 return av_sat_sub64(file->outpoint, file->file_inpoint);
327
1/2
✓ Branch 0 taken 20 times.
✗ Branch 1 not taken.
20 if (avf->duration > 0)
328 20 return avf->duration - (file->file_inpoint - file->file_start_time);
329 if (file->next_dts != AV_NOPTS_VALUE)
330 return file->next_dts - file->file_inpoint;
331 return AV_NOPTS_VALUE;
332 }
333
334 68 static int open_file(AVFormatContext *avf, unsigned fileno)
335 {
336 68 ConcatContext *cat = avf->priv_data;
337 68 ConcatFile *file = &cat->files[fileno];
338 68 AVDictionary *options = NULL;
339 int ret;
340
341
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 5 times.
68 if (cat->avf)
342 63 avformat_close_input(&cat->avf);
343
344 68 cat->avf = avformat_alloc_context();
345
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (!cat->avf)
346 return AVERROR(ENOMEM);
347
348 68 cat->avf->flags |= avf->flags & ~AVFMT_FLAG_CUSTOM_IO;
349 68 cat->avf->interrupt_callback = avf->interrupt_callback;
350
351
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if ((ret = ff_copy_whiteblacklists(cat->avf, avf)) < 0)
352 return ret;
353
354 68 ret = av_dict_copy(&options, file->options, 0);
355
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
356 return ret;
357
358
2/4
✓ Branch 1 taken 68 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 68 times.
136 if ((ret = avformat_open_input(&cat->avf, file->url, NULL, &options)) < 0 ||
359 68 (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) {
360 av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url);
361 av_dict_free(&options);
362 avformat_close_input(&cat->avf);
363 return ret;
364 }
365
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (options) {
366 av_log(avf, AV_LOG_WARNING, "Unused options for '%s'.\n", file->url);
367 /* TODO log unused options once we have a proper string API */
368 av_dict_free(&options);
369 }
370 68 cat->cur_file = file;
371
2/2
✓ Branch 0 taken 63 times.
✓ Branch 1 taken 5 times.
68 file->start_time = !fileno ? 0 :
372 63 cat->files[fileno - 1].start_time +
373 63 cat->files[fileno - 1].duration;
374
1/2
✓ Branch 0 taken 68 times.
✗ Branch 1 not taken.
68 file->file_start_time = (cat->avf->start_time == AV_NOPTS_VALUE) ? 0 : cat->avf->start_time;
375
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 58 times.
68 file->file_inpoint = (file->inpoint == AV_NOPTS_VALUE) ? file->file_start_time : file->inpoint;
376 68 file->duration = get_best_effort_duration(file, cat->avf);
377
378
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (cat->segment_time_metadata) {
379 av_dict_set_int(&file->metadata, "lavf.concatdec.start_time", file->start_time, 0);
380 if (file->duration != AV_NOPTS_VALUE)
381 av_dict_set_int(&file->metadata, "lavf.concatdec.duration", file->duration, 0);
382 }
383
384
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 68 times.
68 if ((ret = match_streams(avf)) < 0)
385 return ret;
386
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 10 times.
68 if (file->inpoint != AV_NOPTS_VALUE) {
387
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 58 times.
58 if ((ret = avformat_seek_file(cat->avf, -1, INT64_MIN, file->inpoint, file->inpoint, 0)) < 0)
388 return ret;
389 }
390 68 return 0;
391 }
392
393 5 static int concat_read_close(AVFormatContext *avf)
394 {
395 5 ConcatContext *cat = avf->priv_data;
396 unsigned i, j;
397
398
2/2
✓ Branch 0 taken 68 times.
✓ Branch 1 taken 5 times.
73 for (i = 0; i < cat->nb_files; i++) {
399 68 av_freep(&cat->files[i].url);
400
2/2
✓ Branch 0 taken 136 times.
✓ Branch 1 taken 68 times.
204 for (j = 0; j < cat->files[i].nb_streams; j++) {
401
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 136 times.
136 if (cat->files[i].streams[j].bsf)
402 av_bsf_free(&cat->files[i].streams[j].bsf);
403 }
404 68 av_freep(&cat->files[i].streams);
405 68 av_dict_free(&cat->files[i].metadata);
406 68 av_dict_free(&cat->files[i].options);
407 }
408
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->avf)
409 5 avformat_close_input(&cat->avf);
410 5 av_freep(&cat->files);
411 5 return 0;
412 }
413
414 #define MAX_ARGS 3
415 #define NEEDS_UNSAFE (1 << 0)
416 #define NEEDS_FILE (1 << 1)
417 #define NEEDS_STREAM (1 << 2)
418
419 typedef struct ParseSyntax {
420 const char *keyword;
421 char args[MAX_ARGS];
422 uint8_t flags;
423 } ParseSyntax;
424
425 typedef enum ParseDirective {
426 DIR_FFCONCAT,
427 DIR_FILE,
428 DIR_DURATION,
429 DIR_INPOINT,
430 DIR_OUTPOINT,
431 DIR_FPMETA,
432 DIR_FPMETAS,
433 DIR_OPTION,
434 DIR_STREAM,
435 DIR_EXSID,
436 DIR_STMETA,
437 DIR_STCODEC,
438 DIR_STEDATA,
439 DIR_CHAPTER,
440 } ParseDirective;
441
442 static const ParseSyntax syntax[] = {
443 [DIR_FFCONCAT ] = { "ffconcat", "kk", 0 },
444 [DIR_FILE ] = { "file", "s", 0 },
445 [DIR_DURATION ] = { "duration", "d", NEEDS_FILE },
446 [DIR_INPOINT ] = { "inpoint", "d", NEEDS_FILE },
447 [DIR_OUTPOINT ] = { "outpoint", "d", NEEDS_FILE },
448 [DIR_FPMETA ] = { "file_packet_meta", "ks", NEEDS_FILE },
449 [DIR_FPMETAS ] = { "file_packet_metadata", "s", NEEDS_FILE },
450 [DIR_OPTION ] = { "option", "ks", NEEDS_FILE | NEEDS_UNSAFE },
451 [DIR_STREAM ] = { "stream", "", 0 },
452 [DIR_EXSID ] = { "exact_stream_id", "i", NEEDS_STREAM },
453 [DIR_STMETA ] = { "stream_meta", "ks", NEEDS_STREAM },
454 [DIR_STCODEC ] = { "stream_codec", "k", NEEDS_STREAM },
455 [DIR_STEDATA ] = { "stream_extradata", "k", NEEDS_STREAM },
456 [DIR_CHAPTER ] = { "chapter", "idd", 0 },
457 };
458
459 5 static int concat_parse_script(AVFormatContext *avf)
460 {
461 5 ConcatContext *cat = avf->priv_data;
462 5 unsigned nb_files_alloc = 0;
463 AVBPrint bp;
464 uint8_t *cursor, *keyword;
465 5 ConcatFile *file = NULL;
466 5 AVStream *stream = NULL;
467 5 AVChapter *chapter = NULL;
468 5 unsigned line = 0, arg;
469 const ParseSyntax *dir;
470 char *arg_kw[MAX_ARGS];
471 5 char *arg_str[MAX_ARGS] = { 0 };
472 int64_t arg_int[MAX_ARGS];
473 int ret;
474
475 5 av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
476
477
2/2
✓ Branch 1 taken 273 times.
✓ Branch 2 taken 5 times.
278 while ((ret = ff_read_line_to_bprint_overwrite(avf->pb, &bp)) >= 0) {
478 273 line++;
479 273 cursor = bp.str;
480 273 keyword = get_keyword(&cursor);
481
3/4
✓ Branch 0 taken 199 times.
✓ Branch 1 taken 74 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 199 times.
273 if (!*keyword || *keyword == '#')
482 74 continue;
483
1/2
✓ Branch 0 taken 741 times.
✗ Branch 1 not taken.
741 for (dir = syntax; dir < syntax + FF_ARRAY_ELEMS(syntax); dir++)
484
2/2
✓ Branch 0 taken 199 times.
✓ Branch 1 taken 542 times.
741 if (!strcmp(dir->keyword, keyword))
485 199 break;
486
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
199 if (dir >= syntax + FF_ARRAY_ELEMS(syntax)) {
487 av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n",
488 line, keyword);
489 FAIL(AVERROR_INVALIDDATA);
490 }
491
492 /* Flags check */
493
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 199 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
199 if ((dir->flags & NEEDS_UNSAFE) && cat->safe) {
494 av_log(avf, AV_LOG_ERROR, "Line %d: %s not allowed if safe\n", line, keyword);
495 FAIL(AVERROR_INVALIDDATA);
496 }
497
3/4
✓ Branch 0 taken 120 times.
✓ Branch 1 taken 79 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 120 times.
199 if ((dir->flags & NEEDS_FILE) && !cat->nb_files) {
498 av_log(avf, AV_LOG_ERROR, "Line %d: %s without file\n", line, keyword);
499 FAIL(AVERROR_INVALIDDATA);
500 }
501
3/4
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 195 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
199 if ((dir->flags & NEEDS_STREAM) && !avf->nb_streams) {
502 av_log(avf, AV_LOG_ERROR, "Line %d: %s without stream\n", line, keyword);
503 FAIL(AVERROR_INVALIDDATA);
504 }
505
506 /* Arguments parsing */
507
3/4
✓ Branch 0 taken 407 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 208 times.
✓ Branch 3 taken 199 times.
407 for (arg = 0; arg < FF_ARRAY_ELEMS(dir->args) && dir->args[arg]; arg++) {
508
4/5
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 16 times.
✓ Branch 3 taken 74 times.
✗ Branch 4 not taken.
208 switch (dir->args[arg]) {
509 116 case 'd': /* duration */
510 116 arg_kw[arg] = get_keyword(&cursor);
511 116 ret = av_parse_time(&arg_int[arg], arg_kw[arg], 1);
512
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 116 times.
116 if (ret < 0) {
513 av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n",
514 line, arg_kw[arg]);
515 goto fail;
516 }
517 116 break;
518 2 case 'i': /* integer */
519 2 arg_int[arg] = strtol(get_keyword(&cursor), NULL, 0);
520 2 break;
521 16 case 'k': /* keyword */
522 16 arg_kw[arg] = get_keyword(&cursor);
523 16 break;
524 74 case 's': /* string */
525
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 av_assert0(!arg_str[arg]);
526 74 arg_str[arg] = av_get_token((const char **)&cursor, SPACE_CHARS);
527
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (!arg_str[arg])
528 FAIL(AVERROR(ENOMEM));
529
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 74 times.
74 if (!*arg_str[arg]) {
530 av_log(avf, AV_LOG_ERROR, "Line %d: string required\n", line);
531 FAIL(AVERROR_INVALIDDATA);
532 }
533 74 break;
534 default:
535 FAIL(AVERROR_BUG);
536 }
537 }
538
539 /* Directive action */
540
9/15
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 68 times.
✓ Branch 2 taken 3 times.
✓ Branch 3 taken 58 times.
✓ Branch 4 taken 55 times.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✗ Branch 7 not taken.
✓ Branch 8 taken 2 times.
✓ Branch 9 taken 2 times.
✓ Branch 10 taken 2 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
✗ Branch 13 not taken.
✗ Branch 14 not taken.
199 switch ((ParseDirective)(dir - syntax)) {
541
542 5 case DIR_FFCONCAT:
543
2/4
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
5 if (strcmp(arg_kw[0], "version") || strcmp(arg_kw[1], "1.0")) {
544 av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line);
545 FAIL(AVERROR_INVALIDDATA);
546 }
547 199 break;
548
549 68 case DIR_FILE:
550 68 ret = add_file(avf, arg_str[0], &file, &nb_files_alloc);
551 68 arg_str[0] = NULL;
552
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (ret < 0)
553 goto fail;
554 68 break;
555
556 3 case DIR_DURATION:
557 3 file->user_duration = arg_int[0];
558 3 break;
559
560 58 case DIR_INPOINT:
561 58 file->inpoint = arg_int[0];
562 58 break;
563
564 55 case DIR_OUTPOINT:
565 55 file->outpoint = arg_int[0];
566 55 break;
567
568 4 case DIR_FPMETA:
569 4 ret = av_dict_set(&file->metadata, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
570 4 arg_str[1] = NULL;
571
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
4 if (ret < 0)
572 FAIL(ret);
573 4 break;
574
575 case DIR_FPMETAS:
576 if ((ret = av_dict_parse_string(&file->metadata, arg_str[0], "=", "", 0)) < 0) {
577 av_log(avf, AV_LOG_ERROR, "Line %d: failed to parse metadata string\n", line);
578 FAIL(AVERROR_INVALIDDATA);
579 }
580 av_log(avf, AV_LOG_WARNING,
581 "'file_packet_metadata key=value:key=value' is deprecated, "
582 "use multiple 'file_packet_meta key value' instead\n");
583 av_freep(&arg_str[0]);
584 break;
585
586 case DIR_OPTION:
587 ret = av_dict_set(&file->options, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
588 arg_str[1] = NULL;
589 if (ret < 0)
590 FAIL(ret);
591 break;
592
593 2 case DIR_STREAM:
594 2 stream = avformat_new_stream(avf, NULL);
595
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (!stream)
596 FAIL(AVERROR(ENOMEM));
597 2 break;
598
599 2 case DIR_EXSID:
600 2 stream->id = arg_int[0];
601 2 break;
602 2 case DIR_STMETA:
603 2 ret = av_dict_set(&stream->metadata, arg_kw[0], arg_str[1], AV_DICT_DONT_STRDUP_VAL);
604 2 arg_str[1] = NULL;
605
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2 times.
2 if (ret < 0)
606 FAIL(ret);
607 2 break;
608
609 case DIR_STCODEC: {
610 const AVCodecDescriptor *codec = avcodec_descriptor_get_by_name(arg_kw[0]);
611 if (!codec) {
612 av_log(avf, AV_LOG_ERROR, "Line %d: codec '%s' not found\n", line, arg_kw[0]);
613 FAIL(AVERROR_DECODER_NOT_FOUND);
614 }
615 stream->codecpar->codec_type = codec->type;
616 stream->codecpar->codec_id = codec->id;
617 break;
618 }
619
620 case DIR_STEDATA: {
621 int size = ff_hex_to_data(NULL, arg_kw[0]);
622 ret = ff_alloc_extradata(stream->codecpar, size);
623 if (ret < 0)
624 FAIL(ret);
625 ff_hex_to_data(stream->codecpar->extradata, arg_kw[0]);
626 break;
627 }
628
629 case DIR_CHAPTER:
630 chapter = avpriv_new_chapter(avf, arg_int[0], AV_TIME_BASE_Q,
631 arg_int[1], arg_int[2], NULL);
632 if (!chapter)
633 FAIL(ENOMEM);
634 break;
635
636 default:
637 FAIL(AVERROR_BUG);
638 }
639 }
640
641
3/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 3 times.
5 if (file->inpoint != AV_NOPTS_VALUE && file->outpoint != AV_NOPTS_VALUE) {
642
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 if (file->inpoint > file->outpoint ||
643
1/2
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
3 file->outpoint - (uint64_t)file->inpoint > INT64_MAX)
644 ret = AVERROR_INVALIDDATA;
645 }
646
647 5 fail:
648
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 5 times.
20 for (arg = 0; arg < MAX_ARGS; arg++)
649 15 av_freep(&arg_str[arg]);
650 5 av_bprint_finalize(&bp, NULL);
651
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 return ret == AVERROR_EOF ? 0 : ret;
652 }
653
654 5 static int concat_read_header(AVFormatContext *avf)
655 {
656 5 ConcatContext *cat = avf->priv_data;
657 5 int64_t time = 0;
658 unsigned i;
659 int ret;
660
661 5 ret = concat_parse_script(avf);
662
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (ret < 0)
663 return ret;
664
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (!cat->nb_files) {
665 av_log(avf, AV_LOG_ERROR, "No files to concat\n");
666 return AVERROR_INVALIDDATA;
667 }
668
669
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 for (i = 0; i < cat->nb_files; i++) {
670
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->files[i].start_time == AV_NOPTS_VALUE)
671 5 cat->files[i].start_time = time;
672 else
673 time = cat->files[i].start_time;
674
1/2
✓ Branch 0 taken 5 times.
✗ Branch 1 not taken.
5 if (cat->files[i].user_duration == AV_NOPTS_VALUE) {
675
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
5 if (cat->files[i].inpoint == AV_NOPTS_VALUE || cat->files[i].outpoint == AV_NOPTS_VALUE ||
676 cat->files[i].outpoint - (uint64_t)cat->files[i].inpoint != av_sat_sub64(cat->files[i].outpoint, cat->files[i].inpoint)
677 )
678 break;
679 cat->files[i].user_duration = cat->files[i].outpoint - cat->files[i].inpoint;
680 }
681 cat->files[i].duration = cat->files[i].user_duration;
682 if (time + (uint64_t)cat->files[i].user_duration > INT64_MAX)
683 return AVERROR_INVALIDDATA;
684 time += cat->files[i].user_duration;
685 }
686
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 5 times.
5 if (i == cat->nb_files) {
687 avf->duration = time;
688 cat->seekable = 1;
689 }
690
691 5 cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID :
692 MATCH_ONE_TO_ONE;
693
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if ((ret = open_file(avf, 0)) < 0)
694 return ret;
695
696 5 return 0;
697 }
698
699 68 static int open_next_file(AVFormatContext *avf)
700 {
701 68 ConcatContext *cat = avf->priv_data;
702 68 unsigned fileno = cat->cur_file - cat->files;
703
704 68 cat->cur_file->duration = get_best_effort_duration(cat->cur_file, cat->avf);
705
706
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 63 times.
68 if (++fileno >= cat->nb_files) {
707 5 cat->eof = 1;
708 5 return AVERROR_EOF;
709 }
710 63 return open_file(avf, fileno);
711 }
712
713 1081 static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt)
714 {
715 int ret;
716
717
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (cs->bsf) {
718 ret = av_bsf_send_packet(cs->bsf, pkt);
719 if (ret < 0) {
720 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb filter "
721 "failed to send input packet\n");
722 return ret;
723 }
724
725 while (!ret)
726 ret = av_bsf_receive_packet(cs->bsf, pkt);
727
728 if (ret < 0 && (ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)) {
729 av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb filter "
730 "failed to receive output packet\n");
731 return ret;
732 }
733 }
734 1081 return 0;
735 }
736
737 /* Returns true if the packet dts is greater or equal to the specified outpoint. */
738 1134 static int packet_after_outpoint(ConcatContext *cat, AVPacket *pkt)
739 {
740
3/4
✓ Branch 0 taken 570 times.
✓ Branch 1 taken 564 times.
✓ Branch 2 taken 570 times.
✗ Branch 3 not taken.
1134 if (cat->cur_file->outpoint != AV_NOPTS_VALUE && pkt->dts != AV_NOPTS_VALUE) {
741 570 return av_compare_ts(pkt->dts, cat->avf->streams[pkt->stream_index]->time_base,
742 570 cat->cur_file->outpoint, AV_TIME_BASE_Q) >= 0;
743 }
744 564 return 0;
745 }
746
747 1086 static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt)
748 {
749 1086 ConcatContext *cat = avf->priv_data;
750 int ret;
751 int64_t delta;
752 ConcatStream *cs;
753 AVStream *st;
754 FFStream *sti;
755
756
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
1086 if (cat->eof)
757 return AVERROR_EOF;
758
759
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1086 times.
1086 if (!cat->avf)
760 return AVERROR(EIO);
761
762 while (1) {
763 1149 ret = av_read_frame(cat->avf, pkt);
764
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 1134 times.
1149 if (ret == AVERROR_EOF) {
765
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 13 times.
15 if ((ret = open_next_file(avf)) < 0)
766 2 return ret;
767 13 continue;
768 }
769
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1134 times.
1134 if (ret < 0)
770 return ret;
771
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1134 times.
1134 if ((ret = match_streams(avf)) < 0) {
772 return ret;
773 }
774
2/2
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 1081 times.
1134 if (packet_after_outpoint(cat, pkt)) {
775 53 av_packet_unref(pkt);
776
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 50 times.
53 if ((ret = open_next_file(avf)) < 0)
777 3 return ret;
778 50 continue;
779 }
780 1081 cs = &cat->cur_file->streams[pkt->stream_index];
781
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
1081 if (cs->out_stream_index < 0) {
782 av_packet_unref(pkt);
783 continue;
784 }
785 1081 break;
786 }
787
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1081 times.
1081 if ((ret = filter_packet(avf, cs, pkt)) < 0)
788 return ret;
789
790 1081 st = cat->avf->streams[pkt->stream_index];
791 1081 sti = ffstream(st);
792 5405 av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s",
793 1081 (unsigned)(cat->cur_file - cat->files), pkt->stream_index,
794 1081 av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
795 1081 av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
796
797 1081 delta = av_rescale_q(cat->cur_file->start_time - cat->cur_file->file_inpoint,
798 1081 AV_TIME_BASE_Q,
799 1081 cat->avf->streams[pkt->stream_index]->time_base);
800
1/2
✓ Branch 0 taken 1081 times.
✗ Branch 1 not taken.
1081 if (pkt->pts != AV_NOPTS_VALUE)
801 1081 pkt->pts += delta;
802
1/2
✓ Branch 0 taken 1081 times.
✗ Branch 1 not taken.
1081 if (pkt->dts != AV_NOPTS_VALUE)
803 1081 pkt->dts += delta;
804 1081 av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n",
805 1081 av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base),
806 1081 av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base));
807
2/2
✓ Branch 0 taken 131 times.
✓ Branch 1 taken 950 times.
1081 if (cat->cur_file->metadata) {
808 size_t metadata_len;
809 131 char* packed_metadata = av_packet_pack_dictionary(cat->cur_file->metadata, &metadata_len);
810
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 131 times.
131 if (!packed_metadata)
811 return AVERROR(ENOMEM);
812 131 ret = av_packet_add_side_data(pkt, AV_PKT_DATA_STRINGS_METADATA,
813 packed_metadata, metadata_len);
814
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 131 times.
131 if (ret < 0) {
815 av_freep(&packed_metadata);
816 return ret;
817 }
818 }
819
820
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1081 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1081 if (cat->cur_file->duration == AV_NOPTS_VALUE && sti->cur_dts != AV_NOPTS_VALUE) {
821 int64_t next_dts = av_rescale_q(sti->cur_dts, st->time_base, AV_TIME_BASE_Q);
822 if (cat->cur_file->next_dts == AV_NOPTS_VALUE || next_dts > cat->cur_file->next_dts) {
823 cat->cur_file->next_dts = next_dts;
824 }
825 }
826
827 1081 pkt->stream_index = cs->out_stream_index;
828 1081 return 0;
829 }
830
831 static int try_seek(AVFormatContext *avf, int stream,
832 int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
833 {
834 ConcatContext *cat = avf->priv_data;
835 int64_t t0 = cat->cur_file->start_time - cat->cur_file->file_inpoint;
836
837 ts -= t0;
838 min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0;
839 max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0;
840 if (stream >= 0) {
841 if (stream >= cat->avf->nb_streams)
842 return AVERROR(EIO);
843 ff_rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base,
844 &min_ts, &ts, &max_ts);
845 }
846 return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags);
847 }
848
849 static int real_seek(AVFormatContext *avf, int stream,
850 int64_t min_ts, int64_t ts, int64_t max_ts, int flags, AVFormatContext *cur_avf)
851 {
852 ConcatContext *cat = avf->priv_data;
853 int ret, left, right;
854
855 if (stream >= 0) {
856 if (stream >= avf->nb_streams)
857 return AVERROR(EINVAL);
858 ff_rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q,
859 &min_ts, &ts, &max_ts);
860 }
861
862 left = 0;
863 right = cat->nb_files;
864
865 /* Always support seek to start */
866 if (ts <= 0)
867 right = 1;
868 else if (!cat->seekable)
869 return AVERROR(ESPIPE); /* XXX: can we use it? */
870
871 while (right - left > 1) {
872 int mid = (left + right) / 2;
873 if (ts < cat->files[mid].start_time)
874 right = mid;
875 else
876 left = mid;
877 }
878
879 if (cat->cur_file != &cat->files[left]) {
880 if ((ret = open_file(avf, left)) < 0)
881 return ret;
882 } else {
883 cat->avf = cur_avf;
884 }
885
886 ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
887 if (ret < 0 &&
888 left < cat->nb_files - 1 &&
889 cat->files[left + 1].start_time < max_ts) {
890 if (cat->cur_file == &cat->files[left])
891 cat->avf = NULL;
892 if ((ret = open_file(avf, left + 1)) < 0)
893 return ret;
894 ret = try_seek(avf, stream, min_ts, ts, max_ts, flags);
895 }
896 return ret;
897 }
898
899 static int concat_seek(AVFormatContext *avf, int stream,
900 int64_t min_ts, int64_t ts, int64_t max_ts, int flags)
901 {
902 ConcatContext *cat = avf->priv_data;
903 ConcatFile *cur_file_saved = cat->cur_file;
904 AVFormatContext *cur_avf_saved = cat->avf;
905 int ret;
906
907 if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME))
908 return AVERROR(ENOSYS);
909 cat->avf = NULL;
910 if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags, cur_avf_saved)) < 0) {
911 if (cat->cur_file != cur_file_saved) {
912 if (cat->avf)
913 avformat_close_input(&cat->avf);
914 }
915 cat->avf = cur_avf_saved;
916 cat->cur_file = cur_file_saved;
917 } else {
918 if (cat->cur_file != cur_file_saved) {
919 avformat_close_input(&cur_avf_saved);
920 }
921 cat->eof = 0;
922 }
923 return ret;
924 }
925
926 #define OFFSET(x) offsetof(ConcatContext, x)
927 #define DEC AV_OPT_FLAG_DECODING_PARAM
928
929 static const AVOption options[] = {
930 { "safe", "enable safe mode",
931 OFFSET(safe), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
932 { "auto_convert", "automatically convert bitstream format",
933 OFFSET(auto_convert), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DEC },
934 { "segment_time_metadata", "output file segment start time and duration as packet metadata",
935 OFFSET(segment_time_metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
936 { NULL }
937 };
938
939 static const AVClass concat_class = {
940 .class_name = "concat demuxer",
941 .item_name = av_default_item_name,
942 .option = options,
943 .version = LIBAVUTIL_VERSION_INT,
944 };
945
946
947 const FFInputFormat ff_concat_demuxer = {
948 .p.name = "concat",
949 .p.long_name = NULL_IF_CONFIG_SMALL("Virtual concatenation script"),
950 .p.priv_class = &concat_class,
951 .priv_data_size = sizeof(ConcatContext),
952 .flags_internal = FF_INFMT_FLAG_INIT_CLEANUP,
953 .read_probe = concat_probe,
954 .read_header = concat_read_header,
955 .read_packet = concat_read_packet,
956 .read_close = concat_read_close,
957 .read_seek2 = concat_seek,
958 };
959