On Mon, 30 Mar 2015 12:49:45 -0700 Vignesh Venkatasubramanian <vigne...@google.com> wrote:
> This patch adds support for WebM Live Muxing by adding a new WebM > Chunk muxer. It writes out live WebM Chunks which can be used for > playback using Live DASH Clients. > > Please see muxers.texi for sample usage. > > Signed-off-by: Vignesh Venkatasubramanian <vigne...@google.com> > --- > Changelog | 1 + > doc/muxers.texi | 43 ++++++++ > libavformat/Makefile | 5 +- > libavformat/allformats.c | 1 + > libavformat/matroskaenc.c | 12 ++- > libavformat/version.h | 2 +- > libavformat/webm_chunk.c | 251 > ++++++++++++++++++++++++++++++++++++++++++++++ > 7 files changed, 309 insertions(+), 6 deletions(-) > create mode 100644 libavformat/webm_chunk.c > > diff --git a/Changelog b/Changelog > index 75da156..2c7f283 100644 > --- a/Changelog > +++ b/Changelog > @@ -12,6 +12,7 @@ version <next>: > - Detelecine filter > - Intel QSV-accelerated H.264 encoding > - MMAL-accelerated H.264 decoding > +- WebM Live Chunk Muxer > > > version 2.6: > diff --git a/doc/muxers.texi b/doc/muxers.texi > index a8225fc..d9d900b 100644 > --- a/doc/muxers.texi > +++ b/doc/muxers.texi > @@ -1236,4 +1236,47 @@ ffmpeg -f webm_dash_manifest -i video1.webm \ > manifest.xml > @end example > > +@section webm_chunk > + > +WebM Live Chunk Muxer. > + > +This muxer writes out WebM headers and chunks as separate files which can be > +consumed by clients that support WebM Live streams via DASH. > + > +@subsection Options > + > +This muxer supports the following options: > + > +@table @option > +@item chunk_start_index > +Index of the first chunk (defaults to 0). > + > +@item header > +Filename of the header where the initialization data will be written. > + > +@item audio_chunk_duration > +Duration of each audio chunk in milliseconds (defaults to 5000). > +@end table > + > +@subsection Example > +@example > +ffmpeg -f v4l2 -i /dev/video0 \ > + -f alsa -i hw:0 \ > + -map 0:0 \ > + -c:v libvpx-vp9 \ > + -s 640x360 -keyint_min 30 -g 30 \ > + -f webm_chunk \ > + -header webm_live_video_360.hdr \ > + -chunk_start_index 1 \ > + webm_live_video_360_%d.chk \ > + -map 1:0 \ > + -c:a libvorbis \ > + -b:a 128k \ > + -f webm_chunk \ > + -header webm_live_audio_128.hdr \ > + -chunk_start_index 1 \ > + -audio_chunk_duration 1000 \ > + webm_live_audio_128_%d.chk > +@end example > + > @c man end MUXERS > diff --git a/libavformat/Makefile b/libavformat/Makefile > index 2118ff2..e40ed4d 100644 > --- a/libavformat/Makefile > +++ b/libavformat/Makefile > @@ -237,7 +237,7 @@ OBJS-$(CONFIG_MATROSKA_DEMUXER) += matroskadec.o > matroska.o \ > OBJS-$(CONFIG_MATROSKA_MUXER) += matroskaenc.o matroska.o \ > isom.o avc.o hevc.o \ > flacenc_header.o avlanguage.o > vorbiscomment.o wv.o \ > - webmdashenc.o > + webmdashenc.o webm_chunk.o > OBJS-$(CONFIG_MD5_MUXER) += md5enc.o > OBJS-$(CONFIG_MGSTS_DEMUXER) += mgsts.o > OBJS-$(CONFIG_MICRODVD_DEMUXER) += microdvddec.o subtitles.o > @@ -451,12 +451,13 @@ OBJS-$(CONFIG_WEBM_MUXER) += > matroskaenc.o matroska.o \ > isom.o avc.o hevc.o \ > flacenc_header.o avlanguage.o \ > wv.o vorbiscomment.o \ > - webmdashenc.o > + webmdashenc.o webm_chunk.o > OBJS-$(CONFIG_WEBM_DASH_MANIFEST_DEMUXER)+= matroskadec.o matroska.o \ > isom.o rmsipr.o flac_picture.o \ > oggparsevorbis.o vorbiscomment.o > \ > flac_picture.o replaygain.o > OBJS-$(CONFIG_WEBM_DASH_MANIFEST_MUXER) += webmdashenc.o matroska.o > +OBJS-$(CONFIG_WEBM_CHUNK_MUXER) += webm_chunk.o matroska.o > OBJS-$(CONFIG_WEBP_MUXER) += webpenc.o > OBJS-$(CONFIG_WEBVTT_DEMUXER) += webvttdec.o subtitles.o > OBJS-$(CONFIG_WEBVTT_MUXER) += webvttenc.o > diff --git a/libavformat/allformats.c b/libavformat/allformats.c > index 26ccc27..f56493c 100644 > --- a/libavformat/allformats.c > +++ b/libavformat/allformats.c > @@ -316,6 +316,7 @@ void av_register_all(void) > REGISTER_DEMUXER (WC3, wc3); > REGISTER_MUXER (WEBM, webm); > REGISTER_MUXDEMUX(WEBM_DASH_MANIFEST, webm_dash_manifest); > + REGISTER_MUXER (WEBM_CHUNK, webm_chunk); > REGISTER_MUXER (WEBP, webp); > REGISTER_MUXDEMUX(WEBVTT, webvtt); > REGISTER_DEMUXER (WSAUD, wsaud); > diff --git a/libavformat/matroskaenc.c b/libavformat/matroskaenc.c > index 6b2e390..027605e 100644 > --- a/libavformat/matroskaenc.c > +++ b/libavformat/matroskaenc.c > @@ -120,6 +120,7 @@ typedef struct MatroskaMuxContext { > int64_t cluster_time_limit; > int is_dash; > int dash_track_number; > + int is_live; > > uint32_t chapter_id_offset; > int wrote_chapters; > @@ -1407,7 +1408,9 @@ static int mkv_write_header(AVFormatContext *s) > // reserve space for the duration > mkv->duration = 0; > mkv->duration_offset = avio_tell(pb); > - put_ebml_void(pb, 11); // assumes double-precision > float to be written > + if (!mkv->is_live) { > + put_ebml_void(pb, 11); // assumes double-precision > float to be written > + } > end_ebml_master(pb, segment_info); > > ret = mkv_write_tracks(s); > @@ -1431,7 +1434,7 @@ static int mkv_write_header(AVFormatContext *s) > return ret; > } > > - if (!s->pb->seekable) > + if (!s->pb->seekable && !mkv->is_live) > mkv_write_seekhead(pb, mkv->main_seekhead); > > mkv->cues = mkv_start_cues(mkv->segment_offset); > @@ -1950,7 +1953,9 @@ static int mkv_write_trailer(AVFormatContext *s) > avio_seek(pb, currentpos, SEEK_SET); > } > > - end_ebml_master(pb, mkv->segment); > + if (!mkv->is_live) { > + end_ebml_master(pb, mkv->segment); > + } > av_freep(&mkv->tracks); > av_freep(&mkv->cues->entries); > av_freep(&mkv->cues); > @@ -2014,6 +2019,7 @@ static const AVOption options[] = { > { "cluster_time_limit", "Store at most the provided number of > milliseconds in a cluster.", > OFFSET(cluster_time_limit), AV_OPT_TYPE_INT64, { .i64 = -1 }, -1, INT64_MAX, > FLAGS }, > { "dash", "Create a WebM file conforming to WebM DASH specification", > OFFSET(is_dash), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, > { "dash_track_number", "Track number for the DASH stream", > OFFSET(dash_track_number), AV_OPT_TYPE_INT, { .i64 = 1 }, 0, 127, FLAGS }, > + { "live", "Write files assuming it is a live stream.", OFFSET(is_live), > AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, > { "allow_raw_vfw", "allow RAW VFW mode", OFFSET(allow_raw_vfw), > AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 1, FLAGS }, > { NULL }, > }; > diff --git a/libavformat/version.h b/libavformat/version.h > index a183d7f..ff85227 100644 > --- a/libavformat/version.h > +++ b/libavformat/version.h > @@ -30,7 +30,7 @@ > #include "libavutil/version.h" > > #define LIBAVFORMAT_VERSION_MAJOR 56 > -#define LIBAVFORMAT_VERSION_MINOR 26 > +#define LIBAVFORMAT_VERSION_MINOR 27 > #define LIBAVFORMAT_VERSION_MICRO 101 > > #define LIBAVFORMAT_VERSION_INT AV_VERSION_INT(LIBAVFORMAT_VERSION_MAJOR, \ > diff --git a/libavformat/webm_chunk.c b/libavformat/webm_chunk.c > new file mode 100644 > index 0000000..1c7cd00 > --- /dev/null > +++ b/libavformat/webm_chunk.c > @@ -0,0 +1,251 @@ > +/* > + * Copyright (c) 2015, Vignesh Venkatasubramanian > + * > + * 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 > + */ > + > +/** > + * @file WebM Chunk Muxer > + * The chunk muxer enables writing WebM Live chunks where there is a header > + * chunk, followed by data chunks where each Cluster is written out as a > Chunk. > + */ > + > +#include <float.h> > +#include <time.h> > + > +#include "avformat.h" > +#include "avio.h" > +#include "internal.h" > + > +#include "libavutil/avassert.h" > +#include "libavutil/log.h" > +#include "libavutil/opt.h" > +#include "libavutil/avstring.h" > +#include "libavutil/parseutils.h" > +#include "libavutil/mathematics.h" > +#include "libavutil/time.h" > +#include "libavutil/time_internal.h" > +#include "libavutil/timestamp.h" > + > +typedef struct WebMChunkContext { > + const AVClass *class; > + int chunk_start_index; > + char *header_filename; > + int chunk_duration; > + int chunk_count; > + int chunk_index; > + uint64_t duration_written; > + int prev_pts; > + AVOutputFormat *oformat; > + AVFormatContext *avf; > +} WebMChunkContext; > + > +static int chunk_mux_init(AVFormatContext *s) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc; > + int ret; > + > + ret = avformat_alloc_output_context2(&wc->avf, wc->oformat, NULL, NULL); > + if (ret < 0) > + return ret; > + oc = wc->avf; > + > + oc->interrupt_callback = s->interrupt_callback; > + oc->max_delay = s->max_delay; > + av_dict_copy(&oc->metadata, s->metadata, 0); > + > + oc->priv_data = av_mallocz(oc->oformat->priv_data_size); > + if (!oc->priv_data) { > + avio_close(oc->pb); > + return AVERROR(ENOMEM); > + } > + *(const AVClass**)oc->priv_data = oc->oformat->priv_class; > + av_opt_set_defaults(oc->priv_data); > + av_opt_set_int(oc->priv_data, "dash", 1, 0); > + av_opt_set_int(oc->priv_data, "cluster_time_limit", wc->chunk_duration, > 0); > + av_opt_set_int(oc->priv_data, "live", 1, 0); > + > + oc->streams = s->streams; > + oc->nb_streams = s->nb_streams; > + > + return 0; > +} > + > +static int set_chunk_filename(AVFormatContext *s, int is_header) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = wc->avf; > + if (is_header) { > + if (!wc->header_filename) { > + return AVERROR(EINVAL); > + } > + av_strlcpy(oc->filename, wc->header_filename, > strlen(wc->header_filename) + 1); > + } else { > + if (av_get_frame_filename(oc->filename, sizeof(oc->filename), > + s->filename, wc->chunk_index) < 0) { > + av_log(oc, AV_LOG_ERROR, "Invalid chunk filename template > '%s'\n", s->filename); > + return AVERROR(EINVAL); > + } > + } > + return 0; > +} > + > +static int webm_chunk_write_header(AVFormatContext *s) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = NULL; > + int ret; > + > + // DASH Streams can only have either one track per file. > + if (s->nb_streams != 1) { return AVERROR_INVALIDDATA; } > + > + wc->chunk_count = 0; > + wc->chunk_index = wc->chunk_start_index; > + wc->oformat = av_guess_format("webm", s->filename, "video/webm"); > + if (!wc->oformat) { return AVERROR_MUXER_NOT_FOUND; } > + > + if ((ret = chunk_mux_init(s)) < 0) { return ret; } > + oc = wc->avf; > + if ((ret = set_chunk_filename(s, 1)) < 0) { return ret; } > + if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, > + &s->interrupt_callback, NULL)) < 0) { > + return ret; > + } > + oc->pb->seekable = 0; > + if ((ret = oc->oformat->write_header(oc)) < 0) { return ret; } > + avio_close(oc->pb); > + return 0; > +} > + > +static int chunk_start(AVFormatContext *s) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = wc->avf; > + int ret; > + > + if ((ret = set_chunk_filename(s, 0)) < 0) { return ret; } > + wc->chunk_index++; > + if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE, > + &s->interrupt_callback, NULL)) < 0) { > + return ret; > + } > + oc->pb->seekable = 0; > + return 0; > +} > + > +static int chunk_end(AVFormatContext *s) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = wc->avf; > + int ret; > + > + if (wc->chunk_start_index == wc->chunk_index) { return 0; } > + // Flush the cluster in WebM muxer. > + oc->oformat->write_packet(oc, NULL); > + if ((ret = avio_close(oc->pb)) < 0) { > + return ret; > + } > + wc->chunk_count++; > + return 0; > +} > + > +static int webm_chunk_write_packet(AVFormatContext *s, AVPacket *pkt) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = wc->avf; > + AVStream *st = s->streams[pkt->stream_index]; > + int ret; > + > + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO) { > + wc->duration_written += av_rescale_q(pkt->pts - wc->prev_pts, > + st->time_base, > + (AVRational) {1, 1000}); > + wc->prev_pts = pkt->pts; > + } > + > + // For video, a new chunk is started only on key frames. For audio, a new > + // chunk is started based on chunk_duration. > + if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO && > + (pkt->flags & AV_PKT_FLAG_KEY)) || > + (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && > + (pkt->pts == 0 || wc->duration_written >= wc->chunk_duration))) { > + wc->duration_written = 0; > + if ((ret = chunk_end(s)) < 0 || (ret = chunk_start(s)) < 0) { > + goto fail; > + } > + } > + > + if (st->codec->codec_type == AVMEDIA_TYPE_AUDIO && pkt->pts == 0) { > + goto fail; > + } > + if ((ret = oc->oformat->write_packet(oc, pkt)) < 0) { > + goto fail; > + } > + > +fail: > + if (ret < 0) { > + oc->streams = NULL; > + oc->nb_streams = 0; > + avformat_free_context(oc); > + } > + > + return ret; > +} > + > +static int webm_chunk_write_trailer(AVFormatContext *s) > +{ > + WebMChunkContext *wc = s->priv_data; > + AVFormatContext *oc = wc->avf; > + oc->oformat->write_trailer(oc); > + chunk_end(s); > + oc->streams = NULL; > + oc->nb_streams = 0; > + avformat_free_context(oc); > + return 0; > +} > + > +#define OFFSET(x) offsetof(WebMChunkContext, x) > +static const AVOption options[] = { > + { "chunk_start_index", "start index of the chunk", > OFFSET(chunk_start_index), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, > AV_OPT_FLAG_ENCODING_PARAM }, > + { "header", "filename of the header where the initialization data will > be written", OFFSET(header_filename), AV_OPT_TYPE_STRING, { 0 }, 0, 0, > AV_OPT_FLAG_ENCODING_PARAM }, > + { "audio_chunk_duration", "duration of each chunk in milliseconds", > OFFSET(chunk_duration), AV_OPT_TYPE_INT, {.i64 = 5000}, 0, INT_MAX, > AV_OPT_FLAG_ENCODING_PARAM }, > + { NULL }, > +}; > + > +#if CONFIG_WEBM_CHUNK_MUXER > +static const AVClass webm_chunk_class = { > + .class_name = "WebM Chunk Muxer", > + .item_name = av_default_item_name, > + .option = options, > + .version = LIBAVUTIL_VERSION_INT, > +}; > + > +AVOutputFormat ff_webm_chunk_muxer = { > + .name = "webm_chunk", > + .long_name = NULL_IF_CONFIG_SMALL("WebM Chunk Muxer"), > + .mime_type = "video/webm", > + .extensions = "chk", > + .flags = AVFMT_NOFILE | AVFMT_GLOBALHEADER | AVFMT_NEEDNUMBER | > + AVFMT_TS_NONSTRICT | AVFMT_ALLOW_FLUSH, > + .priv_data_size = sizeof(WebMChunkContext), > + .write_header = webm_chunk_write_header, > + .write_packet = webm_chunk_write_packet, > + .write_trailer = webm_chunk_write_trailer, > + .priv_class = &webm_chunk_class, > +}; > +#endif When are we going to get a DASH demuxer? _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org http://ffmpeg.org/mailman/listinfo/ffmpeg-devel