00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022 #include <float.h>
00023
00024 #include "libavutil/mathematics.h"
00025 #include "libavutil/parseutils.h"
00026 #include "libavutil/avstring.h"
00027 #include "libavutil/opt.h"
00028 #include "libavutil/log.h"
00029
00030 #include "avformat.h"
00031 #include "internal.h"
00032
00033 typedef struct ListEntry {
00034 char name[1024];
00035 int duration;
00036 struct ListEntry *next;
00037 } ListEntry;
00038
00039 typedef struct HLSContext {
00040 const AVClass *class;
00041 int number;
00042 AVOutputFormat *oformat;
00043 AVFormatContext *avf;
00044 float time;
00045 int size;
00046 int wrap;
00047 int64_t recording_time;
00048 int has_video;
00049 int64_t start_pts;
00050 int64_t end_pts;
00051 ListEntry *list;
00052 ListEntry *end_list;
00053 char *basename;
00054 AVIOContext *pb;
00055 } HLSContext;
00056
00057 static int hls_mux_init(AVFormatContext *s)
00058 {
00059 HLSContext *hls = s->priv_data;
00060 AVFormatContext *oc;
00061 int i;
00062
00063 hls->avf = oc = avformat_alloc_context();
00064 if (!oc)
00065 return AVERROR(ENOMEM);
00066
00067 oc->oformat = hls->oformat;
00068 oc->interrupt_callback = s->interrupt_callback;
00069
00070 for (i = 0; i < s->nb_streams; i++) {
00071 AVStream *st;
00072 if (!(st = avformat_new_stream(oc, NULL)))
00073 return AVERROR(ENOMEM);
00074 avcodec_copy_context(st->codec, s->streams[i]->codec);
00075 st->sample_aspect_ratio = s->streams[i]->sample_aspect_ratio;
00076 }
00077
00078 return 0;
00079 }
00080
00081 static int append_entry(HLSContext *hls, uint64_t duration)
00082 {
00083 ListEntry *en = av_malloc(sizeof(*en));
00084
00085 if (!en)
00086 return AVERROR(ENOMEM);
00087
00088 av_get_frame_filename(en->name, sizeof(en->name), hls->basename,
00089 hls->number -1);
00090
00091 en->duration = duration;
00092 en->next = NULL;
00093
00094 if (!hls->list)
00095 hls->list = en;
00096 else
00097 hls->end_list->next = en;
00098
00099 hls->end_list = en;
00100
00101 if (hls->number >= hls->size) {
00102 en = hls->list;
00103 hls->list = en->next;
00104 av_free(en);
00105 }
00106
00107 return 0;
00108 }
00109
00110 static void free_entries(HLSContext *hls)
00111 {
00112 ListEntry *p = hls->list, *en;
00113
00114 while(p) {
00115 en = p;
00116 p = p->next;
00117 av_free(en);
00118 }
00119 }
00120
00121 static int hls_window(AVFormatContext *s, int last)
00122 {
00123 HLSContext *hls = s->priv_data;
00124 ListEntry *en;
00125 int ret = 0;
00126
00127 if ((ret = avio_open2(&hls->pb, s->filename, AVIO_FLAG_WRITE,
00128 &s->interrupt_callback, NULL)) < 0)
00129 goto fail;
00130
00131 avio_printf(hls->pb, "#EXTM3U\n");
00132 avio_printf(hls->pb, "#EXT-X-VERSION:3\n");
00133 avio_printf(hls->pb, "#EXT-X-TARGETDURATION:%d\n", (int)hls->time);
00134 avio_printf(hls->pb, "#EXT-X-MEDIA-SEQUENCE:%d\n",
00135 FFMAX(0, hls->number - hls->size));
00136
00137 for (en = hls->list; en; en = en->next) {
00138 avio_printf(hls->pb, "#EXTINF:%d,\n", en->duration);
00139 avio_printf(hls->pb, "%s\n", en->name);
00140 }
00141
00142 if (last)
00143 avio_printf(hls->pb, "#EXT-X-ENDLIST\n");
00144
00145 fail:
00146 avio_closep(&hls->pb);
00147 return ret;
00148 }
00149
00150 static int hls_start(AVFormatContext *s)
00151 {
00152 HLSContext *c = s->priv_data;
00153 AVFormatContext *oc = c->avf;
00154 int err = 0;
00155
00156 if (c->wrap)
00157 c->number %= c->wrap;
00158
00159 if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
00160 c->basename, c->number++) < 0)
00161 return AVERROR(EINVAL);
00162
00163 if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
00164 &s->interrupt_callback, NULL)) < 0)
00165 return err;
00166
00167 if (oc->oformat->priv_class && oc->priv_data)
00168 av_opt_set(oc->priv_data, "mpegts_flags", "resend_headers", 0);
00169
00170 return 0;
00171 }
00172
00173 static int hls_write_header(AVFormatContext *s)
00174 {
00175 HLSContext *hls = s->priv_data;
00176 int ret, i;
00177 char *p;
00178 const char *pattern = "%d.ts";
00179 int basename_size = strlen(s->filename) + strlen(pattern);
00180
00181 hls->number = 0;
00182
00183 hls->recording_time = hls->time * 1000000;
00184 hls->start_pts = AV_NOPTS_VALUE;
00185
00186 for (i = 0; i < s->nb_streams; i++)
00187 hls->has_video +=
00188 s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO;
00189
00190 if (hls->has_video > 1)
00191 av_log(s, AV_LOG_WARNING,
00192 "More than a single video stream present, "
00193 "expect issues decoding it.\n");
00194
00195 hls->oformat = av_guess_format("mpegts", NULL, NULL);
00196
00197 if (!hls->oformat) {
00198 ret = AVERROR_MUXER_NOT_FOUND;
00199 goto fail;
00200 }
00201
00202 hls->basename = av_malloc(basename_size);
00203
00204 if (!hls->basename) {
00205 ret = AVERROR(ENOMEM);
00206 goto fail;
00207 }
00208
00209 strcpy(hls->basename, s->filename);
00210
00211 p = strrchr(hls->basename, '.');
00212
00213 if (p)
00214 *p = '\0';
00215
00216 av_strlcat(hls->basename, "%d.ts", basename_size);
00217
00218 if ((ret = hls_mux_init(s)) < 0)
00219 goto fail;
00220
00221 if ((ret = hls_start(s)) < 0)
00222 goto fail;
00223
00224 if ((ret = avformat_write_header(hls->avf, NULL)) < 0)
00225 return ret;
00226
00227
00228 fail:
00229 if (ret) {
00230 av_free(hls->basename);
00231 if (hls->avf)
00232 avformat_free_context(hls->avf);
00233 }
00234 return ret;
00235 }
00236
00237 static int hls_write_packet(AVFormatContext *s, AVPacket *pkt)
00238 {
00239 HLSContext *hls = s->priv_data;
00240 AVFormatContext *oc = hls->avf;
00241 AVStream *st = s->streams[pkt->stream_index];
00242 int64_t end_pts = hls->recording_time * hls->number;
00243 int ret;
00244
00245 if (hls->start_pts == AV_NOPTS_VALUE) {
00246 hls->start_pts = pkt->pts;
00247 hls->end_pts = pkt->pts;
00248 }
00249 end_pts += hls->start_pts;
00250
00251 if ((hls->has_video && st->codec->codec_type == AVMEDIA_TYPE_VIDEO) &&
00252 av_compare_ts(pkt->pts, st->time_base, end_pts, AV_TIME_BASE_Q) >= 0 &&
00253 pkt->flags & AV_PKT_FLAG_KEY) {
00254
00255 append_entry(hls, av_rescale(pkt->pts - hls->end_pts,
00256 st->time_base.num,
00257 st->time_base.den));
00258 hls->end_pts = pkt->pts;
00259
00260 av_write_frame(oc, NULL);
00261 avio_close(oc->pb);
00262
00263 ret = hls_start(s);
00264
00265 if (ret)
00266 return ret;
00267
00268 oc = hls->avf;
00269
00270 if ((ret = hls_window(s, 0)) < 0)
00271 return ret;
00272 }
00273
00274 ret = ff_write_chained(oc, pkt->stream_index, pkt, s);
00275
00276 return ret;
00277 }
00278
00279 static int hls_write_trailer(struct AVFormatContext *s)
00280 {
00281 HLSContext *hls = s->priv_data;
00282 AVFormatContext *oc = hls->avf;
00283
00284 av_write_trailer(oc);
00285 avio_closep(&oc->pb);
00286 avformat_free_context(oc);
00287 av_free(hls->basename);
00288 hls_window(s, 1);
00289
00290 free_entries(hls);
00291 avio_close(hls->pb);
00292 return 0;
00293 }
00294
00295 #define OFFSET(x) offsetof(HLSContext, x)
00296 #define E AV_OPT_FLAG_ENCODING_PARAM
00297 static const AVOption options[] = {
00298 { "start_number", "first number in the sequence", OFFSET(number), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
00299 {"hls_time", "segment length in seconds", OFFSET(time), AV_OPT_TYPE_FLOAT, {.dbl = 2}, 0, FLT_MAX, E},
00300 {"hls_list_size", "maximum number of playlist entries", OFFSET(size), AV_OPT_TYPE_INT, {.i64 = 5}, 0, INT_MAX, E},
00301 {"hls_wrap", "number after which the index wraps", OFFSET(wrap), AV_OPT_TYPE_INT, {.i64 = 0}, 0, INT_MAX, E},
00302 { NULL },
00303 };
00304
00305 static const AVClass hls_class = {
00306 .class_name = "hls muxer",
00307 .item_name = av_default_item_name,
00308 .option = options,
00309 .version = LIBAVUTIL_VERSION_INT,
00310 };
00311
00312
00313 AVOutputFormat ff_hls_muxer = {
00314 .name = "hls",
00315 .long_name = NULL_IF_CONFIG_SMALL("Apple HTTP Live Streaming"),
00316 .extensions = "m3u8",
00317 .priv_data_size = sizeof(HLSContext),
00318 .audio_codec = AV_CODEC_ID_MP2,
00319 .video_codec = AV_CODEC_ID_MPEG2VIDEO,
00320 .flags = AVFMT_NOFILE | AVFMT_ALLOW_FLUSH,
00321 .write_header = hls_write_header,
00322 .write_packet = hls_write_packet,
00323 .write_trailer = hls_write_trailer,
00324 .priv_class = &hls_class,
00325 };