diff options
Diffstat (limited to 'libavfilter/vsrc_cellauto.c')
-rw-r--r-- | libavfilter/vsrc_cellauto.c | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/libavfilter/vsrc_cellauto.c b/libavfilter/vsrc_cellauto.c new file mode 100644 index 0000000000..4961f762f4 --- /dev/null +++ b/libavfilter/vsrc_cellauto.c @@ -0,0 +1,340 @@ +/* + * Copyright (c) Stefano Sabatini 2011 + * + * 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 + */ + +/** + * @file + * cellular automaton video source, based on Stephen Wolfram "experimentus crucis" + */ + +/* #define DEBUG */ + +#include "libavutil/file.h" +#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 { + const AVClass *class; + int w, h; + char *filename; + char *rule_str; + uint8_t *file_buf; + size_t file_bufsize; + uint8_t *buf; + int buf_prev_row_idx, buf_row_idx; + uint8_t rule; + uint64_t pts; + AVRational frame_rate; + double random_fill_ratio; + uint32_t random_seed; + int stitch, scroll, start_full; + int64_t generation; ///< the generation number, starting from 0 + AVLFG lfg; + char *pattern; +} CellAutoContext; + +#define OFFSET(x) offsetof(CellAutoContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption cellauto_options[] = { + { "filename", "read initial pattern from file", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, + { "f", "read initial pattern from file", OFFSET(filename), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, + { "pattern", "set initial pattern", OFFSET(pattern), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, + { "p", "set initial pattern", OFFSET(pattern), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, + { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, + { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, INT_MAX, FLAGS }, + { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, FLAGS }, + { "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = NULL}, 0, 0, FLAGS }, + { "rule", "set rule", OFFSET(rule), AV_OPT_TYPE_INT, {.i64 = 110}, 0, 255, FLAGS }, + { "random_fill_ratio", "set fill ratio for filling initial grid randomly", OFFSET(random_fill_ratio), AV_OPT_TYPE_DOUBLE, {.dbl = 1/M_PHI}, 0, 1, FLAGS }, + { "ratio", "set fill ratio for filling initial grid randomly", OFFSET(random_fill_ratio), AV_OPT_TYPE_DOUBLE, {.dbl = 1/M_PHI}, 0, 1, FLAGS }, + { "random_seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_INT, {.i64 = -1}, -1, UINT32_MAX, FLAGS }, + { "seed", "set the seed for filling the initial grid randomly", OFFSET(random_seed), AV_OPT_TYPE_INT, {.i64 = -1}, -1, UINT32_MAX, FLAGS }, + { "scroll", "scroll pattern downward", OFFSET(scroll), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, + { "start_full", "start filling the whole video", OFFSET(start_full), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, FLAGS }, + { "full", "start filling the whole video", OFFSET(start_full), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, + { "stitch", "stitch boundaries", OFFSET(stitch), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(cellauto); + +#ifdef DEBUG +static void show_cellauto_row(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + int i; + uint8_t *row = s->buf + s->w * s->buf_row_idx; + char *line = av_malloc(s->w + 1); + if (!line) + return; + + for (i = 0; i < s->w; i++) + line[i] = row[i] ? '@' : ' '; + line[i] = 0; + av_log(ctx, AV_LOG_DEBUG, "generation:%"PRId64" row:%s|\n", s->generation, line); + av_free(line); +} +#endif + +static int init_pattern_from_string(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + char *p; + int i, w = 0; + + w = strlen(s->pattern); + av_log(ctx, AV_LOG_DEBUG, "w:%d\n", w); + + if (s->w) { + if (w > s->w) { + av_log(ctx, AV_LOG_ERROR, + "The specified width is %d which cannot contain the provided string width of %d\n", + s->w, w); + return AVERROR(EINVAL); + } + } else { + /* width was not specified, set it to width of the provided row */ + s->w = w; + s->h = (double)s->w * M_PHI; + } + + s->buf = av_mallocz_array(sizeof(uint8_t) * s->w, s->h); + if (!s->buf) + return AVERROR(ENOMEM); + + /* fill buf */ + p = s->pattern; + for (i = (s->w - w)/2;; i++) { + av_log(ctx, AV_LOG_DEBUG, "%d %c\n", i, *p == '\n' ? 'N' : *p); + if (*p == '\n' || !*p) + break; + else + s->buf[i] = !!av_isgraph(*(p++)); + } + + return 0; +} + +static int init_pattern_from_file(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + int ret; + + ret = av_file_map(s->filename, + &s->file_buf, &s->file_bufsize, 0, ctx); + if (ret < 0) + return ret; + + /* create a string based on the read file */ + s->pattern = av_malloc(s->file_bufsize + 1); + if (!s->pattern) + return AVERROR(ENOMEM); + memcpy(s->pattern, s->file_buf, s->file_bufsize); + s->pattern[s->file_bufsize] = 0; + + return init_pattern_from_string(ctx); +} + +static av_cold int init(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + int ret; + + if (!s->w && !s->filename && !s->pattern) + av_opt_set(s, "size", "320x518", 0); + + if (s->filename && s->pattern) { + av_log(ctx, AV_LOG_ERROR, "Only one of the filename or pattern options can be used\n"); + return AVERROR(EINVAL); + } + + if (s->filename) { + if ((ret = init_pattern_from_file(ctx)) < 0) + return ret; + } else if (s->pattern) { + if ((ret = init_pattern_from_string(ctx)) < 0) + return ret; + } else { + /* fill the first row randomly */ + int i; + + s->buf = av_mallocz_array(sizeof(uint8_t) * s->w, s->h); + if (!s->buf) + return AVERROR(ENOMEM); + if (s->random_seed == -1) + s->random_seed = av_get_random_seed(); + + av_lfg_init(&s->lfg, s->random_seed); + + for (i = 0; i < s->w; i++) { + double r = (double)av_lfg_get(&s->lfg) / UINT32_MAX; + if (r <= s->random_fill_ratio) + s->buf[i] = 1; + } + } + + av_log(ctx, AV_LOG_VERBOSE, + "s:%dx%d r:%d/%d rule:%d stitch:%d scroll:%d full:%d seed:%u\n", + s->w, s->h, s->frame_rate.num, s->frame_rate.den, + s->rule, s->stitch, s->scroll, s->start_full, + s->random_seed); + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + + av_file_unmap(s->file_buf, s->file_bufsize); + av_freep(&s->buf); + av_freep(&s->pattern); +} + +static int config_props(AVFilterLink *outlink) +{ + CellAutoContext *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 evolve(AVFilterContext *ctx) +{ + CellAutoContext *s = ctx->priv; + int i, v, pos[3]; + uint8_t *row, *prev_row = s->buf + s->buf_row_idx * s->w; + enum { NW, N, NE }; + + s->buf_prev_row_idx = s->buf_row_idx; + s->buf_row_idx = s->buf_row_idx == s->h-1 ? 0 : s->buf_row_idx+1; + row = s->buf + s->w * s->buf_row_idx; + + for (i = 0; i < s->w; i++) { + if (s->stitch) { + pos[NW] = i-1 < 0 ? s->w-1 : i-1; + pos[N] = i; + pos[NE] = i+1 == s->w ? 0 : i+1; + v = prev_row[pos[NW]]<<2 | prev_row[pos[N]]<<1 | prev_row[pos[NE]]; + } else { + v = 0; + v|= i-1 >= 0 ? prev_row[i-1]<<2 : 0; + v|= prev_row[i ]<<1 ; + v|= i+1 < s->w ? prev_row[i+1] : 0; + } + row[i] = !!(s->rule & (1<<v)); + ff_dlog(ctx, "i:%d context:%c%c%c -> cell:%d\n", i, + v&4?'@':' ', v&2?'@':' ', v&1?'@':' ', row[i]); + } + + s->generation++; +} + +static void fill_picture(AVFilterContext *ctx, AVFrame *picref) +{ + CellAutoContext *s = ctx->priv; + int i, j, k, row_idx = 0; + uint8_t *p0 = picref->data[0]; + + if (s->scroll && s->generation >= s->h) + /* show on top the oldest row */ + row_idx = (s->buf_row_idx + 1) % s->h; + + /* fill the output picture with the whole buffer */ + for (i = 0; i < s->h; i++) { + uint8_t byte = 0; + uint8_t *row = s->buf + row_idx*s->w; + uint8_t *p = p0; + for (k = 0, j = 0; j < s->w; j++) { + byte |= row[j]<<(7-k++); + if (k==8 || j == s->w-1) { + k = 0; + *p++ = byte; + byte = 0; + } + } + row_idx = (row_idx + 1) % s->h; + p0 += picref->linesize[0]; + } +} + +static int request_frame(AVFilterLink *outlink) +{ + CellAutoContext *s = outlink->src->priv; + AVFrame *picref = ff_get_video_buffer(outlink, s->w, s->h); + if (!picref) + return AVERROR(ENOMEM); + picref->sample_aspect_ratio = (AVRational) {1, 1}; + if (s->generation == 0 && s->start_full) { + int i; + for (i = 0; i < s->h-1; i++) + evolve(outlink->src); + } + fill_picture(outlink->src, picref); + evolve(outlink->src); + + picref->pts = s->pts++; + +#ifdef DEBUG + show_cellauto_row(outlink->src); +#endif + return ff_filter_frame(outlink, picref); +} + +static int query_formats(AVFilterContext *ctx) +{ + static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_MONOBLACK, 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 cellauto_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .request_frame = request_frame, + .config_props = config_props, + }, + { NULL } +}; + +AVFilter ff_vsrc_cellauto = { + .name = "cellauto", + .description = NULL_IF_CONFIG_SMALL("Create pattern generated by an elementary cellular automaton."), + .priv_size = sizeof(CellAutoContext), + .priv_class = &cellauto_class, + .init = init, + .uninit = uninit, + .query_formats = query_formats, + .inputs = NULL, + .outputs = cellauto_outputs, +}; |