FFmpeg音頻播放器(1)-簡(jiǎn)介
FFmpeg音頻播放器(2)-編譯動(dòng)態(tài)庫(kù)
FFmpeg音頻播放器(3)-將FFmpeg加入到Android中
FFmpeg音頻播放器(4)-將mp3解碼成pcm
FFmpeg音頻播放器(5)-單輸入filter(volume,atempo)
FFmpeg音頻播放器(6)-多輸入filter(amix)
FFmpeg音頻播放器(7)-使用OpenSLES播放音頻
FFmpeg音頻播放器(8)-創(chuàng)建FFmpeg播放器
FFmpeg音頻播放器(9)-播放控制
FFmpeg另一個(gè)強(qiáng)大之處在于它實(shí)現(xiàn)了各式各樣的filter,可以將音視頻出來(lái)成不同的效果,視頻可以裁剪锹漱、縮放吨拍、旋轉(zhuǎn)画恰、合并授段、添加水印等效果辅搬,音頻可以去噪揍庄、回聲救鲤、延遲久窟、混音、變速等效果本缠。一個(gè)filter的輸出可以作為另一個(gè)filter的輸入斥扛。通過(guò)filter組合使用,我們可以定制自己想要的音視頻特效丹锹。此次分兩節(jié)講兩種音頻filter的api用法稀颁,一種是單個(gè)輸入volume(音量調(diào)節(jié)),atempo(變速),另外是多輸入filter(在下一節(jié)講到)。
單輸入filter流程
解碼出AVFrame -> abuffer-> 其他過(guò)濾器(volume)...->aformat->abuffersink->過(guò)濾后的AVFrame
這里看到有三個(gè)通用的過(guò)濾器楣黍,abuffer,aformat,abuffersink匾灶。
abuffer用于接收輸入frame,形成待處理的數(shù)據(jù)緩存,abuffersink用于傳出輸出Frame租漂,aformat過(guò)濾器約束最終的輸出格式(采樣率粘昨,聲道數(shù),存儲(chǔ)位數(shù)等)窜锯,這三個(gè)不可缺少张肾。
而中間的其他過(guò)濾器可以串聯(lián)多個(gè)filter,如volume锚扎,atempo
初始化過(guò)濾器
這里我們先要知道三個(gè)重要的結(jié)構(gòu)體
AVFilterGraph (管理所有的filter)
AVFilterContext (filter上下文)
AVFilter(具體過(guò)濾器)
具體步驟
1.注冊(cè)所有filter
avfilter_register_all();
2.創(chuàng)建AVFilterGraph,分配內(nèi)存
AVFilterGraph *graph = avfilter_graph_alloc();
3.獲取過(guò)濾器,初始化上下文
AVFilter *abuffer = avfilter_get_by_name("abuffer");
AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, abuffer, "src");
4.設(shè)置參數(shù)
avfilter_init_str(abuffer_ctx,"sample_rate=48000:sample_fmt=s16p:channel_layout=stereo");
這里參數(shù)設(shè)置還可以通過(guò)av_dict_set設(shè)置具體某個(gè)變量吞瞪,參數(shù)的說(shuō)明可以看官網(wǎng)文檔#abuffer
5.根據(jù)3.4步驟依次初始化volume,aformat,abuffersink過(guò)濾器以及上下文
6.按照處理順序依次鏈接過(guò)濾器上下文
avfilter_link(abuffer_ctx, 0, volume_ctx, 0);
avfilter_link(volume_ctx, 0, aformat_ctx, 0);
avfilter_link(aformat_ctx, 0, sink_ctx, 0);
7.配置AVFilterGraph
avfilter_graph_config(graph, NULL);
具體代碼如下
int init_volume_filter(AVFilterGraph **pGraph, AVFilterContext **src, AVFilterContext **out,
char *value) {
//初始化AVFilterGraph
AVFilterGraph *graph = avfilter_graph_alloc();
//獲取abuffer用于接收輸入端
AVFilter *abuffer = avfilter_get_by_name("abuffer");
AVFilterContext *abuffer_ctx = avfilter_graph_alloc_filter(graph, abuffer, "src");
//設(shè)置參數(shù),這里需要匹配原始音頻采樣率驾孔、數(shù)據(jù)格式(位數(shù))
if (avfilter_init_str(abuffer_ctx, "sample_rate=48000:sample_fmt=s16p:channel_layout=stereo") <
0) {
LOGE("error init abuffer filter");
return -1;
}
//初始化volume filter
AVFilter *volume = avfilter_get_by_name("volume");
AVFilterContext *volume_ctx = avfilter_graph_alloc_filter(graph, volume, "volume");
//這里采用av_dict_set設(shè)置參數(shù)
AVDictionary *args = NULL;
av_dict_set(&args, "volume", value, 0);//這里傳入外部參數(shù)芍秆,可以動(dòng)態(tài)修改
if (avfilter_init_dict(volume_ctx, &args) < 0) {
LOGE("error init volume filter");
return -1;
}
AVFilter *aformat = avfilter_get_by_name("aformat");
AVFilterContext *aformat_ctx = avfilter_graph_alloc_filter(graph, aformat, "aformat");
if (avfilter_init_str(aformat_ctx,
"sample_rates=48000:sample_fmts=s16p:channel_layouts=stereo") < 0) {
LOGE("error init aformat filter");
return -1;
}
//初始化sink用于輸出
AVFilter *sink = avfilter_get_by_name("abuffersink");
AVFilterContext *sink_ctx = avfilter_graph_alloc_filter(graph, sink, "sink");
if (avfilter_init_str(sink_ctx, NULL) < 0) {//無(wú)需參數(shù)
LOGE("error init sink filter");
return -1;
}
//鏈接各個(gè)filter上下文
if (avfilter_link(abuffer_ctx, 0, volume_ctx, 0) != 0) {
LOGE("error link to volume filter");
return -1;
}
if (avfilter_link(volume_ctx, 0, aformat_ctx, 0) != 0) {
LOGE("error link to aformat filter");
return -1;
}
if (avfilter_link(aformat_ctx, 0, sink_ctx, 0) != 0) {
LOGE("error link to sink filter");
return -1;
}
if (avfilter_graph_config(graph, NULL) < 0) {
LOGI("error config filter graph");
return -1;
}
*pGraph = graph;
*src = abuffer_ctx;
*out = sink_ctx;
LOGI("init filter success...");
return 0;
}
這里通過(guò)傳參數(shù)value,來(lái)動(dòng)態(tài)修改音量
使用過(guò)濾器
使用方法很簡(jiǎn)單翠勉,將解碼后的AVFrame通過(guò)av_buffersrc_add_frame(abuffer_ctx,frame)加入到輸入過(guò)濾器上下文abuffer_ctx中妖啥,通過(guò)av_buffersink_get_frame(sink_ctx,frame)獲取處理完成的frame。
代碼如下
AVFilterGraph *graph;
AVFilterContext *in_ctx;
AVFilterContext *out_ctx;
//注冊(cè)所有過(guò)濾器
avfilter_register_all();
init_volume_filter(&graph, &in_ctx, &out_ctx, "0.5");
//初始化
while (av_read_frame(fmt_ctx, packet) == 0) {//將音頻數(shù)據(jù)讀入packet
if (packet->stream_index == audio_stream_index) {//取音頻索引packet
... 解碼音頻
if (got_frame > 0) {
LOGI("decode frame:%d", index++);
if (index == 1000) {//模擬動(dòng)態(tài)修改音量
init_volume_filter(&graph, &in_ctx, &out_ctx, "0.01");
}
if (index == 2000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
}
if (index == 3000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "0.01");
}
if (index == 4000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
}
if (av_buffersrc_add_frame(in_ctx, frame) < 0) {//將frame放入輸入filter上下文
LOGE("error add frame");
break;
}
while (av_buffersink_get_frame(out_ctx, frame) >= 0) {//從輸出filter上下文中獲取frame
fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]),
out_file); //想將單個(gè)聲道pcm數(shù)據(jù)寫(xiě)入文件
}
}
}
}
最終解碼出來(lái)pcm和原始mp3波形對(duì)比
可以明顯看出音量已經(jīng)發(fā)生變化对碌。
在播放音頻時(shí)荆虱,可以聽(tīng)見(jiàn)有一些噪聲,需要swr_convert來(lái)重新采樣朽们,取出完整的pcm數(shù)據(jù)怀读。
//初始化SwrContext
SwrContext *swr_ctx = swr_alloc();
enum AVSampleFormat in_sample = codec_ctx->sample_fmt;
enum AVSampleFormat out_sample = AV_SAMPLE_FMT_S16;
int inSampleRate = codec_ctx->sample_rate;
int outSampleRate = inSampleRate;
uint64_t in_ch_layout = codec_ctx->channel_layout;
uint64_t outChannelLayout = AV_CH_LAYOUT_STEREO;
swr_alloc_set_opts(swr_ctx, outChannelLayout, out_sample, outSampleRate, in_ch_layout, in_sample,
inSampleRate, 0, NULL);
swr_init(swr_ctx);
int out_ch_layout_nb = av_get_channel_layout_nb_channels(out_ch_layout);//聲道個(gè)數(shù)
uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_SIZE);//重采樣數(shù)據(jù)
寫(xiě)入pcm數(shù)據(jù)之前,用swr_convert重新采樣一下
while (av_buffersink_get_frame(out_ctx, frame) >= 0) {//從輸出filter上下文中獲取frame
// fwrite(frame->data[0], 1, static_cast<size_t>(frame->linesize[0]),
// out_file); //想將單個(gè)聲道pcm數(shù)據(jù)寫(xiě)入文件
swr_convert(swr_ctx, &out_buffer, MAX_AUDIO_SIZE,
(const uint8_t **) frame->data, frame->nb_samples);
int out_size = av_samples_get_buffer_size(NULL,out_ch_layout_nb,frame->nb_samples,out_sample_fmt,0);
fwrite(out_buffer,1,out_size,out_file);
}
這次我們寫(xiě)入的是完整2個(gè)聲道的數(shù)據(jù)骑脱,而且也沒(méi)有噪聲了菜枷。
使用變速過(guò)濾器atempo給音頻變速
將volumefilter改成atempo,注意參數(shù)設(shè)置名為tempo(沒(méi)有a,不知道為什么)
//初始化volume filter
AVFilter *volume = avfilter_get_by_name("atempo");
AVFilterContext *volume_ctx = avfilter_graph_alloc_filter(graph, volume, "atempo");
//這里采用av_dict_set設(shè)置參數(shù)
AVDictionary *args = NULL;
av_dict_set(&args, "tempo", value, 0);//調(diào)節(jié)音量為原先的一半
if (avfilter_init_dict(volume_ctx, &args) < 0) {
LOGE("error init volume filter");
return -1;
}
解碼時(shí)模擬動(dòng)態(tài)修改速度改成如下
if (index == 1000) {//模擬動(dòng)態(tài)修改音量
init_volume_filter(&graph, &in_ctx, &out_ctx, "1.0");
}
if (index == 2000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "0.8");
}
if (index == 3000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "1.5");
}
if (index == 4000) {
init_volume_filter(&graph, &in_ctx, &out_ctx, "2.0");
}
成功后叁丧,就可以一個(gè)不同速度的音頻啤誊,使用Audition打開(kāi)岳瞭,選擇48000,2通道播放蚊锹,可以聽(tīng)出它先是按照0.5,1.0,0.8,1.5,2.0的播放的寝优,而且音調(diào)保持不變,沒(méi)有因?yàn)樗俣鹊母淖兌兏呋蛘咦兊汀?br> 項(xiàng)目地址