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"
32 #include "libavutil/log.h"
33 #include "libavutil/opt.h"
34 #include "libavutil/mathematics.h"
36 #define MAX_FILENAME_SIZE 1024
38 typedef struct WebMChunkContext
{
40 char *header_filename
;
44 uint64_t duration_written
;
50 static int webm_chunk_init(AVFormatContext
*s
)
52 WebMChunkContext
*wc
= s
->priv_data
;
53 ff_const59 AVOutputFormat
*oformat
;
55 AVStream
*st
, *ost
= s
->streams
[0];
56 AVDictionary
*dict
= NULL
;
59 // DASH Streams can only have one track per file.
60 if (s
->nb_streams
!= 1)
61 return AVERROR(EINVAL
);
63 if (!wc
->header_filename
) {
64 av_log(s
, AV_LOG_ERROR
, "No header filename provided\n");
65 return AVERROR(EINVAL
);
68 wc
->prev_pts
= AV_NOPTS_VALUE
;
70 oformat
= av_guess_format("webm", s
->url
, "video/webm");
72 return AVERROR_MUXER_NOT_FOUND
;
74 ret
= avformat_alloc_output_context2(&wc
->avf
, oformat
, NULL
, NULL
);
79 ff_format_set_url(oc
, wc
->header_filename
);
80 wc
->header_filename
= NULL
;
82 oc
->interrupt_callback
= s
->interrupt_callback
;
83 oc
->max_delay
= s
->max_delay
;
84 oc
->flags
= s
->flags
& ~AVFMT_FLAG_FLUSH_PACKETS
;
85 oc
->strict_std_compliance
= s
->strict_std_compliance
;
86 oc
->avoid_negative_ts
= s
->avoid_negative_ts
;
88 oc
->flush_packets
= 0;
90 if ((ret
= av_dict_copy(&oc
->metadata
, s
->metadata
, 0)) < 0)
93 if (!(st
= avformat_new_stream(oc
, NULL
)))
94 return AVERROR(ENOMEM
);
96 if ((ret
= avcodec_parameters_copy(st
->codecpar
, ost
->codecpar
)) < 0 ||
97 (ret
= av_dict_copy(&st
->metadata
, ost
->metadata
, 0)) < 0)
100 st
->sample_aspect_ratio
= ost
->sample_aspect_ratio
;
101 st
->disposition
= ost
->disposition
;
102 avpriv_set_pts_info(st
, ost
->pts_wrap_bits
, ost
->time_base
.num
,
106 if ((ret
= av_dict_set(&dict
, "method", wc
->http_method
, 0)) < 0)
108 ret
= s
->io_open(s
, &oc
->pb
, oc
->url
, AVIO_FLAG_WRITE
, &dict
);
112 oc
->pb
->seekable
= 0;
114 if ((ret
= av_dict_set_int(&dict
, "dash", 1, 0)) < 0 ||
115 (ret
= av_dict_set_int(&dict
, "cluster_time_limit",
116 wc
->chunk_duration
, 0)) < 0 ||
117 (ret
= av_dict_set_int(&dict
, "live", 1, 0)) < 0)
120 ret
= avformat_init_output(oc
, &dict
);
126 // Copy the timing info back to the original stream
127 // so that the timestamps of the packets are directly usable
128 avpriv_set_pts_info(ost
, st
->pts_wrap_bits
, st
->time_base
.num
,
131 // This ensures that the timestamps will already be properly shifted
132 // when the packets arrive here, so we don't need to shift again.
133 s
->avoid_negative_ts
= oc
->avoid_negative_ts
;
134 s
->internal
->avoid_negative_ts_use_pts
=
135 oc
->internal
->avoid_negative_ts_use_pts
;
136 oc
->avoid_negative_ts
= 0;
141 static int get_chunk_filename(AVFormatContext
*s
, char filename
[MAX_FILENAME_SIZE
])
143 WebMChunkContext
*wc
= s
->priv_data
;
145 return AVERROR(EINVAL
);
147 if (av_get_frame_filename(filename
, MAX_FILENAME_SIZE
,
148 s
->url
, wc
->chunk_index
- 1) < 0) {
149 av_log(s
, AV_LOG_ERROR
, "Invalid chunk filename template '%s'\n", s
->url
);
150 return AVERROR(EINVAL
);
155 static int webm_chunk_write_header(AVFormatContext
*s
)
157 WebMChunkContext
*wc
= s
->priv_data
;
158 AVFormatContext
*oc
= wc
->avf
;
161 ret
= avformat_write_header(oc
, NULL
);
162 ff_format_io_close(s
, &oc
->pb
);
163 wc
->header_written
= 1;
169 static int chunk_start(AVFormatContext
*s
)
171 WebMChunkContext
*wc
= s
->priv_data
;
172 AVFormatContext
*oc
= wc
->avf
;
175 ret
= avio_open_dyn_buf(&oc
->pb
);
182 static int chunk_end(AVFormatContext
*s
, int flush
)
184 WebMChunkContext
*wc
= s
->priv_data
;
185 AVFormatContext
*oc
= wc
->avf
;
190 char filename
[MAX_FILENAME_SIZE
];
191 AVDictionary
*options
= NULL
;
197 // Flush the cluster in WebM muxer.
198 av_write_frame(oc
, NULL
);
199 buffer_size
= avio_close_dyn_buf(oc
->pb
, &buffer
);
201 ret
= get_chunk_filename(s
, filename
);
205 if ((ret
= av_dict_set(&options
, "method", wc
->http_method
, 0)) < 0)
207 ret
= s
->io_open(s
, &pb
, filename
, AVIO_FLAG_WRITE
, &options
);
208 av_dict_free(&options
);
211 avio_write(pb
, buffer
, buffer_size
);
212 ff_format_io_close(s
, &pb
);
215 return (ret
< 0) ? ret
: 0;
218 static int webm_chunk_write_packet(AVFormatContext
*s
, AVPacket
*pkt
)
220 WebMChunkContext
*wc
= s
->priv_data
;
221 AVFormatContext
*oc
= wc
->avf
;
222 AVStream
*st
= s
->streams
[pkt
->stream_index
];
225 if (st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
) {
226 if (wc
->prev_pts
!= AV_NOPTS_VALUE
)
227 wc
->duration_written
+= av_rescale_q(pkt
->pts
- wc
->prev_pts
,
229 (AVRational
) {1, 1000});
230 wc
->prev_pts
= pkt
->pts
;
233 // For video, a new chunk is started only on key frames. For audio, a new
234 // chunk is started based on chunk_duration. Also, a new chunk is started
235 // unconditionally if there is no currently open chunk.
236 if (!oc
->pb
|| (st
->codecpar
->codec_type
== AVMEDIA_TYPE_VIDEO
&&
237 (pkt
->flags
& AV_PKT_FLAG_KEY
)) ||
238 (st
->codecpar
->codec_type
== AVMEDIA_TYPE_AUDIO
&&
239 wc
->duration_written
>= wc
->chunk_duration
)) {
240 wc
->duration_written
= 0;
241 if ((ret
= chunk_end(s
, 1)) < 0 || (ret
= chunk_start(s
)) < 0) {
246 // We only have one stream, so use the non-interleaving av_write_frame.
247 return av_write_frame(oc
, pkt
);
250 static int webm_chunk_write_trailer(AVFormatContext
*s
)
252 WebMChunkContext
*wc
= s
->priv_data
;
253 AVFormatContext
*oc
= wc
->avf
;
257 ret
= chunk_start(s
);
261 ret
= av_write_trailer(oc
);
264 return chunk_end(s
, 0);
267 static void webm_chunk_deinit(AVFormatContext
*s
)
269 WebMChunkContext
*wc
= s
->priv_data
;
274 if (wc
->header_written
)
275 ffio_free_dyn_buf(&wc
->avf
->pb
);
277 ff_format_io_close(s
, &wc
->avf
->pb
);
278 avformat_free_context(wc
->avf
);
282 #define OFFSET(x) offsetof(WebMChunkContext, x)
283 static const AVOption options
[] = {
284 { "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
},
285 { "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
},
286 { "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
},
287 { "method", "set the HTTP method", OFFSET(http_method
), AV_OPT_TYPE_STRING
, {.str
= NULL
}, 0, 0, AV_OPT_FLAG_ENCODING_PARAM
},
291 static const AVClass webm_chunk_class
= {
292 .class_name
= "WebM Chunk Muxer",
293 .item_name
= av_default_item_name
,
295 .version
= LIBAVUTIL_VERSION_INT
,
298 AVOutputFormat ff_webm_chunk_muxer
= {
299 .name
= "webm_chunk",
300 .long_name
= NULL_IF_CONFIG_SMALL("WebM Chunk Muxer"),
301 .mime_type
= "video/webm",
303 .flags
= AVFMT_NOFILE
| AVFMT_GLOBALHEADER
| AVFMT_NEEDNUMBER
|
305 .priv_data_size
= sizeof(WebMChunkContext
),
306 .init
= webm_chunk_init
,
307 .write_header
= webm_chunk_write_header
,
308 .write_packet
= webm_chunk_write_packet
,
309 .write_trailer
= webm_chunk_write_trailer
,
310 .deinit
= webm_chunk_deinit
,
311 .priv_class
= &webm_chunk_class
,