Browse code

libavfilter: add video movie source

See thread:
Subject: [PATCH] movie video source
Date: 2010-12-31 15:35:30 GMT

Signed-off-by: Janne Grunau <janne-ffmpeg@jannau.net>

Stefano Sabatini authored on 2011/01/29 19:22:41
Showing 5 changed files
... ...
@@ -75,6 +75,7 @@ version <next>:
75 75
 - ffmpeg -copytb option added
76 76
 - IVF muxer added
77 77
 - Wing Commander IV movies decoder added
78
+- movie source added
78 79
 
79 80
 
80 81
 version 0.6:
... ...
@@ -1093,6 +1093,61 @@ to the pad with identifier "in".
1093 1093
 "color=red@@0.2:qcif:10 [color]; [in][color] overlay [out]"
1094 1094
 @end example
1095 1095
 
1096
+@section movie
1097
+
1098
+Read a video stream from a movie container.
1099
+
1100
+It accepts the syntax: @var{movie_name}[:@var{options}] where
1101
+@var{movie_name} is the name of the resource to read (not necessarily
1102
+a file but also a device or a stream accessed through some protocol),
1103
+and @var{options} is an optional sequence of @var{key}=@var{value}
1104
+pairs, separated by ":".
1105
+
1106
+The description of the accepted options follows.
1107
+
1108
+@table @option
1109
+
1110
+@item format_name, f
1111
+Specifies the format assumed for the movie to read, and can be either
1112
+the name of a container or an input device. If not specified the
1113
+format is guessed from @var{movie_name} or by probing.
1114
+
1115
+@item seek_point, sp
1116
+Specifies the seek point in seconds, the frames will be output
1117
+starting from this seek point, the parameter is evaluated with
1118
+@code{av_strtod} so the numerical value may be suffixed by an IS
1119
+postfix. Default value is "0".
1120
+
1121
+@item stream_index, si
1122
+Specifies the index of the video stream to read. If the value is -1,
1123
+the best suited video stream will be automatically selected. Default
1124
+value is "-1".
1125
+
1126
+@end table
1127
+
1128
+This filter allows to overlay a second video on top of main input of
1129
+a filtergraph as shown in this graph:
1130
+@example
1131
+input -----------> deltapts0 --> overlay --> output
1132
+                                    ^
1133
+                                    |
1134
+movie --> scale--> deltapts1 -------+
1135
+@end example
1136
+
1137
+Some examples follow:
1138
+@example
1139
+# skip 3.2 seconds from the start of the avi file in.avi, and overlay it
1140
+# on top of the input labelled as "in".
1141
+movie=in.avi:seek_point=3.2, scale=180:-1, setpts=PTS-STARTPTS [movie];
1142
+[in] setpts=PTS-STARTPTS, [movie] overlay=16:16 [out]
1143
+
1144
+# read from a video4linux2 device, and overlay it on top of the input
1145
+# labelled as "in"
1146
+movie=/dev/video0:f=video4linux2, scale=180:-1, setpts=PTS-STARTPTS [movie];
1147
+[in] setpts=PTS-STARTPTS, [movie] overlay=16:16 [out]
1148
+
1149
+@end example
1150
+
1096 1151
 @section nullsrc
1097 1152
 
1098 1153
 Null video source, never return images. It is mainly useful as a
... ...
@@ -2,6 +2,7 @@ include $(SUBDIR)../config.mak
2 2
 
3 3
 NAME = avfilter
4 4
 FFLIBS = avcore avutil
5
+FFLIBS-$(CONFIG_MOVIE_FILTER) += avformat avcodec
5 6
 FFLIBS-$(CONFIG_SCALE_FILTER) += swscale
6 7
 FFLIBS-$(CONFIG_MP_FILTER) += avcodec
7 8
 
... ...
@@ -52,6 +53,7 @@ OBJS-$(CONFIG_YADIF_FILTER)                  += vf_yadif.o
52 52
 OBJS-$(CONFIG_BUFFER_FILTER)                 += vsrc_buffer.o
53 53
 OBJS-$(CONFIG_COLOR_FILTER)                  += vf_pad.o
54 54
 OBJS-$(CONFIG_FREI0R_SRC_FILTER)             += vf_frei0r.o
55
+OBJS-$(CONFIG_MOVIE_FILTER)                  += vsrc_movie.o
55 56
 OBJS-$(CONFIG_NULLSRC_FILTER)                += vsrc_nullsrc.o
56 57
 
57 58
 OBJS-$(CONFIG_NULLSINK_FILTER)               += vsink_nullsink.o
... ...
@@ -72,6 +72,7 @@ void avfilter_register_all(void)
72 72
     REGISTER_FILTER (BUFFER,      buffer,      vsrc);
73 73
     REGISTER_FILTER (COLOR,       color,       vsrc);
74 74
     REGISTER_FILTER (FREI0R,      frei0r_src,  vsrc);
75
+    REGISTER_FILTER (MOVIE,       movie,       vsrc);
75 76
     REGISTER_FILTER (NULLSRC,     nullsrc,     vsrc);
76 77
 
77 78
     REGISTER_FILTER (NULLSINK,    nullsink,    vsink);
78 79
new file mode 100644
... ...
@@ -0,0 +1,311 @@
0
+/*
1
+ * Copyright (c) 2010 Stefano Sabatini
2
+ * Copyright (c) 2008 Victor Paesa
3
+ *
4
+ * This file is part of FFmpeg.
5
+ *
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.
10
+ *
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.
15
+ *
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
19
+ */
20
+
21
+/**
22
+ * @file
23
+ * movie video source
24
+ *
25
+ * @todo use direct rendering (no allocation of a new frame)
26
+ * @todo support a PTS correction mechanism
27
+ * @todo support more than one output stream
28
+ */
29
+
30
+/* #define DEBUG */
31
+
32
+#include <float.h>
33
+#include "libavutil/avstring.h"
34
+#include "libavutil/opt.h"
35
+#include "libavcore/imgutils.h"
36
+#include "libavformat/avformat.h"
37
+#include "avfilter.h"
38
+
39
+typedef struct {
40
+    const AVClass *class;
41
+    int64_t seek_point;   ///< seekpoint in microseconds
42
+    double seek_point_d;
43
+    char *format_name;
44
+    char *file_name;
45
+    int stream_index;
46
+
47
+    AVFormatContext *format_ctx;
48
+    AVCodecContext *codec_ctx;
49
+    int is_done;
50
+    AVFrame *frame;   ///< video frame to store the decoded images in
51
+
52
+    int w, h;
53
+    AVFilterBufferRef *picref;
54
+} MovieContext;
55
+
56
+#define OFFSET(x) offsetof(MovieContext, x)
57
+
58
+static const AVOption movie_options[]= {
59
+{"format_name",  "set format name",         OFFSET(format_name),  FF_OPT_TYPE_STRING, 0,  CHAR_MIN, CHAR_MAX },
60
+{"f",            "set format name",         OFFSET(format_name),  FF_OPT_TYPE_STRING, 0,  CHAR_MIN, CHAR_MAX },
61
+{"stream_index", "set stream index",        OFFSET(stream_index), FF_OPT_TYPE_INT,   -1,  -1,       INT_MAX  },
62
+{"si",           "set stream index",        OFFSET(stream_index), FF_OPT_TYPE_INT,   -1,  -1,       INT_MAX  },
63
+{"seek_point",   "set seekpoint (seconds)", OFFSET(seek_point_d), FF_OPT_TYPE_DOUBLE, 0,  0,        (INT64_MAX-1) / 1000000 },
64
+{"sp",           "set seekpoint (seconds)", OFFSET(seek_point_d), FF_OPT_TYPE_DOUBLE, 0,  0,        (INT64_MAX-1) / 1000000 },
65
+{NULL},
66
+};
67
+
68
+static const char *movie_get_name(void *ctx)
69
+{
70
+    return "movie";
71
+}
72
+
73
+static const AVClass movie_class = {
74
+    "MovieContext",
75
+    movie_get_name,
76
+    movie_options
77
+};
78
+
79
+static int movie_init(AVFilterContext *ctx)
80
+{
81
+    MovieContext *movie = ctx->priv;
82
+    AVInputFormat *iformat = NULL;
83
+    AVCodec *codec;
84
+    int ret;
85
+    int64_t timestamp;
86
+
87
+    av_register_all();
88
+
89
+    // Try to find the movie format (container)
90
+    iformat = movie->format_name ? av_find_input_format(movie->format_name) : NULL;
91
+
92
+    movie->format_ctx = NULL;
93
+    if ((ret = av_open_input_file(&movie->format_ctx, movie->file_name, iformat, 0, NULL)) < 0) {
94
+        av_log(ctx, AV_LOG_ERROR,
95
+               "Failed to av_open_input_file '%s'\n", movie->file_name);
96
+        return ret;
97
+    }
98
+    if ((ret = av_find_stream_info(movie->format_ctx)) < 0)
99
+        av_log(ctx, AV_LOG_WARNING, "Failed to find stream info\n");
100
+
101
+    // if seeking requested, we execute it
102
+    if (movie->seek_point > 0) {
103
+        timestamp = movie->seek_point;
104
+        // add the stream start time, should it exist
105
+        if (movie->format_ctx->start_time != AV_NOPTS_VALUE) {
106
+            if (timestamp > INT64_MAX - movie->format_ctx->start_time) {
107
+                av_log(ctx, AV_LOG_ERROR,
108
+                       "%s: seek value overflow with start_time:%"PRId64" seek_point:%"PRId64"\n",
109
+                       movie->file_name, movie->format_ctx->start_time, movie->seek_point);
110
+                return AVERROR(EINVAL);
111
+            }
112
+            timestamp += movie->format_ctx->start_time;
113
+        }
114
+        if ((ret = av_seek_frame(movie->format_ctx, -1, timestamp, AVSEEK_FLAG_BACKWARD)) < 0) {
115
+            av_log(ctx, AV_LOG_ERROR, "%s: could not seek to position %"PRId64"\n",
116
+                   movie->file_name, timestamp);
117
+            return ret;
118
+        }
119
+    }
120
+
121
+    /* select the video stream */
122
+    if ((ret = av_find_best_stream(movie->format_ctx, AVMEDIA_TYPE_VIDEO,
123
+                                   movie->stream_index, -1, NULL, 0)) < 0) {
124
+        av_log(ctx, AV_LOG_ERROR, "No video stream with index '%d' found\n",
125
+               movie->stream_index);
126
+        return ret;
127
+    }
128
+    movie->stream_index = ret;
129
+    movie->codec_ctx = movie->format_ctx->streams[movie->stream_index]->codec;
130
+
131
+    /*
132
+     * So now we've got a pointer to the so-called codec context for our video
133
+     * stream, but we still have to find the actual codec and open it.
134
+     */
135
+    codec = avcodec_find_decoder(movie->codec_ctx->codec_id);
136
+    if (!codec) {
137
+        av_log(ctx, AV_LOG_ERROR, "Failed to find any codec\n");
138
+        return AVERROR(EINVAL);
139
+    }
140
+
141
+    if ((ret = avcodec_open(movie->codec_ctx, codec)) < 0) {
142
+        av_log(ctx, AV_LOG_ERROR, "Failed to open codec\n");
143
+        return ret;
144
+    }
145
+
146
+    if (!(movie->frame = avcodec_alloc_frame()) ) {
147
+        av_log(ctx, AV_LOG_ERROR, "Failed to alloc frame\n");
148
+        return AVERROR(ENOMEM);
149
+    }
150
+
151
+    movie->w = movie->codec_ctx->width;
152
+    movie->h = movie->codec_ctx->height;
153
+
154
+    av_log(ctx, AV_LOG_INFO, "seek_point:%lld format_name:%s file_name:%s stream_index:%d\n",
155
+           movie->seek_point, movie->format_name, movie->file_name,
156
+           movie->stream_index);
157
+
158
+    return 0;
159
+}
160
+
161
+static av_cold int init(AVFilterContext *ctx, const char *args, void *opaque)
162
+{
163
+    MovieContext *movie = ctx->priv;
164
+    int ret;
165
+    movie->class = &movie_class;
166
+    av_opt_set_defaults2(movie, 0, 0);
167
+
168
+    if (args)
169
+        movie->file_name = av_get_token(&args, ":");
170
+    if (!movie->file_name || !*movie->file_name) {
171
+        av_log(ctx, AV_LOG_ERROR, "No filename provided!\n");
172
+        return AVERROR(EINVAL);
173
+    }
174
+
175
+    if (*args++ == ':' && (ret = av_set_options_string(movie, args, "=", ":")) < 0) {
176
+        av_log(ctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
177
+        return ret;
178
+    }
179
+
180
+    movie->seek_point = movie->seek_point_d * 1000000 + 0.5;
181
+
182
+    return movie_init(ctx);
183
+}
184
+
185
+static av_cold void uninit(AVFilterContext *ctx)
186
+{
187
+    MovieContext *movie = ctx->priv;
188
+
189
+    av_free(movie->file_name);
190
+    av_free(movie->format_name);
191
+    if (movie->codec_ctx)
192
+        avcodec_close(movie->codec_ctx);
193
+    if (movie->format_ctx)
194
+        av_close_input_file(movie->format_ctx);
195
+    avfilter_unref_buffer(movie->picref);
196
+    av_freep(&movie->frame);
197
+}
198
+
199
+static int query_formats(AVFilterContext *ctx)
200
+{
201
+    MovieContext *movie = ctx->priv;
202
+    enum PixelFormat pix_fmts[] = { movie->codec_ctx->pix_fmt, PIX_FMT_NONE };
203
+
204
+    avfilter_set_common_formats(ctx, avfilter_make_format_list(pix_fmts));
205
+    return 0;
206
+}
207
+
208
+static int config_output_props(AVFilterLink *outlink)
209
+{
210
+    MovieContext *movie = outlink->src->priv;
211
+
212
+    outlink->w = movie->w;
213
+    outlink->h = movie->h;
214
+    outlink->time_base = movie->format_ctx->streams[movie->stream_index]->time_base;
215
+
216
+    return 0;
217
+}
218
+
219
+static int movie_get_frame(AVFilterLink *outlink)
220
+{
221
+    MovieContext *movie = outlink->src->priv;
222
+    AVPacket pkt;
223
+    int ret, frame_decoded;
224
+    AVStream *st = movie->format_ctx->streams[movie->stream_index];
225
+
226
+    if (movie->is_done == 1)
227
+        return 0;
228
+
229
+    while ((ret = av_read_frame(movie->format_ctx, &pkt)) >= 0) {
230
+        // Is this a packet from the video stream?
231
+        if (pkt.stream_index == movie->stream_index) {
232
+            movie->codec_ctx->reordered_opaque = pkt.pos;
233
+            avcodec_decode_video2(movie->codec_ctx, movie->frame, &frame_decoded, &pkt);
234
+
235
+            if (frame_decoded) {
236
+                /* FIXME: avoid the memcpy */
237
+                movie->picref = avfilter_get_video_buffer(outlink, AV_PERM_WRITE | AV_PERM_PRESERVE |
238
+                                                          AV_PERM_REUSE2, outlink->w, outlink->h);
239
+                av_image_copy(movie->picref->data, movie->picref->linesize,
240
+                              movie->frame->data,  movie->frame->linesize,
241
+                              movie->picref->format, outlink->w, outlink->h);
242
+
243
+                /* FIXME: use a PTS correction mechanism as that in
244
+                 * ffplay.c when some API will be available for that */
245
+                /* use pkt_dts if pkt_pts is not available */
246
+                movie->picref->pts = movie->frame->pkt_pts == AV_NOPTS_VALUE ?
247
+                    movie->frame->pkt_dts : movie->frame->pkt_pts;
248
+
249
+                movie->picref->pos                    = movie->frame->reordered_opaque;
250
+                movie->picref->video->pixel_aspect = st->sample_aspect_ratio.num ?
251
+                    st->sample_aspect_ratio : movie->codec_ctx->sample_aspect_ratio;
252
+                movie->picref->video->interlaced      = movie->frame->interlaced_frame;
253
+                movie->picref->video->top_field_first = movie->frame->top_field_first;
254
+                av_dlog(outlink->src,
255
+                        "movie_get_frame(): file:'%s' pts:%"PRId64" time:%lf pos:%"PRId64" aspect:%d/%d\n",
256
+                        movie->file_name, movie->picref->pts,
257
+                        (double)movie->picref->pts * av_q2d(st->time_base),
258
+                        movie->picref->pos,
259
+                        movie->picref->video->pixel_aspect.num, movie->picref->video->pixel_aspect.den);
260
+                // We got it. Free the packet since we are returning
261
+                av_free_packet(&pkt);
262
+
263
+                return 0;
264
+            }
265
+        }
266
+        // Free the packet that was allocated by av_read_frame
267
+        av_free_packet(&pkt);
268
+    }
269
+
270
+    // On multi-frame source we should stop the mixing process when
271
+    // the movie source does not have more frames
272
+    if (ret == AVERROR_EOF)
273
+        movie->is_done = 1;
274
+    return ret;
275
+}
276
+
277
+static int request_frame(AVFilterLink *outlink)
278
+{
279
+    AVFilterBufferRef *outpicref;
280
+    MovieContext *movie = outlink->src->priv;
281
+    int ret;
282
+
283
+    if (movie->is_done)
284
+        return AVERROR_EOF;
285
+    if ((ret = movie_get_frame(outlink)) < 0)
286
+        return ret;
287
+
288
+    outpicref = avfilter_ref_buffer(movie->picref, ~0);
289
+    avfilter_start_frame(outlink, outpicref);
290
+    avfilter_draw_slice(outlink, 0, outlink->h, 1);
291
+    avfilter_end_frame(outlink);
292
+
293
+    return 0;
294
+}
295
+
296
+AVFilter avfilter_vsrc_movie = {
297
+    .name          = "movie",
298
+    .description   = NULL_IF_CONFIG_SMALL("Read from a movie source."),
299
+    .priv_size     = sizeof(MovieContext),
300
+    .init          = init,
301
+    .uninit        = uninit,
302
+    .query_formats = query_formats,
303
+
304
+    .inputs    = (AVFilterPad[]) {{ .name = NULL }},
305
+    .outputs   = (AVFilterPad[]) {{ .name            = "default",
306
+                                    .type            = AVMEDIA_TYPE_VIDEO,
307
+                                    .request_frame   = request_frame,
308
+                                    .config_props    = config_output_props, },
309
+                                  { .name = NULL}},
310
+};