diff mbox

[FFmpeg-devel] avfilter: add starfield video source

Message ID 20170907192933.18523-1-onemda@gmail.com
State New
Headers show

Commit Message

Paul B Mahol Sept. 7, 2017, 7:29 p.m. UTC
Signed-off-by: Paul B Mahol <onemda@gmail.com>
---
 libavfilter/Makefile         |   1 +
 libavfilter/allfilters.c     |   1 +
 libavfilter/vsrc_starfield.c | 191 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 193 insertions(+)
 create mode 100644 libavfilter/vsrc_starfield.c

Comments

Moritz Barsnick Sept. 20, 2017, 1:21 p.m. UTC | #1
On Thu, Sep 07, 2017 at 21:29:33 +0200, Paul B Mahol wrote:
> Signed-off-by: Paul B Mahol <onemda@gmail.com>

Works fine for me.

>  libavfilter/Makefile         |   1 +
>  libavfilter/allfilters.c     |   1 +
>  libavfilter/vsrc_starfield.c | 191 +++++++++++++++++++++++++++++++++++++++++++

Missing some texi documentation.

> +static const AVOption starfield_options[] = {
> +    { "size",  "set video size",   OFFSET(w),          AV_OPT_TYPE_IMAGE_SIZE, {.str = "hd720"},   0,         0, FLAGS },
> +    { "rate",  "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"},      0,   INT_MAX, FLAGS },

When using this, I immediately missed the usual vsrc option
abbreviations "s" and "r", respectively.

> +    { "stars", "set stars number", OFFSET(nb_stars),   AV_OPT_TYPE_INT,        {.i64 = 1024},      1,     81920, FLAGS },

"number of stars"?

> +    { "speed", "set speed",        OFFSET(speed),      AV_OPT_TYPE_RATIONAL,   {.dbl = 1.01},      1,         2, FLAGS },

Why 1.01 and not 1.0? (Just wondering.)

> +    .description   = NULL_IF_CONFIG_SMALL("Create retro 3D star field."),

Nice, "retro". :-) (I don't mind at all, I just had to giggle and
wonder why a star field would be considered retro.)

Functionally: The stars seem to jump from one pixel location to the
next, and not interpolate across pixels - very unsmooth and annoying.
This is quite obvious e.g. if you create the video at 360x240, and view
it several times as large. The anti-aliasing might be a future enhancement
though.

Other "features" might be configurable fore/background colors, star
size (is that random right now?), and stuff...

Question: Why do the stars appear white(-ish), although their R, G, B
components are each randomly initialized?

Moritz
diff mbox

Patch

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 1e460ab..4cc99f0 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -352,6 +352,7 @@  OBJS-$(CONFIG_NULLSRC_FILTER)                += vsrc_testsrc.o
 OBJS-$(CONFIG_RGBTESTSRC_FILTER)             += vsrc_testsrc.o
 OBJS-$(CONFIG_SMPTEBARS_FILTER)              += vsrc_testsrc.o
 OBJS-$(CONFIG_SMPTEHDBARS_FILTER)            += vsrc_testsrc.o
+OBJS-$(CONFIG_STARFIELD_FILTER)              += vsrc_starfield.o
 OBJS-$(CONFIG_TESTSRC_FILTER)                += vsrc_testsrc.o
 OBJS-$(CONFIG_TESTSRC2_FILTER)               += vsrc_testsrc.o
 OBJS-$(CONFIG_YUVTESTSRC_FILTER)             += vsrc_testsrc.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 9a2cfea..f1f87ca 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -363,6 +363,7 @@  static void register_all(void)
     REGISTER_FILTER(RGBTESTSRC,     rgbtestsrc,     vsrc);
     REGISTER_FILTER(SMPTEBARS,      smptebars,      vsrc);
     REGISTER_FILTER(SMPTEHDBARS,    smptehdbars,    vsrc);
+    REGISTER_FILTER(STARFIELD,      starfield,      vsrc);
     REGISTER_FILTER(TESTSRC,        testsrc,        vsrc);
     REGISTER_FILTER(TESTSRC2,       testsrc2,       vsrc);
     REGISTER_FILTER(YUVTESTSRC,     yuvtestsrc,     vsrc);
diff --git a/libavfilter/vsrc_starfield.c b/libavfilter/vsrc_starfield.c
new file mode 100644
index 0000000..96b8667
--- /dev/null
+++ b/libavfilter/vsrc_starfield.c
@@ -0,0 +1,191 @@ 
+/*
+ * Copyright (c) 2017 Paul B Mahol
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/internal.h"
+#include "libavutil/lfg.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "libavutil/random_seed.h"
+#include "libavutil/avstring.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "formats.h"
+#include "video.h"
+
+typedef struct Star {
+    int x, y;
+    AVRational z;
+    unsigned r, g, b;
+} Star;
+
+typedef struct StarFieldContext {
+    const AVClass *class;
+
+    int w, h;
+    uint64_t pts;
+    AVRational frame_rate;
+    AVRational speed;
+    int nb_stars;
+    int64_t seed;
+
+    AVLFG lfg;
+    Star *stars;
+} StarFieldContext;
+
+#define OFFSET(x) offsetof(StarFieldContext, x)
+#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM
+
+static const AVOption starfield_options[] = {
+    { "size",  "set video size",   OFFSET(w),          AV_OPT_TYPE_IMAGE_SIZE, {.str = "hd720"},   0,         0, FLAGS },
+    { "rate",  "set video rate",   OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"},      0,   INT_MAX, FLAGS },
+    { "stars", "set stars number", OFFSET(nb_stars),   AV_OPT_TYPE_INT,        {.i64 = 1024},      1,     81920, FLAGS },
+    { "seed",  "set seed",         OFFSET(seed),       AV_OPT_TYPE_INT64,      {.i64 = -1},       -1, INT64_MAX, FLAGS },
+    { "speed", "set speed",        OFFSET(speed),      AV_OPT_TYPE_RATIONAL,   {.dbl = 1.01},      1,         2, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(starfield);
+
+static void init_star(StarFieldContext *s, Star *star, int depth)
+{
+    star->x = av_lfg_get(&s->lfg) - INT_MAX;
+    star->y = av_lfg_get(&s->lfg) - INT_MAX;
+    star->z.num = star->z.den = 1;
+    star->r = av_lfg_get(&s->lfg) >> av_log2(depth / 32);
+    star->g = av_lfg_get(&s->lfg) >> av_log2(depth / 32);
+    star->b = av_lfg_get(&s->lfg) >> av_log2(depth / 32);
+}
+
+static av_cold int init(AVFilterContext *ctx)
+{
+    StarFieldContext *s = ctx->priv;
+    int i;
+
+    s->stars = av_calloc(s->nb_stars, sizeof(*s->stars));
+    if (!s->stars)
+        return AVERROR(ENOMEM);
+
+    if (s->seed == -1)
+        s->seed = av_get_random_seed();
+    av_lfg_init(&s->lfg, s->seed);
+
+    for (i = 0; i < s->nb_stars; i++) {
+        init_star(s, &s->stars[i], i+1);
+    }
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    StarFieldContext *s = ctx->priv;
+
+    av_freep(&s->stars);
+}
+
+static int config_props(AVFilterLink *outlink)
+{
+    StarFieldContext *s = outlink->src->priv;
+
+    outlink->w = s->w;
+    outlink->h = s->h;
+    outlink->time_base = av_inv_q(s->frame_rate);
+
+    return 0;
+}
+
+static void fill_picture(AVFilterContext *ctx, AVFrame *out)
+{
+    StarFieldContext *s = ctx->priv;
+    const int divx = UINT_MAX / out->width;
+    const int divy = UINT_MAX / out->height;
+    const int hw = out->width >> 1;
+    const int hh = out->height >> 1;
+    const int linesize = out->linesize[0];
+    int i;
+
+    for (i = 0; i < out->height; i++) {
+        memset(out->data[0] + out->linesize[0] * i, 0, out->width * 4);
+    }
+
+    for (i = 0; i < s->nb_stars; i++) {
+        Star *star = &s->stars[i];
+        int x, y;
+
+        x = av_rescale(star->x / divx, star->z.num, star->z.den) + hw;
+        y = av_rescale(star->y / divy, star->z.num, star->z.den) + hh;
+        if (x < 0 || x >= out->width ||
+            y < 0 || y >= out->height) {
+            init_star(s, &s->stars[i], i+1);
+            continue;
+        }
+
+        out->data[0][y * linesize + x * 4 + 0] = av_clip_uint8((av_rescale(star->r, star->z.num, star->z.den) >> 24));
+        out->data[0][y * linesize + x * 4 + 1] = av_clip_uint8((av_rescale(star->g, star->z.num, star->z.den) >> 24));
+        out->data[0][y * linesize + x * 4 + 2] = av_clip_uint8((av_rescale(star->b, star->z.num, star->z.den) >> 24));
+
+        star->z = av_mul_q(star->z, s->speed);
+    }
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    StarFieldContext *s = outlink->src->priv;
+    AVFrame *out = ff_get_video_buffer(outlink, s->w, s->h);
+    if (!out)
+        return AVERROR(ENOMEM);
+    out->sample_aspect_ratio = (AVRational) {1, 1};
+    fill_picture(outlink->src, out);
+
+    out->pts = s->pts++;
+
+    return ff_filter_frame(outlink, out);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB0, AV_PIX_FMT_NONE };
+    AVFilterFormats *fmts_list = ff_make_format_list(pix_fmts);
+    if (!fmts_list)
+        return AVERROR(ENOMEM);
+    return ff_set_common_formats(ctx, fmts_list);
+}
+
+static const AVFilterPad starfield_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .request_frame = request_frame,
+        .config_props  = config_props,
+    },
+    { NULL }
+};
+
+AVFilter ff_vsrc_starfield = {
+    .name          = "starfield",
+    .description   = NULL_IF_CONFIG_SMALL("Create retro 3D star field."),
+    .priv_size     = sizeof(StarFieldContext),
+    .priv_class    = &starfield_class,
+    .init          = init,
+    .uninit        = uninit,
+    .query_formats = query_formats,
+    .inputs        = NULL,
+    .outputs       = starfield_outputs,
+};