lavf/segment: add segment_list_type option, extend format for the segment list file
[ffmpeg.git] / libavformat / segment.c
1 /*
2  * Generic segmenter
3  * Copyright (c) 2011, Luca Barbato
4  *
5  * This file is part of Libav.
6  *
7  * Libav is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * Libav is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with Libav; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21
22 #include <float.h>
23
24 #include "avformat.h"
25 #include "internal.h"
26
27 #include "libavutil/log.h"
28 #include "libavutil/opt.h"
29 #include "libavutil/avstring.h"
30 #include "libavutil/parseutils.h"
31 #include "libavutil/mathematics.h"
32
33 typedef enum {
34     LIST_TYPE_FLAT = 0,
35     LIST_TYPE_EXT,
36     LIST_TYPE_NB,
37 } ListType;
38
39 typedef struct {
40     const AVClass *class;  /**< Class for private options. */
41     int number;
42     AVFormatContext *avf;
43     char *format;          ///< format to use for output segment files
44     char *list;            ///< filename for the segment list file
45     int   list_size;       ///< number of entries for the segment list file
46     ListType list_type;    ///< set the list type
47     AVIOContext *list_pb;  ///< list file put-byte context
48     float time;            ///< segment duration
49     int  wrap;             ///< number after which the index wraps
50     int64_t recording_time;
51     int has_video;
52     double start_time, end_time;
53 } SegmentContext;
54
55 static int segment_start(AVFormatContext *s)
56 {
57     SegmentContext *seg = s->priv_data;
58     AVFormatContext *oc = seg->avf;
59     int err = 0;
60
61     if (seg->wrap)
62         seg->number %= seg->wrap;
63
64     if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
65                               s->filename, seg->number++) < 0) {
66         av_log(oc, AV_LOG_ERROR, "Invalid segment filename template '%s'\n", s->filename);
67         return AVERROR(EINVAL);
68     }
69
70     if ((err = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
71                           &s->interrupt_callback, NULL)) < 0)
72         return err;
73
74     if (!oc->priv_data && oc->oformat->priv_data_size > 0) {
75         oc->priv_data = av_mallocz(oc->oformat->priv_data_size);
76         if (!oc->priv_data) {
77             avio_close(oc->pb);
78             return AVERROR(ENOMEM);
79         }
80         if (oc->oformat->priv_class) {
81             *(const AVClass**)oc->priv_data = oc->oformat->priv_class;
82             av_opt_set_defaults(oc->priv_data);
83         }
84     }
85
86     if ((err = oc->oformat->write_header(oc)) < 0) {
87         goto fail;
88     }
89
90     return 0;
91
92 fail:
93     av_log(oc, AV_LOG_ERROR, "Failure occurred when starting segment '%s'\n",
94            oc->filename);
95     avio_close(oc->pb);
96     av_freep(&oc->priv_data);
97
98     return err;
99 }
100
101 static int segment_end(AVFormatContext *s)
102 {
103     SegmentContext *seg = s->priv_data;
104     AVFormatContext *oc = seg->avf;
105     int ret = 0;
106
107     if (oc->oformat->write_trailer)
108         ret = oc->oformat->write_trailer(oc);
109
110     if (ret < 0)
111         av_log(s, AV_LOG_ERROR, "Failure occurred when ending segment '%s'\n",
112                oc->filename);
113
114     if (seg->list) {
115         if (seg->list_size && !(seg->number % seg->list_size)) {
116             avio_close(seg->list_pb);
117             if ((ret = avio_open2(&seg->list_pb, seg->list, AVIO_FLAG_WRITE,
118                                   &s->interrupt_callback, NULL)) < 0)
119                 goto end;
120         }
121
122         if (seg->list_type == LIST_TYPE_FLAT) {
123             avio_printf(seg->list_pb, "%s\n", oc->filename);
124         } else if (seg->list_type == LIST_TYPE_EXT) {
125             avio_printf(seg->list_pb, "%s,%f,%f\n", oc->filename, seg->start_time, seg->end_time);
126         }
127         avio_flush(seg->list_pb);
128     }
129
130 end:
131     avio_close(oc->pb);
132     if (oc->oformat->priv_class)
133         av_opt_free(oc->priv_data);
134     av_freep(&oc->priv_data);
135
136     return ret;
137 }
138
139 static int seg_write_header(AVFormatContext *s)
140 {
141     SegmentContext *seg = s->priv_data;
142     AVFormatContext *oc;
143     int ret, i;
144
145     seg->number = 0;
146     seg->recording_time = seg->time * 1000000;
147
148     oc = avformat_alloc_context();
149
150     if (!oc)
151         return AVERROR(ENOMEM);
152
153     if (seg->list)
154         if ((ret = avio_open2(&seg->list_pb, seg->list, AVIO_FLAG_WRITE,
155                               &s->interrupt_callback, NULL)) < 0)
156             goto fail;
157
158     for (i = 0; i< s->nb_streams; i++)
159         seg->has_video +=
160             (s->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO);
161
162     if (seg->has_video > 1)
163         av_log(s, AV_LOG_WARNING,
164                "More than a single video stream present, "
165                "expect issues decoding it.\n");
166
167     oc->oformat = av_guess_format(seg->format, s->filename, NULL);
168
169     if (!oc->oformat) {
170         ret = AVERROR_MUXER_NOT_FOUND;
171         goto fail;
172     }
173     if (oc->oformat->flags & AVFMT_NOFILE) {
174         av_log(s, AV_LOG_ERROR, "format %s not supported.\n",
175                oc->oformat->name);
176         ret = AVERROR(EINVAL);
177         goto fail;
178     }
179
180     seg->avf = oc;
181
182     oc->streams = s->streams;
183     oc->nb_streams = s->nb_streams;
184
185     if (av_get_frame_filename(oc->filename, sizeof(oc->filename),
186                               s->filename, seg->number++) < 0) {
187         ret = AVERROR(EINVAL);
188         goto fail;
189     }
190
191     if ((ret = avio_open2(&oc->pb, oc->filename, AVIO_FLAG_WRITE,
192                           &s->interrupt_callback, NULL)) < 0)
193         goto fail;
194
195     if ((ret = avformat_write_header(oc, NULL)) < 0) {
196         avio_close(oc->pb);
197         goto fail;
198     }
199
200 fail:
201     if (ret) {
202         if (oc) {
203             oc->streams = NULL;
204             oc->nb_streams = 0;
205             avformat_free_context(oc);
206         }
207         if (seg->list)
208             avio_close(seg->list_pb);
209     }
210     return ret;
211 }
212
213 static int seg_write_packet(AVFormatContext *s, AVPacket *pkt)
214 {
215     SegmentContext *seg = s->priv_data;
216     AVFormatContext *oc = seg->avf;
217     AVStream *st = oc->streams[pkt->stream_index];
218     int64_t end_pts = seg->recording_time * seg->number;
219     int ret;
220
221     /* if the segment has video, start a new segment *only* with a key video frame */
222     if ((st->codec->codec_type == AVMEDIA_TYPE_VIDEO || !seg->has_video) &&
223         av_compare_ts(pkt->pts, st->time_base,
224                       end_pts, AV_TIME_BASE_Q) >= 0 &&
225         pkt->flags & AV_PKT_FLAG_KEY) {
226
227         av_log(s, AV_LOG_DEBUG, "Next segment starts with packet stream:%d pts:%"PRId64" pts_time:%f\n",
228                pkt->stream_index, pkt->pts, pkt->pts * av_q2d(st->time_base));
229
230         if ((ret = segment_end(s)) < 0 || (ret = segment_start(s)) < 0)
231             goto fail;
232         seg->start_time = (double)pkt->pts * av_q2d(st->time_base);
233     } else if (pkt->pts != AV_NOPTS_VALUE) {
234         seg->end_time = FFMAX(seg->end_time,
235                               (double)(pkt->pts + pkt->duration) * av_q2d(st->time_base));
236     }
237
238     ret = oc->oformat->write_packet(oc, pkt);
239
240 fail:
241     if (ret < 0) {
242         oc->streams = NULL;
243         oc->nb_streams = 0;
244         if (seg->list)
245             avio_close(seg->list_pb);
246         avformat_free_context(oc);
247     }
248
249     return ret;
250 }
251
252 static int seg_write_trailer(struct AVFormatContext *s)
253 {
254     SegmentContext *seg = s->priv_data;
255     AVFormatContext *oc = seg->avf;
256     int ret = segment_end(s);
257     if (seg->list)
258         avio_close(seg->list_pb);
259     oc->streams = NULL;
260     oc->nb_streams = 0;
261     avformat_free_context(oc);
262     return ret;
263 }
264
265 #define OFFSET(x) offsetof(SegmentContext, x)
266 #define E AV_OPT_FLAG_ENCODING_PARAM
267 static const AVOption options[] = {
268     { "segment_format",    "set container format used for the segments", OFFSET(format),  AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
269     { "segment_time",      "set segment length in seconds",              OFFSET(time),    AV_OPT_TYPE_FLOAT,  {.dbl = 2},     0, FLT_MAX, E },
270     { "segment_list",      "set the segment list filename",              OFFSET(list),    AV_OPT_TYPE_STRING, {.str = NULL},  0, 0,       E },
271     { "segment_list_size", "set the maximum number of playlist entries", OFFSET(list_size), AV_OPT_TYPE_INT,  {.dbl = 5},     0, INT_MAX, E },
272     { "segment_list_type", "set the segment list type",                  OFFSET(list_type), AV_OPT_TYPE_INT,  {.dbl = LIST_TYPE_FLAT}, 0, LIST_TYPE_NB-1, E, "list_type" },
273     { "flat", "flat format",     0, AV_OPT_TYPE_CONST, {.dbl=LIST_TYPE_FLAT }, INT_MIN, INT_MAX, 0, "list_type" },
274     { "ext",  "extended format", 0, AV_OPT_TYPE_CONST, {.dbl=LIST_TYPE_EXT  }, INT_MIN, INT_MAX, 0, "list_type" },
275     { "segment_wrap",      "set number after which the index wraps",     OFFSET(wrap),    AV_OPT_TYPE_INT,    {.dbl = 0},     0, INT_MAX, E },
276     { NULL },
277 };
278
279 static const AVClass seg_class = {
280     .class_name = "segment muxer",
281     .item_name  = av_default_item_name,
282     .option     = options,
283     .version    = LIBAVUTIL_VERSION_INT,
284 };
285
286 AVOutputFormat ff_segment_muxer = {
287     .name           = "segment",
288     .long_name      = NULL_IF_CONFIG_SMALL("segment muxer"),
289     .priv_data_size = sizeof(SegmentContext),
290     .flags          = AVFMT_GLOBALHEADER | AVFMT_NOFILE,
291     .write_header   = seg_write_header,
292     .write_packet   = seg_write_packet,
293     .write_trailer  = seg_write_trailer,
294     .priv_class     = &seg_class,
295 };
296
297 static const AVClass sseg_class = {
298     .class_name = "stream_segment muxer",
299     .item_name  = av_default_item_name,
300     .option     = options,
301     .version    = LIBAVUTIL_VERSION_INT,
302 };
303
304 AVOutputFormat ff_stream_segment_muxer = {
305     .name           = "stream_segment,ssegment",
306     .long_name      = NULL_IF_CONFIG_SMALL("streaming segment muxer"),
307     .priv_data_size = sizeof(SegmentContext),
308     .flags          = AVFMT_NOFILE,
309     .write_header   = seg_write_header,
310     .write_packet   = seg_write_packet,
311     .write_trailer  = seg_write_trailer,
312     .priv_class     = &sseg_class,
313 };