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