C# 使用FFmpeg增加/减少音频音量

C# 使用FFmpeg增加/减少音频音量,c#,ffmpeg,volume,C#,Ffmpeg,Volume,我目前正在使用C#调用来调用FFmpeg API来处理视频和音频。我有以下代码从视频中提取音频并将其写入文件 while (ffmpeg.av_read_frame(formatContext, &packet) >= 0) { if (packet.stream_index == streamIndex) { while (packet.size > 0) { int frameDecoded;

我目前正在使用C#调用来调用FFmpeg API来处理视频和音频。我有以下代码从视频中提取音频并将其写入文件

while (ffmpeg.av_read_frame(formatContext, &packet) >= 0)
{
    if (packet.stream_index == streamIndex)
    {
        while (packet.size > 0)
        {
            int frameDecoded;
            int frameDecodedResult = ffmpeg.avcodec_decode_audio4(codecContext, frame, &frameDecoded, packet);

            if (frameDecoded > 0 && frameDecodedResult >= 0)
            {
                //writeAudio.WriteFrame(frame);

                packet.data += totalBytesDecoded;
                packet.size -= totalBytesDecoded;
            }
        }

        frameIndex++;
    }

    Avcodec.av_free_packet(&packet);
}
这一切都正常工作。我目前正在使用FFmpeg.AutoGen项目进行API访问

我希望能够在音频写入文件之前增加/减少音量,但我似乎找不到命令或任何帮助。是否必须手动完成

更新1:

在收到一些帮助后,这是我的课堂布局:

public unsafe class FilterVolume
{
    #region Private Member Variables

    private AVFilterGraph* m_filterGraph = null;
    private AVFilterContext* m_aBufferSourceFilterContext = null;
    private AVFilterContext* m_aBufferSinkFilterContext = null;

    #endregion

    #region Private Constant Member Variables

    private const int EAGAIN = 11;

    #endregion

    public FilterVolume(AVCodecContext* codecContext, AVStream* stream, float volume)
    {
        CodecContext = codecContext;
        Stream = stream;
        Volume = volume;

        Initialise();
    }

    public AVFrame* Adjust(AVFrame* frame)
    {
        AVFrame* returnFilteredFrame = ffmpeg.av_frame_alloc();

        if (m_aBufferSourceFilterContext != null && m_aBufferSinkFilterContext != null)
        {
            int bufferSourceAddFrameResult = ffmpeg.av_buffersrc_add_frame(m_aBufferSourceFilterContext, frame);
            if (bufferSourceAddFrameResult < 0)
            {
            }

            int bufferSinkGetFrameResult = ffmpeg.av_buffersink_get_frame(m_aBufferSinkFilterContext, returnFilteredFrame);
            if (bufferSinkGetFrameResult < 0 && bufferSinkGetFrameResult != -EAGAIN)
            {
            }
        }

        return returnFilteredFrame;
    }

    public void Dispose()
    {
        Cleanup(m_filterGraph);
    }

    #region Private Properties

    private AVCodecContext* CodecContext { get; set; }
    private AVStream* Stream { get; set; }
    private float Volume { get; set; }

    #endregion

    #region Private Setup Helper Functions

    private void Initialise()
    {
        m_filterGraph = GetAllocatedFilterGraph();

        string aBufferFilterArguments = string.Format("sample_fmt={0}:channel_layout={1}:sample_rate={2}:time_base={3}/{4}",
            (int)CodecContext->sample_fmt,
            CodecContext->channel_layout,
            CodecContext->sample_rate,
            Stream->time_base.num,
            Stream->time_base.den);

        AVFilterContext* aBufferSourceFilterContext = CreateFilter("abuffer", m_filterGraph, aBufferFilterArguments);
        AVFilterContext* volumeFilterContext = CreateFilter("volume", m_filterGraph, string.Format("volume={0}", Volume));
        AVFilterContext* aBufferSinkFilterContext = CreateFilter("abuffersink", m_filterGraph);

        LinkFilter(aBufferSourceFilterContext, volumeFilterContext);
        LinkFilter(volumeFilterContext, aBufferSinkFilterContext);

        SetFilterGraphConfiguration(m_filterGraph, null);

        m_aBufferSourceFilterContext = aBufferSourceFilterContext;
        m_aBufferSinkFilterContext = aBufferSinkFilterContext;
    }

    #endregion

    #region Private Cleanup Helper Functions

    private static void Cleanup(AVFilterGraph* filterGraph)
    {
        if (filterGraph != null)
        {
            ffmpeg.avfilter_graph_free(&filterGraph);
        }
    }

    #endregion

    #region Provate Helpers

    private AVFilterGraph* GetAllocatedFilterGraph()
    {
        AVFilterGraph* filterGraph = ffmpeg.avfilter_graph_alloc();
        if (filterGraph == null)
        {
        }

        return filterGraph;
    }

    private AVFilter* GetFilterByName(string name)
    {
        AVFilter* filter = ffmpeg.avfilter_get_by_name(name);
        if (filter == null)
        {
        }

        return filter;
    }

    private void SetFilterGraphConfiguration(AVFilterGraph* filterGraph, void* logContext)
    {
        int filterGraphConfigResult = ffmpeg.avfilter_graph_config(filterGraph, logContext);
        if (filterGraphConfigResult < 0)
        {
        }
    }

    private AVFilterContext* CreateFilter(string filterName, AVFilterGraph* filterGraph, string filterArguments = null)
    {
        AVFilter* filter = GetFilterByName(filterName);
        AVFilterContext* filterContext;

        int aBufferFilterCreateResult = ffmpeg.avfilter_graph_create_filter(&filterContext, filter, filterName, filterArguments, null, filterGraph);
        if (aBufferFilterCreateResult < 0)
        {
        }

        return filterContext;
    }

    private void LinkFilter(AVFilterContext* source, AVFilterContext* destination)
    {
        int filterLinkResult = ffmpeg.avfilter_link(source, 0, destination, 0);
        if (filterLinkResult < 0)
        {
        }
    }

    #endregion
}
更新2:

破解它。过滤器参数字符串中的“channel_layout”选项应为十六进制。这是字符串格式的外观:

string aBufferFilterArguments = string.Format("sample_fmt={0}:channel_layout=0x{1}:sample_rate={2}:time_base={3}/{4}",
    (int)CodecContext->sample_fmt,
    CodecContext->channel_layout,
    CodecContext->sample_rate,
    Stream->time_base.num,
    Stream->time_base.den);

我不知道您使用的是什么API,但ffmpeg有一个命令,允许增加或减少音频:

减至一半:

ffmpeg -i input.wav -af "volume=0.5" output.wav
增加50%:

ffmpeg -i input.wav -af "volume=1.5" output.wav
或以dB为单位:

ffmpeg -i input.wav -af "volume=10dB" output.wav

希望它能帮助您

您需要做的是构建一个过滤图并通过该图处理音频流。在您的例子中,图形只是输入(“abuffer”)->VOLUME->OUTPUT(“abuffersink”)。 下面是一个示例控制台应用程序,演示了这一点。它松散地基于ffmpeg样本,并且

您可以这样使用它:

ChangeVolume.exe http://www.quirksmode.org/html5/videos/big_buck_bunny.mp4 bunny_half.mp4 0.5
代码如下:

class Program
{
    static unsafe void Main(string[] args)
    {
        Console.WriteLine(@"Current directory: " + Environment.CurrentDirectory);
        Console.WriteLine(@"Running in {0}-bit mode.", Environment.Is64BitProcess ? @"64" : @"32");

        // adapt this to your context
        var ffmpegPath = string.Format(@"../../../FFmpeg/bin/{0}", Environment.Is64BitProcess ? @"x64" : @"x86");
        InteropHelper.SetDllDirectory(ffmpegPath);

        int ret, i;
        if (args.Length < 3)
        {
            Console.WriteLine("usage: ChangeVolume input output <volume ratio>");
            return;
        }

        string in_filename = args[0];
        string out_filename = args[1];
        double ratio = double.Parse(args[2]); 
        ffmpeg.av_register_all();
        ffmpeg.avfilter_register_all();

        // open input file
        AVFormatContext* ifmt_ctx = null;
        InteropHelper.Check(ffmpeg.avformat_open_input(&ifmt_ctx, in_filename, null, null));

        // dump input
        ffmpeg.av_dump_format(ifmt_ctx, 0, in_filename, 0);

        // get streams info to determine audio stream index
        InteropHelper.Check(ffmpeg.avformat_find_stream_info(ifmt_ctx, null));

        // determine input decoder
        AVCodec* dec;
        int audio_stream_index = ffmpeg.av_find_best_stream(ifmt_ctx, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
        AVCodecContext* dec_ctx = ifmt_ctx->streams[audio_stream_index]->codec;

        // open input decoder
        InteropHelper.Check(ffmpeg.avcodec_open2(dec_ctx, dec, null));

        // build a filter graph
        AVFilterContext* buffersrc_ctx;
        AVFilterContext* buffersink_ctx;
        AVFilterGraph* filter_graph = init_filter_graph(ifmt_ctx, dec_ctx, audio_stream_index, &buffersrc_ctx, &buffersink_ctx, ratio);

        // prepare output
        AVFormatContext* ofmt_ctx = null;
        InteropHelper.Check(ffmpeg.avformat_alloc_output_context2(&ofmt_ctx, null, null, out_filename));
        InteropHelper.Check(ofmt_ctx);

        // create output streams
        AVCodecContext* enc_ctx = null;
        ofmt_ctx->oformat->flags |= InteropHelper.AVFMT_NOTIMESTAMPS;
        for (i = 0; i < ifmt_ctx->nb_streams; i++)
        {
            AVStream* in_stream = ifmt_ctx->streams[i];
            if (in_stream->codec->codec_type == AVMediaType.AVMEDIA_TYPE_DATA) // skip these
                continue;

            AVStream* out_stream = ffmpeg.avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
            InteropHelper.Check(out_stream);

            InteropHelper.Check(ffmpeg.avcodec_copy_context(out_stream->codec, in_stream->codec));

            out_stream->codec->codec_tag = 0;
            if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_GLOBALHEADER) != 0)
            {
                out_stream->codec->flags |= InteropHelper.AV_CODEC_FLAG_GLOBAL_HEADER;
            }

            if (i == audio_stream_index)
            {
                // create audio encoder from audio decoder
                AVCodec* enc = ffmpeg.avcodec_find_encoder(dec_ctx->codec_id);
                InteropHelper.Check(enc);

                enc_ctx = ffmpeg.avcodec_alloc_context3(enc);
                InteropHelper.Check(enc_ctx);

                enc_ctx->sample_rate = dec_ctx->sample_rate;
                enc_ctx->channel_layout = dec_ctx->channel_layout;
                enc_ctx->channels = ffmpeg.av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
                enc_ctx->sample_fmt = enc->sample_fmts[0];
                enc_ctx->time_base.num = 1;
                enc_ctx->time_base.den = enc_ctx->sample_rate;
                InteropHelper.Check(ffmpeg.avcodec_open2(enc_ctx, enc, null));
            }
        }

        // dump output
        ffmpeg.av_dump_format(ofmt_ctx, 0, out_filename, 1);

        if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_NOFILE) == 0)
        {
            // open output file
            InteropHelper.Check(ffmpeg.avio_open(&ofmt_ctx->pb, out_filename, InteropHelper.AVIO_FLAG_WRITE));
        }

        // write output file header
        InteropHelper.Check(ffmpeg.avformat_write_header(ofmt_ctx, null));

        // read all packets and process
        AVFrame* frame = ffmpeg.av_frame_alloc();
        AVFrame* filt_frame = ffmpeg.av_frame_alloc();
        while (true)
        {
            AVStream* in_stream;
            AVStream* out_stream;
            AVPacket pkt;
            ret = ffmpeg.av_read_frame(ifmt_ctx, &pkt);
            if (ret < 0)
                break;

            in_stream = ifmt_ctx->streams[pkt.stream_index];
            if (in_stream->codec->codec_type == AVMediaType.AVMEDIA_TYPE_DATA)
                continue;

            // audio stream? we need to pass it through our filter graph
            if (pkt.stream_index == audio_stream_index)
            {
                // decode audio (packet -> frame)
                int got_frame = 0;
                InteropHelper.Check(ffmpeg.avcodec_decode_audio4(dec_ctx, frame, &got_frame, &pkt));

                if (got_frame > 0)
                {
                    // add the frame into the filter graph
                    InteropHelper.Check(ffmpeg.av_buffersrc_add_frame(buffersrc_ctx, frame));
                    while (true)
                    {
                        // get the frame out from the filter graph
                        ret = ffmpeg.av_buffersink_get_frame(buffersink_ctx, filt_frame);
                        const int EAGAIN = 11;
                        if (ret == -EAGAIN)
                            break;

                        InteropHelper.Check(ret);

                        // encode audio (frame -> packet)
                        AVPacket enc_pkt = new AVPacket();
                        int got_packet = 0;
                        InteropHelper.Check(ffmpeg.avcodec_encode_audio2(enc_ctx, &enc_pkt, filt_frame, &got_packet));
                        enc_pkt.stream_index = pkt.stream_index;
                        InteropHelper.Check(ffmpeg.av_interleaved_write_frame(ofmt_ctx, &enc_pkt));
                        ffmpeg.av_frame_unref(filt_frame);
                    }
                }
            }
            else
            {
                // write other (video) streams
                out_stream = ofmt_ctx->streams[pkt.stream_index];
                pkt.pts = ffmpeg.av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
                pkt.dts = ffmpeg.av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
                pkt.duration = ffmpeg.av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
                pkt.pos = -1;
                InteropHelper.Check(ffmpeg.av_interleaved_write_frame(ofmt_ctx, &pkt));
            }
            ffmpeg.av_packet_unref(&pkt);
        }

        // write trailer, close file
        ffmpeg.av_write_trailer(ofmt_ctx);
        ffmpeg.avformat_close_input(&ifmt_ctx);
        if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_NOFILE) == 0)
        {
            ffmpeg.avio_closep(&ofmt_ctx->pb);
        }

        ffmpeg.avformat_free_context(ofmt_ctx);

        ffmpeg.av_frame_free(&filt_frame);
        ffmpeg.av_frame_free(&frame);

        ffmpeg.avfilter_graph_free(&filter_graph);
        return;
    }

    static unsafe AVFilterGraph* init_filter_graph(AVFormatContext* format, AVCodecContext* codec, int audio_stream_index, AVFilterContext** buffersrc_ctx, AVFilterContext** buffersink_ctx, double volumeRatio)
    {
        // create graph
        var filter_graph = ffmpeg.avfilter_graph_alloc();
        InteropHelper.Check(filter_graph);

        // add input filter
        var abuffersrc = ffmpeg.avfilter_get_by_name("abuffer");
        if (abuffersrc == null) InteropHelper.CheckTag("\x00F8FIL");
        string args = string.Format("sample_fmt={0}:channel_layout={1}:sample_rate={2}:time_base={3}/{4}",
            (int)codec->sample_fmt,
            codec->channel_layout,
            codec->sample_rate,
            format->streams[audio_stream_index]->time_base.num,
            format->streams[audio_stream_index]->time_base.den);
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(buffersrc_ctx, abuffersrc, "IN", args, null, filter_graph));

        // add volume filter
        var volume = ffmpeg.avfilter_get_by_name("volume");
        if (volume == null) InteropHelper.CheckTag("\x00F8FIL");
        AVFilterContext* volume_ctx;
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(&volume_ctx, volume, "VOL", "volume=" + volumeRatio.ToString(CultureInfo.InvariantCulture), null, filter_graph));

        // add output filter
        var abuffersink = ffmpeg.avfilter_get_by_name("abuffersink");
        if (abuffersink == null) InteropHelper.CheckTag("\x00F8FIL");
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(buffersink_ctx, abuffersink, "OUT", "", null, filter_graph));

        // connect input -> volume -> output
        InteropHelper.Check(ffmpeg.avfilter_link(*buffersrc_ctx, 0, volume_ctx, 0));
        InteropHelper.Check(ffmpeg.avfilter_link(volume_ctx, 0, *buffersink_ctx, 0));
        InteropHelper.Check(ffmpeg.avfilter_graph_config(filter_graph, null));
        return filter_graph;
    }
}
类程序
{
静态不安全void Main(字符串[]args)
{
Console.WriteLine(@“当前目录:”+Environment.CurrentDirectory);
Console.WriteLine(@“在{0}-位模式下运行”,Environment.Is64BitProcess?@“64”:@“32”);
//根据您的环境调整此选项
var ffmpegPath=string.Format(@./../../../FFmpeg/bin/{0}),Environment.is64位进程?@“x64”:@“x86”);
InteropHelper.SetDllDirectory(ffmpegPath);
int ret,i;
如果(参数长度<3)
{
Console.WriteLine(“用法:ChangeVolume输入输出”);
返回;
}
_filename=args[0]中的字符串;
字符串输出_filename=args[1];
double ratio=double.Parse(args[2]);
ffmpeg.av_register_all();
ffmpeg.avfilter_register_all();
//打开输入文件
AVFormatContext*ifmt_ctx=null;
InteropHelper.Check(ffmpeg.avformat_open_input(&ifmt_ctx,in_filename,null,null));
//转储输入
av_dump_格式(ifmt_ctx,0,in_文件名,0);
//获取流信息以确定音频流索引
InteropHelper.Check(ffmpeg.avformat_find_stream_info(ifmt_ctx,null));
//确定输入解码器
AVCodec*dec;
int audio\u stream\u index=ffmpeg.av\u find\u best\u stream(ifmt\u ctx、AVMediaType.AVMEDIA\u TYPE\u audio、-1、&dec,0);
AVCodecContext*dec_ctx=ifmt_ctx->streams[audio_stream_index]->编解码器;
//开放输入解码器
InteropHelper.Check(ffmpeg.avcodec_open2(dec_ctx,dec,null));
//构建一个过滤图
AVFilterContext*缓冲区src_ctx;
AVFilterContext*buffersink_ctx;
AVFilterGraph*filter\u graph=init\u filter\u graph(ifmt\u ctx、dec\u ctx、音频流索引、缓冲区src\u ctx、缓冲区sink\u ctx、比率);
//准备输出
AVFormatContext*ofmt_ctx=null;
InteropHelper.Check(ffmpeg.avformat_alloc_output_context2(&ofmt_ctx,null,null,out_filename));
检查(ofmt_ctx);
//创建输出流
AVCodecContext*enc_ctx=null;
ofmt_ctx->oformat->flags |=InteropHelper.AVFMT_NOTIMESTAMPS;
对于(i=0;inb\u streams;i++)
{
AVStream*in_stream=ifmt_ctx->streams[i];
如果(在\u流->编解码器->编解码器\u类型==AVMediaType.AVMEDIA\u类型\u数据中)//跳过这些
继续;
AVStream*out\u stream=ffmpeg.avformat\u new\u stream(ofmt\u ctx,in\u stream->codec->codec);
InteropHelper.Check(out_流);
InteropHelper.Check(ffmpeg.avcodec\u copy\u上下文(out\u stream->codec,in\u stream->codec));
out\u stream->codec->codec\u tag=0;
if((ofmt_ctx->oformat->flags&InteropHelper.AVFMT_GLOBALHEADER)!=0)
{
out_stream->codec->flags |=InteropHelper.AV_codec_FLAG_GLOBAL_头;
}
if(i==音频流索引)
{
//从音频解码器创建音频编码器
AVCodec*enc=ffmpeg.AVCodec\u find\u编码器(dec\u ctx->codec\u id);
互通检查(enc);
enc_ctx=ffmpeg.avcodec_alloc_context3(enc);
互通检查(enc_ctx);
enc_ctx->采样率=dec_ctx->采样率;
enc_ctx->channel_layout=dec_ctx->channel_layout;
enc\u ctx->channels=ffmpeg.av\u get\u channel\u layout\u nb\u channels(enc\u ctx->channel\u layout);
enc_ctx->sample_fmt=enc->sample_fmts[0];
enc_ctx->time_base.num=1;
enc\u ctx->time\u base.den=enc\u ctx->sample\u rate;
InteropHelper.Check(ffmpeg.avcodec_open2(enc_ctx,enc,null));
}
}
//转储输出
av_dump_格式(ofmt_ctx,0,out_文件名,1);
if((ofmt_ctx->oformat->flags&InteropHelper.AVFMT_NOFILE)==0)
{
//打开输出文件
检查(ffmpeg.avio_open(&ofmt_ctx->pb,out_filename,InteropHelper.avio_FLAG_WRITE));
}
//写入输出文件头
InteropHelper.Check(ffmpeg.avformat_write_头(ofmt_ctx,null));
//读取所有数据包并进行处理
AVFrame*frame=ffmpeg.av_frame_alloc();
AVFrame*filt_frame=ffmpeg.av_frame_alloc();
while(true)
{
AVStream*在_流中;
AVStream*输出流;
AVPacket-pkt;
ret=ffmpeg.av\u read\u帧(ifmt\u ctx和pkt);
如果(ret<0)
打破
in_stream=ifmt_ctx->streams[pkt.stream_index];
如果(在溪流中)->cod
class Program
{
    static unsafe void Main(string[] args)
    {
        Console.WriteLine(@"Current directory: " + Environment.CurrentDirectory);
        Console.WriteLine(@"Running in {0}-bit mode.", Environment.Is64BitProcess ? @"64" : @"32");

        // adapt this to your context
        var ffmpegPath = string.Format(@"../../../FFmpeg/bin/{0}", Environment.Is64BitProcess ? @"x64" : @"x86");
        InteropHelper.SetDllDirectory(ffmpegPath);

        int ret, i;
        if (args.Length < 3)
        {
            Console.WriteLine("usage: ChangeVolume input output <volume ratio>");
            return;
        }

        string in_filename = args[0];
        string out_filename = args[1];
        double ratio = double.Parse(args[2]); 
        ffmpeg.av_register_all();
        ffmpeg.avfilter_register_all();

        // open input file
        AVFormatContext* ifmt_ctx = null;
        InteropHelper.Check(ffmpeg.avformat_open_input(&ifmt_ctx, in_filename, null, null));

        // dump input
        ffmpeg.av_dump_format(ifmt_ctx, 0, in_filename, 0);

        // get streams info to determine audio stream index
        InteropHelper.Check(ffmpeg.avformat_find_stream_info(ifmt_ctx, null));

        // determine input decoder
        AVCodec* dec;
        int audio_stream_index = ffmpeg.av_find_best_stream(ifmt_ctx, AVMediaType.AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0);
        AVCodecContext* dec_ctx = ifmt_ctx->streams[audio_stream_index]->codec;

        // open input decoder
        InteropHelper.Check(ffmpeg.avcodec_open2(dec_ctx, dec, null));

        // build a filter graph
        AVFilterContext* buffersrc_ctx;
        AVFilterContext* buffersink_ctx;
        AVFilterGraph* filter_graph = init_filter_graph(ifmt_ctx, dec_ctx, audio_stream_index, &buffersrc_ctx, &buffersink_ctx, ratio);

        // prepare output
        AVFormatContext* ofmt_ctx = null;
        InteropHelper.Check(ffmpeg.avformat_alloc_output_context2(&ofmt_ctx, null, null, out_filename));
        InteropHelper.Check(ofmt_ctx);

        // create output streams
        AVCodecContext* enc_ctx = null;
        ofmt_ctx->oformat->flags |= InteropHelper.AVFMT_NOTIMESTAMPS;
        for (i = 0; i < ifmt_ctx->nb_streams; i++)
        {
            AVStream* in_stream = ifmt_ctx->streams[i];
            if (in_stream->codec->codec_type == AVMediaType.AVMEDIA_TYPE_DATA) // skip these
                continue;

            AVStream* out_stream = ffmpeg.avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
            InteropHelper.Check(out_stream);

            InteropHelper.Check(ffmpeg.avcodec_copy_context(out_stream->codec, in_stream->codec));

            out_stream->codec->codec_tag = 0;
            if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_GLOBALHEADER) != 0)
            {
                out_stream->codec->flags |= InteropHelper.AV_CODEC_FLAG_GLOBAL_HEADER;
            }

            if (i == audio_stream_index)
            {
                // create audio encoder from audio decoder
                AVCodec* enc = ffmpeg.avcodec_find_encoder(dec_ctx->codec_id);
                InteropHelper.Check(enc);

                enc_ctx = ffmpeg.avcodec_alloc_context3(enc);
                InteropHelper.Check(enc_ctx);

                enc_ctx->sample_rate = dec_ctx->sample_rate;
                enc_ctx->channel_layout = dec_ctx->channel_layout;
                enc_ctx->channels = ffmpeg.av_get_channel_layout_nb_channels(enc_ctx->channel_layout);
                enc_ctx->sample_fmt = enc->sample_fmts[0];
                enc_ctx->time_base.num = 1;
                enc_ctx->time_base.den = enc_ctx->sample_rate;
                InteropHelper.Check(ffmpeg.avcodec_open2(enc_ctx, enc, null));
            }
        }

        // dump output
        ffmpeg.av_dump_format(ofmt_ctx, 0, out_filename, 1);

        if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_NOFILE) == 0)
        {
            // open output file
            InteropHelper.Check(ffmpeg.avio_open(&ofmt_ctx->pb, out_filename, InteropHelper.AVIO_FLAG_WRITE));
        }

        // write output file header
        InteropHelper.Check(ffmpeg.avformat_write_header(ofmt_ctx, null));

        // read all packets and process
        AVFrame* frame = ffmpeg.av_frame_alloc();
        AVFrame* filt_frame = ffmpeg.av_frame_alloc();
        while (true)
        {
            AVStream* in_stream;
            AVStream* out_stream;
            AVPacket pkt;
            ret = ffmpeg.av_read_frame(ifmt_ctx, &pkt);
            if (ret < 0)
                break;

            in_stream = ifmt_ctx->streams[pkt.stream_index];
            if (in_stream->codec->codec_type == AVMediaType.AVMEDIA_TYPE_DATA)
                continue;

            // audio stream? we need to pass it through our filter graph
            if (pkt.stream_index == audio_stream_index)
            {
                // decode audio (packet -> frame)
                int got_frame = 0;
                InteropHelper.Check(ffmpeg.avcodec_decode_audio4(dec_ctx, frame, &got_frame, &pkt));

                if (got_frame > 0)
                {
                    // add the frame into the filter graph
                    InteropHelper.Check(ffmpeg.av_buffersrc_add_frame(buffersrc_ctx, frame));
                    while (true)
                    {
                        // get the frame out from the filter graph
                        ret = ffmpeg.av_buffersink_get_frame(buffersink_ctx, filt_frame);
                        const int EAGAIN = 11;
                        if (ret == -EAGAIN)
                            break;

                        InteropHelper.Check(ret);

                        // encode audio (frame -> packet)
                        AVPacket enc_pkt = new AVPacket();
                        int got_packet = 0;
                        InteropHelper.Check(ffmpeg.avcodec_encode_audio2(enc_ctx, &enc_pkt, filt_frame, &got_packet));
                        enc_pkt.stream_index = pkt.stream_index;
                        InteropHelper.Check(ffmpeg.av_interleaved_write_frame(ofmt_ctx, &enc_pkt));
                        ffmpeg.av_frame_unref(filt_frame);
                    }
                }
            }
            else
            {
                // write other (video) streams
                out_stream = ofmt_ctx->streams[pkt.stream_index];
                pkt.pts = ffmpeg.av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
                pkt.dts = ffmpeg.av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AVRounding.AV_ROUND_NEAR_INF | AVRounding.AV_ROUND_PASS_MINMAX);
                pkt.duration = ffmpeg.av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
                pkt.pos = -1;
                InteropHelper.Check(ffmpeg.av_interleaved_write_frame(ofmt_ctx, &pkt));
            }
            ffmpeg.av_packet_unref(&pkt);
        }

        // write trailer, close file
        ffmpeg.av_write_trailer(ofmt_ctx);
        ffmpeg.avformat_close_input(&ifmt_ctx);
        if ((ofmt_ctx->oformat->flags & InteropHelper.AVFMT_NOFILE) == 0)
        {
            ffmpeg.avio_closep(&ofmt_ctx->pb);
        }

        ffmpeg.avformat_free_context(ofmt_ctx);

        ffmpeg.av_frame_free(&filt_frame);
        ffmpeg.av_frame_free(&frame);

        ffmpeg.avfilter_graph_free(&filter_graph);
        return;
    }

    static unsafe AVFilterGraph* init_filter_graph(AVFormatContext* format, AVCodecContext* codec, int audio_stream_index, AVFilterContext** buffersrc_ctx, AVFilterContext** buffersink_ctx, double volumeRatio)
    {
        // create graph
        var filter_graph = ffmpeg.avfilter_graph_alloc();
        InteropHelper.Check(filter_graph);

        // add input filter
        var abuffersrc = ffmpeg.avfilter_get_by_name("abuffer");
        if (abuffersrc == null) InteropHelper.CheckTag("\x00F8FIL");
        string args = string.Format("sample_fmt={0}:channel_layout={1}:sample_rate={2}:time_base={3}/{4}",
            (int)codec->sample_fmt,
            codec->channel_layout,
            codec->sample_rate,
            format->streams[audio_stream_index]->time_base.num,
            format->streams[audio_stream_index]->time_base.den);
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(buffersrc_ctx, abuffersrc, "IN", args, null, filter_graph));

        // add volume filter
        var volume = ffmpeg.avfilter_get_by_name("volume");
        if (volume == null) InteropHelper.CheckTag("\x00F8FIL");
        AVFilterContext* volume_ctx;
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(&volume_ctx, volume, "VOL", "volume=" + volumeRatio.ToString(CultureInfo.InvariantCulture), null, filter_graph));

        // add output filter
        var abuffersink = ffmpeg.avfilter_get_by_name("abuffersink");
        if (abuffersink == null) InteropHelper.CheckTag("\x00F8FIL");
        InteropHelper.Check(ffmpeg.avfilter_graph_create_filter(buffersink_ctx, abuffersink, "OUT", "", null, filter_graph));

        // connect input -> volume -> output
        InteropHelper.Check(ffmpeg.avfilter_link(*buffersrc_ctx, 0, volume_ctx, 0));
        InteropHelper.Check(ffmpeg.avfilter_link(volume_ctx, 0, *buffersink_ctx, 0));
        InteropHelper.Check(ffmpeg.avfilter_graph_config(filter_graph, null));
        return filter_graph;
    }
}
public class InteropHelper
{
    [DllImport("kernel32", SetLastError = true)]
    public static extern bool SetDllDirectory(string lpPathName);

    public static readonly int AVERROR_EOF = -GetTag("EOF ");
    public static readonly int AVERROR_UNKNOWN = -GetTag("UNKN");
    public static readonly int AVFMT_GLOBALHEADER = 0x0040;
    public static readonly int AVFMT_NOFILE = 0x0001;
    public static readonly int AVIO_FLAG_WRITE = 2;
    public static readonly int AV_CODEC_FLAG_GLOBAL_HEADER = (1 << 22);
    public static readonly int AV_ROUND_ZERO = 0;
    public static readonly int AV_ROUND_INF = 1;
    public static readonly int AV_ROUND_DOWN = 2;
    public static readonly int AV_ROUND_UP = 3;
    public static readonly int AV_ROUND_PASS_MINMAX = 8192;
    public static readonly int AV_ROUND_NEAR_INF = 5;
    public static readonly int AVFMT_NOTIMESTAMPS = 0x0080;

    public static unsafe void Check(void* ptr)
    {
        if (ptr != null)
            return;

        const int ENOMEM = 12;
        Check(-ENOMEM);
    }

    public static unsafe void Check(IntPtr ptr)
    {
        if (ptr != IntPtr.Zero)
            return;

        Check((void*)null);
    }

    // example: "\x00F8FIL" is "Filter not found" (check libavutil/error.h)
    public static void CheckTag(string tag)
    {
        Check(-GetTag(tag));
    }

    public static int GetTag(string tag)
    {
        var bytes = new byte[4];
        for (int i = 0; i < 4; i++)
        {
            bytes[i] = (byte)tag[i];
        }
        return BitConverter.ToInt32(bytes, 0);
    }

    public static void Check(int res)
    {
        if (res >= 0)
            return;

        string err = "ffmpeg error " + res;
        string text = GetErrorText(res);
        if (!string.IsNullOrWhiteSpace(text))
        {
            err += ": " + text;
        }
        throw new Exception(err);
    }

    public static string GetErrorText(int res)
    {
        IntPtr err = Marshal.AllocHGlobal(256);
        try
        {
            ffmpeg.av_strerror(res, err, 256);
            return Marshal.PtrToStringAnsi(err);
        }
        finally
        {
            Marshal.FreeHGlobal(err);
        }
    }
}