摘要
這篇文章介紹音頻解碼,示例程序是讀取一個mp3格式或者aac格式的音頻文件,解碼輸出爲未壓縮的pcm音頻文件。
初始化FFmepg和FormatContext
和視頻解碼一樣,先使用av_register_all
註冊所有相關組件,然後使用avformat_open_input
打開指定的媒體文件,並使用avformat_find_stream_info
獲取媒體流相關信息,把這些格式信息映射到AVFormatContext *mFormatCtx
這個結構中。
使用函數av_dump_format
可以從控制檯輸出媒體文件相關信息。
bool AudioDecoding::init(const char * audioFile)
{
av_register_all();
if ((avformat_open_input(&mFormatCtx, audioFile, 0, 0)) < 0) {
printf("Failed to open input file\n");
return true;
}
if ((avformat_find_stream_info(mFormatCtx, 0)) < 0) {
printf("Failed to retrieve input stream information\n");
return true;
}
av_dump_format(mFormatCtx, 0, audioFile, 0);
return false;
}
配置編解碼器CodecContext
- 根據輸入文件格式查找相應的解碼器,因爲使用的是aac/mp3格式的音頻文件,只有一個音頻流,所以就不用查找流Index了,直接使用streams[0]就可以了。
- 根據解碼器申請CodecContext。
- 填充CodecContext信息
- 完成CodecContext初始化。
bool AudioDecoding::initCodecContext()
{
// 根據id查找對應解碼器
AVCodec *dec = avcodec_find_decoder(mFormatCtx->streams[0]->codecpar->codec_id);
if (!dec) {
printf("Failed to find codec!\n");
return true;
}
// 申請CodecContext
if (!(mCodecCtx = avcodec_alloc_context3(dec))) {
printf("Failed to allocate the codec context\n");
return true;
}
// 填充CodecContext信息
if (avcodec_parameters_to_context(mCodecCtx, mFormatCtx->streams[0]->codecpar) < 0) {
printf("Failed to copy codec parameters to decoder context!\n");
return true;
}
// 初始化AVCodecContext結構
if (avcodec_open2(mCodecCtx, dec, NULL) < 0) {
printf("Failed to open codec\n");
return true;
}
return false;
}
音頻解碼,保存pcm文件
- 循環讀取輸入文件的數據,直接讀取完畢。
- 依次處理各個讀取到的packet,進行解碼。
- 解碼出來的是未壓縮的音頻數據,把這些音頻數據寫入文件。
需要注意的是,輸入音頻格式不同時,解碼後輸出的pcm音頻數據格式也會不同,我們這裏沒有進行重採樣處理。本程序當使用echo.mp3(sample_fmt = AV_SAMPLE_FMT_S16P)作爲輸入文件時,,一個採樣點是16bit,輸出文件取名爲out_s16le.pcm;使用echo.aac(sample_fmt = AV_SAMPLE_FMT_FLTP)作爲輸入文件,一個採樣點是32bit,輸出文件取名爲out_f32le.pcm,以便於播放。
關於音頻解碼輸出PCM數據的分析,請參考另一篇文章《音頻解碼輸出PCM格式數據分析》。
bool AudioDecoding::readFrameProc()
{
// 使用echo.mp3作爲輸入文件,文件名爲s16le.pcm
// 使用echo.aac作爲輸入文件,文件名則爲f32le.pcm
FILE *fd = fopen("../assets/out_s16le.pcm", "wb");
if (!fd) {
printf("Failed to open input file\n");
return true;
}
AVPacket packet;
av_init_packet(&packet);
AVFrame *frame = av_frame_alloc();
//循環讀取音頻數據packet
while (int num = av_read_frame(mFormatCtx, &packet) >= 0) {
//音頻解碼,同視頻解碼一樣都使用這對函數
avcodec_send_packet(mCodecCtx, &packet);
int ret = avcodec_receive_frame(mCodecCtx, frame);
if (!ret) {
// number of bytes per sample, 16bit is 2 Bytes
//根據格式查詢音頻採樣點深度,一個採樣點使用多少字節表示。
//本文使用的mp3文件(sample_fmt = AV_SAMPLE_FMT_S16P),一個採樣點是16bit,則返回字節數爲2;
//使用的aac文件(sample_fmt = AV_SAMPLE_FMT_FLTP),一個採樣點是32bit,則返回字節數4。
int sampleBytes = av_get_bytes_per_sample(mCodecCtx->sample_fmt);
// 使用Planar格式寫音頻文件,nb_samples爲這個frame中一個聲道的採樣點的個數,channels爲聲道個數
for (int i = 0; i < frame->nb_samples; i++)
for (int ch = 0; ch < mCodecCtx->channels; ch++)
fwrite(frame->data[ch] + sampleBytes*i, 1, sampleBytes, fd);
}
av_packet_unref(&packet);
}
av_frame_free(&frame);
fclose(fd);
return false;
}
釋放系統資源
AudioDecoding::~AudioDecoding()
{
avcodec_free_context(&mCodecCtx);
avformat_close_input(&mFormatCtx);
}
PCM音頻文件播放方法
上面說過使用MP3輸入時,輸出文件名爲out_s16le.pcm,使用aac格式文件作爲輸入時,輸出文件名爲out_f32le.pcm。可以使用ffplay播放pcm文件,這兩個文件對應的播放命令爲:
ffplay -f s16le -ac 2 -ar 44100 out_s16le.pcm
ffplay -f f32le -ac 2 -ar 44100 out_f32le.pcm
如果播放不正常,很可能是播放參數不正確。
示例程序代碼
上述示例的完整代碼可以從Github下載: https://github.com/lmshao/FFmpeg-Basic 。