mirror of
https://git.ffmpeg.org/ffmpeg.git
synced 2024-12-17 21:14:47 +00:00
eb447d5159
This makes sure the muxers are set up in the way they expect with no data left around from the previous run (which could cause various issues including memory leaks, depending on the chaine muxer). This fixes memory leaks with the mpegts and flv muxers. It also makes the usage of chained muxers correct. Signed-off-by: Martin Storsjö <martin@martin.st>
273 lines
7.8 KiB
C
273 lines
7.8 KiB
C
/*
|
|
* Generic segmenter
|
|
* Copyright (c) 2011, Luca Barbato
|
|
*
|
|
* This file is part of Libav.
|
|
*
|
|
* Libav is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* Libav is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with Libav; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
|
|
#include <float.h>
|
|
|
|
#include "avformat.h"
|
|
#include "internal.h"
|
|
|
|
#include "libavutil/log.h"
|
|
#include "libavutil/opt.h"
|
|
#include "libavutil/avstring.h"
|
|
#include "libavutil/parseutils.h"
|
|
#include "libavutil/mathematics.h"
|
|
|
|
typedef struct {
|
|
const AVClass *class; /**< Class for private options. */
|
|
int number;
|
|
AVOutputFormat *oformat;
|
|
AVFormatContext *avf;
|
|
char *format; /**< Set by a private option. */
|
|
char *list; /**< Set by a private option. */
|
|
float time; /**< Set by a private option. */
|
|
int size; /**< Set by a private option. */
|
|
int wrap; /**< Set by a private option. */
|
|
int64_t offset_time;
|
|
int64_t recording_time;
|
|
int has_video;
|
|
AVIOContext *pb;
|
|
} SegmentContext;
|
|
|
|
static int segment_mux_init(AVFormatContext *s)
|
|
{
|
|
SegmentContext *seg = s->priv_data;
|
|
AVFormatContext *oc;
|
|
int i;
|
|
|
|
seg->avf = oc = avformat_alloc_context();
|
|
if (!oc)
|
|
return AVERROR(ENOMEM);
|
|
|
|
oc->oformat = seg->oformat;
|
|
oc->interrupt_callback = s->interrupt_callback;
|
|
|
|
for (i = 0; i < s->nb_streams; i++) {
|
|
AVStream *st;
|
|
if (!(st = avformat_new_stream(oc, NULL)))
|
|
return AVERROR(ENOMEM);
|
|
avcodec_copy_context(st->codec, s->streams[i]->codec);
|
|
st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int segment_start(AVFormatContext *s)
|
|
{
|
|
SegmentContext *c = s->priv_data;
|
|
AVFormatContext *oc = c->avf;
|
|
int err = 0;
|
|
|
|
avformat_free_context(oc);
|
|
c->avf = NULL;
|
|
if ((err = segment_mux_init(s)) < 0)
|
|
return err;
|
|
oc = c->avf;
|
|
|
|
if (c->wrap)
|
|
c->number %= c->wrap;
|
|
|
|
if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
|
|
s->filename, c->number++) < 0)
|
|
return AVERROR(EINVAL);
|
|
|
|
if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
|
|
&s->interrupt_callback, NULL)) < 0)
|
|
return err;
|
|
|
|
if ((err = avformat_write_header(oc, NULL)) < 0)
|
|
return err;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int segment_end(AVFormatContext *oc)
|
|
{
|
|
int ret = 0;
|
|
|
|
av_write_trailer(oc);
|
|
avio_close(oc->pb);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int seg_write_header(AVFormatContext *s)
|
|
{
|
|
SegmentContext *seg = s->priv_data;
|
|
AVFormatContext *oc = NULL;
|
|
int ret, i;
|
|
|
|
seg->number = 0;
|
|
seg->offset_time = 0;
|
|
seg->recording_time = seg->time * 1000000;
|
|
|
|
if (seg->list)
|
|
if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE,
|
|
&s->interrupt_callback, NULL)) < 0)
|
|
goto fail;
|
|
|
|
for (i = 0; i < s->nb_streams; i++)
|
|
seg->has_video +=
|
|
(s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO);
|
|
|
|
if (seg->has_video > 1)
|
|
av_log(s, AV_LOG_WARNING,
|
|
"More than a single video stream present, "
|
|
"expect issues decoding it.\n");
|
|
|
|
seg->oformat = av_guess_format(seg->format, s->filename, NULL);
|
|
|
|
if (!seg->oformat) {
|
|
ret = AVERROR_MUXER_NOT_FOUND;
|
|
goto fail;
|
|
}
|
|
if (seg->oformat->flags & AVFMT_NOFILE) {
|
|
av_log(s, AV_LOG_ERROR, "format %s not supported.\n",
|
|
oc->oformat->name);
|
|
ret = AVERROR(EINVAL);
|
|
goto fail;
|
|
}
|
|
|
|
if ((ret = segment_mux_init(s)) < 0)
|
|
goto fail;
|
|
oc = seg->avf;
|
|
|
|
if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
|
|
s->filename, seg->number++) < 0) {
|
|
ret = AVERROR(EINVAL);
|
|
goto fail;
|
|
}
|
|
|
|
if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
|
|
&s->interrupt_callback, NULL)) < 0)
|
|
goto fail;
|
|
|
|
if ((ret = avformat_write_header(oc, NULL)) < 0) {
|
|
avio_close(oc->pb);
|
|
goto fail;
|
|
}
|
|
|
|
if (seg->list) {
|
|
avio_printf(seg->pb, "%s\n", oc->filename);
|
|
avio_flush(seg->pb);
|
|
}
|
|
|
|
fail:
|
|
if (ret) {
|
|
if (seg->list)
|
|
avio_close(seg->pb);
|
|
if (seg->avf)
|
|
avformat_free_context(seg->avf);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
|
|
{
|
|
SegmentContext *seg = s->priv_data;
|
|
AVFormatContext *oc = seg->avf;
|
|
AVStream *st = s->streams[pkt->stream_index];
|
|
int64_t end_pts = seg->recording_time * seg->number;
|
|
int ret;
|
|
|
|
if ((seg->has_video && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) &&
|
|
av_compare_ts(pkt->pts, st->time_base,
|
|
end_pts, AV_TIME_BASE_Q) >= 0 &&
|
|
pkt->flags & AV_PKT_FLAG_KEY) {
|
|
|
|
av_log(s, AV_LOG_DEBUG, "Next segment starts at %d %"PRId64"\n",
|
|
pkt->stream_index, pkt->pts);
|
|
|
|
ret = segment_end(oc);
|
|
|
|
if (!ret)
|
|
ret = segment_start(s);
|
|
|
|
if (ret)
|
|
goto fail;
|
|
|
|
oc = seg->avf;
|
|
|
|
if (seg->list) {
|
|
avio_printf(seg->pb, "%s\n", oc->filename);
|
|
avio_flush(seg->pb);
|
|
if (seg->size && !(seg->number % seg->size)) {
|
|
avio_close(seg->pb);
|
|
if ((ret = avio_open2(&seg->pb, seg->list, AVIO_FLAG_WRITE,
|
|
&s->interrupt_callback, NULL)) < 0)
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
ret = ff_write_chained(oc, pkt->stream_index, pkt, s);
|
|
|
|
fail:
|
|
if (ret < 0) {
|
|
if (seg->list)
|
|
avio_close(seg->pb);
|
|
avformat_free_context(oc);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int seg_write_trailer(struct AVFormatContext *s)
|
|
{
|
|
SegmentContext *seg = s->priv_data;
|
|
AVFormatContext *oc = seg->avf;
|
|
int ret = segment_end(oc);
|
|
if (seg->list)
|
|
avio_close(seg->pb);
|
|
avformat_free_context(oc);
|
|
return ret;
|
|
}
|
|
|
|
#define OFFSET(x) offsetof(SegmentContext, x)
|
|
#define E AV_OPT_FLAG_ENCODING_PARAM
|
|
static const AVOption options[] = {
|
|
{ "segment_format", "container format used for the segments", OFFSET(format), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
|
|
{ "segment_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E },
|
|
{ "segment_list", "output the segment list", OFFSET(list), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, E },
|
|
{ "segment_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E },
|
|
{ "segment_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E },
|
|
{ NULL },
|
|
};
|
|
|
|
static const AVClass seg_class = {
|
|
.class_name = "segment muxer",
|
|
.item_name = av_default_item_name,
|
|
.option = options,
|
|
.version = LIBAVUTIL_VERSION_INT,
|
|
};
|
|
|
|
|
|
AVOutputFormat ff_segment_muxer = {
|
|
.name = "segment",
|
|
.long_name = NULL_IF_CONFIG_SMALL("segment"),
|
|
.priv_data_size = sizeof(SegmentContext),
|
|
.flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
|
|
.write_header = seg_write_header,
|
|
.write_packet = seg_write_packet,
|
|
.write_trailer = seg_write_trailer,
|
|
.priv_class = &seg_class,
|
|
};
|