FFmpeg音頻播放器(5)-單輸入filter(volume,atempo)

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)目地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末枫耳,一起剝皮案震驚了整個(gè)濱河市乏矾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迁杨,老刑警劉巖钻心,帶你破解...
    沈念sama閱讀 222,946評(píng)論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異铅协,居然都是意外死亡捷沸,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,336評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門狐史,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痒给,“玉大人,你說(shuō)我怎么就攤上這事骏全〔园兀” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,716評(píng)論 0 364
  • 文/不壞的土叔 我叫張陵姜贡,是天一觀的道長(zhǎng)试吁。 經(jīng)常有香客問(wèn)我,道長(zhǎng)楼咳,這世上最難降的妖魔是什么熄捍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,222評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮母怜,結(jié)果婚禮上余耽,老公的妹妹穿的比我還像新娘。我一直安慰自己苹熏,他們只是感情好碟贾,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,223評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著柜裸,像睡著了一般缕陕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上疙挺,一...
    開(kāi)封第一講書(shū)人閱讀 52,807評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音怜浅,去河邊找鬼铐然。 笑死蔬崩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搀暑。 我是一名探鬼主播沥阳,決...
    沈念sama閱讀 41,235評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼自点!你這毒婦竟也來(lái)了桐罕?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 40,189評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤桂敛,失蹤者是張志新(化名)和其女友劉穎功炮,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體术唬,經(jīng)...
    沈念sama閱讀 46,712評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薪伏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,775評(píng)論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粗仓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片嫁怀。...
    茶點(diǎn)故事閱讀 40,926評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖借浊,靈堂內(nèi)的尸體忽然破棺而出塘淑,到底是詐尸還是另有隱情,我是刑警寧澤蚂斤,帶...
    沈念sama閱讀 36,580評(píng)論 5 351
  • 正文 年R本政府宣布朴爬,位于F島的核電站,受9級(jí)特大地震影響橡淆,放射性物質(zhì)發(fā)生泄漏召噩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,259評(píng)論 3 336
  • 文/蒙蒙 一逸爵、第九天 我趴在偏房一處隱蔽的房頂上張望具滴。 院中可真熱鬧,春花似錦师倔、人聲如沸构韵。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,750評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)疲恢。三九已至,卻和暖如春瓷胧,著一層夾襖步出監(jiān)牢的瞬間显拳,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,867評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工搓萧, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留杂数,地道東北人宛畦。 一個(gè)月前我還...
    沈念sama閱讀 49,368評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像揍移,于是被迫代替她去往敵國(guó)和親次和。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,930評(píng)論 2 361