This is meant to address trac ticket #1483.
Stefano Sabatini authored on 2012/12/10 04:26:30... | ... |
@@ -608,6 +608,14 @@ the specified time and the time set by @var{force_key_frames}. |
608 | 608 |
Specify a list of split points. @var{times} contains a list of comma |
609 | 609 |
separated duration specifications, in increasing order. |
610 | 610 |
|
611 |
+@item segment_frames @var{frames} |
|
612 |
+Specify a list of split video frame numbers. @var{frames} contains a |
|
613 |
+list of comma separated integer numbers, in increasing order. |
|
614 |
+ |
|
615 |
+This option specifies to start a new segment whenever a reference |
|
616 |
+stream key frame is found and the sequential number (starting from 0) |
|
617 |
+of the frame is greater or equal to the next value in the list. |
|
618 |
+ |
|
611 | 619 |
@item segment_wrap @var{limit} |
612 | 620 |
Wrap around segment index once it reaches @var{limit}. |
613 | 621 |
|
... | ... |
@@ -652,6 +660,13 @@ In order to force key frames on the input file, transcoding is |
652 | 652 |
required. |
653 | 653 |
|
654 | 654 |
@item |
655 |
+Segment the input file by splitting the input file according to the |
|
656 |
+frame numbers sequence specified with the @var{segment_frame} option: |
|
657 |
+@example |
|
658 |
+ffmpeg -i in.mkv -codec copy -map 0 -f segment -segment_list out.csv -segment_frames 100,200,300,500,800 out%03d.nut |
|
659 |
+@end example |
|
660 |
+ |
|
661 |
+@item |
|
655 | 662 |
To convert the @file{in.mkv} to TS segments using the @code{libx264} |
656 | 663 |
and @code{libfaac} encoders: |
657 | 664 |
@example |
... | ... |
@@ -24,6 +24,8 @@ |
24 | 24 |
* @url{http://tools.ietf.org/id/draft-pantos-http-live-streaming-08.txt} |
25 | 25 |
*/ |
26 | 26 |
|
27 |
+/* #define DEBUG */ |
|
28 |
+ |
|
27 | 29 |
#include <float.h> |
28 | 30 |
|
29 | 31 |
#include "avformat.h" |
... | ... |
@@ -45,7 +47,6 @@ typedef enum { |
45 | 45 |
LIST_TYPE_NB, |
46 | 46 |
} ListType; |
47 | 47 |
|
48 |
- |
|
49 | 48 |
#define SEGMENT_LIST_FLAG_CACHE 1 |
50 | 49 |
#define SEGMENT_LIST_FLAG_LIVE 2 |
51 | 50 |
|
... | ... |
@@ -65,9 +66,16 @@ typedef struct { |
65 | 65 |
AVIOContext *list_pb; ///< list file put-byte context |
66 | 66 |
char *time_str; ///< segment duration specification string |
67 | 67 |
int64_t time; ///< segment duration |
68 |
+ |
|
68 | 69 |
char *times_str; ///< segment times specification string |
69 | 70 |
int64_t *times; ///< list of segment interval specification |
70 | 71 |
int nb_times; ///< number of elments in the times array |
72 |
+ |
|
73 |
+ char *frames_str; ///< segment frame numbers specification string |
|
74 |
+ int *frames; ///< list of frame number specification |
|
75 |
+ int nb_frames; ///< number of elments in the frames array |
|
76 |
+ int frame_count; |
|
77 |
+ |
|
71 | 78 |
char *time_delta_str; ///< approximation value duration used for the segment times |
72 | 79 |
int64_t time_delta; |
73 | 80 |
int individual_header_trailer; /**< Set by a private option. */ |
... | ... |
@@ -320,6 +328,65 @@ end: |
320 | 320 |
return ret; |
321 | 321 |
} |
322 | 322 |
|
323 |
+static int parse_frames(void *log_ctx, int **frames, int *nb_frames, |
|
324 |
+ const char *frames_str) |
|
325 |
+{ |
|
326 |
+ char *p; |
|
327 |
+ int i, ret = 0; |
|
328 |
+ char *frames_str1 = av_strdup(frames_str); |
|
329 |
+ char *saveptr = NULL; |
|
330 |
+ |
|
331 |
+ if (!frames_str1) |
|
332 |
+ return AVERROR(ENOMEM); |
|
333 |
+ |
|
334 |
+#define FAIL(err) ret = err; goto end |
|
335 |
+ |
|
336 |
+ *nb_frames = 1; |
|
337 |
+ for (p = frames_str1; *p; p++) |
|
338 |
+ if (*p == ',') |
|
339 |
+ (*nb_frames)++; |
|
340 |
+ |
|
341 |
+ *frames = av_malloc(sizeof(**frames) * *nb_frames); |
|
342 |
+ if (!*frames) { |
|
343 |
+ av_log(log_ctx, AV_LOG_ERROR, "Could not allocate forced frames array\n"); |
|
344 |
+ FAIL(AVERROR(ENOMEM)); |
|
345 |
+ } |
|
346 |
+ |
|
347 |
+ p = frames_str1; |
|
348 |
+ for (i = 0; i < *nb_frames; i++) { |
|
349 |
+ long int f; |
|
350 |
+ char *tailptr; |
|
351 |
+ char *fstr = av_strtok(p, ",", &saveptr); |
|
352 |
+ |
|
353 |
+ p = NULL; |
|
354 |
+ if (!fstr) { |
|
355 |
+ av_log(log_ctx, AV_LOG_ERROR, "Empty frame specification in frame list %s\n", |
|
356 |
+ frames_str); |
|
357 |
+ FAIL(AVERROR(EINVAL)); |
|
358 |
+ } |
|
359 |
+ f = strtol(fstr, &tailptr, 10); |
|
360 |
+ if (*tailptr || f <= 0 || f >= INT_MAX) { |
|
361 |
+ av_log(log_ctx, AV_LOG_ERROR, |
|
362 |
+ "Invalid argument '%s', must be a positive integer <= INT64_MAX\n", |
|
363 |
+ fstr); |
|
364 |
+ FAIL(AVERROR(EINVAL)); |
|
365 |
+ } |
|
366 |
+ (*frames)[i] = f; |
|
367 |
+ |
|
368 |
+ /* check on monotonicity */ |
|
369 |
+ if (i && (*frames)[i-1] > (*frames)[i]) { |
|
370 |
+ av_log(log_ctx, AV_LOG_ERROR, |
|
371 |
+ "Specified frame %d is greater than the following frame %d\n", |
|
372 |
+ (*frames)[i], (*frames)[i-1]); |
|
373 |
+ FAIL(AVERROR(EINVAL)); |
|
374 |
+ } |
|
375 |
+ } |
|
376 |
+ |
|
377 |
+end: |
|
378 |
+ av_free(frames_str1); |
|
379 |
+ return ret; |
|
380 |
+} |
|
381 |
+ |
|
323 | 382 |
static int open_null_ctx(AVIOContext **ctx) |
324 | 383 |
{ |
325 | 384 |
int buf_size = 32768; |
... | ... |
@@ -350,22 +417,26 @@ static int seg_write_header(AVFormatContext *s) |
350 | 350 |
if (!seg->write_header_trailer) |
351 | 351 |
seg->individual_header_trailer = 0; |
352 | 352 |
|
353 |
- if (seg->time_str && seg->times_str) { |
|
353 |
+ if (!!seg->time_str + !!seg->times_str + !!seg->frames_str > 1) { |
|
354 | 354 |
av_log(s, AV_LOG_ERROR, |
355 |
- "segment_time and segment_times options are mutually exclusive, select just one of them\n"); |
|
355 |
+ "segment_time, segment_times, and segment_frames options " |
|
356 |
+ "are mutually exclusive, select just one of them\n"); |
|
356 | 357 |
return AVERROR(EINVAL); |
357 | 358 |
} |
358 | 359 |
|
359 |
- if ((seg->list_flags & SEGMENT_LIST_FLAG_LIVE) && seg->times_str) { |
|
360 |
+ if ((seg->list_flags & SEGMENT_LIST_FLAG_LIVE) && (seg->times_str || seg->frames_str)) { |
|
360 | 361 |
av_log(s, AV_LOG_ERROR, |
361 |
- "segment_flags +live and segment_times options are mutually exclusive:" |
|
362 |
- "specify -segment_time if you want a live-friendly list\n"); |
|
362 |
+ "segment_flags +live and segment_times or segment_frames options are mutually exclusive: " |
|
363 |
+ "specify segment_time option if you want a live-friendly list\n"); |
|
363 | 364 |
return AVERROR(EINVAL); |
364 | 365 |
} |
365 | 366 |
|
366 | 367 |
if (seg->times_str) { |
367 | 368 |
if ((ret = parse_times(s, &seg->times, &seg->nb_times, seg->times_str)) < 0) |
368 | 369 |
return ret; |
370 |
+ } else if (seg->frames_str) { |
|
371 |
+ if ((ret = parse_frames(s, &seg->frames, &seg->nb_frames, seg->frames_str)) < 0) |
|
372 |
+ return ret; |
|
369 | 373 |
} else { |
370 | 374 |
/* set default value if not specified */ |
371 | 375 |
if (!seg->time_str) |
... | ... |
@@ -513,21 +584,31 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) |
513 | 513 |
SegmentContext *seg = s->priv_data; |
514 | 514 |
AVFormatContext *oc = seg->avf; |
515 | 515 |
AVStream *st = s->streams[pkt->stream_index]; |
516 |
- int64_t end_pts; |
|
516 |
+ int64_t end_pts = INT64_MAX; |
|
517 |
+ int start_frame = INT_MAX; |
|
517 | 518 |
int ret; |
518 | 519 |
|
519 | 520 |
if (seg->times) { |
520 | 521 |
end_pts = seg->segment_count <= seg->nb_times ? |
521 | 522 |
seg->times[seg->segment_count-1] : INT64_MAX; |
523 |
+ } else if (seg->frames) { |
|
524 |
+ start_frame = seg->segment_count <= seg->nb_frames ? |
|
525 |
+ seg->frames[seg->segment_count-1] : INT_MAX; |
|
522 | 526 |
} else { |
523 | 527 |
end_pts = seg->time * seg->segment_count; |
524 | 528 |
} |
525 | 529 |
|
530 |
+ av_dlog(s, "packet stream:%d pts:%s pts_time:%s is_key:%d frame:%d\n", |
|
531 |
+ pkt->stream_index, av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), |
|
532 |
+ pkt->flags & AV_PKT_FLAG_KEY, |
|
533 |
+ pkt->stream_index == seg->reference_stream_index ? seg->frame_count : -1); |
|
534 |
+ |
|
526 | 535 |
if (pkt->stream_index == seg->reference_stream_index && |
527 |
- pkt->pts != AV_NOPTS_VALUE && |
|
528 |
- av_compare_ts(pkt->pts, st->time_base, |
|
529 |
- end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0 && |
|
530 |
- pkt->flags & AV_PKT_FLAG_KEY) { |
|
536 |
+ pkt->flags & AV_PKT_FLAG_KEY && |
|
537 |
+ (seg->frame_count >= start_frame || |
|
538 |
+ (pkt->pts != AV_NOPTS_VALUE && |
|
539 |
+ av_compare_ts(pkt->pts, st->time_base, |
|
540 |
+ end_pts-seg->time_delta, AV_TIME_BASE_Q) >= 0))) { |
|
531 | 541 |
ret = segment_end(s, seg->individual_header_trailer); |
532 | 542 |
|
533 | 543 |
if (!ret) |
... | ... |
@@ -548,9 +629,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) |
548 | 548 |
} |
549 | 549 |
|
550 | 550 |
if (seg->is_first_pkt) { |
551 |
- av_log(s, AV_LOG_DEBUG, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s\n", |
|
551 |
+ av_log(s, AV_LOG_DEBUG, "segment:'%s' starts with packet stream:%d pts:%s pts_time:%s frame:%d\n", |
|
552 | 552 |
seg->avf->filename, pkt->stream_index, |
553 |
- av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base)); |
|
553 |
+ av_ts2str(pkt->pts), av_ts2timestr(pkt->pts, &st->time_base), seg->frame_count); |
|
554 | 554 |
seg->is_first_pkt = 0; |
555 | 555 |
} |
556 | 556 |
|
... | ... |
@@ -572,6 +653,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) |
572 | 572 |
ret = ff_write_chained(oc, pkt->stream_index, pkt, s); |
573 | 573 |
|
574 | 574 |
fail: |
575 |
+ if (pkt->stream_index == seg->reference_stream_index) |
|
576 |
+ seg->frame_count++; |
|
577 |
+ |
|
575 | 578 |
if (ret < 0) { |
576 | 579 |
if (seg->list) |
577 | 580 |
avio_close(seg->list_pb); |
... | ... |
@@ -601,6 +685,7 @@ fail: |
601 | 601 |
|
602 | 602 |
av_opt_free(seg); |
603 | 603 |
av_freep(&seg->times); |
604 |
+ av_freep(&seg->frames); |
|
604 | 605 |
|
605 | 606 |
avformat_free_context(oc); |
606 | 607 |
return ret; |
... | ... |
@@ -629,6 +714,7 @@ static const AVOption options[] = { |
629 | 629 |
{ "segment_time", "set segment duration", OFFSET(time_str),AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E }, |
630 | 630 |
{ "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta_str), AV_OPT_TYPE_STRING, {.str = "0"}, 0, 0, E }, |
631 | 631 |
{ "segment_times", "set segment split time points", OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, |
632 |
+ { "segment_frames", "set segment split frame numbers", OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, |
|
632 | 633 |
{ "segment_wrap", "set number after which the index wraps", OFFSET(segment_idx_wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, |
633 | 634 |
{ "segment_start_number", "set the sequence number of the first segment", OFFSET(segment_idx), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, |
634 | 635 |
|
... | ... |
@@ -31,7 +31,7 @@ |
31 | 31 |
|
32 | 32 |
#define LIBAVFORMAT_VERSION_MAJOR 54 |
33 | 33 |
#define LIBAVFORMAT_VERSION_MINOR 50 |
34 |
-#define LIBAVFORMAT_VERSION_MICRO 103 |
|
34 |
+#define LIBAVFORMAT_VERSION_MICRO 104 |
|
35 | 35 |
|
36 | 36 |
#define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ |
37 | 37 |
LIBAVFORMAT_VERSION_MINOR, \ |