[FFmpeg-devel] [RFC][WIP][PATCH] avfilter: add luascript filter

wm4 nfxjfg at googlemail.com
Wed Feb 3 14:50:22 CET 2016


On Tue, 2 Feb 2016 23:25:27 +0100
Paul B Mahol <onemda at gmail.com> wrote:

> From 5caafe2f74d6eb5563b1103193cc8d136edcfd0f Mon Sep 17 00:00:00 2001
> From: Paul B Mahol <onemda at gmail.com>
> Date: Tue, 2 Feb 2016 10:09:50 +0100
> Subject: [RFC][WIP][PATCH] avfilter: add luascript filter
> 
> Signed-off-by: Paul B Mahol <onemda at gmail.com>
> ---
>  configure                   |   4 +
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/src_luascript.c | 428 ++++++++++++++++++++++++++++++++++++++++++++
>  4 files changed, 434 insertions(+)
>  create mode 100644 libavfilter/src_luascript.c
> 
> diff --git a/configure b/configure
> index c415d5a..289c62e 100755
> --- a/configure
> +++ b/configure
> @@ -275,6 +275,7 @@ External library support:
>    --disable-lzma           disable lzma [autodetect]
>    --enable-decklink        enable Blackmagic DeckLink I/O support [no]
>    --enable-mmal            enable decoding via MMAL [no]
> +  --enable-lua51           enable lua5.1, needed for luascript filter [no]
>    --enable-netcdf          enable NetCDF, needed for sofalizer filter [no]
>    --enable-nvenc           enable NVIDIA NVENC support [no]
>    --enable-openal          enable OpenAL 1.1 capture support [no]
> @@ -1494,6 +1495,7 @@ EXTERNAL_LIBRARY_LIST="
>      libzimg
>      libzmq
>      libzvbi
> +    lua51
>      lzma
>      mmal
>      netcdf
> @@ -2867,6 +2869,7 @@ hqdn3d_filter_deps="gpl"
>  interlace_filter_deps="gpl"
>  kerndeint_filter_deps="gpl"
>  ladspa_filter_deps="ladspa dlopen"
> +luascript_filter_deps="lua51"
>  mcdeint_filter_deps="avcodec gpl"
>  movie_filter_deps="avcodec avformat"
>  mpdecimate_filter_deps="gpl"
> @@ -5577,6 +5580,7 @@ enabled mmal &&
>      (check_code cc interface/mmal/mmal.h "MMAL_PARAMETER_VIDEO_MAX_NUM_CALLBACKS" ||
>       die "ERROR: mmal firmware headers too old")
>  
> +enabled lua51             && require_pkg_config lua5.1 lua.h lua_newstate

Lua and pkg-config is a sad state of affair. Almost every Linux distro
and BSD variant use a different name for pkg-config. (Upstream Lua
provides no .pc file.)

>  enabled netcdf            && require_pkg_config netcdf netcdf.h nc_inq_libvers
>  enabled nvenc             && { check_header nvEncodeAPI.h || die "ERROR: nvEncodeAPI.h not found."; } &&
>                               { check_cpp_condition nvEncodeAPI.h "NVENCAPI_MAJOR_VERSION >= 5" ||
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index e76d18e..bd9fce8 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -298,6 +298,7 @@ OBJS-$(CONFIG_SPECTRUMSYNTH_FILTER)          += vaf_spectrumsynth.o window_func.
>  
>  # multimedia sources
>  OBJS-$(CONFIG_AMOVIE_FILTER)                 += src_movie.o
> +OBJS-$(CONFIG_LUASCRIPT_FILTER)              += src_luascript.o
>  OBJS-$(CONFIG_MOVIE_FILTER)                  += src_movie.o
>  
>  # Windows resource file
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 27d54bc..00eb8a4 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -318,6 +318,7 @@ void avfilter_register_all(void)
>  
>      /* multimedia sources */
>      REGISTER_FILTER(AMOVIE,         amovie,         avsrc);
> +    REGISTER_FILTER(LUASCRIPT,      luascript,      avsrc);
>      REGISTER_FILTER(MOVIE,          movie,          avsrc);
>  
>      /* those filters are part of public or internal API => registered
> diff --git a/libavfilter/src_luascript.c b/libavfilter/src_luascript.c
> new file mode 100644
> index 0000000..949256e
> --- /dev/null
> +++ b/libavfilter/src_luascript.c
> @@ -0,0 +1,428 @@
> +/*
> + * Copyright (c) 2016 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 <float.h>
> +#include <stdint.h>
> +
> +#include <lua5.1/lua.h>
> +#include <lua5.1/lualib.h>
> +#include <lua5.1/lauxlib.h>

This include path is not portable.

> +
> +#include "libavutil/attributes.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/avassert.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/imgutils.h"
> +#include "libavutil/internal.h"
> +#include "libavutil/timestamp.h"
> +#include "libavformat/avformat.h"
> +#include "audio.h"
> +#include "avfilter.h"
> +#include "buffersink.h"
> +#include "formats.h"
> +#include "internal.h"
> +#include "video.h"
> +
> +typedef struct FiltersContext {
> +    AVFilterContext *f;
> +    char *options;
> +    char *in;
> +    char *out;
> +} FiltersContext;
> +
> +typedef struct LuaScriptContext {
> +    const AVClass *class;
> +    char *script;
> +    int width;
> +    int height;
> +
> +    AVFilterGraph *graph;
> +    FiltersContext fctx[1024];
> +    AVFilterContext *sink[32];
> +    int nbf;
> +
> +    lua_State *L;
> +} LuaScriptContext;
> +
> +#define OFFSET(x) offsetof(LuaScriptContext, x)
> +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_VIDEO_PARAM
> +
> +static const AVOption luascript_options[]= {
> +    { "script", NULL, OFFSET(script), AV_OPT_TYPE_STRING, .flags = FLAGS },
> +    { NULL },
> +};
> +
> +static int luascript_push_frame(AVFilterContext *ctx, int out_id)
> +{
> +    LuaScriptContext *s  = ctx->priv;
> +    AVFrame *frame = av_frame_alloc();
> +    int ret;
> +
> +    if (!frame)
> +        return AVERROR(ENOMEM);
> +
> +    ret = av_buffersink_get_frame(s->sink[out_id], frame);
> +    if (ret < 0)
> +        return ret;
> +
> +    return ff_filter_frame(ctx->outputs[out_id], frame);
> +}
> +
> +static int luascript_request_frame(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    unsigned out_id = FF_OUTLINK_IDX(outlink);
> +
> +    return luascript_push_frame(ctx, out_id);
> +}
> +
> +static int luascript_config_output_props(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    unsigned out_id = FF_OUTLINK_IDX(outlink);
> +    LuaScriptContext *s  = ctx->priv;
> +    AVFilterLink *sinklink = s->sink[out_id]->inputs[0];
> +
> +    outlink->time_base = sinklink->time_base;
> +    switch (outlink->type) {
> +    case AVMEDIA_TYPE_AUDIO:
> +        outlink->channel_layout = sinklink->channel_layout;
> +        outlink->channels       = sinklink->channels;
> +        outlink->sample_rate    = sinklink->sample_rate;
> +        break;
> +    case AVMEDIA_TYPE_VIDEO:
> +        outlink->w = sinklink->w;
> +        outlink->h = sinklink->h;
> +        outlink->sample_aspect_ratio = sinklink->sample_aspect_ratio;
> +        outlink->frame_rate          = sinklink->frame_rate;
> +        break;
> +    }
> +
> +    return 0;
> +}
> +
> +static AVFilterContext *get_ctx(lua_State *L)
> +{
> +    lua_getfield(L, LUA_REGISTRYINDEX, "ctx");
> +    AVFilterContext *ctx = lua_touserdata(L, -1);
> +    lua_pop(L, 1);
> +    av_assert0(ctx);
> +    return ctx;
> +}
> +
> +static int error_handler(lua_State *L)
> +{
> +    AVFilterContext *ctx = get_ctx(L);
> +
> +    if (luaL_loadstring(L, "return debug.traceback('', 3)") == 0) {
> +        lua_call(L, 0, 1);
> +        const char *tr = lua_tostring(L, -1);
> +        av_log(ctx, AV_LOG_ERROR, "%s\n", tr ? tr : "(unknown)");
> +    }
> +    lua_pop(L, 1);
> +
> +    return 1;
> +}
> +
> +static int load_script(lua_State *L)
> +{
> +    AVFilterContext *ctx = get_ctx(L);
> +    LuaScriptContext *s = ctx->priv;
> +    int ret;
> +
> +    ret = luaL_loadfile(L, s->script);
> +    if (ret) {
> +        lua_error(L);
> +    }
> +    lua_call(L, 0, 0);
> +
> +    return ret;
> +}
> +
> +static int script_register_filter(lua_State *L)
> +{
> +    AVFilterContext *ctx = get_ctx(L);
> +    const char *name = luaL_checkstring(L, 1);
> +    AVFilter *filter = avfilter_get_by_name(name);
> +
> +    if (filter) {
> +        avfilter_register(filter);
> +        return 0;
> +    } else {
> +        lua_pushfstring(L, "No such filter %s", name);
> +        return 1;
> +    }
> +}
> +
> +static int script_filter(lua_State *L)
> +{
> +    AVFilterContext *ctx = get_ctx(L);
> +    LuaScriptContext *s = ctx->priv;
> +    const char *name = luaL_checkstring(L, 1);
> +    const char *options = luaL_checkstring(L, 2);
> +    int ret;
> +
> +    AVFilter *filter = avfilter_get_by_name(name);
> +    if (!filter)
> +        return AVERROR(EINVAL);
> +
> +    s->fctx[s->nbf].f = avfilter_graph_alloc_filter(s->graph, filter, name);
> +    if (!s->fctx[s->nbf].f)
> +        return AVERROR(ENOMEM);
> +    ret = avfilter_init_str(s->fctx[s->nbf].f, options);
> +    if (ret)
> +        return ret;
> +    s->fctx[s->nbf].options = options;
> +    s->nbf++;
> +    return 0;
> +}
> +
> +static int script_log(lua_State *L)
> +{
> +    AVFilterContext *ctx = get_ctx(L);
> +    int msgl = luaL_checkinteger(L, 1);
> +    int last = lua_gettop(L);
> +
> +    lua_getglobal(L, "tostring"); // args... tostring
> +    for (int i = 2; i <= last; i++) {
> +        lua_pushvalue(L, -1); // args... tostring tostring
> +        lua_pushvalue(L, i); // args... tostring tostring args[i]
> +        lua_call(L, 1, 1); // args... tostring str
> +        const char *s = lua_tostring(L, -1);
> +        if (s == NULL)
> +            return luaL_error(L, "Invalid argument");
> +        av_log(ctx, msgl, "%s%s", s, i > 0 ? " " : "");
> +        lua_pop(L, 1);  // args... tostring
> +    }
> +    av_log(ctx, msgl, "\n");
> +
> +    return 0;
> +}
> +
> +#define FN_ENTRY(name) {#name, script_ ## name}
> +struct fn_entry {
> +    const char *name;
> +    int (*fn)(lua_State *L);
> +};
> +
> +static const struct fn_entry main_fns[] = {
> +    FN_ENTRY(log),
> +    FN_ENTRY(filter),
> +    FN_ENTRY(register_filter),
> +    {0}
> +};
> +
> +static void push_module_table(lua_State *L, const char *module)
> +{
> +    av_assert0(L);
> +
> +    lua_getglobal(L, "package"); // package
> +    lua_getfield(L, -1, "loaded"); // package loaded
> +    lua_remove(L, -2); // loaded
> +    lua_getfield(L, -1, module); // loaded module
> +    if (lua_isnil(L, -1)) {
> +        lua_pop(L, 1); // loaded
> +        lua_newtable(L); // loaded module
> +        lua_pushvalue(L, -1); // loaded module module
> +        lua_setfield(L, -3, module); // loaded module
> +    }
> +    lua_remove(L, -2); // module
> +}
> +
> +static void register_package_fns(lua_State *L, const char *module,
> +                                 const struct fn_entry *e)
> +{
> +    push_module_table(L, module);
> +    for (int n = 0; e[n].name; n++) {
> +        lua_pushcclosure(L, e[n].fn, 0);
> +        lua_setfield(L, -2, e[n].name);
> +    }
> +    lua_pop(L, 1);
> +}
> +
> +static void add_functions(AVFilterContext *ctx)
> +{
> +    LuaScriptContext *s = ctx->priv;
> +    lua_State *L = s->L;
> +
> +    register_package_fns(L, "lavfi", main_fns);
> +    push_module_table(L, "lavfi");
> +
> +    lua_pop(L, 1);
> +}
> +
> +static int run_lua(lua_State *L)
> +{
> +    AVFilterContext *ctx = lua_touserdata(L, -1);
> +    LuaScriptContext *s = ctx->priv;
> +    int ret = 0;
> +
> +    lua_pop(L, 1);
> +
> +    luaL_openlibs(L);
> +
> +    lua_pushlightuserdata(L, ctx);
> +    lua_setfield(L, LUA_REGISTRYINDEX, "ctx");
> +
> +    add_functions(ctx);
> +
> +    push_module_table(L, "lavfi");
> +
> +    lua_pushvalue(L, -1);
> +    lua_setglobal(L, "lavfi");
> +
> +    lua_pushstring(L, s->script);
> +    lua_setfield(L, -2, "script_name");
> +
> +    lua_pushcfunction(L, error_handler);
> +    lua_pushcfunction(L, load_script);
> +    if (lua_pcall(L, 0, 0, -2)) {
> +        const char *e = lua_tostring(L, -1);
> +        av_log(ctx, AV_LOG_ERROR, "%s\n", e ? e : "(unknown)");
> +        ret = AVERROR(EINVAL);
> +    }
> +
> +    return ret;
> +}
> +
> +static av_cold int luascript_common_init(AVFilterContext *ctx)
> +{
> +    LuaScriptContext *s = ctx->priv;
> +    lua_State *L = s->L;
> +    int ret = 0;
> +    int i;
> +
> +    if (!s->script) {
> +        av_log(ctx, AV_LOG_ERROR, "No filename provided!\n");
> +        return AVERROR(EINVAL);
> +    }
> +
> +    s->L = L = luaL_newstate();
> +    if (!L)
> +        return AVERROR(ENOMEM);
> +
> +    s->graph = avfilter_graph_alloc();
> +    if (!s->graph)
> +        return AVERROR(ENOMEM);
> +
> +    if (lua_cpcall(L, run_lua, ctx)) {
> +        const char *err = "unknown error";
> +        if (lua_type(L, -1) == LUA_TSTRING)
> +            err = lua_tostring(L, -1);
> +        av_log(ctx, AV_LOG_ERROR, "%s\n", err);
> +        ret = AVERROR(EINVAL);
> +    }
> +
> +    for (i = 0; i < s->fctx[0].f->nb_outputs; i++) {
> +        AVFilterPad pad = { 0 };
> +        AVFilter *sink;
> +
> +        pad.type          = avfilter_pad_get_type(s->fctx[0].f->output_pads, i);
> +        pad.name          = av_strdup(avfilter_pad_get_name(s->fctx[0].f->output_pads, i));
> +        if (!pad.name)
> +            return AVERROR(ENOMEM);
> +        pad.config_props  = luascript_config_output_props;
> +        pad.request_frame = luascript_request_frame;
> +        ff_insert_outpad(ctx, i, &pad);
> +
> +        if (pad.type == AVMEDIA_TYPE_VIDEO)
> +            sink = avfilter_get_by_name("buffersink");
> +        else
> +            sink = avfilter_get_by_name("abuffersink");
> +        if (!sink) {
> +            return AVERROR(EINVAL);
> +        }
> +
> +        s->sink[i] = avfilter_graph_alloc_filter(s->graph, sink, "sink");
> +        if (!s->sink[i]) {
> +            return AVERROR(ENOMEM);
> +        }
> +
> +        ret = avfilter_init_str(s->sink[i], NULL);
> +        if (ret < 0) {
> +            return ret;
> +        }
> +
> +        avfilter_link(s->fctx[0].f, i, s->sink[i], i);
> +    }
> +
> +    ret = avfilter_graph_config(s->graph, NULL);
> +    if (ret < 0) {
> +        av_log(ctx, AV_LOG_ERROR, "Error configuring the filter graph\n");
> +        return ret;
> +    }
> +
> +    return ret;
> +}
> +
> +static av_cold void luascript_uninit(AVFilterContext *ctx)
> +{
> +    LuaScriptContext *s = ctx->priv;
> +
> +    lua_close(s->L);
> +    avfilter_graph_free(&s->graph);
> +}
> +
> +static int luascript_query_formats(AVFilterContext *ctx)
> +{
> +    LuaScriptContext *s = ctx->priv;
> +    int64_t list64[] = { 0, -1 };
> +    int list[] = { 0, -1 };
> +    int i, ret;
> +
> +    for (i = 0; i < ctx->nb_outputs; i++) {
> +        AVFilterLink *outlink = ctx->outputs[i];
> +        AVFilterLink *sinklink = s->sink[i]->inputs[0];
> +
> +        list[0] = sinklink->format;
> +        if ((ret = ff_formats_ref(ff_make_format_list(list),
> +                                  &outlink->in_formats)) < 0)
> +            return ret;
> +        switch (outlink->type) {
> +        case AVMEDIA_TYPE_AUDIO:
> +            list[0] = sinklink->sample_rate;
> +            if ((ret = ff_formats_ref(ff_make_format_list(list),
> +                                      &outlink->in_samplerates)) < 0)
> +                return ret;
> +            list64[0] = sinklink->channel_layout;
> +            if ((ret = ff_channel_layouts_ref(avfilter_make_format64_list(list64),
> +                                              &outlink->in_channel_layouts)) < 0)
> +                return ret;
> +            break;
> +        }
> +    }
> +
> +    return 0;
> +}
> +
> +AVFILTER_DEFINE_CLASS(luascript);
> +
> +AVFilter ff_avsrc_luascript = {
> +    .name          = "luascript",
> +    .description   = NULL_IF_CONFIG_SMALL("Lavfi lua script."),
> +    .priv_size     = sizeof(LuaScriptContext),
> +    .priv_class    = &luascript_class,
> +    .init          = luascript_common_init,
> +    .uninit        = luascript_uninit,
> +    .query_formats = luascript_query_formats,
> +    .inputs        = NULL,
> +    .outputs       = NULL,
> +    .flags         = AVFILTER_FLAG_DYNAMIC_OUTPUTS,
> +};

I'm not sure if I understand the implications. Do you have an example
file?




More information about the ffmpeg-devel mailing list