Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Copyright (c) 2013 Paul B Mahol | |
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 | * audio to video multimedia vectorscope filter | |
24 | */ | |
25 | ||
26 | #include "libavutil/avassert.h" | |
27 | #include "libavutil/channel_layout.h" | |
28 | #include "libavutil/opt.h" | |
29 | #include "libavutil/parseutils.h" | |
30 | #include "avfilter.h" | |
31 | #include "formats.h" | |
32 | #include "audio.h" | |
33 | #include "video.h" | |
34 | #include "internal.h" | |
35 | ||
36 | enum VectorScopeMode { | |
37 | LISSAJOUS, | |
38 | LISSAJOUS_XY, | |
39 | MODE_NB, | |
40 | }; | |
41 | ||
42 | typedef struct AudioVectorScopeContext { | |
43 | const AVClass *class; | |
44 | AVFrame *outpicref; | |
45 | int w, h; | |
46 | int hw, hh; | |
47 | enum VectorScopeMode mode; | |
48 | int contrast[3]; | |
49 | int fade[3]; | |
50 | double zoom; | |
51 | AVRational frame_rate; | |
52 | } AudioVectorScopeContext; | |
53 | ||
54 | #define OFFSET(x) offsetof(AudioVectorScopeContext, x) | |
55 | #define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM | |
56 | ||
57 | static const AVOption avectorscope_options[] = { | |
58 | { "mode", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" }, | |
59 | { "m", "set mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64=LISSAJOUS}, 0, MODE_NB-1, FLAGS, "mode" }, | |
60 | { "lissajous", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS}, 0, 0, FLAGS, "mode" }, | |
61 | { "lissajous_xy", "", 0, AV_OPT_TYPE_CONST, {.i64=LISSAJOUS_XY}, 0, 0, FLAGS, "mode" }, | |
62 | { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, 0, FLAGS }, | |
63 | { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, 0, FLAGS }, | |
64 | { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS }, | |
65 | { "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="400x400"}, 0, 0, FLAGS }, | |
66 | { "rc", "set red contrast", OFFSET(contrast[0]), AV_OPT_TYPE_INT, {.i64=40}, 0, 255, FLAGS }, | |
67 | { "gc", "set green contrast", OFFSET(contrast[1]), AV_OPT_TYPE_INT, {.i64=160}, 0, 255, FLAGS }, | |
68 | { "bc", "set blue contrast", OFFSET(contrast[2]), AV_OPT_TYPE_INT, {.i64=80}, 0, 255, FLAGS }, | |
69 | { "rf", "set red fade", OFFSET(fade[0]), AV_OPT_TYPE_INT, {.i64=15}, 0, 255, FLAGS }, | |
70 | { "gf", "set green fade", OFFSET(fade[1]), AV_OPT_TYPE_INT, {.i64=10}, 0, 255, FLAGS }, | |
71 | { "bf", "set blue fade", OFFSET(fade[2]), AV_OPT_TYPE_INT, {.i64=5}, 0, 255, FLAGS }, | |
72 | { "zoom", "set zoom factor", OFFSET(zoom), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 1, 10, FLAGS }, | |
73 | { NULL } | |
74 | }; | |
75 | ||
76 | AVFILTER_DEFINE_CLASS(avectorscope); | |
77 | ||
78 | static void draw_dot(AudioVectorScopeContext *p, unsigned x, unsigned y) | |
79 | { | |
80 | const int linesize = p->outpicref->linesize[0]; | |
81 | uint8_t *dst; | |
82 | ||
83 | if (p->zoom > 1) { | |
84 | if (y >= p->h || x >= p->w) | |
85 | return; | |
86 | } else { | |
87 | y = FFMIN(y, p->h - 1); | |
88 | x = FFMIN(x, p->w - 1); | |
89 | } | |
90 | ||
91 | dst = &p->outpicref->data[0][y * linesize + x * 4]; | |
92 | dst[0] = FFMIN(dst[0] + p->contrast[0], 255); | |
93 | dst[1] = FFMIN(dst[1] + p->contrast[1], 255); | |
94 | dst[2] = FFMIN(dst[2] + p->contrast[2], 255); | |
95 | } | |
96 | ||
97 | static void fade(AudioVectorScopeContext *p) | |
98 | { | |
99 | const int linesize = p->outpicref->linesize[0]; | |
100 | int i, j; | |
101 | ||
102 | if (p->fade[0] || p->fade[1] || p->fade[2]) { | |
103 | uint8_t *d = p->outpicref->data[0]; | |
104 | for (i = 0; i < p->h; i++) { | |
105 | for (j = 0; j < p->w*4; j+=4) { | |
106 | d[j+0] = FFMAX(d[j+0] - p->fade[0], 0); | |
107 | d[j+1] = FFMAX(d[j+1] - p->fade[1], 0); | |
108 | d[j+2] = FFMAX(d[j+2] - p->fade[2], 0); | |
109 | } | |
110 | d += linesize; | |
111 | } | |
112 | } | |
113 | } | |
114 | ||
115 | static int query_formats(AVFilterContext *ctx) | |
116 | { | |
117 | AVFilterFormats *formats = NULL; | |
118 | AVFilterChannelLayouts *layout = NULL; | |
119 | AVFilterLink *inlink = ctx->inputs[0]; | |
120 | AVFilterLink *outlink = ctx->outputs[0]; | |
121 | static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_S16, AV_SAMPLE_FMT_FLT, AV_SAMPLE_FMT_NONE }; | |
122 | static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGBA, AV_PIX_FMT_NONE }; | |
123 | ||
124 | formats = ff_make_format_list(sample_fmts); | |
125 | if (!formats) | |
126 | return AVERROR(ENOMEM); | |
127 | ff_formats_ref(formats, &inlink->out_formats); | |
128 | ||
129 | ff_add_channel_layout(&layout, AV_CH_LAYOUT_STEREO); | |
130 | ff_channel_layouts_ref(layout, &inlink->out_channel_layouts); | |
131 | ||
132 | formats = ff_all_samplerates(); | |
133 | if (!formats) | |
134 | return AVERROR(ENOMEM); | |
135 | ff_formats_ref(formats, &inlink->out_samplerates); | |
136 | ||
137 | formats = ff_make_format_list(pix_fmts); | |
138 | if (!formats) | |
139 | return AVERROR(ENOMEM); | |
140 | ff_formats_ref(formats, &outlink->in_formats); | |
141 | ||
142 | return 0; | |
143 | } | |
144 | ||
145 | static int config_input(AVFilterLink *inlink) | |
146 | { | |
147 | AVFilterContext *ctx = inlink->dst; | |
148 | AudioVectorScopeContext *p = ctx->priv; | |
149 | int nb_samples; | |
150 | ||
151 | nb_samples = FFMAX(1024, ((double)inlink->sample_rate / av_q2d(p->frame_rate)) + 0.5); | |
152 | inlink->partial_buf_size = | |
153 | inlink->min_samples = | |
154 | inlink->max_samples = nb_samples; | |
155 | ||
156 | return 0; | |
157 | } | |
158 | ||
159 | static int config_output(AVFilterLink *outlink) | |
160 | { | |
161 | AudioVectorScopeContext *p = outlink->src->priv; | |
162 | ||
163 | outlink->w = p->w; | |
164 | outlink->h = p->h; | |
165 | outlink->sample_aspect_ratio = (AVRational){1,1}; | |
166 | outlink->frame_rate = p->frame_rate; | |
167 | ||
168 | p->hw = p->w / 2; | |
169 | p->hh = p->h / 2; | |
170 | ||
171 | return 0; | |
172 | } | |
173 | ||
174 | static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) | |
175 | { | |
176 | AVFilterContext *ctx = inlink->dst; | |
177 | AVFilterLink *outlink = ctx->outputs[0]; | |
178 | AudioVectorScopeContext *p = ctx->priv; | |
179 | const int hw = p->hw; | |
180 | const int hh = p->hh; | |
181 | unsigned x, y; | |
182 | const double zoom = p->zoom; | |
183 | int i; | |
184 | ||
185 | if (!p->outpicref || p->outpicref->width != outlink->w || | |
186 | p->outpicref->height != outlink->h) { | |
187 | av_frame_free(&p->outpicref); | |
188 | p->outpicref = ff_get_video_buffer(outlink, outlink->w, outlink->h); | |
189 | if (!p->outpicref) { | |
190 | av_frame_free(&insamples); | |
191 | return AVERROR(ENOMEM); | |
192 | } | |
193 | ||
194 | for (i = 0; i < outlink->h; i++) | |
195 | memset(p->outpicref->data[0] + i * p->outpicref->linesize[0], 0, outlink->w * 4); | |
196 | } | |
197 | p->outpicref->pts = insamples->pts; | |
198 | ||
199 | fade(p); | |
200 | ||
201 | switch (insamples->format) { | |
202 | case AV_SAMPLE_FMT_S16: | |
203 | for (i = 0; i < insamples->nb_samples; i++) { | |
204 | int16_t *src = (int16_t *)insamples->data[0] + i * 2; | |
205 | ||
206 | if (p->mode == LISSAJOUS) { | |
207 | x = ((src[1] - src[0]) * zoom / (float)(UINT16_MAX) + 1) * hw; | |
208 | y = (1.0 - (src[0] + src[1]) * zoom / (float)UINT16_MAX) * hh; | |
209 | } else { | |
210 | x = (src[1] * zoom / (float)INT16_MAX + 1) * hw; | |
211 | y = (src[0] * zoom / (float)INT16_MAX + 1) * hh; | |
212 | } | |
213 | ||
214 | draw_dot(p, x, y); | |
215 | } | |
216 | break; | |
217 | case AV_SAMPLE_FMT_FLT: | |
218 | for (i = 0; i < insamples->nb_samples; i++) { | |
219 | float *src = (float *)insamples->data[0] + i * 2; | |
220 | ||
221 | if (p->mode == LISSAJOUS) { | |
222 | x = ((src[1] - src[0]) * zoom / 2 + 1) * hw; | |
223 | y = (1.0 - (src[0] + src[1]) * zoom / 2) * hh; | |
224 | } else { | |
225 | x = (src[1] * zoom + 1) * hw; | |
226 | y = (src[0] * zoom + 1) * hh; | |
227 | } | |
228 | ||
229 | draw_dot(p, x, y); | |
230 | } | |
231 | break; | |
232 | } | |
233 | ||
234 | av_frame_free(&insamples); | |
235 | ||
236 | return ff_filter_frame(outlink, av_frame_clone(p->outpicref)); | |
237 | } | |
238 | ||
239 | static av_cold void uninit(AVFilterContext *ctx) | |
240 | { | |
241 | AudioVectorScopeContext *p = ctx->priv; | |
242 | ||
243 | av_frame_free(&p->outpicref); | |
244 | } | |
245 | ||
246 | static const AVFilterPad audiovectorscope_inputs[] = { | |
247 | { | |
248 | .name = "default", | |
249 | .type = AVMEDIA_TYPE_AUDIO, | |
250 | .config_props = config_input, | |
251 | .filter_frame = filter_frame, | |
252 | }, | |
253 | { NULL } | |
254 | }; | |
255 | ||
256 | static const AVFilterPad audiovectorscope_outputs[] = { | |
257 | { | |
258 | .name = "default", | |
259 | .type = AVMEDIA_TYPE_VIDEO, | |
260 | .config_props = config_output, | |
261 | }, | |
262 | { NULL } | |
263 | }; | |
264 | ||
265 | AVFilter ff_avf_avectorscope = { | |
266 | .name = "avectorscope", | |
267 | .description = NULL_IF_CONFIG_SMALL("Convert input audio to vectorscope video output."), | |
268 | .uninit = uninit, | |
269 | .query_formats = query_formats, | |
270 | .priv_size = sizeof(AudioVectorScopeContext), | |
271 | .inputs = audiovectorscope_inputs, | |
272 | .outputs = audiovectorscope_outputs, | |
273 | .priv_class = &avectorscope_class, | |
274 | }; |