1. 程式人生 > >FFMPEG實現PCM編碼(採用封裝格式實現)

FFMPEG實現PCM編碼(採用封裝格式實現)

技術在於交流、溝通,轉載請註明出處並保持作品的完整性。

原文:https://blog.csdn.net/hiwubihe/article/details/81260882

 

[音訊編解碼系列文章]

  1. 音訊編解碼基礎
  2. FFMPEG實現音訊重取樣
  3. FFMPEG實現PCM編碼(採用封裝格式實現)
  4. FFMPEG實現PCM編碼(不採用封裝格式實現)
  5. FAAC庫實現PCM編碼
  6. FAAD庫實現RAW格式AAC解碼
  7. FAAD庫實現RAW格式AAC封裝成ADTS格式
  8. FAAD庫實現ADTS格式解碼
  9. FFMPEG實現對AAC解碼(採用封裝格式實現)
  10. FFMPEG實現對AAC解碼(不採用封裝格式實現)

本篇基於FFMPEG實現把PCM編碼成AAC或者MP3格式的視訊檔案,編碼的位元率都是64kbps,程式碼中AAC格式編碼不需要音訊重取樣,而MP3格式編碼只支援樣本平行儲存的方式。在編碼中,首先PCM檔案的格式是一種編碼方式,這種是波形編碼,而各種壓縮演算法實現本身會支援一個格式,如對通道,樣本格式的要求,所以當PCM檔案格式與編碼實現不一致時,就需要對PCM資料重取樣,然後編碼。MP3格式據說編碼位元率在128Kbps的情況下,高頻部分損失比較嚴重,後面可以分析一下。本篇編碼是把AAC或者MP3當做一種封裝格式如MP4這種封裝格式進行的,下篇將介紹一種只打開編碼器,不開啟封裝格式,獲取原始資料直接送入編碼器的方式。ffmpeg編碼的AAC是ADTS格式。

PCM編碼AAC或者MP3程式碼:

/*******************************************************************************
Copyright (c) wubihe Tech. Co., Ltd. All rights reserved.
--------------------------------------------------------------------------------

Date Created:	2014-10-25
Author:			wubihe QQ:1269122125 Email:
[email protected]
Description: 程式碼實現PCM編碼AAC,MP3格式 -------------------------------------------------------------------------------- Modification History DATE AUTHOR DESCRIPTION -------------------------------------------------------------------------------- ********************************************************************************/ #include <stdio.h> #define __STDC_CONSTANT_MACROS #ifdef _WIN32 //Windows extern "C" { #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswresample/swresample.h" }; #else //Linux... #ifdef __cplusplus extern "C" { #endif #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #ifdef __cplusplus }; #endif #endif #define INPUT_FILE_NAME ("huangdun_r48000_FMT_S16_c2.pcm") //輸出檔案字首 #define OUTPUT_FILE_NAME_PREFIX ("huangdun") //輸出檔案字尾 //#define OUTPUT_FILE_NAME_SUFFIX ("aac") //輸出檔案字尾 #define OUTPUT_FILE_NAME_SUFFIX ("mp3") //輸出檔案位元率 該值越大 音訊質量越好 音質損失越小 #define OUTPUT_FILE_BIT_RATE (64000) int flush_encoder(AVFormatContext *fmt_ctx,unsigned int stream_index) { int ret; int got_frame; AVPacket enc_pkt; if (!(fmt_ctx->streams[stream_index]->codec->codec->capabilities &CODEC_CAP_DELAY)) return 0; while (1) { enc_pkt.data = NULL; enc_pkt.size = 0; av_init_packet(&enc_pkt); //輸入視訊幀為NULL ret = avcodec_encode_audio2 (fmt_ctx->streams[stream_index]->codec, &enc_pkt,NULL, &got_frame); av_frame_free(NULL); if (ret < 0) break; if (!got_frame) { ret=0; break; } printf("Flush Encoder: Succeed to encode 1 frame!\tsize:%5d\n",enc_pkt.size); /* mux encoded frame */ ret = av_write_frame(fmt_ctx, &enc_pkt); if (ret < 0) break; } return ret; } int main() { static char*pFormatName[]= { "FMT_U8","FMT_S16","FMT_S32","FMT_FLT","FMT_DBL", "FMT_U8P","FMT_S16P","FMT_S32P","FMT_FLTP","FMT_DBLP" }; //各種不同格式對應位元組數 static int mapSampleBytes[AV_SAMPLE_FMT_NB] ={1,2,4,4,8,1,2,4,4,8}; //PCM原始資料格式 uint64_t iInputLayout = AV_CH_LAYOUT_STEREO; int iInputChans = av_get_channel_layout_nb_channels(iInputLayout); AVSampleFormat eInputSampleFormat = AV_SAMPLE_FMT_S16; int iInputSampleRate = 48000; //不同樣本格式長度 int iInputSampleBytes = mapSampleBytes[eInputSampleFormat]; //PCM需要重取樣的格式 部分編碼器不支援原始PCM的資料格式如MP3 uint64_t iOutputLayout = AV_CH_LAYOUT_STEREO; int iOutputChans = av_get_channel_layout_nb_channels(iOutputLayout); AVSampleFormat eOutputSampleFormat ; int iOutputSampleRate = 48000; if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"aac") == 0) { eOutputSampleFormat = AV_SAMPLE_FMT_S16; } else if(strcmp(OUTPUT_FILE_NAME_SUFFIX,"mp3") == 0) { //MP3不支援AV_SAMPLE_FMT_S16這種格式 eOutputSampleFormat = AV_SAMPLE_FMT_S16P; } else { } //編碼樣本長度 int iOutputSampleBytes = mapSampleBytes[eOutputSampleFormat]; //是否需要重取樣 bool bNeedResample = false; if(eInputSampleFormat != eOutputSampleFormat) { bNeedResample = true; } //是否平面儲存結構 bool bPlanner = false; if((eOutputSampleFormat>=AV_SAMPLE_FMT_U8P) &&(eOutputSampleFormat<=AV_SAMPLE_FMT_DBLP)) { bPlanner = true; } //開啟輸入檔案 FILE *pInputFile = fopen("huangdun_r48000_FMT_S16_c2.pcm", "rb"); if(pInputFile == NULL) { } //開啟輸出檔案 char szOutFileName[256]={0}; sprintf(szOutFileName,"%s_br%d_sr%d.%s",OUTPUT_FILE_NAME_PREFIX,OUTPUT_FILE_BIT_RATE,iOutputSampleRate,OUTPUT_FILE_NAME_SUFFIX); FILE *pOutputFile = fopen(szOutFileName, "wb"); //開啟中間測試檔案 char szTempFileName[256]={0}; sprintf(szTempFileName,"%s_sr%d_c1.pcm",OUTPUT_FILE_NAME_PREFIX,iOutputSampleRate); FILE *pTempFile = fopen(szTempFileName, "wb"); int iReturn; ///////////編碼器操作//////////////////////////////// //註冊所有編解碼器 av_register_all(); //封裝格式上下文 AVFormatContext中有AVInputFormat和AVOutputFormat //解複用時avformat_open_input()初始化AVInputFormat,複用時使用者自己初始化AVOutputFormat AVFormatContext* pFormatCtx; AVOutputFormat * fmt; //Method 1.分配一個封裝格式 pFormatCtx = avformat_alloc_context(); //根據字尾名 填充 輸出格式上下文 fmt = av_guess_format(NULL, szOutFileName, NULL); pFormatCtx->oformat = fmt; //Method 2. //avformat_alloc_output_context2(&pFormatCtx, NULL, NULL, out_file); //fmt = pFormatCtx->oformat; //新增一個流 AVStream *audio_st = avformat_new_stream(pFormatCtx, 0); if (audio_st==NULL) { return -1; } //新增一個輸出路徑 if (avio_open(&pFormatCtx->pb,szOutFileName, AVIO_FLAG_READ_WRITE) < 0) { printf("Failed to open output file!\n"); return -1; } //Show some information 日誌資訊 av_dump_format(pFormatCtx, 0, szOutFileName, 1); //初始化編碼器相關結構體 獲取輸出流中的編碼上下文 AVCodecContext* pCodecCtx = audio_st->codec; pCodecCtx->codec_id = fmt->audio_codec ; pCodecCtx->codec_type = AVMEDIA_TYPE_AUDIO ; //立體聲 pCodecCtx->channel_layout = iOutputLayout ; pCodecCtx->channels = iOutputChans ; //編碼位元率 AAC支援多種位元率 一般位元率越高 視訊質量越好 需要傳輸頻寬越大 pCodecCtx->bit_rate = OUTPUT_FILE_BIT_RATE; pCodecCtx->sample_rate = iOutputSampleRate; //PCM樣本深度為AV_SAMPLE_FMT_S16 但不是所有格式的編碼都支援這種格式 pCodecCtx->sample_fmt = eOutputSampleFormat; //編碼器 AVCodec* pCodec = avcodec_find_encoder(pCodecCtx->codec_id); if (!pCodec) { printf("Can not find encoder!\n"); return -1; } //開啟解碼器 有可能失敗 -22 錯誤,原因不同的編碼格式支援的樣本格式不一樣 //如封裝AAC格式樣本格式是AV_SAMPLE_FMT_FLT,開啟就出錯 if ((iReturn = avcodec_open2(pCodecCtx, pCodec,NULL)) < 0) { printf("Failed to open encoder :[%d]!\n",iReturn); return -1; } //重採用上下文 SwrContext *pSwrCtx = NULL; //原始資料幀 AVFrame* pRawframe = NULL; //原始幀一Planer的大小 非平面分佈的情況就是快取總大小 int iRawLineSize = 0; //原始幀快取大小 int iRawBuffSize = 0; //原始幀快取 uint8_t *pRawBuff= NULL; //重取樣後資料幀 AVFrame* pConvertframe = NULL; //重取樣後一Planer的大小 int iConvertLineSize = 0; //重取樣後快取大小 int iConvertBuffSize = 0; //重取樣後幀快取 uint8_t *pConvertBuff = NULL; //1幀資料樣本數 int iFrameSamples = pCodecCtx->frame_size; // 儲存原始資料 iRawLineSize = 0; iRawBuffSize = av_samples_get_buffer_size(&iRawLineSize, iInputChans, iFrameSamples, eInputSampleFormat, 0); pRawBuff = (uint8_t *)av_malloc(iRawBuffSize); //原始資料儲存在AVFrame結構體中 pRawframe = av_frame_alloc(); pRawframe->nb_samples = iFrameSamples; pRawframe->format = eInputSampleFormat; pRawframe->channels = iInputChans; iReturn = avcodec_fill_audio_frame(pRawframe, iInputChans, eInputSampleFormat, (const uint8_t*)pRawBuff, iRawBuffSize, 0); if(iReturn<0) { return -1; } if(bNeedResample) { pSwrCtx = swr_alloc_set_opts(NULL,iOutputLayout, eOutputSampleFormat, iOutputSampleRate, iInputLayout,eInputSampleFormat , iInputSampleRate,0, NULL); swr_init(pSwrCtx); // 儲存轉換後資料 iConvertLineSize = 0; iConvertBuffSize = av_samples_get_buffer_size(&iConvertLineSize, iOutputChans, iFrameSamples, eOutputSampleFormat, 0); pConvertBuff = (uint8_t *)av_malloc(iConvertBuffSize); //轉換後資料儲存在AVFrame結構體中 pConvertframe = av_frame_alloc(); pConvertframe->nb_samples = iFrameSamples; pConvertframe->format = eOutputSampleFormat; pConvertframe->channels = iOutputChans; iReturn = avcodec_fill_audio_frame(pConvertframe, iOutputChans, eOutputSampleFormat, (const uint8_t*)pConvertBuff, iConvertBuffSize, 0); if(iReturn<0) { return -1; } } //編碼以後的資料是AVPacket AVPacket pkt; if(!bNeedResample) { av_new_packet(&pkt,iRawBuffSize); } else { av_new_packet(&pkt,iConvertBuffSize); } //Write Header avformat_write_header(pFormatCtx,NULL); //統計讀取樣本數 long long lReadTotalSamples = 0; //每次讀取樣本數 int iReadSamples; //統計所有的幀數 int iFrameNum =0; //是否編碼成功 int got_frame =0; //臨時 AVFrame* pTempFrame=NULL; //讀取資料 儲存在pConvertframe->data int iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile); while(iRealRead>0) { iReadSamples = iRealRead/(iInputSampleBytes*iInputChans); if(bNeedResample) { swr_convert(pSwrCtx, (uint8_t**)pConvertframe->data, iFrameSamples ,(const uint8_t**)pRawframe->data, iFrameSamples ); if(bPlanner) { //只儲存一個通道 因為儲存多個通道測試工具 audacity看不了 fwrite(pConvertframe->data[0],pConvertframe->linesize[0],1,pTempFile); } printf("Convert Frame :%d\n",++iFrameNum); pTempFrame = pConvertframe; } else { pTempFrame = pRawframe; } pTempFrame->pts = lReadTotalSamples; got_frame = 0; //Encode if(avcodec_encode_audio2(pCodecCtx, &pkt,pTempFrame, &got_frame)<0) { printf("Failed to encode!\n"); return -1; } if (got_frame==1) { printf("Succeed to encode 1 frame! \tsize:%5d\n",pkt.size); pkt.stream_index = audio_st->index; av_write_frame(pFormatCtx, &pkt); av_free_packet(&pkt); } //統計樣本數以轉換前為準 轉換前後樣本數是一樣的 lReadTotalSamples += (iReadSamples); iRealRead = fread(pRawBuff, 1, iRawBuffSize, pInputFile); } //重新整理編碼器 if(flush_encoder(pFormatCtx,0)<0) { printf("Flushing encoder failed\n"); return -1; } fclose(pInputFile); fclose(pOutputFile); fclose(pTempFile); av_free(pRawBuff); if(bNeedResample) { av_free(pConvertBuff); swr_free(&pSwrCtx); } printf("Convert Success!!\n"); getchar(); return 0; }

 

 

編碼AAC生成檔案 huangdun_br64000_sr48000.aac,可以直接用普通播放器播放如VLC

編碼mp3生成檔案 huangdun_br64000_sr48000.mp3,可以直接用普通播放器播放如VLC

MP3格式頻譜分析

原始PCM格式頻譜圖

編碼MP3後的頻譜圖

從上面頻譜圖可以看出MP3格式編碼對高頻部分衰減確實很厲害,圖中11000HZ---15000HZ部分全部衰減掉了。現在把程式中編碼位元率提高到128Kbps,來看看效果。

可以看到高頻部分15000HZ衰減有明顯減少,由此得出結論MP3編碼格式優點壓縮率高,能夠在低位元速率的情況下提高較好的音質,適合網路傳輸,但是缺點是位元率低於128kbps的情況,高頻部分有很高的衰減。

DEMO編譯環境:   Win7_64bit+VS2008

DEMO下載地址:https://download.csdn.net/download/hiwubihe/10569791