Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions doc/muxers.texi
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
95 changes: 64 additions & 31 deletions libavformat/segment.c
Original file line number Diff line number Diff line change
Expand Up @@ -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}
*/

Expand Down Expand Up @@ -121,6 +121,9 @@ typedef struct SegmentContext {
int break_non_keyframes;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the FFmpeg mailing list, Michael Niedermayer wrote (reply to this):


--===============8122640100284245730==
Content-Type: multipart/signed; micalg=pgp-sha512;
	protocol="application/pgp-signature"; boundary="8AfFfcQwKZvQE1LI"
Content-Disposition: inline


--8AfFfcQwKZvQE1LI
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

On Sat, Jun 14, 2025 at 12:59:10AM +0000, softworkz wrote:
> From: softworkz <[email protected]>
>=20
> Signed-off-by: softworkz <[email protected]>
> ---
>  libavformat/segment.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)

should be ok

thx

[...]
--=20
Michael     GnuPG fingerprint: 9FF2128B147EF6730BADF133611EC787040B0FAB

Frequently ignored answer#1 FFmpeg bugs should be sent to our bugtracker. U=
ser
questions about the command line tools should be sent to the ffmpeg-user ML.
And questions about how to use libav* should be sent to the libav-user ML.

--8AfFfcQwKZvQE1LI
Content-Type: application/pgp-signature; name="signature.asc"

-----BEGIN PGP SIGNATURE-----

iF0EABEKAB0WIQSf8hKLFH72cwut8TNhHseHBAsPqwUCaE3eQAAKCRBhHseHBAsP
q266AJ9SI6yx8cvPZxKopJww1UchP4vodwCfYzQTYFxFp6OFaHU9o8q8Oh9eDtQ=
=LqfM
-----END PGP SIGNATURE-----

--8AfFfcQwKZvQE1LI--

--===============8122640100284245730==
Content-Type: text/plain; charset="us-ascii"
MIME-Version: 1.0
Content-Transfer-Encoding: 7bit
Content-Disposition: inline

_______________________________________________
ffmpeg-devel mailing list
[email protected]
https://ffmpeg.org/mailman/listinfo/ffmpeg-devel

To unsubscribe, visit link above, or email
[email protected] with subject "unsubscribe".

--===============8122640100284245730==--

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];

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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");
}
Expand All @@ -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);

Expand All @@ -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 */
Expand All @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand All @@ -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();
Expand Down Expand Up @@ -920,17 +948,20 @@ 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);

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;

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 },
Expand All @@ -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 },
};

Expand Down