[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