1
mirror of https://git.videolan.org/git/ffmpeg.git synced 2024-08-03 09:49:58 +02:00
ffmpeg/libavformat/hdsenc.c
Anton Khirnov 9200514ad8 lavf: replace AVStream.codec with AVStream.codecpar
Currently, AVStream contains an embedded AVCodecContext instance, which
is used by demuxers to export stream parameters to the caller and by
muxers to receive stream parameters from the caller. It is also used
internally as the codec context that is passed to parsers.

In addition, it is also widely used by the callers as the decoding (when
demuxer) or encoding (when muxing) context, though this has been
officially discouraged since Libav 11.

There are multiple important problems with this approach:
    - the fields in AVCodecContext are in general one of
        * stream parameters
        * codec options
        * codec state
      However, it's not clear which ones are which. It is consequently
      unclear which fields are a demuxer allowed to set or a muxer allowed to
      read. This leads to erratic behaviour depending on whether decoding or
      encoding is being performed or not (and whether it uses the AVStream
      embedded codec context).
    - various synchronization issues arising from the fact that the same
      context is used by several different APIs (muxers/demuxers,
      parsers, bitstream filters and encoders/decoders) simultaneously, with
      there being no clear rules for who can modify what and the different
      processes being typically delayed with respect to each other.
    - avformat_find_stream_info() making it necessary to support opening
      and closing a single codec context multiple times, thus
      complicating the semantics of freeing various allocated objects in the
      codec context.

Those problems are resolved by replacing the AVStream embedded codec
context with a newly added AVCodecParameters instance, which stores only
the stream parameters exported by the demuxers or read by the muxers.
2016-02-23 17:01:58 +01:00

592 lines
19 KiB
C

/*
* Live HDS fragmenter
* Copyright (c) 2013 Martin Storsjo
*
* 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 "config.h"
#include <float.h>
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#include "avformat.h"
#include "internal.h"
#include "os_support.h"
#include "libavutil/avstring.h"
#include "libavutil/base64.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/mathematics.h"
#include "libavutil/opt.h"
typedef struct Fragment {
char file[1024];
int64_t start_time, duration;
int n;
} Fragment;
typedef struct OutputStream {
int bitrate;
int first_stream;
AVFormatContext *ctx;
int ctx_inited;
uint8_t iobuf[32768];
char temp_filename[1024];
int64_t frag_start_ts, last_ts;
AVIOContext *out;
int packets_written;
int nb_fragments, fragments_size, fragment_index;
Fragment **fragments;
int has_audio, has_video;
uint8_t *metadata;
int metadata_size;
uint8_t *extra_packets[2];
int extra_packet_sizes[2];
int nb_extra_packets;
} OutputStream;
typedef struct HDSContext {
const AVClass *class; /* Class for private options. */
int window_size;
int extra_window_size;
int min_frag_duration;
int remove_at_exit;
OutputStream *streams;
int nb_streams;
} HDSContext;
static int parse_header(OutputStream *os, const uint8_t *buf, int buf_size)
{
if (buf_size < 13)
return AVERROR_INVALIDDATA;
if (memcmp(buf, "FLV", 3))
return AVERROR_INVALIDDATA;
buf += 13;
buf_size -= 13;
while (buf_size >= 11 + 4) {
int type = buf[0];
int size = AV_RB24(&buf[1]) + 11 + 4;
if (size > buf_size)
return AVERROR_INVALIDDATA;
if (type == 8 || type == 9) {
if (os->nb_extra_packets >= FF_ARRAY_ELEMS(os->extra_packets))
return AVERROR_INVALIDDATA;
os->extra_packet_sizes[os->nb_extra_packets] = size;
os->extra_packets[os->nb_extra_packets] = av_malloc(size);
if (!os->extra_packets[os->nb_extra_packets])
return AVERROR(ENOMEM);
memcpy(os->extra_packets[os->nb_extra_packets], buf, size);
os->nb_extra_packets++;
} else if (type == 0x12) {
if (os->metadata)
return AVERROR_INVALIDDATA;
os->metadata_size = size - 11 - 4;
os->metadata = av_malloc(os->metadata_size);
if (!os->metadata)
return AVERROR(ENOMEM);
memcpy(os->metadata, buf + 11, os->metadata_size);
}
buf += size;
buf_size -= size;
}
if (!os->metadata)
return AVERROR_INVALIDDATA;
return 0;
}
static int hds_write(void *opaque, uint8_t *buf, int buf_size)
{
OutputStream *os = opaque;
if (os->out) {
avio_write(os->out, buf, buf_size);
} else {
if (!os->metadata_size) {
int ret;
// Assuming the IO buffer is large enough to fit the
// FLV header and all metadata and extradata packets
if ((ret = parse_header(os, buf, buf_size)) < 0)
return ret;
}
}
return buf_size;
}
static void hds_free(AVFormatContext *s)
{
HDSContext *c = s->priv_data;
int i, j;
if (!c->streams)
return;
for (i = 0; i < s->nb_streams; i++) {
OutputStream *os = &c->streams[i];
if (os->out)
ff_format_io_close(s, &os->out);
if (os->ctx && os->ctx_inited)
av_write_trailer(os->ctx);
if (os->ctx && os->ctx->pb)
av_free(os->ctx->pb);
if (os->ctx)
avformat_free_context(os->ctx);
av_free(os->metadata);
for (j = 0; j < os->nb_extra_packets; j++)
av_free(os->extra_packets[j]);
for (j = 0; j < os->nb_fragments; j++)
av_free(os->fragments[j]);
av_free(os->fragments);
}
av_freep(&c->streams);
}
static int write_manifest(AVFormatContext *s, int final)
{
HDSContext *c = s->priv_data;
AVIOContext *out;
char filename[1024], temp_filename[1024];
int ret, i;
float duration = 0;
if (c->nb_streams > 0)
duration = c->streams[0].last_ts * av_q2d(s->streams[0]->time_base);
snprintf(filename, sizeof(filename), "%s/index.f4m", s->filename);
snprintf(temp_filename, sizeof(temp_filename), "%s/index.f4m.tmp", s->filename);
ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
return ret;
}
avio_printf(out, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
avio_printf(out, "<manifest xmlns=\"http://ns.adobe.com/f4m/1.0\">\n");
avio_printf(out, "\t<id>%s</id>\n", av_basename(s->filename));
avio_printf(out, "\t<streamType>%s</streamType>\n",
final ? "recorded" : "live");
avio_printf(out, "\t<deliveryType>streaming</deliveryType>\n");
if (final)
avio_printf(out, "\t<duration>%f</duration>\n", duration);
for (i = 0; i < c->nb_streams; i++) {
OutputStream *os = &c->streams[i];
int b64_size = AV_BASE64_SIZE(os->metadata_size);
char *base64 = av_malloc(b64_size);
if (!base64) {
ff_format_io_close(s, &out);
return AVERROR(ENOMEM);
}
av_base64_encode(base64, b64_size, os->metadata, os->metadata_size);
avio_printf(out, "\t<bootstrapInfo profile=\"named\" url=\"stream%d.abst\" id=\"bootstrap%d\" />\n", i, i);
avio_printf(out, "\t<media bitrate=\"%d\" url=\"stream%d\" bootstrapInfoId=\"bootstrap%d\">\n", os->bitrate/1000, i, i);
avio_printf(out, "\t\t<metadata>%s</metadata>\n", base64);
avio_printf(out, "\t</media>\n");
av_free(base64);
}
avio_printf(out, "</manifest>\n");
avio_flush(out);
ff_format_io_close(s, &out);
return ff_rename(temp_filename, filename);
}
static void update_size(AVIOContext *out, int64_t pos)
{
int64_t end = avio_tell(out);
avio_seek(out, pos, SEEK_SET);
avio_wb32(out, end - pos);
avio_seek(out, end, SEEK_SET);
}
/* Note, the .abst files need to be served with the "binary/octet"
* mime type, otherwise at least the OSMF player can easily fail
* with "stream not found" when polling for the next fragment. */
static int write_abst(AVFormatContext *s, OutputStream *os, int final)
{
HDSContext *c = s->priv_data;
AVIOContext *out;
char filename[1024], temp_filename[1024];
int i, ret;
int64_t asrt_pos, afrt_pos;
int start = 0, fragments;
int index = s->streams[os->first_stream]->id;
int64_t cur_media_time = 0;
if (c->window_size)
start = FFMAX(os->nb_fragments - c->window_size, 0);
fragments = os->nb_fragments - start;
if (final)
cur_media_time = os->last_ts;
else if (os->nb_fragments)
cur_media_time = os->fragments[os->nb_fragments - 1]->start_time;
snprintf(filename, sizeof(filename),
"%s/stream%d.abst", s->filename, index);
snprintf(temp_filename, sizeof(temp_filename),
"%s/stream%d.abst.tmp", s->filename, index);
ret = s->io_open(s, &out, temp_filename, AVIO_FLAG_WRITE, NULL);
if (ret < 0) {
av_log(s, AV_LOG_ERROR, "Unable to open %s for writing\n", temp_filename);
return ret;
}
avio_wb32(out, 0); // abst size
avio_wl32(out, MKTAG('a','b','s','t'));
avio_wb32(out, 0); // version + flags
avio_wb32(out, os->fragment_index - 1); // BootstrapinfoVersion
avio_w8(out, final ? 0 : 0x20); // profile, live, update
avio_wb32(out, 1000); // timescale
avio_wb64(out, cur_media_time);
avio_wb64(out, 0); // SmpteTimeCodeOffset
avio_w8(out, 0); // MovieIdentifer (null string)
avio_w8(out, 0); // ServerEntryCount
avio_w8(out, 0); // QualityEntryCount
avio_w8(out, 0); // DrmData (null string)
avio_w8(out, 0); // MetaData (null string)
avio_w8(out, 1); // SegmentRunTableCount
asrt_pos = avio_tell(out);
avio_wb32(out, 0); // asrt size
avio_wl32(out, MKTAG('a','s','r','t'));
avio_wb32(out, 0); // version + flags
avio_w8(out, 0); // QualityEntryCount
avio_wb32(out, 1); // SegmentRunEntryCount
avio_wb32(out, 1); // FirstSegment
avio_wb32(out, final ? (os->fragment_index - 1) : 0xffffffff); // FragmentsPerSegment
update_size(out, asrt_pos);
avio_w8(out, 1); // FragmentRunTableCount
afrt_pos = avio_tell(out);
avio_wb32(out, 0); // afrt size
avio_wl32(out, MKTAG('a','f','r','t'));
avio_wb32(out, 0); // version + flags
avio_wb32(out, 1000); // timescale
avio_w8(out, 0); // QualityEntryCount
avio_wb32(out, fragments); // FragmentRunEntryCount
for (i = start; i < os->nb_fragments; i++) {
avio_wb32(out, os->fragments[i]->n);
avio_wb64(out, os->fragments[i]->start_time);
avio_wb32(out, os->fragments[i]->duration);
}
update_size(out, afrt_pos);
update_size(out, 0);
ff_format_io_close(s, &out);
return ff_rename(temp_filename, filename);
}
static int init_file(AVFormatContext *s, OutputStream *os, int64_t start_ts)
{
int ret, i;
ret = s->io_open(s, &os->out, os->temp_filename, AVIO_FLAG_WRITE, NULL);
if (ret < 0)
return ret;
avio_wb32(os->out, 0);
avio_wl32(os->out, MKTAG('m','d','a','t'));
for (i = 0; i < os->nb_extra_packets; i++) {
AV_WB24(os->extra_packets[i] + 4, start_ts);
os->extra_packets[i][7] = (start_ts >> 24) & 0x7f;
avio_write(os->out, os->extra_packets[i], os->extra_packet_sizes[i]);
}
return 0;
}
static void close_file(AVFormatContext *s, OutputStream *os)
{
int64_t pos = avio_tell(os->out);
avio_seek(os->out, 0, SEEK_SET);
avio_wb32(os->out, pos);
avio_flush(os->out);
ff_format_io_close(s, &os->out);
}
static int hds_write_header(AVFormatContext *s)
{
HDSContext *c = s->priv_data;
int ret = 0, i;
AVOutputFormat *oformat;
if (mkdir(s->filename, 0777) == -1 && errno != EEXIST) {
ret = AVERROR(errno);
goto fail;
}
oformat = av_guess_format("flv", NULL, NULL);
if (!oformat) {
ret = AVERROR_MUXER_NOT_FOUND;
goto fail;
}
c->streams = av_mallocz(sizeof(*c->streams) * s->nb_streams);
if (!c->streams) {
ret = AVERROR(ENOMEM);
goto fail;
}
for (i = 0; i < s->nb_streams; i++) {
OutputStream *os = &c->streams[c->nb_streams];
AVFormatContext *ctx;
AVStream *st = s->streams[i];
if (!st->codecpar->bit_rate) {
av_log(s, AV_LOG_ERROR, "No bit rate set for stream %d\n", i);
ret = AVERROR(EINVAL);
goto fail;
}
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
if (os->has_video) {
c->nb_streams++;
os++;
}
os->has_video = 1;
} else if (st->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (os->has_audio) {
c->nb_streams++;
os++;
}
os->has_audio = 1;
} else {
av_log(s, AV_LOG_ERROR, "Unsupported stream type in stream %d\n", i);
ret = AVERROR(EINVAL);
goto fail;
}
os->bitrate += s->streams[i]->codecpar->bit_rate;
if (!os->ctx) {
os->first_stream = i;
ctx = avformat_alloc_context();
if (!ctx) {
ret = AVERROR(ENOMEM);
goto fail;
}
os->ctx = ctx;
ctx->oformat = oformat;
ctx->interrupt_callback = s->interrupt_callback;
ctx->pb = avio_alloc_context(os->iobuf, sizeof(os->iobuf),
AVIO_FLAG_WRITE, os,
NULL, hds_write, NULL);
if (!ctx->pb) {
ret = AVERROR(ENOMEM);
goto fail;
}
} else {
ctx = os->ctx;
}
s->streams[i]->id = c->nb_streams;
if (!(st = avformat_new_stream(ctx, NULL))) {
ret = AVERROR(ENOMEM);
goto fail;
}
avcodec_parameters_copy(st->codecpar, s->streams[i]->codecpar);
st->codecpar->codec_tag = 0;
st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
st->time_base = s->streams[i]->time_base;
}
if (c->streams[c->nb_streams].ctx)
c->nb_streams++;
for (i = 0; i < c->nb_streams; i++) {
OutputStream *os = &c->streams[i];
int j;
if ((ret = avformat_write_header(os->ctx, NULL)) < 0) {
goto fail;
}
os->ctx_inited = 1;
avio_flush(os->ctx->pb);
for (j = 0; j < os->ctx->nb_streams; j++)
s->streams[os->first_stream + j]->time_base = os->ctx->streams[j]->time_base;
snprintf(os->temp_filename, sizeof(os->temp_filename),
"%s/stream%d_temp", s->filename, i);
ret = init_file(s, os, 0);
if (ret < 0)
goto fail;
if (!os->has_video && c->min_frag_duration <= 0) {
av_log(s, AV_LOG_WARNING,
"No video stream in output stream %d and no min frag duration set\n", i);
ret = AVERROR(EINVAL);
}
os->fragment_index = 1;
write_abst(s, os, 0);
}
ret = write_manifest(s, 0);
fail:
if (ret)
hds_free(s);
return ret;
}
static int add_fragment(OutputStream *os, const char *file,
int64_t start_time, int64_t duration)
{
Fragment *frag;
if (duration == 0)
duration = 1;
if (os->nb_fragments >= os->fragments_size) {
int ret;
os->fragments_size = (os->fragments_size + 1) * 2;
if ((ret = av_reallocp_array(&os->fragments, os->fragments_size,
sizeof(*os->fragments))) < 0) {
os->fragments_size = 0;
os->nb_fragments = 0;
return ret;
}
}
frag = av_mallocz(sizeof(*frag));
if (!frag)
return AVERROR(ENOMEM);
av_strlcpy(frag->file, file, sizeof(frag->file));
frag->start_time = start_time;
frag->duration = duration;
frag->n = os->fragment_index;
os->fragments[os->nb_fragments++] = frag;
os->fragment_index++;
return 0;
}
static int hds_flush(AVFormatContext *s, OutputStream *os, int final,
int64_t end_ts)
{
HDSContext *c = s->priv_data;
int i, ret = 0;
char target_filename[1024];
int index = s->streams[os->first_stream]->id;
if (!os->packets_written)
return 0;
avio_flush(os->ctx->pb);
os->packets_written = 0;
close_file(s, os);
snprintf(target_filename, sizeof(target_filename),
"%s/stream%dSeg1-Frag%d", s->filename, index, os->fragment_index);
ret = ff_rename(os->temp_filename, target_filename);
if (ret < 0)
return ret;
add_fragment(os, target_filename, os->frag_start_ts, end_ts - os->frag_start_ts);
if (!final) {
ret = init_file(s, os, end_ts);
if (ret < 0)
return ret;
}
if (c->window_size || (final && c->remove_at_exit)) {
int remove = os->nb_fragments - c->window_size - c->extra_window_size;
if (final && c->remove_at_exit)
remove = os->nb_fragments;
if (remove > 0) {
for (i = 0; i < remove; i++) {
unlink(os->fragments[i]->file);
av_free(os->fragments[i]);
}
os->nb_fragments -= remove;
memmove(os->fragments, os->fragments + remove,
os->nb_fragments * sizeof(*os->fragments));
}
}
if (ret >= 0)
ret = write_abst(s, os, final);
return ret;
}
static int hds_write_packet(AVFormatContext *s, AVPacket *pkt)
{
HDSContext *c = s->priv_data;
AVStream *st = s->streams[pkt->stream_index];
OutputStream *os = &c->streams[s->streams[pkt->stream_index]->id];
int64_t end_dts = os->fragment_index * (int64_t) c->min_frag_duration;
int ret;
if (st->first_dts == AV_NOPTS_VALUE)
st->first_dts = pkt->dts;
if ((!os->has_video || st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) &&
av_compare_ts(pkt->dts - st->first_dts, st->time_base,
end_dts, AV_TIME_BASE_Q) >= 0 &&
pkt->flags & AV_PKT_FLAG_KEY && os->packets_written) {
if ((ret = hds_flush(s, os, 0, pkt->dts)) < 0)
return ret;
}
// Note, these fragment start timestamps, that represent a whole
// OutputStream, assume all streams in it have the same time base.
if (!os->packets_written)
os->frag_start_ts = pkt->dts;
os->last_ts = pkt->dts;
os->packets_written++;
return ff_write_chained(os->ctx, pkt->stream_index - os->first_stream, pkt, s);
}
static int hds_write_trailer(AVFormatContext *s)
{
HDSContext *c = s->priv_data;
int i;
for (i = 0; i < c->nb_streams; i++)
hds_flush(s, &c->streams[i], 1, c->streams[i].last_ts);
write_manifest(s, 1);
if (c->remove_at_exit) {
char filename[1024];
snprintf(filename, sizeof(filename), "%s/index.f4m", s->filename);
unlink(filename);
for (i = 0; i < c->nb_streams; i++) {
snprintf(filename, sizeof(filename), "%s/stream%d.abst", s->filename, i);
unlink(filename);
}
rmdir(s->filename);
}
hds_free(s);
return 0;
}
#define OFFSET(x) offsetof(HDSContext, x)
#define E AV_OPT_FLAG_ENCODING_PARAM
static const AVOption options[] = {
{ "window_size", "number of fragments kept in the manifest", OFFSET(window_size), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, E },
{ "extra_window_size", "number of fragments kept outside of the manifest before removing from disk", OFFSET(extra_window_size), AV_OPT_TYPE_INT, { .i64 = 5 }, 0, INT_MAX, E },
{ "min_frag_duration", "minimum fragment duration (in microseconds)", OFFSET(min_frag_duration), AV_OPT_TYPE_INT64, { .i64 = 10000000 }, 0, INT_MAX, E },
{ "remove_at_exit", "remove all fragments when finished", OFFSET(remove_at_exit), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, E },
{ NULL },
};
static const AVClass hds_class = {
.class_name = "HDS muxer",
.item_name = av_default_item_name,
.option = options,
.version = LIBAVUTIL_VERSION_INT,
};
AVOutputFormat ff_hds_muxer = {
.name = "hds",
.long_name = NULL_IF_CONFIG_SMALL("HDS Muxer"),
.priv_data_size = sizeof(HDSContext),
.audio_codec = AV_CODEC_ID_AAC,
.video_codec = AV_CODEC_ID_H264,
.flags = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
.write_header = hds_write_header,
.write_packet = hds_write_packet,
.write_trailer = hds_write_trailer,
.priv_class = &hds_class,
};