如何使用libavfilter库给pcm音频采样数据添加音频滤镜?
一.初始化音频滤镜
初始化音频滤镜的方法基本上和初始化视频滤镜的方法相同,不懂的可以看上篇博客,这里直接给出代码:
//audio_filter_core.cpp
#define INPUT_SAMPLERATE 44100
#define INPUT_FORMAT AV_SAMPLE_FMT_FLTP
#define INPUT_CHANNEL_LAYOUT AV_CH_LAYOUT_STEREO
static AVFilterGraph *filter_graph;
static AVFilterContext *abuffersrc_ctx;
static AVFilterContext *volume_ctx;
static AVFilterContext *aformat_ctx;
static AVFilterContext *abuffersink_ctx;
static AVFrame *input_frame= nullptr,*output_frame= nullptr;
int32_t init_audio_filter(const char *volume_factor){
int32_t result=0;
char ch_layout[64];
char options_str[1024];
AVDictionary *options_dict= nullptr;
//创建滤镜图
filter_graph=avfilter_graph_alloc();
if(!filter_graph){
cerr<<"Error:Unable to create filter graph."<<endl;
return -1;
}
//创建abuffer滤镜
const AVFilter *abuffer= avfilter_get_by_name("abuffer");
if(!abuffer){
cerr<<"Error:Could not find abuffer filter."<<endl;
return -1;
}
abuffersrc_ctx= avfilter_graph_alloc_filter(filter_graph,abuffer,"src");
if(!abuffersrc_ctx){
cerr<<"Error:could not allocate the abuffer instance."<<endl;
return -1;
}
av_get_channel_layout_string(ch_layout,sizeof(ch_layout),0,INPUT_CHANNEL_LAYOUT);
av_opt_set(abuffersrc_ctx,"channel_layout",ch_layout,AV_OPT_SEARCH_CHILDREN);
av_opt_set(abuffersrc_ctx,"sample_fmt",av_get_sample_fmt_name(INPUT_FORMAT),AV_OPT_SEARCH_CHILDREN);
av_opt_set_q(abuffersrc_ctx,"time_base",(AVRational){1,INPUT_SAMPLERATE},AV_OPT_SEARCH_CHILDREN);
av_opt_set_int(abuffersrc_ctx,"sample_rate",INPUT_SAMPLERATE,AV_OPT_SEARCH_CHILDREN);
result= avfilter_init_str(abuffersrc_ctx, nullptr);
if(result<0){
cerr<<"Error:could not initialize the abuffer filter."<<endl;
return -1;
}
//创建volume滤镜
const AVFilter *volume= avfilter_get_by_name("volume");
if(!volume){
cerr<<"Error:could not find volume filter."<<endl;
return -1;
}
volume_ctx= avfilter_graph_alloc_filter(filter_graph,volume,"volume");
if(!volume_ctx){
cerr<<"Error:could not allocate volume filter instance."<<endl;
return -1;
}
av_dict_set(&options_dict,"volume",volume_factor,0);
result= avfilter_init_dict(volume_ctx,&options_dict);
av_dict_free(&options_dict);
if(result<0){
cerr<<"Error:could not initialize volume filter instance."<<endl;
return -1;
}
//创建aformat滤镜
const AVFilter *aformat=avfilter_get_by_name("aformat");
if(!aformat){
cerr<<"Error:could not find aformat filter."<<endl;
return -1;
}
aformat_ctx= avfilter_graph_alloc_filter(filter_graph,aformat,"aformat");
if(!aformat_ctx){
cerr<<"Error:could not allocate aformat filter instance."<<endl;
return -1;
}
snprintf(options_str,sizeof(options_str),"sample_fmts=%s:sample_rates=%d:channel_layouts=stereo",av_get_sample_fmt_name(AV_SAMPLE_FMT_S16),44100);
result= avfilter_init_str(aformat_ctx,options_str);
if(result<0){
cerr<<"Error:could not initialize aformat filter."<<endl;
return -1;
}
//创建abuffersink滤镜
const AVFilter *abuffersink= avfilter_get_by_name("abuffersink");
if(!abuffersink){
cerr<<"Error:could not find abuffersink filter."<<endl;
return -1;
}
abuffersink_ctx= avfilter_graph_alloc_filter(filter_graph,abuffersink,"sink");
if(!abuffersink_ctx){
cerr<<"Error:could not allocate abuffersink filter instance."<<endl;
return -1;
}
result= avfilter_init_str(abuffersink_ctx, nullptr);
if(result<0){
cerr<<"Error:could not initialize abuffersink filter."<<endl;
return -1;
}
//连接创建好的滤镜
result=avfilter_link(abuffersrc_ctx,0,volume_ctx,0);
if(result>=0){
result=avfilter_link(volume_ctx,0,aformat_ctx,0);
}
if(result>=0){
result=avfilter_link(aformat_ctx,0,abuffersink_ctx,0);
}
if(result<0){
fprintf(stderr,"Error connecting filters\n");
return -1;
}
//配置滤镜图
result=avfilter_graph_config(filter_graph, nullptr);
if(result<0){
cerr<<"Error:Error configuring the filter graph."<<endl;
return -1;
}
//创建输入输出帧
input_frame=av_frame_alloc();
output_frame=av_frame_alloc();
if(!input_frame||!output_frame){
cerr<<"Error:frame allocation failed."<<endl;
return -1;
}
return 0;
}
二.初始化输入音频帧
在这一步需要给输入音频帧设置一些参数,包括采样率,采样点个数,声道布局,音频帧格式等,然后就可以给音频帧分配内存空间了。代码如下:
//audio_filter_core.cpp
static int32_t init_frames(){
int result=0;
input_frame->sample_rate=44100;
input_frame->format=AV_SAMPLE_FMT_FLTP;
input_frame->channel_layout=AV_CH_LAYOUT_STEREO;
input_frame->nb_samples=1024;
result= av_frame_get_buffer(input_frame,0);
if(result<0){
cerr<<"Error:av_frame_get_buffer failed."<<endl;
return -1;
}
result= av_frame_make_writable(input_frame);
if(result<0){
cerr<<"Error:av_frame_make_writable failed."<<endl;
return -1;
}
return 0;
}
三.循环编辑音频帧
在这一步需要注意的是,每次将输入音频帧放入滤镜图前,都要做一次初始化音频帧操作,否则会报错:filter context - fmt: fltp r: 44100 layout: 3 ch: 2, incoming frame - fmt: (null) r: 0 layout: 3 ch: 2 pts_time: NOPTS【Changing audio frame properties on the fly is not supported.】。注意一定是每次,不要只初始化一次,这样只有第一帧初始化了,后面的帧还是会报错,因为输入帧的格式要和滤镜上下文保持一致,如果没有每次都初始化,后面的帧的格式和采样率就识别不到,为null了。下面给出代码:
//audio_filter_core.cpp
static int32_t filter_frame(){
int32_t result= av_buffersrc_add_frame(abuffersrc_ctx,input_frame);
if(result<0){
cerr<<"Error:add frame to buffersrc failed."<<endl;
return -1;
}
while(1){
result=av_buffersink_get_frame(abuffersink_ctx,output_frame);
if(result==AVERROR(EAGAIN)||result==AVERROR_EOF){
return 1;
}
else if(result<0){
cerr<<"Error:av_buffersink_get_frame failed."<<endl;
return -1;
}
cout<<"Output channels:"<<output_frame->channels<<",nb_samples:"<<output_frame->nb_samples<<",sample_fmt:"<<output_frame->format<<endl;
write_samples_to_pcm2(output_frame);
av_frame_unref(output_frame);
}
return 0;
}
int32_t audio_filtering(){
int32_t result=0;
while(!end_of_input_file()){
init_frames();//每次都要执行
result=read_pcm_to_frame2(input_frame,INPUT_FORMAT,2);
if(result<0){
cerr<<"Error:read_pcm_to_frame2 failed."<<endl;
return -1;
}
result=filter_frame();
if(result<0){
cerr<<"Error:filter_frame failed."<<endl;
return -1;
}
}
return 0;
}
四.将编辑后的数据写入输出文件
在这一步需要注意的是,由于在滤镜图中有一个滤镜实例将音频帧的采样格式设置为了AV_SAMPLE_FMT_S16,这是packed格式的帧,左右声道的数据交错存储在frame->data[0]指向的内存单元中,所以在写入的时候,需要注意这一点。下面给出代码:
//io_data.cpp
int32_t write_samples_to_pcm2(AVFrame* frame){
int16_t* samples = reinterpret_cast<int16_t*>(frame->data[0]);
int dataSize = frame->nb_samples * frame->channels * sizeof(int16_t);
fwrite(samples, 1, dataSize, output_file);
return 0;
}
数据读入代码:
//io_data.cpp
static FILE* output_file= nullptr;
int32_t read_pcm_to_frame2(AVFrame *frame,enum AVSampleFormat sample_fmt,int channels){
int data_size= av_get_bytes_per_sample(sample_fmt);
if(data_size<0){
cerr<<"Error:Failed to calculate data size."<<endl;
return -1;
}
for(int i=0;i<frame->nb_samples;i++){
for(int ch=0;ch<channels;ch++){
fread(frame->data[ch]+i*data_size,1,data_size,input_file);
}
}
return 0;
}
int32_t open_input_output_files(const char* input_name,const char* output_name){
if(strlen(input_name)==0||strlen(output_name)==0){
cout<<"Error:empty input or output file name."<<endl;
return -1;
}
close_input_output_files();
input_file=fopen(input_name,"rb");//rb:读取一个二进制文件,该文件必须存在
if(input_file==nullptr){
cerr<<"Error:failed to open input file."<<endl;
return -1;
}
output_file=fopen(output_name,"wb");//wb:打开或新建一个二进制文件,只允许写
if(output_file== nullptr){
cout<<"Error:failed to open output file."<<endl;
return -1;
}
return 0;
}
void close_input_output_files(){
if(input_file!= nullptr){
fclose(input_file);
input_file= nullptr;
}
if(output_file!= nullptr){
fclose(output_file);
output_file= nullptr;
}
}
int32_t end_of_input_file(){
return feof(input_file);
}
五.销毁资源
audio_filter_core.cpp
static void free_frames(){
av_frame_free(&input_frame);
av_frame_free(&output_frame);
}
void destroy_audio_filter(){
free_frames();
avfilter_graph_free(&filter_graph);
}
六.main函数实现
int main(){
const char *input_file_name="../input.pcm";
const char *output_file_name="../output.pcm";
const char *volume_factor="0.9";
int32_t result=0;
result= open_input_output_files(input_file_name,output_file_name);
if(result<0){
return -1;
}
result= init_audio_filter(volume_factor);
if(result<0){
return -1;
}
result=audio_filtering();
if(result<0){
return -1;
}
destroy_audio_filter();
close_input_output_files();
return 0;
}
最后,可以使用下面的指令测试输出的pcm文件:
ffplay -ac 2 -ar 44100 -f s16le -i output.pcm
如何使用libavfilter库给pcm音频采样数据添加音频滤镜?的更多相关文章
- 视音频数据处理入门:PCM音频采样数据处理
===================================================== 视音频数据处理入门系列文章: 视音频数据处理入门:RGB.YUV像素数据处理 视音频数据处理 ...
- 音频采样中left-or right-justified(左对齐,右对齐), I2S时钟关系
音频采样中left-or right-justified(左对齐,右对齐), I2S时钟关系 原创 2014年02月11日 13:56:51 4951 0 0 刚刚过完春节,受假期综合症影响脑袋有点发 ...
- Java中解析wav音频文件信息:音频声道数,采样频率,采样位数、声音尺寸
前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 音频解析方法: public static int toInt(byte[] b) { return ((b[3] << 2 ...
- WebRTC 音频采样算法 附完整C++示例代码
之前有大概介绍了音频采样相关的思路,详情见<简洁明了的插值音频重采样算法例子 (附完整C代码)>. 音频方面的开源项目很多很多. 最知名的莫过于谷歌开源的WebRTC, 其中的音频模块就包 ...
- 常用音频协议介绍&&有关音频编码的知识与技术参数
(转载)常用音频协议介绍 会议电视常用音频协议介绍及对比白皮书 一.数字化音频原理:声音其实是一种能量波,因此也有频率和振幅的特征,频率对应于时间轴线,振幅对应于电平轴线.通常人耳可以听到的频率在20 ...
- iOS从零开始学习直播之音频1.播放本地音频文件
现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...
- sqlserver 多库查询 sp_addlinkedserver使用方法(添加链接服务器)
sqlserver 多库查询 sp_addlinkedserver使用方法(添加链接服务器) 我们日常使用SQL Server数据库时,经常遇到需要在实例Instance01中跨实例访问Instanc ...
- iOS 直播-获取音频(视频)数据
iOS 直播-获取音频(视频)数据 // // ViewController.m // capture-test // // Created by caoxu on 16/6/3. // Copyri ...
- HI3516EV100 RTMP添加音频
1.先试试只发音频 失败 2.保存音频,发送视频,成功
- Android Volley 库通过网络获取 JSON 数据
本文内容 什么是 Volley 库 Volley 能做什么 Volley 架构 环境 演示 Volley 库通过网络获取 JSON 数据 参考资料 Android 关于网络操作一般都会介绍 HttpC ...
随机推荐
- [工具/IDE]IDEA常用效率插件
0 代码规范性检查 Alibaba Java Coding Guidelines 1 maven Maven Helper / Maven Wrapper support 2 mybatis Free ...
- [Windows/Linux]判别服务器: 虚拟机 | 物理机 ?
物理主机,一般称: [宿主机] 虚拟机信息,一般涉及如下关键词: VMware : VMware 虚拟化技术 Vistualbox KVM(Kernel-based Virtual Machine): ...
- 6.Get和Post
1.概述 URL是一个资源描述符,一个URL用于描述一个网上资源 Get用于获取/查询资源信息,Post用于更新资源信息 2.联系和区别 2.1.Get后退刷新无害,Post需要重新提交: 2.2.G ...
- 四月十五号java基础知识
1.今天下午做了一个题感受很深,自己做题没有思路或者有点思路死磕也没有搞清楚,看起来很简单的问题,在我手里很难 做咯许久还是室友帮忙解决的,后面重新打一遍还是出问题,找他解决的,问了问他我自己的问题, ...
- 【Voyage】GDOI 2023 旅游记 || ECHO.
\(\color{#FFFFFF}{那是什么样的旅途呢}\) \(\color{#FFFFFF}{真的会害怕很多东西呢.想想害怕的其实不止这样一件事,便产生了"其实都一样没关系的,都应该踏过 ...
- 新手小白需要了解的 Go 基础细节杂谈
今日记录一下 学习 golang 这门语言遇到的一些比较特殊的细节,供大家参考. 所以,在我们输出内容的时候,可以包含很多的非 ASCII 码字符.实际上,Go 是天生支持 UTF-8 的,任何字符都 ...
- wpf RelativeSource绑定
RelativeSource有四种类型 Self FindAncestor TemplatedParent PreviousData a.Self Self用于绑定源和绑定目标相同的场景中.对象的一个 ...
- Java并发(二)----初次使用多线程并行提高效率
1.并行 并行代表充分利用多核 cpu 的优势,提高运行效率. 想象下面的场景,执行 3 个计算,最后将计算结果汇总. 计算 1 花费 10 ms 计算 2 花费 11 ms 计算 3 花费 ...
- 快速上手Linux核心命令(六):Linux的文本编辑器vi和vim
@ 目录 前言 简介 小试牛刀 vi/vim 工作原理及三种模式 常用快捷键 命令行图解 前言 上一篇中已经预告,我们这篇主要说Linux中vi/vim 编辑器.它是我们使用Linux系统不可缺少的工 ...
- abp(net core)+easyui+efcore实现仓储管理系统——供应商管理升级之上(六十三)
abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统--ABP总体介绍(一) abp(net core)+ ...