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 36enum VectorScopeMode { 37 LISSAJOUS, 38 LISSAJOUS_XY, 39 MODE_NB, 40}; 41 42typedef 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 57static 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 76AVFILTER_DEFINE_CLASS(avectorscope); 77 78static 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 97static 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 115static 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 145static 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 159static 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 174static 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 239static av_cold void uninit(AVFilterContext *ctx) 240{ 241 AudioVectorScopeContext *p = ctx->priv; 242 243 av_frame_free(&p->outpicref); 244} 245 246static 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 256static const AVFilterPad audiovectorscope_outputs[] = { 257 { 258 .name = "default", 259 .type = AVMEDIA_TYPE_VIDEO, 260 .config_props = config_output, 261 }, 262 { NULL } 263}; 264 265AVFilter 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}; 275