You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

595 lines
19 KiB

/*
* Live HDS fragmenter
* Copyright (c) 2013 Martin Storsjo
*
* This file is part of FFmpeg.
*
* FFmpeg 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.
*
* FFmpeg 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 FFmpeg; 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 "avio_internal.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)
av_freep(&os->ctx->pb);
if (os->ctx)
avformat_free_context(os->ctx);
av_freep(&os->metadata);
for (j = 0; j < os->nb_extra_packets; j++)
av_freep(&os->extra_packets[j]);
for (j = 0; j < os->nb_fragments; j++)
av_freep(&os->fragments[j]);
av_freep(&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;
double 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, s);
}
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, s);
}
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);
av_log(s, AV_LOG_ERROR , "Failed to create directory %s\n", s->filename);
goto fail;
}
oformat = av_guess_format("flv", NULL, NULL);
if (!oformat) {
ret = AVERROR_MUXER_NOT_FOUND;
goto fail;
}
c->streams = av_mallocz_array(s->nb_streams, sizeof(*c->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];
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.
11 years ago
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;
}
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.
11 years ago
if (st->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
if (os->has_video) {
c->nb_streams++;
os++;
}
os->has_video = 1;
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.
11 years ago
} 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;
}
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.
11 years ago
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->flags = s->flags;
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;
}
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.
11 years ago
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, s);
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_freep(&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;
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.
11 years ago
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, 0);
}
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_BOOL, { .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,
};