From e7dfaf16a403972eb6aed5ce8f84c0085bd2fb5a Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Thu, 13 Feb 2014 08:50:16 +0100 Subject: [PATCH] libavfilter: example audio filtering program Based on a patch by Andrew Kelley Signed-off-by: Diego Biurrun --- .gitignore | 1 + configure | 2 + doc/Makefile | 3 +- doc/examples/filter_audio.c | 364 ++++++++++++++++++++++++++++++++++++ 4 files changed, 369 insertions(+), 1 deletion(-) create mode 100644 doc/examples/filter_audio.c diff --git a/.gitignore b/.gitignore index 23339508e2..07ba48598a 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,7 @@ /doc/avoptions_format.texi /doc/doxy/html/ /doc/examples/avcodec +/doc/examples/filter_audio /doc/examples/metadata /doc/examples/output /doc/examples/transcode_aac diff --git a/configure b/configure index bd26c9a9d1..2f2a0f7452 100755 --- a/configure +++ b/configure @@ -1098,6 +1098,7 @@ COMPONENT_LIST=" EXAMPLE_LIST=" avcodec_example + filter_audio_example metadata_example output_example transcode_aac_example @@ -2030,6 +2031,7 @@ scale_filter_deps="swscale" # examples avcodec_example_deps="avcodec avutil" +filter_audio_example_deps="avfilter avutil" metadata_example_deps="avformat avutil" output_example_deps="avcodec avformat avutil swscale" transcode_aac_example_deps="avcodec avformat avresample" diff --git a/doc/Makefile b/doc/Makefile index 0cb2335768..1c205a5236 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -16,10 +16,11 @@ DOCS-$(CONFIG_TEXI2HTML) += $(HTMLPAGES) DOCS = $(DOCS-yes) DOC_EXAMPLES-$(CONFIG_AVCODEC_EXAMPLE) += avcodec +DOC_EXAMPLES-$(CONFIG_FILTER_AUDIO_EXAMPLE) += filter_audio DOC_EXAMPLES-$(CONFIG_METADATA_EXAMPLE) += metadata DOC_EXAMPLES-$(CONFIG_OUTPUT_EXAMPLE) += output DOC_EXAMPLES-$(CONFIG_TRANSCODE_AAC_EXAMPLE) += transcode_aac -ALL_DOC_EXAMPLES = avcodec metadata output transcode_aac +ALL_DOC_EXAMPLES = avcodec filter_audio metadata output transcode_aac DOC_EXAMPLES := $(DOC_EXAMPLES-yes:%=doc/examples/%$(EXESUF)) ALL_DOC_EXAMPLES := $(ALL_DOC_EXAMPLES:%=doc/examples/%$(EXESUF)) diff --git a/doc/examples/filter_audio.c b/doc/examples/filter_audio.c new file mode 100644 index 0000000000..71f985ee13 --- /dev/null +++ b/doc/examples/filter_audio.c @@ -0,0 +1,364 @@ +/* + * copyright (c) 2013 Andrew Kelley + * + * This file is part of Libav. + * + * Libav 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. + * + * Libav 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 Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file + * libavfilter API usage example. + * + * @example filter_audio.c + * This example will generate a sine wave audio, + * pass it through a simple filter chain, and then compute the MD5 checksum of + * the output data. + * + * The filter chain it uses is: + * (input) -> abuffer -> volume -> aformat -> abuffersink -> (output) + * + * abuffer: This provides the endpoint where you can feed the decoded samples. + * volume: In this example we hardcode it to 0.90. + * aformat: This converts the samples to the samplefreq, channel layout, + * and sample format required by the audio device. + * abuffersink: This provides the endpoint where you can read the samples after + * they have passed through the filter chain. + */ + +#include +#include +#include +#include + +#include "libavutil/channel_layout.h" +#include "libavutil/md5.h" +#include "libavutil/opt.h" +#include "libavutil/samplefmt.h" + +#include "libavfilter/avfilter.h" +#include "libavfilter/buffersink.h" +#include "libavfilter/buffersrc.h" + +#define INPUT_SAMPLERATE 48000 +#define INPUT_FORMAT AV_SAMPLE_FMT_FLTP +#define INPUT_CHANNEL_LAYOUT AV_CH_LAYOUT_5POINT0 + +#define VOLUME_VAL 0.90 + +static int init_filter_graph(AVFilterGraph **graph, AVFilterContext **src, + AVFilterContext **sink) +{ + AVFilterGraph *filter_graph; + AVFilterContext *abuffer_ctx; + AVFilter *abuffer; + AVFilterContext *volume_ctx; + AVFilter *volume; + AVFilterContext *aformat_ctx; + AVFilter *aformat; + AVFilterContext *abuffersink_ctx; + AVFilter *abuffersink; + + AVDictionary *options_dict = NULL; + uint8_t options_str[1024]; + uint8_t ch_layout[64]; + + int err; + + /* Create a new filtergraph, which will contain all the filters. */ + filter_graph = avfilter_graph_alloc(); + if (!filter_graph) { + fprintf(stderr, "Unable to create filter graph.\n"); + return AVERROR(ENOMEM); + } + + /* Create the abuffer filter; + * it will be used for feeding the data into the graph. */ + abuffer = avfilter_get_by_name("abuffer"); + if (!abuffer) { + fprintf(stderr, "Could not find the abuffer filter.\n"); + return AVERROR_FILTER_NOT_FOUND; + } + + abuffer_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "src"); + if (!abuffer_ctx) { + fprintf(stderr, "Could not allocate the abuffer instance.\n"); + return AVERROR(ENOMEM); + } + + /* Set the filter options through the AVOptions API. */ + av_get_channel_layout_string(ch_layout, sizeof(ch_layout), 0, INPUT_CHANNEL_LAYOUT); + av_opt_set (abuffer_ctx, "channel_layout", ch_layout, AV_OPT_SEARCH_CHILDREN); + av_opt_set (abuffer_ctx, "sample_fmt", av_get_sample_fmt_name(INPUT_FORMAT), AV_OPT_SEARCH_CHILDREN); + av_opt_set_q (abuffer_ctx, "time_base", (AVRational){ 1, INPUT_SAMPLERATE }, AV_OPT_SEARCH_CHILDREN); + av_opt_set_int(abuffer_ctx, "sample_rate", INPUT_SAMPLERATE, AV_OPT_SEARCH_CHILDREN); + + /* Now initialize the filter; we pass NULL options, since we have already + * set all the options above. */ + err = avfilter_init_str(abuffer_ctx, NULL); + if (err < 0) { + fprintf(stderr, "Could not initialize the abuffer filter.\n"); + return err; + } + + /* Create volume filter. */ + volume = avfilter_get_by_name("volume"); + if (!volume) { + fprintf(stderr, "Could not find the volume filter.\n"); + return AVERROR_FILTER_NOT_FOUND; + } + + volume_ctx = avfilter_graph_alloc_filter(filter_graph, volume, "volume"); + if (!volume_ctx) { + fprintf(stderr, "Could not allocate the volume instance.\n"); + return AVERROR(ENOMEM); + } + + /* A different way of passing the options is as key/value pairs in a + * dictionary. */ + av_dict_set(&options_dict, "volume", AV_STRINGIFY(VOLUME_VAL), 0); + err = avfilter_init_dict(volume_ctx, &options_dict); + av_dict_free(&options_dict); + if (err < 0) { + fprintf(stderr, "Could not initialize the volume filter.\n"); + return err; + } + + /* Create the aformat filter; + * it ensures that the output is of the format we want. */ + aformat = avfilter_get_by_name("aformat"); + if (!aformat) { + fprintf(stderr, "Could not find the aformat filter.\n"); + return AVERROR_FILTER_NOT_FOUND; + } + + aformat_ctx = avfilter_graph_alloc_filter(filter_graph, aformat, "aformat"); + if (!aformat_ctx) { + fprintf(stderr, "Could not allocate the aformat instance.\n"); + return AVERROR(ENOMEM); + } + + /* A third way of passing the options is in a string of the form + * key1=value1:key2=value2.... */ + snprintf(options_str, sizeof(options_str), + "sample_fmts=%s:sample_rates=%d:channel_layouts=0x%"PRIx64, + av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), 44100, + (uint64_t)AV_CH_LAYOUT_STEREO); + err = avfilter_init_str(aformat_ctx, options_str); + if (err < 0) { + av_log(NULL, AV_LOG_ERROR, "Could not initialize the aformat filter.\n"); + return err; + } + + /* Finally create the abuffersink filter; + * it will be used to get the filtered data out of the graph. */ + abuffersink = avfilter_get_by_name("abuffersink"); + if (!abuffersink) { + fprintf(stderr, "Could not find the abuffersink filter.\n"); + return AVERROR_FILTER_NOT_FOUND; + } + + abuffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink, "sink"); + if (!abuffersink_ctx) { + fprintf(stderr, "Could not allocate the abuffersink instance.\n"); + return AVERROR(ENOMEM); + } + + /* This filter takes no options. */ + err = avfilter_init_str(abuffersink_ctx, NULL); + if (err < 0) { + fprintf(stderr, "Could not initialize the abuffersink instance.\n"); + return err; + } + + /* Connect the filters; + * in this simple case the filters just form a linear chain. */ + err = avfilter_link(abuffer_ctx, 0, volume_ctx, 0); + if (err >= 0) + err = avfilter_link(volume_ctx, 0, aformat_ctx, 0); + if (err >= 0) + err = avfilter_link(aformat_ctx, 0, abuffersink_ctx, 0); + if (err < 0) { + fprintf(stderr, "Error connecting filters\n"); + return err; + } + + /* Configure the graph. */ + err = avfilter_graph_config(filter_graph, NULL); + if (err < 0) { + av_log(NULL, AV_LOG_ERROR, "Error configuring the filter graph\n"); + return err; + } + + *graph = filter_graph; + *src = abuffer_ctx; + *sink = abuffersink_ctx; + + return 0; +} + +/* Do something useful with the filtered data: this simple + * example just prints the MD5 checksum of each plane to stdout. */ +static int process_output(struct AVMD5 *md5, AVFrame *frame) +{ + int planar = av_sample_fmt_is_planar(frame->format); + int channels = av_get_channel_layout_nb_channels(frame->channel_layout); + int planes = planar ? channels : 1; + int bps = av_get_bytes_per_sample(frame->format); + int plane_size = bps * frame->nb_samples * (planar ? 1 : channels); + int i, j; + + for (i = 0; i < planes; i++) { + uint8_t checksum[16]; + + av_md5_init(md5); + av_md5_sum(checksum, frame->extended_data[i], plane_size); + + fprintf(stdout, "plane %d: 0x", i); + for (j = 0; j < sizeof(checksum); j++) + fprintf(stdout, "%02X", checksum[j]); + fprintf(stdout, "\n"); + } + fprintf(stdout, "\n"); + + return 0; +} + +/* Construct a frame of audio data to be filtered; + * this simple example just synthesizes a sine wave. */ +static int get_input(AVFrame *frame, int frame_num) +{ + int err, i, j; + +#define FRAME_SIZE 1024 + + /* Set up the frame properties and allocate the buffer for the data. */ + frame->sample_rate = INPUT_SAMPLERATE; + frame->format = INPUT_FORMAT; + frame->channel_layout = INPUT_CHANNEL_LAYOUT; + frame->nb_samples = FRAME_SIZE; + frame->pts = frame_num * FRAME_SIZE; + + err = av_frame_get_buffer(frame, 0); + if (err < 0) + return err; + + /* Fill the data for each channel. */ + for (i = 0; i < 5; i++) { + float *data = (float*)frame->extended_data[i]; + + for (j = 0; j < frame->nb_samples; j++) + data[j] = sin(2 * M_PI * (frame_num + j) * (i + 1) / FRAME_SIZE); + } + + return 0; +} + +int main(int argc, char *argv[]) +{ + struct AVMD5 *md5; + AVFilterGraph *graph; + AVFilterContext *src, *sink; + AVFrame *frame; + uint8_t errstr[1024]; + float duration; + int err, nb_frames, i; + + if (argc < 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + duration = atof(argv[1]); + nb_frames = duration * INPUT_SAMPLERATE / FRAME_SIZE; + if (nb_frames <= 0) { + fprintf(stderr, "Invalid duration: %s\n", argv[1]); + return 1; + } + + avfilter_register_all(); + + /* Allocate the frame we will be using to store the data. */ + frame = av_frame_alloc(); + if (!frame) { + fprintf(stderr, "Error allocating the frame\n"); + return 1; + } + + md5 = av_md5_alloc(); + if (!md5) { + fprintf(stderr, "Error allocating the MD5 context\n"); + return 1; + } + + /* Set up the filtergraph. */ + err = init_filter_graph(&graph, &src, &sink); + if (err < 0) { + fprintf(stderr, "Unable to init filter graph:"); + goto fail; + } + + /* the main filtering loop */ + for (i = 0; i < nb_frames; i++) { + /* get an input frame to be filtered */ + err = get_input(frame, i); + if (err < 0) { + fprintf(stderr, "Error generating input frame:"); + goto fail; + } + + /* Send the frame to the input of the filtergraph. */ + err = av_buffersrc_add_frame(src, frame); + if (err < 0) { + av_frame_unref(frame); + fprintf(stderr, "Error submitting the frame to the filtergraph:"); + goto fail; + } + + /* Get all the filtered output that is available. */ + while ((err = av_buffersink_get_frame(sink, frame)) >= 0) { + /* now do something with our filtered frame */ + err = process_output(md5, frame); + if (err < 0) { + fprintf(stderr, "Error processing the filtered frame:"); + goto fail; + } + av_frame_unref(frame); + } + + if (err == AVERROR(EAGAIN)) { + /* Need to feed more frames in. */ + continue; + } else if (err == AVERROR_EOF) { + /* Nothing more to do, finish. */ + break; + } else if (err < 0) { + /* An error occurred. */ + fprintf(stderr, "Error filtering the data:"); + goto fail; + } + } + + avfilter_graph_free(&graph); + av_frame_free(&frame); + av_freep(&md5); + + return 0; + +fail: + av_strerror(err, errstr, sizeof(errstr)); + fprintf(stderr, "%s\n", errstr); + return 1; +}