2 * Copyright (c) 2015, Vignesh Venkatasubramanian
4 * This file is part of FFmpeg.
6 * FFmpeg is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
11 * FFmpeg is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with FFmpeg; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 * @file WebM Chunk Muxer
23 * The chunk muxer enables writing WebM Live chunks where there is a header
24 * chunk, followed by data chunks where each Cluster is written out as a Chunk.
29 #include "avio_internal.h"
33 #include "libavutil/bprint.h"
34 #include "libavutil/log.h"
35 #include "libavutil/mem.h"
36 #include "libavutil/opt.h"
37 #include "libavutil/mathematics.h"
39 typedef struct WebMChunkContext
{
41 char *header_filename
;
45 uint64_t duration_written
;
51 static int webm_chunk_init(AVFormatContext
*s
)
53 WebMChunkContext
*wc
= s
->priv_data
;
54 const AVOutputFormat
*oformat
;
56 AVStream
*st
, *ost
= s
->streams
[0];
57 AVDictionary
*dict
= NULL
;
60 // DASH Streams can only have one track per file.
61 if (s
->nb_streams
!= 1)
62 return AVERROR(EINVAL
);
64 if (!wc
->header_filename
) {
65 av_log(s
, AV_LOG_ERROR
, "No header filename provided\n");
66 return AVERROR(EINVAL
);
69 wc
->prev_pts
= AV_NOPTS_VALUE
;
71 oformat
= av_guess_format("webm", s
->url
, "video/webm");
73 return AVERROR_MUXER_NOT_FOUND
;
75 ret
= avformat_alloc_output_context2(&wc
->avf
, oformat
, NULL
, NULL
);
80 ff_format_set_url(oc
, wc
->header_filename
);
81 wc
->header_filename
= NULL
;
83 oc
->interrupt_callback
= s
->interrupt_callback
;
84 oc
->max_delay
= s
->max_delay
;
85 oc
->flags
= s
->flags
& ~AVFMT_FLAG_FLUSH_PACKETS
;
86 oc
->strict_std_compliance
= s
->strict_std_compliance
;
87 oc
->avoid_negative_ts
= s
->avoid_negative_ts
;
89 oc
->flush_packets
= 0;
91 if ((ret
= av_dict_copy(&oc
->metadata
, s
->metadata
, 0)) < 0)
94 st
= ff_stream_clone(oc
, ost
);
96 return AVERROR(ENOMEM
);
99 if ((ret
= av_dict_set(&dict
, "method", wc
->http_method
, 0)) < 0)
101 ret
= s
->io_open(s
, &oc
->pb
, oc
->url
, AVIO_FLAG_WRITE
, &dict
);
105 oc
->pb
->seekable
= 0;
107 if ((ret
= av_dict_set_int(&dict
, "dash", 1, 0)) < 0 ||
108 (ret
= av_dict_set_int(&dict
, "cluster_time_limit",
109 wc
->chunk_duration
, 0)) < 0 ||
110 (ret
= av_dict_set_int(&dict
, "live", 1, 0)) < 0)
113 ret
= avformat_init_output(oc
, &dict
);
119 // Copy the timing info back to the original stream
120 // so that the timestamps of the packets are directly usable
121 avpriv_set_pts_info(ost
, st
->pts_wrap_bits
, st
->time_base
.num
,
124 // This ensures that the timestamps will already be properly shifted
125 // when the packets arrive here, so we don't need to shift again.
126 s
->avoid_negative_ts
= oc
->avoid_negative_ts
;
127 ffformatcontext(s
)->avoid_negative_ts_use_pts
=
128 ffformatcontext(oc
)->avoid_negative_ts_use_pts
;
129 oc
->avoid_negative_ts
= AVFMT_AVOID_NEG_TS_DISABLED
;
130 ffformatcontext(oc
)->avoid_negative_ts_status
= AVOID_NEGATIVE_TS_DISABLED
;
135 static int get_chunk_filename(AVFormatContext
*s
, AVBPrint
*filename
)
137 WebMChunkContext
*wc
= s
->priv_data
;
138 int ret
= ff_bprint_get_frame_filename(filename
, s
->url
, wc
->chunk_index
- 1, 0);
140 av_log(s
, AV_LOG_ERROR
, "Invalid chunk filename template '%s'\n", s
->url
);
146 static int webm_chunk_write_header(AVFormatContext
*s
)
148 WebMChunkContext
*wc
= s
->priv_data
;
149 AVFormatContext
*oc
= wc
->avf
;
150 AVStream
*st
= s
->streams
[0], *ost
= oc
->streams
[0];
153 ret
= avformat_write_header(oc
, NULL
);
154 ff_format_io_close(s
, &oc
->pb
);
155 ffstream(st
)->lowest_ts_allowed
= ffstream(ost
)->lowest_ts_allowed
;
156 ffstream(ost
)->lowest_ts_allowed
= 0;
157 wc
->header_written
= 1;
163 static int chunk_start(AVFormatContext
*s
)
165 WebMChunkContext
*wc
= s
->priv_data
;
166 AVFormatContext
*oc
= wc
->avf
;
169 ret
= avio_open_dyn_buf(&oc
->pb
);
176 static int chunk_end(AVFormatContext
*s
, int flush
)
178 WebMChunkContext
*wc
= s
->priv_data
;
179 AVFormatContext
*oc
= wc
->avf
;
185 AVDictionary
*options
= NULL
;
191 // Flush the cluster in WebM muxer.
192 av_write_frame(oc
, NULL
);
193 buffer_size
= avio_close_dyn_buf(oc
->pb
, &buffer
);
195 av_bprint_init(&filename
, 0, AV_BPRINT_SIZE_UNLIMITED
);
196 ret
= get_chunk_filename(s
, &filename
);
200 if ((ret
= av_dict_set(&options
, "method", wc
->http_method
, 0)) < 0)
202 ret
= s
->io_open(s
, &pb
, filename
.str
, AVIO_FLAG_WRITE
, &options
);
203 av_dict_free(&options
);
206 avio_write(pb
, buffer
, buffer_size
);
207 ff_format_io_close(s
, &pb
);
209 av_bprint_finalize(&filename
, NULL
);
211 return (ret
< 0) ? ret
: 0;
214 static int webm_chunk_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
216 WebMChunkContext
*wc
= s
->priv_data
;
217 AVFormatContext
*oc
= wc
->avf
;
218 AVStream
*st
= s
->streams
[pkt
->stream_index
];
221 if (st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
) {
222 if (wc
->prev_pts
!= AV_NOPTS_VALUE
)
223 wc
->duration_written
+= av_rescale_q(pkt
->pts
- wc
->prev_pts
,
225 (AVRational
) {1, 1000});
226 wc
->prev_pts
= pkt
->pts
;
229 // For video, a new chunk is started only on key frames. For audio, a new
230 // chunk is started based on chunk_duration. Also, a new chunk is started
231 // unconditionally if there is no currently open chunk.
232 if (!oc
->pb
|| (st
->codecpar
->codec_type
== AVMEDIA_TYPE_VIDEO
&&
233 (pkt
->flags
& AV_PKT_FLAG_KEY
)) ||
234 (st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
&&
235 wc
->duration_written
>= wc
->chunk_duration
)) {
236 wc
->duration_written
= 0;
237 if ((ret
= chunk_end(s
, 1)) < 0 || (ret
= chunk_start(s
)) < 0) {
242 // We only have one stream, so use the non-interleaving av_write_frame.
243 return av_write_frame(oc
, pkt
);
246 static int webm_chunk_write_trailer(AVFormatContext
*s
)
248 WebMChunkContext
*wc
= s
->priv_data
;
249 AVFormatContext
*oc
= wc
->avf
;
253 ret
= chunk_start(s
);
257 ret
= av_write_trailer(oc
);
260 return chunk_end(s
, 0);
263 static void webm_chunk_deinit(AVFormatContext
*s
)
265 WebMChunkContext
*wc
= s
->priv_data
;
270 if (wc
->header_written
)
271 ffio_free_dyn_buf(&wc
->avf
->pb
);
273 ff_format_io_close(s
, &wc
->avf
->pb
);
274 avformat_free_context(wc
->avf
);
278 #define OFFSET(x) offsetof(WebMChunkContext, x)
279 static const AVOption options
[] = {
280 { "chunk_start_index", "start index of the chunk", OFFSET(chunk_index
), AV_OPT_TYPE_INT
, {.i64
= 0}, 0, INT_MAX
, AV_OPT_FLAG_ENCODING_PARAM
},
281 { "header", "filename of the header where the initialization data will be written", OFFSET(header_filename
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
282 { "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
},
283 { "method", "set the HTTP method", OFFSET(http_method
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
287 static const AVClass webm_chunk_class
= {
288 .class_name
= "WebM Chunk Muxer",
289 .item_name
= av_default_item_name
,
291 .version
= LIBAVUTIL_VERSION_INT
,
294 const FFOutputFormat ff_webm_chunk_muxer
= {
295 .p
.name
= "webm_chunk",
296 .p
.long_name
= NULL_IF_CONFIG_SMALL("WebM Chunk Muxer"),
297 .p
.mime_type
= "video/webm",
298 .p
.extensions
= "chk",
299 .p
.flags
= AVFMT_NOFILE
| AVFMT_GLOBALHEADER
| AVFMT_NEEDNUMBER
|
301 .p
.priv_class
= &webm_chunk_class
,
302 .priv_data_size
= sizeof(WebMChunkContext
),
303 .init
= webm_chunk_init
,
304 .write_header
= webm_chunk_write_header
,
305 .write_packet
= webm_chunk_write_packet
,
306 .write_trailer
= webm_chunk_write_trailer
,
307 .deinit
= webm_chunk_deinit
,