[FFmpeg filter] 音频过滤器amix简化版(处理PCM文件)

241 阅读3分钟

FFmpeg版本4.2.5

流程图

FFmpeg简化版音频过滤器.png

关键函数

  • avfilter_graph_alloc():分配过滤器图
  • avfilter_graph_parse_ptr():将由字符串描述的图形添加到图形中。在图形过滤器描述中,如果第一个过滤器的输入标签没有指定,则假定为“in”;如果未指定最后一个过滤器的输出标签,则假定为“out”。
  • avfilter_graph_config():检查有效性并配置图中的所有链接和格式。
  • av_buffersrc_add_frame():将帧添加到缓冲区源。
  • av_buffersink_get_frame():从接收器中获取一个包含过滤数据的框架并将其放入框架中。

代码

#include <exception>
#include <map>

extern "C"
{
#include <libavutil/avassert.h>
#include <libavformat/avformat.h>
#include <libavfilter/avfilter.h>
#include <libavutil/opt.h>
#include <libavfilter/buffersrc.h>
#include <libavfilter/buffersink.h>
}

#define PCM1_FRAME_SIZE 4096  //(2*1024*2)
#define SAMPLE_RATE 44100
#define SAMPLE_FORMAT AV_SAMPLE_FMT_S16
#define CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
#define CHANNELS 2
#define BIT_PER_SAMPLE 16

typedef struct {
    char *filename;
    FILE *file;
    int sample_rate;
    AVSampleFormat sample_format;
    int channels;
    int channel_layout;
    char channel_layout_str[20];
    int bitPerSample;

    AVFilterContext *filterContext;
} AudioInfo;

std::map<int, AudioInfo> audioInfos;
std::shared_ptr<AudioInfo> outAudioInfo;
AVFilterGraph *graph;
int ret = 0;

/**
 *  打开输入文件
 * @param _audioInfo
 * @return 0 succeed
 */
int openInput(AudioInfo *audioInfo) {
    audioInfo->file = fopen(audioInfo->filename, "rb");
    if (!audioInfo->file) {
        fprintf(stderr, "%s open failed\n", audioInfo->filename);
        return -1;
    }
    return 0;
}

/**
 * 释放资源
 */
void exit() {
    for (auto audioInfo: audioInfos) {
        fclose(audioInfo.second.file);
        avfilter_free(audioInfo.second.filterContext);
    }
    fclose(outAudioInfo->file);
    avfilter_free(outAudioInfo->filterContext);
    avfilter_graph_free(&graph);
    exit(1);
}

int addFrame(AudioInfo *audioInfo, uint8_t *buffer, size_t size) {
    if (buffer && size > 0) {
        std::shared_ptr<AVFrame> avFrame(av_frame_alloc(), [](AVFrame *ptr) { av_frame_free(&ptr); });
        avFrame->sample_rate = audioInfo->sample_rate;
        avFrame->format = audioInfo->sample_format;
        avFrame->channels = audioInfo->channels;
        avFrame->nb_samples = (int) size * 8 / audioInfo->bitPerSample / audioInfo->channels;
        if (avFrame->nb_samples) {
            if (av_frame_get_buffer(avFrame.get(), 0) < 0) {
                fprintf(stderr, "av_frame_get_buffer failed\n");
                exit();
            }
        }
        //todo 注意:这里要传extended_data[0],不然会把其他数据覆盖?
        std::memcpy(avFrame->extended_data[0], buffer, size);
        if ((ret = av_buffersrc_add_frame(audioInfo->filterContext, avFrame.get())) != 0) {
            return -1;
        }
    } else {
        if ((ret = av_buffersrc_add_frame(audioInfo->filterContext, nullptr)) != 0) {
            return -1;
        }
    }
    return 0;
}

/**
 * 获取过滤器处理后的帧
 * @param outBuffer
 * @return
 */
int getFrame(uint8_t *outBuffer) {
    //这里利用智能指针释放AVFrame
    std::shared_ptr<AVFrame> avFrame(av_frame_alloc(), [](AVFrame *ptr) { av_frame_free(&ptr); });
    ret = av_buffersink_get_frame(outAudioInfo->filterContext, avFrame.get());
    if (ret < 0) {
        return 0;
    }
    //写入前判断下frame buffer大小
    int size = av_samples_get_buffer_size(nullptr, avFrame->channels, avFrame->nb_samples,
                                          static_cast<AVSampleFormat>(avFrame->format), 1);
    //todo? 这里10000随便给了,这里需要了解下这个判断的意义
    if (size > 10000) {
        return 0;
    }
    //注意:这里要传avFrame->extended_data[0]
    std::memcpy(outBuffer, avFrame->extended_data[0], size);
    return size;
}

/**
 * 初始化过滤器图
 * @param duration
 * @return
 */
int initFilter(char *duration) {

    graph = avfilter_graph_alloc();
    if (!graph) {
        fprintf(stderr, "avfilter_graph_alloc failed\n");
        return -1;
    }

    AVFilterInOut *in = avfilter_inout_alloc();
    AVFilterInOut *out = avfilter_inout_alloc();
    char filters[512] = {0};
    char sample_format[4];
    /**
     * 1、这里的Parsed_abuffer_0,Parsed_abuffer_1。。。是自动生成的,规则为[Parsed_过滤器名_索引],索引就是第几个过滤器
     * 2、必须有abuffer和abuffersink(视频的话是buffer和buffersink)
     */
    snprintf(filters, sizeof(filters),
             "abuffer=sample_rate=%d:channels=%d:channel_layout=%s:sample_fmt=%s[input1];"//Parsed_abuffer_0
             "abuffer=sample_rate=%d:channels=%d:channel_layout=%s:sample_fmt=%s[input2];"//Parsed_abuffer_1
             "[input1][input2]amix=inputs=2:duration=%s:dropout_transition=3[amix];"//Parsed_amix_2
             "[amix]aformat=sample_rates=%d:sample_fmts=%s:channel_layouts=%s[aformat];"//Parsed_aformat_3
             "[aformat]abuffersink",//Parsed_abuffersink_4
             audioInfos[0].sample_rate,
             audioInfos[0].channels,
             audioInfos[0].channel_layout_str,
             av_get_sample_fmt_string(sample_format, 4, audioInfos[0].sample_format),
             audioInfos[1].sample_rate,
             audioInfos[1].channels,
             audioInfos[1].channel_layout_str,
             av_get_sample_fmt_string(sample_format, 4, audioInfos[1].sample_format),
             duration,
             outAudioInfo->sample_rate,
             av_get_sample_fmt_string(sample_format, 4, outAudioInfo->sample_format),
             outAudioInfo->channel_layout_str
    );

    fprintf(stdout, "filters:%s\n", filters);
    //通过字符串的方式生成过滤器图
    avfilter_graph_parse_ptr(graph, filters, &in, &out, nullptr);

    if (avfilter_graph_config(graph, nullptr) < 0) {
        fprintf(stderr, "avfilter_graph_alloc failed\n");
        return -1;
    }
    audioInfos[0].filterContext = avfilter_graph_get_filter(graph, "Parsed_abuffer_0");
    if (!audioInfos[0].filterContext) {
        fprintf(stderr, "Parsed_abuffer_1 not find!\n");
        return -1;
    }
    audioInfos[1].filterContext = avfilter_graph_get_filter(graph, "Parsed_abuffer_1");
    if (!audioInfos[1].filterContext) {
        fprintf(stderr, "Parsed_abuffer_2 not find!\n");
        return -1;
    }
    outAudioInfo->filterContext = avfilter_graph_get_filter(graph, "Parsed_abuffersink_4");
    if (!outAudioInfo->filterContext) {
        fprintf(stderr, "Parsed_abuffersink_4 not find!\n");
        return -1;
    }
    return 0;
}

int main(int argc, char **argv) {

    if (argc < 4) {
        fprintf(stderr, "args need pass 2 audio filename, and outfilename!\n");
        return 1;
    }

    uint8_t buffer[PCM1_FRAME_SIZE];
    uint8_t outBuffer[PCM1_FRAME_SIZE];


    audioInfos[0].filename = argv[1];
    audioInfos[1].filename = argv[2];

    for (auto &audioInfo: audioInfos) {
        ret = openInput(&audioInfo.second);
        if (ret != 0) {
            fprintf(stderr, "%s openInput failed!\n", audioInfo.second.filename);
            exit();
        }
        audioInfo.second.sample_rate = SAMPLE_RATE;
        audioInfo.second.channels = CHANNELS;
        audioInfo.second.channel_layout = CHANNEL_LAYOUT;
        av_get_channel_layout_string(audioInfo.second.channel_layout_str,
                                     sizeof(audioInfo.second.channel_layout_str), 0,
                                     audioInfo.second.channel_layout);
        audioInfo.second.bitPerSample = BIT_PER_SAMPLE;
        audioInfo.second.sample_format = SAMPLE_FORMAT;
    }

    outAudioInfo = std::make_shared<AudioInfo>();
    outAudioInfo->filename = argv[3];
    outAudioInfo->file = fopen(outAudioInfo->filename, "wb");
    if (!outAudioInfo->file) {
        fprintf(stderr, "%s open failed\n", outAudioInfo->filename);
        return -1;
    }
    //todo 这里采样率可以改
    outAudioInfo->sample_rate = 96000;
    outAudioInfo->channels = CHANNELS;
    outAudioInfo->channel_layout = CHANNEL_LAYOUT;
    av_get_channel_layout_string(outAudioInfo->channel_layout_str,
                                 sizeof(outAudioInfo->channel_layout_str), 0,
                                 outAudioInfo->channel_layout);
    outAudioInfo->bitPerSample = BIT_PER_SAMPLE;
    outAudioInfo->sample_format = SAMPLE_FORMAT;

    char *duration = "shortest";
    if (initFilter(duration) != 0) {
        fprintf(stderr, "initFilter failed!\n");
        exit();
    }

    size_t len1 = 0, len2 = 0;
    int size = 0;
    while (true) {
        len1 = fread(&buffer, 1, PCM1_FRAME_SIZE, audioInfos[0].file);
        if (addFrame(&audioInfos[0], buffer, len1) != 0) {
            fprintf(stderr, "%s addFrame failed!\n", audioInfos[0].filename);
            exit();
        }
        std::memset(&buffer, 0, sizeof(buffer));
        len2 = fread(&buffer, 1, PCM1_FRAME_SIZE, audioInfos[1].file);
        if (addFrame(&audioInfos[1], buffer, len2) != 0) {
            fprintf(stderr, "%s addFrame failed!\n", audioInfos[1].filename);
            exit();
        }
        if ((size = getFrame(outBuffer)) > 0) {
            fwrite(&outBuffer, 1, size, outAudioInfo->file);
        }
        if (len1 < 0 && len2 < 0) {
            exit();
            break;
        }
    }
}

项目地址

gitee.com/rong5690001…