FFmpeg音視頻解碼流程
- AAC視頻格式解碼爲PCM編碼格式
- h264視頻格式解碼爲YUV編碼格式
音頻解碼流程與視頻解碼流程大同小異(視頻解碼流程詳細見:Qt與FFmpeg聯合開發指南(二)-- 解碼播放本地視頻), 使用不同的解碼器對音頻視頻流進行解碼操作。初始化操作兩者一致。
核心代碼:
先添加音頻採樣數據格式庫:
//音頻採樣數據格式庫
#include <libswresample/swresample.h>
第一步:獲取當前解碼器是屬於什麼類型解碼器
qDebug()<<"第四步:查找解碼器";
int av_stream_index = -1;
int audio_index = -1;
for (int i = 0; i < avformat_context->nb_streams; ++i) {
//循環遍歷每一流
//視頻流、音頻流、字幕流等等...
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
//找到了視頻流
av_stream_index = i;
}
if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
//找到了音頻
audio_index = i;
}
}
if (av_stream_index == -1 )
{
qDebug()<<QString("沒有找到視頻流");
return false;
}
if (audio_index == -1 )
{
qDebug()<<QString("沒有找到音頻流");
return false;
}
第二步:根據流信息打開對應解碼器
//第二點:根據視頻流->查找到視頻解碼器上下文->視頻壓縮數據
AVCodecContext* avcodec_context = avformat_context->streams[av_stream_index]->codec;
//第三點:根據解碼器上下文->獲取解碼器ID
AVCodec* avcodec = avcodec_find_decoder(avcodec_context->codec_id);
if (avcodec == NULL)
{
qDebug()<<QString("沒有找到視頻解碼器");
return false;
}
qDebug()<<"第五步:打開視頻解碼器";
//第五步:打開解碼器
int avcodec_open2_result = avcodec_open2(avcodec_context,avcodec,NULL);
if (avcodec_open2_result != 0)
{
char* error_info = new char[32];
av_strerror(avformat_find_stream_info_result, error_info, 1024);
qDebug()<<QString("異常信息 %1").arg(error_info);
return false;
}
////////音頻
//第二點:根據音頻流->查找到音頻解碼器上下文->音頻壓縮數據
AVCodecContext* avcodec_context_audio = avformat_context->streams[audio_index]->codec;
//第三點:根據解碼器上下文->獲取解碼器ID
AVCodec* avcodec_audio = avcodec_find_decoder(avcodec_context_audio->codec_id);
if (avcodec_audio == NULL)
{
qDebug()<<QString("沒有找到音頻解碼器");
return false;
}
qDebug()<<" 打開音頻解碼器";
//第五步:打開音頻解碼器
int avcodec_open2_result1 = avcodec_open2(avcodec_context_audio,avcodec_audio,NULL);
if (avcodec_open2_result1 != 0)
{
char* error_info = new char[32];
av_strerror(avformat_find_stream_info_result, error_info, 1024);
qDebug()<<QString("異常信息 %1").arg(error_info);
return false;
}
輸出解碼器名稱
qDebug()<<QString("視頻解碼器名稱: %1").arg(avcodec->name);
qDebug()<<QString("音頻解碼器 %1").arg(avcodec_audio->name);
第三步:創建音視頻壓縮數據幀
//循環讀取視頻幀,進行循環解碼->輸出YUV420P視頻->格式:yuv格式
qDebug()<<"第六步:循環讀取視頻幀,進行循環解碼";
//讀取幀數據換成到哪裏->緩存到packet裏面
AVPacket* av_packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//輸入->環境一幀數據->緩衝區->類似於一張圖
AVFrame* av_frame_in = av_frame_alloc();
//輸出->幀數據->視頻像素數據格式->yuv420p
AVFrame* av_frame_out_yuv420p = av_frame_alloc();
//緩衝區分配內存
uint8_t *out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height));
//初始化緩衝區
avpicture_fill((AVPicture *)av_frame_out_yuv420p, out_buffer, AV_PIX_FMT_YUV420P, avcodec_context->width, avcodec_context->height);
//解碼的狀態類型(0:表示解碼完畢,非0:表示正在解碼)
int av_decode_result, y_size, u_size, v_size, current_frame_index = 0,current_audio_index = 0;
//準備一個視頻像素數據格式上下文
//參數一:輸入幀數據寬
//參數二:輸入幀數據高
//參數三:輸入幀數據格式
//參數四:輸出幀數據寬
//參數五:輸出幀數據高
//參數六:輸出幀數據格式->AV_PIX_FMT_YUV420P
//參數七:視頻像素數據格式轉換算法類型
//參數八:字節對齊類型(C/C++裏面)->提高讀取效率
SwsContext* sws_context = sws_getContext(avcodec_context->width,
avcodec_context->height,
avcodec_context->pix_fmt,
avcodec_context->width,
avcodec_context->height,
AV_PIX_FMT_YUV420P,
SWS_BICUBIC,NULL,NULL,NULL);
/////////////////////////////////////////////////////////////視頻處理
//創建音頻壓縮數據幀->acc格式、mp3格式
AVPacket* avpacket_audio = (AVPacket*)av_malloc(sizeof(AVPacket));
//創建音頻採樣數據幀
AVFrame* avframe_audio = av_frame_alloc();
// 確保解碼爲pcm我們需要轉換爲pcm格式
SwrContext*swr_context_audio = swr_alloc();
swr_alloc_set_opts(swr_context_audio,
AV_CH_LAYOUT_STEREO,//立體聲 輸出聲道佈局類型
AV_SAMPLE_FMT_S16, //輸出採樣精度
avcodec_context_audio->sample_rate, //輸出採樣率
avcodec_context_audio->channel_layout, //輸入聲道佈局類型
avcodec_context_audio->sample_fmt, //輸入採樣精度
avcodec_context_audio->sample_rate, //輸入採樣率
NULL, NULL);
//初始化音頻採樣數據上下文
swr_init(swr_context_audio);
//緩衝區大小 = 採樣率(44100HZ) * 採樣精度(16位 = 2字節)
int MAX_AUDIO_SIZE = 44100 * 2;
uint8_t *out_audio= (uint8_t *) av_malloc(MAX_AUDIO_SIZE);;
//獲取輸出的聲道個數
int out_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);
int audio_decode_result = 0;
第四步:循環讀取音頻壓縮數據
FILE* out_file_pcm = fopen("diao.pcm","wb+");
if (out_file_pcm == NULL){
qDebug()<<QString("文件不存在 ");
return false;
}
while (av_read_frame(avformat_context,av_packet) >= 0)
{
//解碼什麼類型流(視頻流、音頻流、字幕流等等...)
if (av_packet->stream_index == av_stream_index)
{
//略 (參照上一篇)
}
//解碼什麼類型流(視頻流、音頻流、字幕流等等...)
if (av_packet->stream_index == audio_index)
{
//音頻解碼
//1、發送一幀音頻壓縮數據包->音頻壓縮數據幀
avcodec_send_packet(avcodec_context_audio, av_packet);
//2、解碼一幀音頻壓縮數據包->得到->一幀音頻採樣數據->音頻採樣數據幀
av_decode_result = avcodec_receive_frame(avcodec_context_audio, avframe_audio);
// 解碼成功
if ( av_decode_result == 0 )
{
//3、類型轉換(音頻採樣數據格式有很多種類型)
//我希望我們的音頻採樣數據格式->pcm格式->保證格式統一->輸出PCM格式文件
//swr_convert:表示音頻採樣數據類型格式轉換器
//參數一:音頻採樣數據上下文
//參數二:輸出音頻採樣數據
//參數三:輸出音頻採樣數據->大小
//參數四:輸入音頻採樣數據
//參數五:輸入音頻採樣數據->大小
swr_convert(swr_context_audio,
&out_audio,
MAX_AUDIO_SIZE,
(const uint8_t **)avframe_audio->data,
avframe_audio->nb_samples);
//4、獲取緩衝區實際存儲大小
//參數一:行大小
//參數二:輸出聲道數量
//參數三:輸入大小
int nb_samples = avframe_audio->nb_samples;
//參數四:輸出音頻採樣數據格式
//參數五:字節對齊方式
int out_buffer_size = av_samples_get_buffer_size(NULL,
out_nb_channels,
nb_samples,
AV_SAMPLE_FMT_S16,
1);
//5、寫入文件
fwrite(out_audio, 1, out_buffer_size, out_file_pcm);
current_audio_index++;
qDebug()<<QString("音頻當前遍歷第 %1 幀").arg(current_audio_index);
}
}
}
第五步:關閉音頻解碼器
av_packet_free(&avpacket_audio);
swr_free(&swr_context_audio);
av_free(out_buffer);
avcodec_close(avcodec_context_audio);
av_frame_free(&avframe_audio);
解碼後存儲地pcm音頻文件使用:Softe Audio Converter軟件進行查看