diff options
Diffstat (limited to 'libavfilter/vsrc_cellauto.c')
-rw-r--r-- | libavfilter/vsrc_cellauto.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/libavfilter/vsrc_cellauto.c b/libavfilter/vsrc_cellauto.c new file mode 100644 index 0000000..5e2df2d --- /dev/null +++ b/libavfilter/vsrc_cellauto.c @@ -0,0 +1,337 @@ +/* + * 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/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, 0, FLAGS }, + { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str = "25"}, 0, 0, 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_INT, {.i64 = 1}, 0, 1, FLAGS }, + { "start_full", "start filling the whole video", OFFSET(start_full), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 1, FLAGS }, + { "full", "start filling the whole video", OFFSET(start_full), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS }, + { "stitch", "stitch boundaries", OFFSET(stitch), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 1, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(cellauto); + +#ifdef DEBUG +static void show_cellauto_row(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + int i; + uint8_t *row = cellauto->buf + cellauto->w * cellauto->buf_row_idx; + char *line = av_malloc(cellauto->w + 1); + if (!line) + return; + + for (i = 0; i < cellauto->w; i++) + line[i] = row[i] ? '@' : ' '; + line[i] = 0; + av_log(ctx, AV_LOG_DEBUG, "generation:%"PRId64" row:%s|\n", cellauto->generation, line); + av_free(line); +} +#endif + +static int init_pattern_from_string(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + char *p; + int i, w = 0; + + w = strlen(cellauto->pattern); + av_log(ctx, AV_LOG_DEBUG, "w:%d\n", w); + + if (cellauto->w) { + if (w > cellauto->w) { + av_log(ctx, AV_LOG_ERROR, + "The specified width is %d which cannot contain the provided string width of %d\n", + cellauto->w, w); + return AVERROR(EINVAL); + } + } else { + /* width was not specified, set it to width of the provided row */ + cellauto->w = w; + cellauto->h = (double)cellauto->w * M_PHI; + } + + cellauto->buf = av_mallocz_array(sizeof(uint8_t) * cellauto->w, cellauto->h); + if (!cellauto->buf) + return AVERROR(ENOMEM); + + /* fill buf */ + p = cellauto->pattern; + for (i = (cellauto->w - w)/2;; i++) { + av_log(ctx, AV_LOG_DEBUG, "%d %c\n", i, *p == '\n' ? 'N' : *p); + if (*p == '\n' || !*p) + break; + else + cellauto->buf[i] = !!av_isgraph(*(p++)); + } + + return 0; +} + +static int init_pattern_from_file(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + int ret; + + ret = av_file_map(cellauto->filename, + &cellauto->file_buf, &cellauto->file_bufsize, 0, ctx); + if (ret < 0) + return ret; + + /* create a string based on the read file */ + cellauto->pattern = av_malloc(cellauto->file_bufsize + 1); + if (!cellauto->pattern) + return AVERROR(ENOMEM); + memcpy(cellauto->pattern, cellauto->file_buf, cellauto->file_bufsize); + cellauto->pattern[cellauto->file_bufsize] = 0; + + return init_pattern_from_string(ctx); +} + +static av_cold int init(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + int ret; + + if (!cellauto->w && !cellauto->filename && !cellauto->pattern) + av_opt_set(cellauto, "size", "320x518", 0); + + if (cellauto->filename && cellauto->pattern) { + av_log(ctx, AV_LOG_ERROR, "Only one of the filename or pattern options can be used\n"); + return AVERROR(EINVAL); + } + + if (cellauto->filename) { + if ((ret = init_pattern_from_file(ctx)) < 0) + return ret; + } else if (cellauto->pattern) { + if ((ret = init_pattern_from_string(ctx)) < 0) + return ret; + } else { + /* fill the first row randomly */ + int i; + + cellauto->buf = av_mallocz_array(sizeof(uint8_t) * cellauto->w, cellauto->h); + if (!cellauto->buf) + return AVERROR(ENOMEM); + if (cellauto->random_seed == -1) + cellauto->random_seed = av_get_random_seed(); + + av_lfg_init(&cellauto->lfg, cellauto->random_seed); + + for (i = 0; i < cellauto->w; i++) { + double r = (double)av_lfg_get(&cellauto->lfg) / UINT32_MAX; + if (r <= cellauto->random_fill_ratio) + cellauto->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", + cellauto->w, cellauto->h, cellauto->frame_rate.num, cellauto->frame_rate.den, + cellauto->rule, cellauto->stitch, cellauto->scroll, cellauto->start_full, + cellauto->random_seed); + return 0; +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + + av_file_unmap(cellauto->file_buf, cellauto->file_bufsize); + av_freep(&cellauto->buf); + av_freep(&cellauto->pattern); +} + +static int config_props(AVFilterLink *outlink) +{ + CellAutoContext *cellauto = outlink->src->priv; + + outlink->w = cellauto->w; + outlink->h = cellauto->h; + outlink->time_base = av_inv_q(cellauto->frame_rate); + + return 0; +} + +static void evolve(AVFilterContext *ctx) +{ + CellAutoContext *cellauto = ctx->priv; + int i, v, pos[3]; + uint8_t *row, *prev_row = cellauto->buf + cellauto->buf_row_idx * cellauto->w; + enum { NW, N, NE }; + + cellauto->buf_prev_row_idx = cellauto->buf_row_idx; + cellauto->buf_row_idx = cellauto->buf_row_idx == cellauto->h-1 ? 0 : cellauto->buf_row_idx+1; + row = cellauto->buf + cellauto->w * cellauto->buf_row_idx; + + for (i = 0; i < cellauto->w; i++) { + if (cellauto->stitch) { + pos[NW] = i-1 < 0 ? cellauto->w-1 : i-1; + pos[N] = i; + pos[NE] = i+1 == cellauto->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 < cellauto->w ? prev_row[i+1] : 0; + } + row[i] = !!(cellauto->rule & (1<<v)); + av_dlog(ctx, "i:%d context:%c%c%c -> cell:%d\n", i, + v&4?'@':' ', v&2?'@':' ', v&1?'@':' ', row[i]); + } + + cellauto->generation++; +} + +static void fill_picture(AVFilterContext *ctx, AVFrame *picref) +{ + CellAutoContext *cellauto = ctx->priv; + int i, j, k, row_idx = 0; + uint8_t *p0 = picref->data[0]; + + if (cellauto->scroll && cellauto->generation >= cellauto->h) + /* show on top the oldest row */ + row_idx = (cellauto->buf_row_idx + 1) % cellauto->h; + + /* fill the output picture with the whole buffer */ + for (i = 0; i < cellauto->h; i++) { + uint8_t byte = 0; + uint8_t *row = cellauto->buf + row_idx*cellauto->w; + uint8_t *p = p0; + for (k = 0, j = 0; j < cellauto->w; j++) { + byte |= row[j]<<(7-k++); + if (k==8 || j == cellauto->w-1) { + k = 0; + *p++ = byte; + byte = 0; + } + } + row_idx = (row_idx + 1) % cellauto->h; + p0 += picref->linesize[0]; + } +} + +static int request_frame(AVFilterLink *outlink) +{ + CellAutoContext *cellauto = outlink->src->priv; + AVFrame *picref = ff_get_video_buffer(outlink, cellauto->w, cellauto->h); + if (!picref) + return AVERROR(ENOMEM); + picref->sample_aspect_ratio = (AVRational) {1, 1}; + if (cellauto->generation == 0 && cellauto->start_full) { + int i; + for (i = 0; i < cellauto->h-1; i++) + evolve(outlink->src); + } + fill_picture(outlink->src, picref); + evolve(outlink->src); + + picref->pts = cellauto->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 }; + ff_set_common_formats(ctx, ff_make_format_list(pix_fmts)); + return 0; +} + +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, +}; |