Commit | Line | Data |
---|---|---|
2ba45a60 DM |
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/avassert.h" | |
22 | #include "libavutil/avstring.h" | |
23 | #include "libavutil/intreadwrite.h" | |
24 | #include "libavutil/opt.h" | |
25 | #include "libavutil/parseutils.h" | |
f6fa7814 | 26 | #include "libavutil/timestamp.h" |
2ba45a60 DM |
27 | #include "avformat.h" |
28 | #include "internal.h" | |
29 | #include "url.h" | |
30 | ||
31 | typedef enum ConcatMatchMode { | |
32 | MATCH_ONE_TO_ONE, | |
33 | MATCH_EXACT_ID, | |
34 | } ConcatMatchMode; | |
35 | ||
36 | typedef struct ConcatStream { | |
37 | AVBitStreamFilterContext *bsf; | |
38 | int out_stream_index; | |
39 | } ConcatStream; | |
40 | ||
41 | typedef struct { | |
42 | char *url; | |
43 | int64_t start_time; | |
44 | int64_t duration; | |
45 | ConcatStream *streams; | |
46 | int nb_streams; | |
47 | } ConcatFile; | |
48 | ||
49 | typedef struct { | |
50 | AVClass *class; | |
51 | ConcatFile *files; | |
52 | ConcatFile *cur_file; | |
53 | unsigned nb_files; | |
54 | AVFormatContext *avf; | |
55 | int safe; | |
56 | int seekable; | |
57 | ConcatMatchMode stream_match_mode; | |
58 | unsigned auto_convert; | |
59 | } ConcatContext; | |
60 | ||
61 | static int concat_probe(AVProbeData *probe) | |
62 | { | |
63 | return memcmp(probe->buf, "ffconcat version 1.0", 20) ? | |
64 | 0 : AVPROBE_SCORE_MAX; | |
65 | } | |
66 | ||
67 | static char *get_keyword(uint8_t **cursor) | |
68 | { | |
69 | char *ret = *cursor += strspn(*cursor, SPACE_CHARS); | |
70 | *cursor += strcspn(*cursor, SPACE_CHARS); | |
71 | if (**cursor) { | |
72 | *((*cursor)++) = 0; | |
73 | *cursor += strspn(*cursor, SPACE_CHARS); | |
74 | } | |
75 | return ret; | |
76 | } | |
77 | ||
78 | static int safe_filename(const char *f) | |
79 | { | |
80 | const char *start = f; | |
81 | ||
82 | for (; *f; f++) { | |
83 | /* A-Za-z0-9_- */ | |
84 | if (!((unsigned)((*f | 32) - 'a') < 26 || | |
85 | (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { | |
86 | if (f == start) | |
87 | return 0; | |
88 | else if (*f == '/') | |
89 | start = f + 1; | |
90 | else if (*f != '.') | |
91 | return 0; | |
92 | } | |
93 | } | |
94 | return 1; | |
95 | } | |
96 | ||
97 | #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0) | |
98 | ||
99 | static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile, | |
100 | unsigned *nb_files_alloc) | |
101 | { | |
102 | ConcatContext *cat = avf->priv_data; | |
103 | ConcatFile *file; | |
104 | char *url = NULL; | |
105 | const char *proto; | |
106 | size_t url_len, proto_len; | |
107 | int ret; | |
108 | ||
109 | if (cat->safe > 0 && !safe_filename(filename)) { | |
110 | av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename); | |
111 | FAIL(AVERROR(EPERM)); | |
112 | } | |
113 | ||
114 | proto = avio_find_protocol_name(filename); | |
115 | proto_len = proto ? strlen(proto) : 0; | |
116 | if (!memcmp(filename, proto, proto_len) && | |
117 | (filename[proto_len] == ':' || filename[proto_len] == ',')) { | |
118 | url = filename; | |
119 | filename = NULL; | |
120 | } else { | |
121 | url_len = strlen(avf->filename) + strlen(filename) + 16; | |
122 | if (!(url = av_malloc(url_len))) | |
123 | FAIL(AVERROR(ENOMEM)); | |
124 | ff_make_absolute_url(url, url_len, avf->filename, filename); | |
125 | av_freep(&filename); | |
126 | } | |
127 | ||
128 | if (cat->nb_files >= *nb_files_alloc) { | |
129 | size_t n = FFMAX(*nb_files_alloc * 2, 16); | |
130 | ConcatFile *new_files; | |
131 | if (n <= cat->nb_files || n > SIZE_MAX / sizeof(*cat->files) || | |
132 | !(new_files = av_realloc(cat->files, n * sizeof(*cat->files)))) | |
133 | FAIL(AVERROR(ENOMEM)); | |
134 | cat->files = new_files; | |
135 | *nb_files_alloc = n; | |
136 | } | |
137 | ||
138 | file = &cat->files[cat->nb_files++]; | |
139 | memset(file, 0, sizeof(*file)); | |
140 | *rfile = file; | |
141 | ||
142 | file->url = url; | |
143 | file->start_time = AV_NOPTS_VALUE; | |
144 | file->duration = AV_NOPTS_VALUE; | |
145 | ||
146 | return 0; | |
147 | ||
148 | fail: | |
149 | av_free(url); | |
150 | av_free(filename); | |
151 | return ret; | |
152 | } | |
153 | ||
154 | static int copy_stream_props(AVStream *st, AVStream *source_st) | |
155 | { | |
156 | int ret; | |
157 | ||
158 | if (st->codec->codec_id || !source_st->codec->codec_id) { | |
159 | if (st->codec->extradata_size < source_st->codec->extradata_size) { | |
160 | ret = ff_alloc_extradata(st->codec, | |
161 | source_st->codec->extradata_size); | |
162 | if (ret < 0) | |
163 | return ret; | |
164 | } | |
165 | memcpy(st->codec->extradata, source_st->codec->extradata, | |
166 | source_st->codec->extradata_size); | |
167 | return 0; | |
168 | } | |
169 | if ((ret = avcodec_copy_context(st->codec, source_st->codec)) < 0) | |
170 | return ret; | |
171 | st->r_frame_rate = source_st->r_frame_rate; | |
172 | st->avg_frame_rate = source_st->avg_frame_rate; | |
173 | st->time_base = source_st->time_base; | |
174 | st->sample_aspect_ratio = source_st->sample_aspect_ratio; | |
175 | return 0; | |
176 | } | |
177 | ||
178 | static int detect_stream_specific(AVFormatContext *avf, int idx) | |
179 | { | |
180 | ConcatContext *cat = avf->priv_data; | |
181 | AVStream *st = cat->avf->streams[idx]; | |
182 | ConcatStream *cs = &cat->cur_file->streams[idx]; | |
183 | AVBitStreamFilterContext *bsf; | |
184 | ||
185 | if (cat->auto_convert && st->codec->codec_id == AV_CODEC_ID_H264 && | |
186 | (st->codec->extradata_size < 4 || AV_RB32(st->codec->extradata) != 1)) { | |
187 | av_log(cat->avf, AV_LOG_INFO, | |
188 | "Auto-inserting h264_mp4toannexb bitstream filter\n"); | |
189 | if (!(bsf = av_bitstream_filter_init("h264_mp4toannexb"))) { | |
190 | av_log(avf, AV_LOG_ERROR, "h264_mp4toannexb bitstream filter " | |
191 | "required for H.264 streams\n"); | |
192 | return AVERROR_BSF_NOT_FOUND; | |
193 | } | |
194 | cs->bsf = bsf; | |
195 | } | |
196 | return 0; | |
197 | } | |
198 | ||
199 | static int match_streams_one_to_one(AVFormatContext *avf) | |
200 | { | |
201 | ConcatContext *cat = avf->priv_data; | |
202 | AVStream *st; | |
203 | int i, ret; | |
204 | ||
205 | for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) { | |
206 | if (i < avf->nb_streams) { | |
207 | st = avf->streams[i]; | |
208 | } else { | |
209 | if (!(st = avformat_new_stream(avf, NULL))) | |
210 | return AVERROR(ENOMEM); | |
211 | } | |
212 | if ((ret = copy_stream_props(st, cat->avf->streams[i])) < 0) | |
213 | return ret; | |
214 | cat->cur_file->streams[i].out_stream_index = i; | |
215 | } | |
216 | return 0; | |
217 | } | |
218 | ||
219 | static int match_streams_exact_id(AVFormatContext *avf) | |
220 | { | |
221 | ConcatContext *cat = avf->priv_data; | |
222 | AVStream *st; | |
223 | int i, j, ret; | |
224 | ||
225 | for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) { | |
226 | st = cat->avf->streams[i]; | |
227 | for (j = 0; j < avf->nb_streams; j++) { | |
228 | if (avf->streams[j]->id == st->id) { | |
229 | av_log(avf, AV_LOG_VERBOSE, | |
230 | "Match slave stream #%d with stream #%d id 0x%x\n", | |
231 | i, j, st->id); | |
232 | if ((ret = copy_stream_props(avf->streams[j], st)) < 0) | |
233 | return ret; | |
234 | cat->cur_file->streams[i].out_stream_index = j; | |
235 | } | |
236 | } | |
237 | } | |
238 | return 0; | |
239 | } | |
240 | ||
241 | static int match_streams(AVFormatContext *avf) | |
242 | { | |
243 | ConcatContext *cat = avf->priv_data; | |
244 | ConcatStream *map; | |
245 | int i, ret; | |
246 | ||
247 | if (cat->cur_file->nb_streams >= cat->avf->nb_streams) | |
248 | return 0; | |
249 | map = av_realloc(cat->cur_file->streams, | |
250 | cat->avf->nb_streams * sizeof(*map)); | |
251 | if (!map) | |
252 | return AVERROR(ENOMEM); | |
253 | cat->cur_file->streams = map; | |
254 | memset(map + cat->cur_file->nb_streams, 0, | |
255 | (cat->avf->nb_streams - cat->cur_file->nb_streams) * sizeof(*map)); | |
256 | ||
257 | for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) | |
258 | map[i].out_stream_index = -1; | |
259 | switch (cat->stream_match_mode) { | |
260 | case MATCH_ONE_TO_ONE: | |
261 | ret = match_streams_one_to_one(avf); | |
262 | break; | |
263 | case MATCH_EXACT_ID: | |
264 | ret = match_streams_exact_id(avf); | |
265 | break; | |
266 | default: | |
267 | ret = AVERROR_BUG; | |
268 | } | |
269 | if (ret < 0) | |
270 | return ret; | |
271 | for (i = cat->cur_file->nb_streams; i < cat->avf->nb_streams; i++) | |
272 | if ((ret = detect_stream_specific(avf, i)) < 0) | |
273 | return ret; | |
274 | cat->cur_file->nb_streams = cat->avf->nb_streams; | |
275 | return 0; | |
276 | } | |
277 | ||
278 | static int open_file(AVFormatContext *avf, unsigned fileno) | |
279 | { | |
280 | ConcatContext *cat = avf->priv_data; | |
281 | ConcatFile *file = &cat->files[fileno]; | |
282 | int ret; | |
283 | ||
284 | if (cat->avf) | |
285 | avformat_close_input(&cat->avf); | |
286 | ||
287 | cat->avf = avformat_alloc_context(); | |
288 | if (!cat->avf) | |
289 | return AVERROR(ENOMEM); | |
290 | ||
291 | cat->avf->interrupt_callback = avf->interrupt_callback; | |
f6fa7814 DM |
292 | |
293 | if ((ret = ff_copy_whitelists(cat->avf, avf)) < 0) | |
294 | return ret; | |
295 | ||
2ba45a60 DM |
296 | if ((ret = avformat_open_input(&cat->avf, file->url, NULL, NULL)) < 0 || |
297 | (ret = avformat_find_stream_info(cat->avf, NULL)) < 0) { | |
298 | av_log(avf, AV_LOG_ERROR, "Impossible to open '%s'\n", file->url); | |
299 | avformat_close_input(&cat->avf); | |
300 | return ret; | |
301 | } | |
302 | cat->cur_file = file; | |
303 | if (file->start_time == AV_NOPTS_VALUE) | |
304 | file->start_time = !fileno ? 0 : | |
305 | cat->files[fileno - 1].start_time + | |
306 | cat->files[fileno - 1].duration; | |
307 | if ((ret = match_streams(avf)) < 0) | |
308 | return ret; | |
309 | return 0; | |
310 | } | |
311 | ||
312 | static int concat_read_close(AVFormatContext *avf) | |
313 | { | |
314 | ConcatContext *cat = avf->priv_data; | |
315 | unsigned i; | |
316 | ||
317 | if (cat->avf) | |
318 | avformat_close_input(&cat->avf); | |
319 | for (i = 0; i < cat->nb_files; i++) { | |
320 | av_freep(&cat->files[i].url); | |
321 | av_freep(&cat->files[i].streams); | |
322 | } | |
323 | av_freep(&cat->files); | |
324 | return 0; | |
325 | } | |
326 | ||
327 | static int concat_read_header(AVFormatContext *avf) | |
328 | { | |
329 | ConcatContext *cat = avf->priv_data; | |
330 | uint8_t buf[4096]; | |
331 | uint8_t *cursor, *keyword; | |
332 | int ret, line = 0, i; | |
333 | unsigned nb_files_alloc = 0; | |
334 | ConcatFile *file = NULL; | |
335 | int64_t time = 0; | |
336 | ||
337 | while (1) { | |
338 | if ((ret = ff_get_line(avf->pb, buf, sizeof(buf))) <= 0) | |
339 | break; | |
340 | line++; | |
341 | cursor = buf; | |
342 | keyword = get_keyword(&cursor); | |
343 | if (!*keyword || *keyword == '#') | |
344 | continue; | |
345 | ||
346 | if (!strcmp(keyword, "file")) { | |
347 | char *filename = av_get_token((const char **)&cursor, SPACE_CHARS); | |
348 | if (!filename) { | |
349 | av_log(avf, AV_LOG_ERROR, "Line %d: filename required\n", line); | |
350 | FAIL(AVERROR_INVALIDDATA); | |
351 | } | |
352 | if ((ret = add_file(avf, filename, &file, &nb_files_alloc)) < 0) | |
353 | goto fail; | |
354 | } else if (!strcmp(keyword, "duration")) { | |
355 | char *dur_str = get_keyword(&cursor); | |
356 | int64_t dur; | |
357 | if (!file) { | |
358 | av_log(avf, AV_LOG_ERROR, "Line %d: duration without file\n", | |
359 | line); | |
360 | FAIL(AVERROR_INVALIDDATA); | |
361 | } | |
362 | if ((ret = av_parse_time(&dur, dur_str, 1)) < 0) { | |
363 | av_log(avf, AV_LOG_ERROR, "Line %d: invalid duration '%s'\n", | |
364 | line, dur_str); | |
365 | goto fail; | |
366 | } | |
367 | file->duration = dur; | |
368 | } else if (!strcmp(keyword, "stream")) { | |
369 | if (!avformat_new_stream(avf, NULL)) | |
370 | FAIL(AVERROR(ENOMEM)); | |
371 | } else if (!strcmp(keyword, "exact_stream_id")) { | |
372 | if (!avf->nb_streams) { | |
373 | av_log(avf, AV_LOG_ERROR, "Line %d: exact_stream_id without stream\n", | |
374 | line); | |
375 | FAIL(AVERROR_INVALIDDATA); | |
376 | } | |
377 | avf->streams[avf->nb_streams - 1]->id = | |
378 | strtol(get_keyword(&cursor), NULL, 0); | |
379 | } else if (!strcmp(keyword, "ffconcat")) { | |
380 | char *ver_kw = get_keyword(&cursor); | |
381 | char *ver_val = get_keyword(&cursor); | |
382 | if (strcmp(ver_kw, "version") || strcmp(ver_val, "1.0")) { | |
383 | av_log(avf, AV_LOG_ERROR, "Line %d: invalid version\n", line); | |
384 | FAIL(AVERROR_INVALIDDATA); | |
385 | } | |
386 | if (cat->safe < 0) | |
387 | cat->safe = 1; | |
388 | } else { | |
389 | av_log(avf, AV_LOG_ERROR, "Line %d: unknown keyword '%s'\n", | |
390 | line, keyword); | |
391 | FAIL(AVERROR_INVALIDDATA); | |
392 | } | |
393 | } | |
394 | if (ret < 0) | |
395 | goto fail; | |
396 | if (!cat->nb_files) | |
397 | FAIL(AVERROR_INVALIDDATA); | |
398 | ||
399 | for (i = 0; i < cat->nb_files; i++) { | |
400 | if (cat->files[i].start_time == AV_NOPTS_VALUE) | |
401 | cat->files[i].start_time = time; | |
402 | else | |
403 | time = cat->files[i].start_time; | |
404 | if (cat->files[i].duration == AV_NOPTS_VALUE) | |
405 | break; | |
406 | time += cat->files[i].duration; | |
407 | } | |
408 | if (i == cat->nb_files) { | |
409 | avf->duration = time; | |
410 | cat->seekable = 1; | |
411 | } | |
412 | ||
413 | cat->stream_match_mode = avf->nb_streams ? MATCH_EXACT_ID : | |
414 | MATCH_ONE_TO_ONE; | |
415 | if ((ret = open_file(avf, 0)) < 0) | |
416 | goto fail; | |
417 | return 0; | |
418 | ||
419 | fail: | |
420 | concat_read_close(avf); | |
421 | return ret; | |
422 | } | |
423 | ||
424 | static int open_next_file(AVFormatContext *avf) | |
425 | { | |
426 | ConcatContext *cat = avf->priv_data; | |
427 | unsigned fileno = cat->cur_file - cat->files; | |
428 | ||
429 | if (cat->cur_file->duration == AV_NOPTS_VALUE) | |
430 | cat->cur_file->duration = cat->avf->duration; | |
431 | ||
432 | if (++fileno >= cat->nb_files) | |
433 | return AVERROR_EOF; | |
434 | return open_file(avf, fileno); | |
435 | } | |
436 | ||
437 | static int filter_packet(AVFormatContext *avf, ConcatStream *cs, AVPacket *pkt) | |
438 | { | |
439 | AVStream *st = avf->streams[cs->out_stream_index]; | |
440 | AVBitStreamFilterContext *bsf; | |
441 | AVPacket pkt2; | |
442 | int ret; | |
443 | ||
444 | av_assert0(cs->out_stream_index >= 0); | |
445 | for (bsf = cs->bsf; bsf; bsf = bsf->next) { | |
446 | pkt2 = *pkt; | |
447 | ret = av_bitstream_filter_filter(bsf, st->codec, NULL, | |
448 | &pkt2.data, &pkt2.size, | |
449 | pkt->data, pkt->size, | |
450 | !!(pkt->flags & AV_PKT_FLAG_KEY)); | |
451 | if (ret < 0) { | |
452 | av_packet_unref(pkt); | |
453 | return ret; | |
454 | } | |
455 | av_assert0(pkt2.buf); | |
456 | if (ret == 0 && pkt2.data != pkt->data) { | |
457 | if ((ret = av_copy_packet(&pkt2, pkt)) < 0) { | |
458 | av_free(pkt2.data); | |
459 | return ret; | |
460 | } | |
461 | ret = 1; | |
462 | } | |
463 | if (ret > 0) { | |
464 | av_free_packet(pkt); | |
465 | pkt2.buf = av_buffer_create(pkt2.data, pkt2.size, | |
466 | av_buffer_default_free, NULL, 0); | |
467 | if (!pkt2.buf) { | |
468 | av_free(pkt2.data); | |
469 | return AVERROR(ENOMEM); | |
470 | } | |
471 | } | |
472 | *pkt = pkt2; | |
473 | } | |
474 | return 0; | |
475 | } | |
476 | ||
477 | static int concat_read_packet(AVFormatContext *avf, AVPacket *pkt) | |
478 | { | |
479 | ConcatContext *cat = avf->priv_data; | |
480 | int ret; | |
481 | int64_t delta; | |
482 | ConcatStream *cs; | |
f6fa7814 | 483 | AVStream *st; |
2ba45a60 DM |
484 | |
485 | while (1) { | |
486 | ret = av_read_frame(cat->avf, pkt); | |
487 | if (ret == AVERROR_EOF) { | |
488 | if ((ret = open_next_file(avf)) < 0) | |
489 | return ret; | |
490 | continue; | |
491 | } | |
492 | if (ret < 0) | |
493 | return ret; | |
494 | if ((ret = match_streams(avf)) < 0) { | |
495 | av_packet_unref(pkt); | |
496 | return ret; | |
497 | } | |
498 | cs = &cat->cur_file->streams[pkt->stream_index]; | |
499 | if (cs->out_stream_index < 0) { | |
500 | av_packet_unref(pkt); | |
501 | continue; | |
502 | } | |
503 | pkt->stream_index = cs->out_stream_index; | |
504 | break; | |
505 | } | |
506 | if ((ret = filter_packet(avf, cs, pkt))) | |
507 | return ret; | |
508 | ||
f6fa7814 DM |
509 | st = cat->avf->streams[pkt->stream_index]; |
510 | av_log(avf, AV_LOG_DEBUG, "file:%d stream:%d pts:%s pts_time:%s dts:%s dts_time:%s", | |
511 | (unsigned)(cat->cur_file - cat->files), pkt->stream_index, | |
512 | av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), | |
513 | av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); | |
514 | ||
2ba45a60 DM |
515 | delta = av_rescale_q(cat->cur_file->start_time - cat->avf->start_time, |
516 | AV_TIME_BASE_Q, | |
517 | cat->avf->streams[pkt->stream_index]->time_base); | |
518 | if (pkt->pts != AV_NOPTS_VALUE) | |
519 | pkt->pts += delta; | |
520 | if (pkt->dts != AV_NOPTS_VALUE) | |
521 | pkt->dts += delta; | |
f6fa7814 DM |
522 | av_log(avf, AV_LOG_DEBUG, " -> pts:%s pts_time:%s dts:%s dts_time:%s\n", |
523 | av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), | |
524 | av_ts2str(pkt->dts), av_ts2timestr(pkt->dts, &st->time_base)); | |
2ba45a60 DM |
525 | return ret; |
526 | } | |
527 | ||
528 | static void rescale_interval(AVRational tb_in, AVRational tb_out, | |
529 | int64_t *min_ts, int64_t *ts, int64_t *max_ts) | |
530 | { | |
531 | *ts = av_rescale_q (* ts, tb_in, tb_out); | |
532 | *min_ts = av_rescale_q_rnd(*min_ts, tb_in, tb_out, | |
533 | AV_ROUND_UP | AV_ROUND_PASS_MINMAX); | |
534 | *max_ts = av_rescale_q_rnd(*max_ts, tb_in, tb_out, | |
535 | AV_ROUND_DOWN | AV_ROUND_PASS_MINMAX); | |
536 | } | |
537 | ||
538 | static int try_seek(AVFormatContext *avf, int stream, | |
539 | int64_t min_ts, int64_t ts, int64_t max_ts, int flags) | |
540 | { | |
541 | ConcatContext *cat = avf->priv_data; | |
542 | int64_t t0 = cat->cur_file->start_time - cat->avf->start_time; | |
543 | ||
544 | ts -= t0; | |
545 | min_ts = min_ts == INT64_MIN ? INT64_MIN : min_ts - t0; | |
546 | max_ts = max_ts == INT64_MAX ? INT64_MAX : max_ts - t0; | |
547 | if (stream >= 0) { | |
548 | if (stream >= cat->avf->nb_streams) | |
549 | return AVERROR(EIO); | |
550 | rescale_interval(AV_TIME_BASE_Q, cat->avf->streams[stream]->time_base, | |
551 | &min_ts, &ts, &max_ts); | |
552 | } | |
553 | return avformat_seek_file(cat->avf, stream, min_ts, ts, max_ts, flags); | |
554 | } | |
555 | ||
556 | static int real_seek(AVFormatContext *avf, int stream, | |
557 | int64_t min_ts, int64_t ts, int64_t max_ts, int flags) | |
558 | { | |
559 | ConcatContext *cat = avf->priv_data; | |
560 | int ret, left, right; | |
561 | ||
562 | if (stream >= 0) { | |
563 | if (stream >= avf->nb_streams) | |
564 | return AVERROR(EINVAL); | |
565 | rescale_interval(avf->streams[stream]->time_base, AV_TIME_BASE_Q, | |
566 | &min_ts, &ts, &max_ts); | |
567 | } | |
568 | ||
569 | left = 0; | |
570 | right = cat->nb_files; | |
571 | while (right - left > 1) { | |
572 | int mid = (left + right) / 2; | |
573 | if (ts < cat->files[mid].start_time) | |
574 | right = mid; | |
575 | else | |
576 | left = mid; | |
577 | } | |
578 | ||
579 | if ((ret = open_file(avf, left)) < 0) | |
580 | return ret; | |
581 | ||
582 | ret = try_seek(avf, stream, min_ts, ts, max_ts, flags); | |
583 | if (ret < 0 && | |
584 | left < cat->nb_files - 1 && | |
585 | cat->files[left + 1].start_time < max_ts) { | |
586 | if ((ret = open_file(avf, left + 1)) < 0) | |
587 | return ret; | |
588 | ret = try_seek(avf, stream, min_ts, ts, max_ts, flags); | |
589 | } | |
590 | return ret; | |
591 | } | |
592 | ||
593 | static int concat_seek(AVFormatContext *avf, int stream, | |
594 | int64_t min_ts, int64_t ts, int64_t max_ts, int flags) | |
595 | { | |
596 | ConcatContext *cat = avf->priv_data; | |
597 | ConcatFile *cur_file_saved = cat->cur_file; | |
598 | AVFormatContext *cur_avf_saved = cat->avf; | |
599 | int ret; | |
600 | ||
601 | if (!cat->seekable) | |
602 | return AVERROR(ESPIPE); /* XXX: can we use it? */ | |
603 | if (flags & (AVSEEK_FLAG_BYTE | AVSEEK_FLAG_FRAME)) | |
604 | return AVERROR(ENOSYS); | |
605 | cat->avf = NULL; | |
606 | if ((ret = real_seek(avf, stream, min_ts, ts, max_ts, flags)) < 0) { | |
607 | if (cat->avf) | |
608 | avformat_close_input(&cat->avf); | |
609 | cat->avf = cur_avf_saved; | |
610 | cat->cur_file = cur_file_saved; | |
611 | } else { | |
612 | avformat_close_input(&cur_avf_saved); | |
613 | } | |
614 | return ret; | |
615 | } | |
616 | ||
617 | #define OFFSET(x) offsetof(ConcatContext, x) | |
618 | #define DEC AV_OPT_FLAG_DECODING_PARAM | |
619 | ||
620 | static const AVOption options[] = { | |
621 | { "safe", "enable safe mode", | |
622 | OFFSET(safe), AV_OPT_TYPE_INT, {.i64 = -1}, -1, 1, DEC }, | |
623 | { "auto_convert", "automatically convert bitstream format", | |
624 | OFFSET(auto_convert), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, DEC }, | |
625 | { NULL } | |
626 | }; | |
627 | ||
628 | static const AVClass concat_class = { | |
629 | .class_name = "concat demuxer", | |
630 | .item_name = av_default_item_name, | |
631 | .option = options, | |
632 | .version = LIBAVUTIL_VERSION_INT, | |
633 | }; | |
634 | ||
635 | ||
636 | AVInputFormat ff_concat_demuxer = { | |
637 | .name = "concat", | |
638 | .long_name = NULL_IF_CONFIG_SMALL("Virtual concatenation script"), | |
639 | .priv_data_size = sizeof(ConcatContext), | |
640 | .read_probe = concat_probe, | |
641 | .read_header = concat_read_header, | |
642 | .read_packet = concat_read_packet, | |
643 | .read_close = concat_read_close, | |
644 | .read_seek2 = concat_seek, | |
645 | .priv_class = &concat_class, | |
646 | }; |