視頻編解碼學(xué)習(xí)二 pcm

??今天的主題是音頻解碼,主要實(shí)現(xiàn)將視頻中的音頻解碼為音頻采用格式pcm。
首先在文章開頭給出兩個(gè)問(wèn)題

第一個(gè)問(wèn)題:音頻解碼和視頻解碼目的是什么萍肆?為什么要進(jìn)行音頻壓縮或者是視頻壓縮卜壕?
目的:壓縮音頻流、視頻流蛔六、字幕流等等…(減小數(shù)據(jù)量)

第二個(gè)問(wèn)題:音頻采樣數(shù)據(jù)作用?
保存音頻中每一個(gè)采樣點(diǎn)的值

我們來(lái)計(jì)算2分鐘pcm音頻的大小:
規(guī)定:采樣率:44100HZ
在圖像學(xué)中:每8位 = 1字節(jié)
編碼(采樣精度):16位 = 2字節(jié)
聲道數(shù)量:2個(gè)
pcm格式體積 = 2 * 60 * 44100 * 2 * 2 = 21MB
mp3 = 2MB

從計(jì)算過(guò)程就可以看出废亭,實(shí)際情況必須采用壓縮來(lái)存儲(chǔ)音頻国章。

那么pcm有哪些格式,我們平常說(shuō)的單聲道豆村,雙聲道又是什么回事液兽?這里又分兩種情況
第一種:單聲道(左右聲道)
第二種:雙聲道(排版順序"左右","左右")
二者都是采樣點(diǎn)順序排版存儲(chǔ)


image.png

那么音頻解碼的環(huán)節(jié)又是怎樣的過(guò)程呢,參照官方給出的流程圖

FFmpeg解碼

從上圖我們可以看出音頻解碼流程
第一步:注冊(cè)所有的組件(編解碼掌动、濾鏡特效處理庫(kù)四啰、封裝格式處理庫(kù)、工具庫(kù)粗恢、音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)柑晒、視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換等等...)
第二步:獲取音頻封裝格式信息
第三步:查找音頻流
第四步:查找音頻解碼器
第五步:打開音頻解碼器
第六步:讀取音頻壓縮數(shù)據(jù)進(jìn)行解碼(循環(huán)解碼)
第七步:關(guān)閉音頻解碼器釋放內(nèi)存

源碼

#include <jni.h>
#include <string>
//導(dǎo)入android-log日志
#include <android/log.h>

//當(dāng)前C++兼容C語(yǔ)言
extern "C"{
//avcodec:編解碼(最重要的庫(kù))
#include "libavcodec/avcodec.h"
//avformat:封裝格式處理
#include "libavformat/avformat.h"
//avutil:工具庫(kù)(大部分庫(kù)都需要這個(gè)庫(kù)的支持)
#include "libavutil/imgutils.h"
//swscale:視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換
#include "libswscale/swscale.h"
//導(dǎo)入音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)
#include "libswresample/swresample.h"

JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegTest
        (JNIEnv *, jobject);
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoder
        (JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath);
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoderAudio
        (JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath);
}

//1、NDK音視頻編解碼:FFmpeg-測(cè)試配置
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegTest(
        JNIEnv *env, jobject jobj) {
    //(char *)表示C語(yǔ)言字符串
    const char *configuration = avcodec_configuration();
    __android_log_print(ANDROID_LOG_INFO,"main","%s",configuration);
}

//NDK音視頻編解碼:FFmpeg-音頻解碼-音頻采樣數(shù)據(jù)pcm格式
JNIEXPORT void JNICALL Java_com_samychen_ffmpeg_FFmpegTest_ffmpegDecoderAudio
        (JNIEnv *env, jobject jobj,jstring jInFilePath,jstring jOutFilePath) {

    //第一步:注冊(cè)所有的組件
    // (編解碼眷射、濾鏡特效處理庫(kù)敦迄、封裝格式處理庫(kù)、工具庫(kù)凭迹、音頻采樣數(shù)據(jù)格式轉(zhuǎn)換庫(kù)罚屋、視頻像素?cái)?shù)據(jù)格式轉(zhuǎn)換等等...)
    av_register_all();
    avcodec_register_all();
    //第二步:獲取音頻封裝格式信息
    AVFormatContext *avformat_context = avformat_alloc_context();
    //參數(shù)一:封裝格式上下文->保存了音頻信息
    //參數(shù)二:輸入文件(你要對(duì)那一個(gè)文件進(jìn)行解封裝)
    //普及知識(shí):env是JNI環(huán)境指針(作用:專門用于管理對(duì)象創(chuàng)建和銷毀)
    const char *cInFilePath = env->GetStringUTFChars(jInFilePath, NULL);
    //參數(shù)三:封裝格式類型(NULL:表示系統(tǒng)自動(dòng)獲取格式類型)
    //返回值:avformat_open_input_result = 0表示成功,否則失敗
    int avformat_open_input_result = avformat_open_input(&avformat_context, cInFilePath, NULL,
                                                         NULL);
    if (avformat_open_input_result != 0) {
        char *error_info;
        av_strerror(avformat_open_input_result, error_info, 1024);
        __android_log_print(ANDROID_LOG_INFO, "main", "獲取失敗嗅绸,錯(cuò)誤信息:%s", error_info);
        return;
    }

    //第三步:查找音頻流
    //返回值:>=0 if OK, AVERROR_xxx on error(>=0表示成功脾猛,否則失敗)
    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);
    if (avformat_find_stream_info_result < 0) {
        char *error_info;
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        __android_log_print(ANDROID_LOG_INFO, "main", "查找失敗,錯(cuò)誤信息:%s", error_info);
        return;
    }


    //第四步:查找音頻解碼器
    //第一點(diǎn):查找音頻流索引位置
    int av_stream_index_audio = -1;
    for (int i = 0; i < avformat_context->nb_streams; ++i) {
        if (avformat_context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
            av_stream_index_audio = i;
            break;
        }
    }
    if (av_stream_index_audio == -1) {
        __android_log_print(ANDROID_LOG_INFO, "main", "沒(méi)有找到音頻流");
        return;
    }

    //第二點(diǎn):查找音頻解碼器上下文(根據(jù)流索引位置->獲取音頻解碼買上下文)
    //新的API
//    AVCodecParameters *avcodec_parameters = avformat_context->streams[av_stream_index_audio]->codecpar;
//    avcodec_parameters->codec_id;

    //老的API
    AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index_audio]->codec;


    //第三點(diǎn):根據(jù)音頻解碼器上下文->獲取到->音頻解碼器
    AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);
    if (avcodec == NULL) {
        __android_log_print(ANDROID_LOG_INFO, "main", "找不到這個(gè)音頻解碼器");
        return;
    }

    //第五步:打開音頻解碼器(調(diào)試運(yùn)行->觀察鎖定C/C++基于NDK開發(fā)異常->便于調(diào)試運(yùn)行)
    int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);
    if (avcodec_open2_result != 0) {
        char *error_info;
        av_strerror(avcodec_open2_result, error_info, 1024);
        __android_log_print(ANDROID_LOG_INFO, "main", "打開音頻解碼器失敗鱼鸠,錯(cuò)誤信息:%s", error_info);
        return;
    }


    //打印音頻信息
    //輸出視頻信息
    //輸出:文件格式
    __android_log_print(ANDROID_LOG_INFO, "main", "文件格式:%s", avformat_context->iformat->name);
    //輸出:解碼器名稱
    __android_log_print(ANDROID_LOG_INFO, "main", "解碼器名稱:%s", avcodec->name);



    //第六步:讀取音頻壓縮數(shù)據(jù)進(jìn)行解碼(循環(huán)解碼)

    //讀取一幀音頻壓縮數(shù)據(jù)(緩沖區(qū))
    AVPacket *av_packet = (AVPacket *) av_malloc(sizeof(AVPacket));

    //接收一幀音頻采樣數(shù)據(jù)(緩沖區(qū))
    AVFrame *av_frame_in = av_frame_alloc();

    //音頻解碼返回結(jié)果
    int avcodec_receive_frame_result;

    //音頻采樣數(shù)據(jù)上下文->SwrContext
    //第一點(diǎn):創(chuàng)建上下文->開辟內(nèi)存空間(聲明)
    SwrContext *swr_context = swr_alloc();
    //第二點(diǎn):給我們的音頻采樣數(shù)據(jù)上下文->綁定數(shù)據(jù)
    //參數(shù)一:音頻采樣數(shù)據(jù)上下文
    //參數(shù)二:輸出聲道布局類型(立體聲猛拴、環(huán)繞羹铅、室內(nèi)等等...)
    //立體聲
    int out_ch_layout = AV_CH_LAYOUT_STEREO;
    //參數(shù)三:輸出音頻采樣數(shù)據(jù)格式(說(shuō)白了:采樣精度)
    AVSampleFormat av_sample_format = AV_SAMPLE_FMT_S16;
    //參數(shù)四:輸出音頻采樣數(shù)據(jù)->采樣率
    int out_sample_rate = avcodec_context->sample_rate;
    //參數(shù)五:輸入聲道布局類型(立體聲、環(huán)繞愉昆、室內(nèi)等等...)->默認(rèn)格式
    int in_ch_layout = av_get_default_channel_layout(avcodec_context->channels);
    //參數(shù)六:輸入音頻采樣數(shù)據(jù)格式(說(shuō)白了:采樣精度)
    AVSampleFormat in_sample_fmt = avcodec_context->sample_fmt;
    //參數(shù)七:輸入音頻采樣數(shù)據(jù)->采樣率
    int in_sample_rate = avcodec_context->sample_rate;
    //參數(shù)八:Log日志偏移量
    //參數(shù)九:Log日志統(tǒng)計(jì)上下文
    swr_alloc_set_opts(swr_context,
                       out_ch_layout,
                       av_sample_format,
                       out_sample_rate,
                       in_ch_layout,
                       in_sample_fmt,
                       in_sample_rate,
                       0, NULL);

    //輸出音頻采樣數(shù)據(jù)緩沖區(qū)(目標(biāo))->人的耳朵最大采樣率->44100HZ
    int out_count = 16000;
    uint8_t *out_buffer = (uint8_t *) av_malloc(out_count);
    int pktsize, flush_complete = 0;
    //獲取聲道數(shù)量
    int out_nb_layout = av_get_channel_layout_nb_channels(out_ch_layout);

    //打開文件
    const char *coutputFilePath = env->GetStringUTFChars(jOutFilePath, NULL);
    FILE *out_file_pcm = fopen(coutputFilePath, "wb+");
    if (out_file_pcm == NULL) {
        __android_log_print(ANDROID_LOG_INFO, "main", "文件不存在");
        return;
    }

    int frame_index = 0;
//    while (av_read_frame(avformat_context, av_packet) == 0) {
//        ++frame_index;
//        if (av_packet->stream_index == av_stream_index_audio) {
//            out_buffer = av_packet->data;
//            pktsize = av_packet->size;
//            int frameFinished = 0;
//            int len = avcodec_decode_audio4(avcodec_context, av_frame_in, &frameFinished,
//                                            av_packet);
//            if (frameFinished) {
//                pktsize -= len;
//                out_buffer += len;
//                int data_size = av_samples_get_buffer_size(NULL,
//                                                           out_nb_layout,
//                                                           av_frame_in->nb_samples,
//                                                           av_sample_format,
//                                                           1);
//                /*****************************************************
//                以下代碼使用swr_convert函數(shù)進(jìn)行轉(zhuǎn)換职员,但是轉(zhuǎn)換后的文件連mp3到pcm文件都不能播放了,所以注釋了
//                const uint8_t *in[] = {frame->data[0]};
//
//                int len=swr_convert(swrContext,out,sizeof(audio_buf)/codecContext->channels/av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P),
//                    in,frame->linesize[0]/codecContext->channels/av_get_bytes_per_sample(codecContext->sample_fmt));
//
//                len=len*codecContext->channels*av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P);
//
//                if (len < 0) {
//                    fprintf(stderr, "audio_resample() failed\n");
//                    break;
//                }
//                if (len == sizeof(audio_buf) / codecContext->channels / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16P)) {
//                    fprintf(stderr, "warning: audio buffer is probably too small\n");
//                    swr_init(swrContext);
//                }
//                *****************************************************/
//                char *data = (char *) malloc(data_size);
//                short *sample_buffer = (short *) av_frame_in->data[0];
//                for (int i = 0; i < data_size / 2; i++) {
//                    data[i * 2] = (char) (sample_buffer[i / 2] & 0xFF);
//                    data[i * 2 + 1] = (char) ((sample_buffer[i / 2] >> 8) & 0xFF);
//
//                }
//                fwrite(data, data_size, 1, out_file_pcm);
//                fflush(out_file_pcm);
//            }
//        }
        //返回值:<0表示讀取完畢跛溉,否則正在讀取
        while (av_read_frame(avformat_context, av_packet) >= 0) {
            //判定當(dāng)前幀是否是音頻流->音頻采樣數(shù)據(jù)
            if (av_packet->stream_index == av_stream_index_audio) {
                //確定是我們的音頻流->解碼
                //解碼一幀音頻流數(shù)據(jù)
                //老的API
                //avcodec_decode_audio4();
                //新的API(發(fā)送->接收)
                //發(fā)送
                avcodec_send_packet(avcodec_context, av_packet);
                //接收
                avcodec_receive_frame_result = avcodec_receive_frame(avcodec_context, av_frame_in);

                if (avcodec_receive_frame_result == 0) {
                    //解碼一幀音頻壓縮數(shù)據(jù)成功->得到了->一幀音頻采樣數(shù)據(jù)
                    //音頻采樣數(shù)據(jù)->轉(zhuǎn)成pcm格式
                    //將輸入->輸出(pcm格式)
                    //參數(shù)一:音頻采樣數(shù)據(jù)上下文->SwrContext
                    //參數(shù)二:輸出音頻采樣數(shù)據(jù)緩沖區(qū)(目標(biāo))
                    //參數(shù)三:輸出緩沖區(qū)大小
                    //參數(shù)四:輸入音頻采樣數(shù)據(jù)緩沖區(qū)
                    //參數(shù)五:輸入緩沖區(qū)大小
                    swr_convert(swr_context,
                                &out_buffer,
                                out_count,
                                (const uint8_t **) av_frame_in->data,
                                av_frame_in->nb_samples);
                    //獲取緩沖區(qū)實(shí)際數(shù)據(jù)大小
                    //參數(shù)一:行大小
                    //參數(shù)二:聲道數(shù)量
                    //參數(shù)三:輸出大小
                    //參數(shù)四:輸出音頻采樣數(shù)據(jù)格式
                    //參數(shù)五:字節(jié)對(duì)齊類型
                    int out_buffer_size = av_samples_get_buffer_size(NULL,
                                                                     out_nb_layout,
                                                                     av_frame_in->nb_samples,
                                                                     av_sample_format,
                                                                     1);
                    //寫入文件
                    fwrite(av_frame_in->data[0], 1, out_buffer_size, out_file_pcm);
                    fflush(out_file_pcm);
                    frame_index++;
                    __android_log_print(ANDROID_LOG_INFO, "main", "當(dāng)前是第%d幀", frame_index);
                    __android_log_print(ANDROID_LOG_INFO, "main", "當(dāng)前是第%d幀", out_buffer);
                }
            }
        }
        //第七步:關(guān)閉音頻解碼器釋放內(nèi)存
        av_packet_free(&av_packet);
        //關(guān)閉流
        fclose(out_file_pcm);

        swr_free(&swr_context);

        av_free(out_buffer);

        avcodec_close(avcodec_context);
        avformat_free_context(avformat_context);

    }

是不是和視頻解碼大體流程完全一樣焊切,唯一的區(qū)別就是視頻像素?cái)?shù)據(jù)格式的轉(zhuǎn)換和音頻采樣數(shù)據(jù)的重新采樣了。
上述代碼實(shí)現(xiàn)完成之后可以在pc端下載Adobe audition cs6來(lái)直接播放

下載

https://github.com/samychen/SDL_FFmpeg_Tutorial

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末芳室,一起剝皮案震驚了整個(gè)濱河市专肪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌堪侯,老刑警劉巖嚎尤,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異伍宦,居然都是意外死亡芽死,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門次洼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)关贵,“玉大人,你說(shuō)我怎么就攤上這事滓玖。” “怎么了质蕉?”我有些...
    開封第一講書人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵势篡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我模暗,道長(zhǎng)禁悠,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任兑宇,我火速辦了婚禮碍侦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘隶糕。我一直安慰自己瓷产,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開白布枚驻。 她就那樣靜靜地躺著濒旦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪再登。 梳的紋絲不亂的頭發(fā)上尔邓,一...
    開封第一講書人閱讀 49,842評(píng)論 1 290
  • 那天晾剖,我揣著相機(jī)與錄音,去河邊找鬼梯嗽。 笑死齿尽,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的灯节。 我是一名探鬼主播循头,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼显晶!你這毒婦竟也來(lái)了贷岸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤磷雇,失蹤者是張志新(化名)和其女友劉穎偿警,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唯笙,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡螟蒸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了崩掘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片七嫌。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖苞慢,靈堂內(nèi)的尸體忽然破棺而出诵原,到底是詐尸還是另有隱情,我是刑警寧澤挽放,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布绍赛,位于F島的核電站,受9級(jí)特大地震影響辑畦,放射性物質(zhì)發(fā)生泄漏吗蚌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一纯出、第九天 我趴在偏房一處隱蔽的房頂上張望蚯妇。 院中可真熱鬧,春花似錦暂筝、人聲如沸箩言。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)分扎。三九已至,卻和暖如春胧洒,著一層夾襖步出監(jiān)牢的瞬間畏吓,已是汗流浹背墨状。 一陣腳步聲響...
    開封第一講書人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留菲饼,地道東北人肾砂。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像宏悦,于是被迫代替她去往敵國(guó)和親镐确。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

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

  • 當(dāng)我老了的時(shí)候 悠然南山不過(guò)癡想 我不想老去 不想最后沉睡于盒中 一生最后只是一張照片 雖生死仍然 當(dāng)我老了的時(shí)候...
    愛(ài)騰訊閱讀 241評(píng)論 1 1
  • 那天息堂,雨下得很大,我撐著傘块促,你在我旁邊荣堰,在傘下,沒(méi)淋濕竭翠。 睡眼還是惺忪 夢(mèng)未醒 雨還在下 雨滴敲在窗上 滴滴答答 ...
    獨(dú)立日的折耳貓閱讀 518評(píng)論 5 10
  • 感覺(jué)有好多事情要做振坚,可是。好累斋扰。
    昵稱已唄使用閱讀 147評(píng)論 2 0