FFMPEG實現PCM編碼(採用封裝格式實現)
技術在於交流、溝通,轉載請註明出處並保持作品的完整性。
原文:https://blog.csdn.net/hiwubihe/article/details/81260882
[音訊編解碼系列文章]
- 音訊編解碼基礎
- FFMPEG實現音訊重取樣
- FFMPEG實現PCM編碼(採用封裝格式實現)
- FFMPEG實現PCM編碼(不採用封裝格式實現)
- FAAC庫實現PCM編碼
- FAAD庫實現RAW格式AAC解碼
- FAAD庫實現RAW格式AAC封裝成ADTS格式
- FAAD庫實現ADTS格式解碼
- FFMPEG實現對AAC解碼(採用封裝格式實現)
- 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