diff --git a/doc/muxers.texi b/doc/muxers.texi index 30c95c3d34e3b..1cca8da1fbcf3 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -3505,6 +3505,18 @@ argument must be a time duration specification, and defaults to 0. If enabled, write an empty segment if there are no packets during the period a segment would usually span. Otherwise, the segment will be filled with the next packet written. Defaults to @code{0}. + +@item segment_write_temp @var{1|0} +Write segments to files with a .tmp extension. Each file is renamed to its +actual name on completion. This can help to prevent segment files from +being accessed before they are complete. Disabled by default (@code{0}). + +@item segment_limit @var{number} +Stops after the specified number of segments has been generated. +This can be helpful to fill gaps in a range of already generated segments, +which is difficult to achieve otherwise as it would either cause the last +segment to be incomplete or to overwrite an existing segment with a partial +data. Default is @code{0} - no limit. @end table Make sure to require a closed GOP when encoding and to set the GOP diff --git a/libavformat/segment.c b/libavformat/segment.c index 65323ec6781a2..57ea54b00ac42 100644 --- a/libavformat/segment.c +++ b/libavformat/segment.c @@ -20,7 +20,7 @@ /** * @file generic segmenter - * M3U8 specification can be find here: + * M3U8 specification can be found here: * @url{http://tools.ietf.org/id/draft-pantos-http-live-streaming} */ @@ -121,6 +121,9 @@ typedef struct SegmentContext { int break_non_keyframes; int write_empty; + int segment_limit; ///< max number of segments to create + + int segment_write_temp; ///< write segments as temp files and rename on completion int use_rename; char temp_list_filename[1024]; @@ -174,7 +177,7 @@ static int segment_mux_init(AVFormatContext *s) return AVERROR(ENOMEM); opar = st->codecpar; if (!oc->oformat->codec_tag || - av_codec_get_id (oc->oformat->codec_tag, ipar->codec_tag) == opar->codec_id || + av_codec_get_id(oc->oformat->codec_tag, ipar->codec_tag) == opar->codec_id || av_codec_get_tag(oc->oformat->codec_tag, ipar->codec_id) <= 0) { opar->codec_tag = ipar->codec_tag; } else { @@ -226,6 +229,14 @@ static int set_segment_filename(AVFormatContext *s) seg->entry_prefix ? seg->entry_prefix : "", av_basename(oc->url)); + // Write segment as a temp file and rename on completion + if(seg->segment_write_temp) { + char *temp_name = av_asprintf("%s%s", buf, ".tmp"); + if (!temp_name) + return AVERROR(ENOMEM); + ff_format_set_url(oc, temp_name); + } + return 0; } @@ -328,17 +339,17 @@ static void segment_list_print_entry(AVIOContext *list_ioctx, list_entry->end_time - list_entry->start_time, list_entry->filename); break; case LIST_TYPE_FFCONCAT: - { - char *buf; - if (av_escape(&buf, list_entry->filename, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_WHITESPACE) < 0) { - av_log(log_ctx, AV_LOG_WARNING, - "Error writing list entry '%s' in list file\n", list_entry->filename); - return; + { + char *buf; + if (av_escape(&buf, list_entry->filename, NULL, AV_ESCAPE_MODE_AUTO, AV_ESCAPE_FLAG_WHITESPACE) < 0) { + av_log(log_ctx, AV_LOG_WARNING, + "Error writing list entry '%s' in list file\n", list_entry->filename); + return; + } + avio_printf(list_ioctx, "file %s\n", buf); + av_free(buf); + break; } - avio_printf(list_ioctx, "file %s\n", buf); - av_free(buf); - break; - } default: av_assert0(!"Invalid list type"); } @@ -356,6 +367,9 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) int i; int err; + if (seg->segment_limit && seg->segment_count >= seg->segment_limit) + return 0; + if (!oc || !oc->pb) return AVERROR(EINVAL); @@ -372,7 +386,7 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) SegmentListEntry *entry = av_mallocz(sizeof(*entry)); if (!entry) { ret = AVERROR(ENOMEM); - goto end; + goto fail; } /* append new element */ @@ -393,7 +407,7 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) } if ((ret = segment_list_open(s)) < 0) - goto end; + goto fail; for (entry = seg->segment_list_entries; entry; entry = entry->next) segment_list_print_entry(seg->list_pb, seg->list_type, entry, s); if (seg->list_type == LIST_TYPE_M3U8 && is_last) @@ -443,14 +457,27 @@ static int segment_end(AVFormatContext *s, int write_trailer, int is_last) av_log(s, AV_LOG_WARNING, "Could not increment stream %d timecode, error occurred during timecode creation.\n", i); continue; } - st_tc.start += (int)((seg->cur_entry.end_time - seg->cur_entry.start_time) * av_q2d(st_rate)); // increment timecode - av_dict_set(&s->streams[i]->metadata, "timecode", av_timecode_make_string(&st_tc, st_buf, 0), 0); + st_tc.start += (int)((seg->cur_entry.end_time - seg->cur_entry.start_time) * av_q2d(st_rate)); // increment timecode + av_dict_set(&s->streams[i]->metadata, "timecode", av_timecode_make_string(&st_tc, st_buf, 0), 0); } } } } -end: + ff_format_io_close(oc, &oc->pb); + + // Now rename the .tmp file to its actual name. + if (seg->segment_write_temp) { + char *final_filename = av_strndup(oc->url, strlen(oc->url) - 4); + if (!final_filename) + return AVERROR(ENOMEM); + ret = ff_rename(oc->url, final_filename, s); + av_free(final_filename); + } + + return ret; + +fail: ff_format_io_close(oc, &oc->pb); return ret; @@ -723,8 +750,8 @@ static int seg_init(AVFormatContext *s) if (seg->list) { if (seg->list_type == LIST_TYPE_UNDEFINED) { - if (av_match_ext(seg->list, "csv" )) seg->list_type = LIST_TYPE_CSV; - else if (av_match_ext(seg->list, "ext" )) seg->list_type = LIST_TYPE_EXT; + if (av_match_ext(seg->list, "csv")) seg->list_type = LIST_TYPE_CSV; + else if (av_match_ext(seg->list, "ext")) seg->list_type = LIST_TYPE_EXT; else if (av_match_ext(seg->list, "m3u8")) seg->list_type = LIST_TYPE_M3U8; else if (av_match_ext(seg->list, "ffcat,ffconcat")) seg->list_type = LIST_TYPE_FFCONCAT; else seg->list_type = LIST_TYPE_FLAT; @@ -857,6 +884,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) int64_t usecs; int64_t wrapped_val; + if (seg->segment_limit && seg->segment_count >= seg->segment_limit) + return 0; + if (!seg->avf || !seg->avf->pb) return AVERROR(EINVAL); @@ -875,11 +905,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) calc_times: if (seg->times) { - end_pts = seg->segment_count < seg->nb_times ? - seg->times[seg->segment_count] : INT64_MAX; + end_pts = seg->segment_count < seg->nb_times ? seg->times[seg->segment_count] : INT64_MAX; } else if (seg->frames) { - start_frame = seg->segment_count < seg->nb_frames ? - seg->frames[seg->segment_count] : INT_MAX; + start_frame = seg->segment_count < seg->nb_frames ? seg->frames[seg->segment_count] : INT_MAX; } else { if (seg->use_clocktime) { int64_t avgt = av_gettime(); @@ -920,10 +948,10 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) (pkt->flags & AV_PKT_FLAG_KEY || seg->break_non_keyframes) && (seg->segment_frame_count > 0 || seg->write_empty) && (seg->cut_pending || seg->frame_count >= start_frame || - (pkt->pts != AV_NOPTS_VALUE && - pkt_pts_avtb - seg->cur_entry.start_pts >= seg->min_seg_duration && - av_compare_ts(pkt->pts, st->time_base, - end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0))) { + (pkt->pts != AV_NOPTS_VALUE && + pkt_pts_avtb - seg->cur_entry.start_pts >= seg->min_seg_duration && + av_compare_ts(pkt->pts, st->time_base, + end_pts - seg->time_delta, AV_TIME_BASE_Q) >= 0))) { /* sanitize end time in case last packet didn't have a defined duration */ if (seg->cur_entry.last_duration == 0) seg->cur_entry.end_time = (double)pkt->pts * av_q2d(st->time_base); @@ -931,6 +959,9 @@ static int seg_write_packet(AVFormatContext *s, AVPacket *pkt) if ((ret = segment_end(s, seg->individual_header_trailer, 0)) < 0) goto fail; + if (seg->segment_limit && seg->segment_count >= seg->segment_limit) + return 0; + if ((ret = segment_start(s, seg->individual_header_trailer)) < 0) goto fail; @@ -1023,8 +1054,8 @@ static int seg_check_bitstream(AVFormatContext *s, AVStream *st, if (ret == 1) { FFStream *const sti = ffstream(st); FFStream *const osti = ffstream(ost); - sti->bsfc = osti->bsfc; - osti->bsfc = NULL; + sti->bsfc = osti->bsfc; + osti->bsfc = NULL; } return ret; } @@ -1057,8 +1088,8 @@ static const AVOption options[] = { { "segment_atclocktime", "set segment to be cut at clocktime", OFFSET(use_clocktime), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E}, { "segment_clocktime_offset", "set segment clocktime offset", OFFSET(clocktime_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, 86400000000LL, E}, { "segment_clocktime_wrap_duration", "set segment clocktime wrapping duration", OFFSET(clocktime_wrap_duration), AV_OPT_TYPE_DURATION, {.i64 = INT64_MAX}, 0, INT64_MAX, E}, - { "segment_time", "set segment duration", OFFSET(time),AV_OPT_TYPE_DURATION, {.i64 = 2000000}, INT64_MIN, INT64_MAX, E }, - { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, INT64_MAX, E }, + { "segment_time", "set segment duration", OFFSET(time),AV_OPT_TYPE_DURATION, {.i64 = 2000000}, 0, INT64_MAX, E }, + { "segment_time_delta","set approximation value used for the segment times", OFFSET(time_delta), AV_OPT_TYPE_DURATION, {.i64 = 0}, INT64_MIN, INT64_MAX, E }, { "min_seg_duration", "set minimum segment duration", OFFSET(min_seg_duration), AV_OPT_TYPE_DURATION, {.i64 = 0}, 0, INT64_MAX, E }, { "segment_times", "set segment split time points", OFFSET(times_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, { "segment_frames", "set segment split frame numbers", OFFSET(frames_str),AV_OPT_TYPE_STRING,{.str = NULL}, 0, 0, E }, @@ -1075,6 +1106,8 @@ static const AVOption options[] = { { "reset_timestamps", "reset timestamps at the beginning of each segment", OFFSET(reset_timestamps), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E }, { "initial_offset", "set initial timestamp offset", OFFSET(initial_offset), AV_OPT_TYPE_DURATION, {.i64 = 0}, -INT64_MAX, INT64_MAX, E }, { "write_empty_segments", "allow writing empty 'filler' segments", OFFSET(write_empty), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E }, + { "segment_write_temp", "write segments as temp files (.tmp) and rename on completion", OFFSET(segment_write_temp), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, E }, + { "segment_limit", "stop output once the specified number of segments has been written", OFFSET(segment_limit), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E }, { NULL }, };