Android NDK開發(fā)之旅34--FFmpeg音頻解碼

Android NDK開發(fā)之旅 目錄

前言

基于Android NDK開發(fā)之旅33--FFmpeg視頻播放這篇文章杉允,我們已經(jīng)學(xué)會視頻解碼基本過程匾七。這篇文章就對音頻解碼進行分析惠猿。
音頻解碼和視頻解碼的套路基本是一樣的, 否則怎么會做到音視頻同步播放呢?


1.FFmpeg音視解碼過程分析

參考視頻解碼過程媚送,得到音頻解碼過程


參考視頻解碼過程

1.1.注冊所有組件

av_register_all();

這個函數(shù)刃泌,可以注冊所有支持的容器和對應(yīng)的codec弄慰。

1.2.打開輸入音頻文件

AVFormatContext *pFormatCtx = avformat_alloc_context();
avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) 

1.3.獲取音頻文件信息

avformat_find_stream_info(pFormatCtx, NULL)
    //獲取音頻流索引位置
    int i = 0, audio_stream_idx = -1;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_idx = i;
            break;
        }
    }
    if (audio_stream_idx == -1)
    {
        LOGI("%s", "找不到音頻流");
        return;
    }

1.4.根據(jù)編解碼上下文中的編碼id查找對應(yīng)的解碼器

    //獲取解碼器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_idx]->codec;
    AVCodec *codec = avcodec_find_decoder(pCodeCtx->codec_id);

1.5.打開解碼器

avcodec_open2(pCodeCtx, codec, NULL)

來打開解碼器厕妖,AVFormatContext首尼、AVStream、AVCodecContext叹放、AVCodec四者之間的關(guān)系為


1.6.配置音頻參數(shù)

    //輸入采樣率格式
    enum AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
    //輸出采樣率格式16bit PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //輸入采樣率
    int in_sample_rate = pCodeCtx->sample_rate;
    //輸出采樣率
    int out_sample_rate = 44100;
    //獲取輸入的聲道布局
    //根據(jù)聲道個數(shù)獲取默認的聲道布局(2個聲道饰恕,默認立體聲)
    //av_get_default_channel_layout(pCodeCtx->channels);
    uint64_t in_ch_layout = pCodeCtx->channel_layout;
    //輸出的聲道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;

    swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);

    swr_init(swrCtx);

1.7. 一幀一幀讀取壓縮的音頻數(shù)據(jù)AVPacket

while (av_read_frame(pFormatCtx, packet) >= 0) {
省略...
}

1.8.解碼一幀音頻數(shù)據(jù)AVPacket->AVFrame

avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet)

2.關(guān)鍵代碼

VideoUtils.class
package com.haocai.ffmpegtest;

public class VideoUtils {

    //音頻解碼
    public native void audioDecode(String input,String output);

    static{
        System.loadLibrary("avutil-54");
        System.loadLibrary("swresample-1");
        System.loadLibrary("avcodec-56");
        System.loadLibrary("avformat-56");
        System.loadLibrary("swscale-3");
        System.loadLibrary("postproc-53");
        System.loadLibrary("avfilter-5");
        System.loadLibrary("avdevice-56");
        System.loadLibrary("myffmpeg");
    }
}

MainActivity.class
    /**
     * 音頻解碼
     */
    public void doAudioDecode(){
        String input = new File(Environment.getExternalStorageDirectory(),"說散就散.mp3").getAbsolutePath();
        String output = new File(Environment.getExternalStorageDirectory(),"說散就散.pcm").getAbsolutePath();
        VideoUtils player = new VideoUtils();
        player.audioDecode(input, output);
        Toast.makeText(this,"正在解碼...",Toast.LENGTH_SHORT).show();
    }
ffmpeg_voicer.c
#include <com_haocai_ffmpegtest_VideoUtils.h>
#include <android/log.h>
#include <android/native_window_jni.h>
#include <android/native_window.h>
#include <stdio.h>
//解碼
#include "include/libavcodec/avcodec.h"
//封裝格式處理
#include "include/libavformat/avformat.h"
//像素處理
#include "include/libswscale/swscale.h"
//重采樣
#include "include/libswresample/swresample.h"


#define  LOG_TAG    "ffmpegandroidplayer"
#define  LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
#define  LOGD(FORMAT,...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,FORMAT, ##__VA_ARGS__)

//音頻解碼 采樣率 新版版可達48000 * 4
#define MAX_AUDIO_FRME_SIZE  2 * 44100

//音頻解碼
JNIEXPORT void JNICALL Java_com_haocai_ffmpegtest_VideoUtils_audioDecode
(JNIEnv *env, jobject jobj, jstring input_jstr, jstring output_jstr) {
    const char* input_cstr = (*env)->GetStringUTFChars(env, input_jstr, NULL);
    const char* output_cstr = (*env)->GetStringUTFChars(env, output_jstr, NULL);
    LOGI("%s", "init");
    //注冊組件
    av_register_all();
    AVFormatContext *pFormatCtx = avformat_alloc_context();
    //打開音頻文件
    if (avformat_open_input(&pFormatCtx, input_cstr, NULL, NULL) != 0) {
        LOGI("%s", "無法打開音頻文件");
        return;
    }
    //獲取輸入文件信息
    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        LOGI("%s", "無法獲取輸入文件信息");
        return;
    }
    //獲取音頻流索引位置
    int i = 0, audio_stream_idx = -1;
    for (; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) {
            audio_stream_idx = i;
            break;
        }
    }
    if (audio_stream_idx == -1)
    {
        LOGI("%s", "找不到音頻流");
        return;
    }
    //獲取解碼器
    AVCodecContext *pCodeCtx = pFormatCtx->streams[audio_stream_idx]->codec;
    AVCodec *codec = avcodec_find_decoder(pCodeCtx->codec_id);
    if (codec == NULL) {
        LOGI("%s", "無法獲取加碼器");
        return;
    }
    //打開解碼器
    if (avcodec_open2(pCodeCtx, codec, NULL) < 0) {
        LOGI("%s", "無法打開解碼器");
        return;
    }

    //壓縮數(shù)據(jù)
    AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    //解壓縮數(shù)據(jù)
    AVFrame *frame = av_frame_alloc();
    //frame->16bit  44100 PCM 統(tǒng)一音頻采樣格式與采樣率
    SwrContext *swrCtx = swr_alloc();
    //重采樣設(shè)置參數(shù)--------------start
    //輸入采樣率格式
    enum AVSampleFormat in_sample_fmt = pCodeCtx->sample_fmt;
    //輸出采樣率格式16bit PCM
    enum AVSampleFormat out_sample_fmt = AV_SAMPLE_FMT_S16;
    //輸入采樣率
    int in_sample_rate = pCodeCtx->sample_rate;
    //輸出采樣率
    int out_sample_rate = 44100;
    //獲取輸入的聲道布局
    //根據(jù)聲道個數(shù)獲取默認的聲道布局(2個聲道,默認立體聲)
    //av_get_default_channel_layout(pCodeCtx->channels);
    uint64_t in_ch_layout = pCodeCtx->channel_layout;
    //輸出的聲道布局
    uint64_t out_ch_layout = AV_CH_LAYOUT_STEREO;


    swr_alloc_set_opts(swrCtx, out_ch_layout, out_sample_fmt, out_sample_rate, in_ch_layout, in_sample_fmt, in_sample_rate, 0, NULL);


    swr_init(swrCtx);

    //獲取輸入輸出的聲道個數(shù)
    int out_channel_nb = av_get_channel_layout_nb_channels(out_ch_layout);
    LOGI("out_count:%d", out_channel_nb);
    //重采樣設(shè)置參數(shù)--------------end

    //16bit 44100 PCM 數(shù)據(jù)
    uint8_t *out_buffer = (uint8_t *)av_malloc(MAX_AUDIO_FRME_SIZE);

    FILE *fp_pcm = fopen(output_cstr, "wb");
    int got_frame = 0, framecount = 0, ret;
    //6.一幀一幀讀取壓縮的音頻數(shù)據(jù)AVPacket
    while (av_read_frame(pFormatCtx, packet) >= 0) {
        if (packet->stream_index == audio_stream_idx) {
            //解碼
            ret = avcodec_decode_audio4(pCodeCtx, frame, &got_frame, packet);

            if (ret < 0) {
                LOGI("%s", "解碼完成");
                break;
            }
            //非0井仰,正在解碼
            if (got_frame > 0) {
                LOGI("解碼:%d", framecount++);
                swr_convert(swrCtx, &out_buffer, MAX_AUDIO_FRME_SIZE, frame->data, frame->nb_samples);
                //獲取sample的size
                int out_buffer_size = av_samples_get_buffer_size(NULL, out_channel_nb, frame->nb_samples, out_sample_fmt, 1);


                fwrite(out_buffer, 1, out_buffer_size, fp_pcm);

            }
        }
        av_free_packet(packet);
    }
    fclose(fp_pcm);
    av_frame_free(&frame);
    av_free(out_buffer);
    swr_free(&swrCtx);
    avcodec_close(pCodeCtx);
    avformat_close_input(&pFormatCtx);

    (*env)->ReleaseStringUTFChars(env, input_jstr, input_cstr);
    (*env)->ReleaseStringUTFChars(env, output_jstr, output_cstr);


}
說明:其它視頻格式也支持

3.輸出結(jié)果

3.1Log輸出

12-12 14:23:40.733 15985-15985/com.haocai.ffmpegtest I/ffmpegandroidplayer: init
12-12 14:23:40.803 15985-15985/com.haocai.ffmpegtest I/ffmpegandroidplayer: out_count:2
12-12 14:23:40.843 15985-15985/com.haocai.ffmpegtest I/ffmpegandroidplayer: 解碼:0
12-12 14:23:40.843 15985-15985/com.haocai.ffmpegtest I/ffmpegandroidplayer: 解碼:1
12-12 14:23:40.843 15985-15985/com.haocai.ffmpegtest I/ffmpegandroidplayer: 解碼:2

3.1.mp3格式解碼生成.pcm格式數(shù)據(jù)

源碼下載

Github:https://github.com/kpioneer123/FFmpegTest

特別感謝:

CrazyDiode

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市破加,隨后出現(xiàn)的幾起案子俱恶,更是在濱河造成了極大的恐慌,老刑警劉巖范舀,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件合是,死亡現(xiàn)場離奇詭異,居然都是意外死亡锭环,警方通過查閱死者的電腦和手機聪全,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辅辩,“玉大人难礼,你說我怎么就攤上這事∶捣妫” “怎么了蛾茉?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撩鹿。 經(jīng)常有香客問我谦炬,道長,這世上最難降的妖魔是什么节沦? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任键思,我火速辦了婚禮,結(jié)果婚禮上甫贯,老公的妹妹穿的比我還像新娘吼鳞。我一直安慰自己,他們只是感情好获搏,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布赖条。 她就那樣靜靜地躺著失乾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪纬乍。 梳的紋絲不亂的頭發(fā)上碱茁,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音仿贬,去河邊找鬼纽竣。 笑死,一個胖子當著我的面吹牛茧泪,可吹牛的內(nèi)容都是我干的蜓氨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼队伟,長吁一口氣:“原來是場噩夢啊……” “哼穴吹!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起嗜侮,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤港令,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后锈颗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顷霹,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年击吱,在試婚紗的時候發(fā)現(xiàn)自己被綠了淋淀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡覆醇,死狀恐怖朵纷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叫乌,我是刑警寧澤柴罐,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站憨奸,受9級特大地震影響革屠,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜排宰,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一似芝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧板甘,春花似錦党瓮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽呛谜。三九已至,卻和暖如春枪萄,著一層夾襖步出監(jiān)牢的瞬間隐岛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工瓷翻, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留聚凹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓齐帚,卻偏偏與公主長得像妒牙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子对妄,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容