/* * filter.c -- audio/video filter handling * Written by Andrew Church * * This file is part of transcode, a video stream processing tool. * transcode is free software, distributable under the terms of the GNU * General Public License (version 2 or later). See the file COPYING * for details. */ #include "transcode.h" #include "filter.h" // temp defines during module system switchover //#define SUPPORT_NMS // support NMS modules? #define SUPPORT_CLASSIC // support classic modules? #ifdef SUPPORT_CLASSIC /* For dlopening old-style modules */ # ifdef HAVE_DLFCN_H # include # else # ifdef OS_DARWIN # include "libdldarwin/dlfcn.h" # endif # endif #endif // SUPPORT_CLASSIC /*************************************************************************/ /* Data for a single filter instance. An ID value of 0 indicates that no * filter is present. */ typedef struct FilterInstance_ { char name[MAX_FILTER_NAME_LEN+1]; // Filter name int id; // Unique ID value for this filter instance int enabled; // Nonzero if filter is inabled #ifdef SUPPORT_CLASSIC void *handle; // DLL handle for old-style modules TCFilterOldEntryFunc entry; // Module entry point for old-style modules #endif #ifdef SUPPORT_NMS #error please add field(s) needed for NMS #endif } FilterInstance; /* Flag: are we initialized? */ static int initialized = 0; /* Filter instance table. */ static FilterInstance filters[MAX_FILTERS]; /* Macro to check that tc_filter_init() has been called, and abort the * function otherwise. Pass the appropriate return value (nothing for a * void function) as the macro parameter. */ #define CHECK_INITIALIZED(...) do { \ if (!initialized) { \ tc_log_warn(__FILE__, "%s() called before initialization!", \ __FUNCTION__); \ return __VA_ARGS__; \ } \ } while (0) /*************************************************************************/ /** * id_to_index: Local helper function to convert a filter ID value to a * filters[] index. Outputs an error message with tc_log_warn() on error. * * Parameters: * id: Filter ID. * func: Calling function's name (__FUNCTION__). * Return value: * filters[] index corresponding to given ID, or -1 on error. */ static int id_to_index(int id, const char *func) { int i; if (id <= 0) { tc_log_warn(__FILE__, "Bad filter ID %d passed to %s()", id, func); return -1; } for (i = 0; i < MAX_FILTERS; i++) { if (filters[i].id == id) break; } if (i >= MAX_FILTERS) { tc_log_warn(__FILE__, "Filter ID %d does not exist in %s()", id, func); return -1; } return i; } /*************************************************************************/ /*************************************************************************/ /* tc_filter_init: Initialize the filter subsystem. Must be called before * any other filter functions. * * Parameters: * None. * Return value: * Nonzero on success, zero on failure. */ int tc_filter_init(void) { int i; if (initialized) { tc_log_warn(__FILE__, "tc_filter_init() called twice!"); return 1; } for (i = 0; i < MAX_FILTERS; i++) filters[i].id = 0; initialized = 1; return 1; } /*************************************************************************/ /* tc_filter_fini: Close down the filter subsystem. If the filter system * has not yet been initialized, do nothing. After calling this function, * no filter functions (other than tc_filter_init()) may be called. * * Parameters: * None. * Return value: * None. */ void tc_filter_fini(void) { int i; if (!initialized) return; for (i = 0; i < MAX_FILTERS; i++) { if (filters[i].id != 0) tc_filter_remove(filters[i].id); } initialized = 0; } /*************************************************************************/ /** * tc_filter_process: Sends the given frame to all enabled filters for * processing. * * Parameters: * frame: Frame to process. * Return value: * None. * Prerequisites: * frame->tag is set to an appropriate value */ void tc_filter_process(frame_list_t *frame) { int last_id; CHECK_INITIALIZED(); if (!frame) { tc_log_warn(__FILE__, "tc_filter_process: frame is NULL!"); return; } /* The order of the filters is given by their ID values--however, this * does not necessarily match the order in the filters[] array. We * keep track of the last ID processed (starting at 0, lower than any * valid ID), then each time through the loop, search for the lowest ID * greater than that value, which will be the next filter to process. * The loop ends when no enabled filter has an ID greater than the last * ID processed. */ last_id = 0; for (;;) { int next_filter = -1, i; for (i = 0; i < MAX_FILTERS; i++) { if (filters[i].id <= last_id || !filters[i].enabled) continue; if (next_filter < 0 || filters[i].id < filters[next_filter].id) next_filter = i; } if (next_filter < 0) break; last_id = filters[next_filter].id; #ifdef SUPPORT_NMS # error please write NMS support code #endif #ifdef SUPPORT_CLASSIC if (!filters[next_filter].entry) { tc_log_warn(__FILE__, "Filter %s (%d) missing entry function" " (bug?), disabling", filters[i].name, last_id); filters[next_filter].enabled = 0; continue; } frame->filter_id = last_id; filters[next_filter].entry(frame, NULL); #endif } // for (;;) } /*************************************************************************/ /** * tc_filter_add: Adds the given filter at the end of the filter chain, * and initializes it using the given option string. * * Parameters: * name: Name of filter to add. * options: Options to pass to filter (if NULL, no options are passed). * Return value: * Filter ID (nonzero) on success, zero on failure. */ int tc_filter_add(const char *name, const char *options) { int i, id; CHECK_INITIALIZED(0); if (!name) { tc_log_warn(__FILE__, "tc_filter_add: name is NULL!"); return 0; } else if (!*name) { tc_log_warn(__FILE__, "tc_filter_add: name is empty!"); return 0; } else if (strlen(name) > MAX_FILTER_NAME_LEN) { tc_log_warn(__FILE__, "tc_filter_add: name \"%s\" is too long!" " (max %d chars)", name, MAX_FILTER_NAME_LEN); return 0; } /* Find the largest ID value currently in use, and use the next value */ id = 0; for (i = 0; i < MAX_FILTERS; i++) { if (filters[i].id > id) id = filters[i].id; } id++; if (id <= 0) { // wraparound check tc_log_warn(__FILE__, "tc_filter_add: out of filter IDs, restart %s", PACKAGE); return 0; } /* Find the first available filter table entry, returning an error if * none is found, and initialize the entry */ for (i = 0; i < MAX_FILTERS; i++) { if (!filters[i].id) break; } if (i >= MAX_FILTERS) { tc_log_warn(__FILE__, "tc_filter_add: no free filter slots! (max %d)", MAX_FILTERS); return 0; } strlcpy(filters[i].name, name, sizeof(filters[i].name)); filters[i].enabled = 0; #ifdef SUPPORT_NMS # error please write NMS support code #endif #ifdef SUPPORT_CLASSIC { char path[1000]; frame_list_t dummy_frame; /* Load the module and look up the tc_filter() address */ if (tc_snprintf(path, sizeof(path), "%s/filter_%s.so", tc_get_vob()->mod_path, name) < 0) { tc_log_error(__FILE__, "tc_filter_add: path buffer overflow"); return 0; } filters[i].handle = dlopen(path, RTLD_NOW); if (!filters[i].handle) { const char *error = dlerror(); if (!error) error = "Unknown error"; tc_log_warn(PACKAGE, "Unable to load filter %s: %s", name, error); return 0; } filters[i].entry = dlsym(filters[i].handle, "tc_filter"); if (!filters[i].entry) { const char *error = dlerror(); if (!error) error = "Unknown error (corrupt module?)"; tc_log_warn(PACKAGE, "Unable to initialize filter %s: %s", name, error); dlclose(filters[i].handle); return 0; } filters[i].id = id; /* loaded, at least */ if (verbose & TC_DEBUG) tc_log_msg(__FILE__, "tc_filter_add: module %s loaded", path); /* Call tc_filter() to initialize the module */ dummy_frame.filter_id = id; dummy_frame.tag = TC_FILTER_INIT; /* Maximum size of a single frame, video or audio */ dummy_frame.size = 0; /* XXX: it seems never used, so 0 should be safe -- FR */ if (filters[i].entry(&dummy_frame, (char *)options) < 0) { tc_warn("Initialization of filter %s failed, skipping.", name); tc_filter_remove(id); } if (verbose & TC_DEBUG) tc_log_msg(__FILE__, "tc_filter_add: filter %s successfully" " initialized", name); } #endif // SUPPORT_CLASSIC /* Module was successfully loaded and initialized, so enable it */ filters[i].enabled = 1; return 1; } /*************************************************************************/ /** * tc_filter_find: Return the ID for the named filter. * * Parameters: * name: Name of filter to find. * Return value: * Filter ID (nonzero) on success, zero on error or if given filter is * not loaded. */ int tc_filter_find(const char *name) { int i; CHECK_INITIALIZED(0); for (i = 0; i < MAX_FILTERS; i++) { if (strcmp(filters[i].name, name) == 0) return filters[i].id; } return 0; } /*************************************************************************/ /** * tc_filter_remove: Remove the given filter. * * Parameters: * id: ID of filter to remove. * Return value: * None. */ void tc_filter_remove(int id) { int i; CHECK_INITIALIZED(); if ((i = id_to_index(id, __FUNCTION__)) < 0) return; #ifdef SUPPORT_NMS # error please write NMS support code #endif #ifdef SUPPORT_CLASSIC if (filters[i].handle) { if (filters[i].entry) { frame_list_t ptr; ptr.tag = TC_FILTER_CLOSE; ptr.filter_id = filters[i].id; filters[i].entry(&ptr, NULL); } else { tc_log_warn(__FILE__, "Filter %s (%d) missing entry function" " (bug?)", filters[i].name, id); } dlclose(filters[i].handle); filters[i].handle = NULL; filters[i].entry = NULL; } #endif memset(filters[i].name, 0, sizeof(filters[i].name)); filters[i].id = 0; filters[i].enabled = 0; } /*************************************************************************/ /** * tc_filter_enable: Enable the given filter. * * Parameters: * id: ID of filter to enable. * Return value: * Nonzero on success, zero on error. */ int tc_filter_enable(int id) { int i; CHECK_INITIALIZED(0); i = id_to_index(id, __FUNCTION__); if (i < 0) return 0; filters[i].enabled = 1; return 1; } /*************************************************************************/ /** * tc_filter_disable: Disable the given filter. * * Parameters: * id: ID of filter to enable. * Return value: * Nonzero on success, zero on error. */ int tc_filter_disable(int id) { int i; CHECK_INITIALIZED(0); i = id_to_index(id, __FUNCTION__); if (i < 0) return 0; filters[i].enabled = 0; return 1; } /*************************************************************************/ /** * tc_filter_configure: Configure the given filter. * * Parameters: * id: ID of filter to configure. * options: Option string for filter. * Return value: * Nonzero on success, zero on error. */ int tc_filter_configure(int id, const char *options) { int i; CHECK_INITIALIZED(0); i = id_to_index(id, __FUNCTION__); if (i < 0) return 0; #ifdef SUPPORT_NMS # error please write NMS support code #endif #ifdef SUPPORT_CLASSIC { frame_list_t dummy_frame; if (!filters[i].entry) { tc_log_warn(__FILE__, "Filter %s (%d) missing entry function" " (bug?), disabling", filters[i].name, id); filters[i].enabled = 0; return 0; } /* Old filter API does a close before reconfiguring */ dummy_frame.filter_id = id; dummy_frame.tag = TC_FILTER_CLOSE; filters[i].entry(&dummy_frame, NULL); dummy_frame.filter_id = id; dummy_frame.tag = TC_FILTER_INIT; dummy_frame.size = 0; /* XXX: it seems never used, so 0 should be safe -- FR */ if (filters[i].entry(&dummy_frame, (char *)options) < 0) { tc_log_warn(PACKAGE, "Reconfiguration of filter %s failed," " disabling.", filters[i].name); filters[i].enabled = 0; return 0; } return 1; } #endif } /*************************************************************************/ /** * tc_filter_get_conf: Return current configuration information for the * given option on the given filter. If `option' is NULL, returns a * summary of configuration information in an undefined format. * * Parameters: * id: ID of filter to retrieve configuration information for. * option: Name of option to retrieve information for, or NULL. * Return value: * A pointer to an unmodifiable string containing the result, or NULL * on error. */ const char *tc_filter_get_conf(int id, const char *option) { int i; CHECK_INITIALIZED(NULL); i = id_to_index(id, __FUNCTION__); if (i < 0) return 0; #ifdef SUPPORT_NMS # error please write NMS support code #endif #ifdef SUPPORT_CLASSIC { frame_list_t dummy_frame; static char buf[PATH_MAX]; memset(buf, 0, sizeof(buf)); dummy_frame.filter_id = id; dummy_frame.tag = TC_FILTER_GET_CONFIG; if (filters[i].entry) { if (filters[i].entry(&dummy_frame, buf) == 0) return buf; } else { tc_log_warn(__FILE__, "Filter %s (%d) missing entry function" " (bug?), disabling", filters[i].name, id); filters[i].enabled = 0; } return NULL; } #endif } /*************************************************************************/ /** * tc_filter_list: Return a list of filters according to the given * parameter. The list is comma-space separated, with each name enclosed * in double quotes; filters are listed in the same order they are applied. * * Parameters: * what: Selects what kind of modules to list (TC_FILTER_LIST_*). * Return value: * A pointer to an unmodifiable string containing the result. */ const char *tc_filter_list(enum tc_filter_list_enum what) { static char buf[MAX_FILTERS * (MAX_FILTER_NAME_LEN+4)]; int last_id; *buf = 0; CHECK_INITIALIZED(buf); /* Use the same logic as in tc_filter_process() to retrieve the filters * in ID order. */ last_id = 0; for (;;) { int next_filter = -1, i; for (i = 0; i < MAX_FILTERS; i++) { if (filters[i].id <= last_id) continue; if (what == TC_FILTER_LIST_ENABLED && !filters[i].enabled) continue; if (what == TC_FILTER_LIST_DISABLED && filters[i].enabled) continue; if (next_filter < 0 || filters[i].id < filters[next_filter].id) next_filter = i; } if (next_filter < 0) break; last_id = filters[next_filter].id; tc_snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), "%s\"%s\"", last_id==0 ? "" : ", ", filters[next_filter].name); } return buf; } /*************************************************************************/ /* * Local variables: * c-file-style: "stroustrup" * c-file-offsets: ((case-label . *) (statement-case-intro . *)) * indent-tabs-mode: nil * End: * * vim: expandtab shiftwidth=4: */