若該文為原創文章,未經允許不得轉載
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客導航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/108799279
各位讀者,知識無窮而人力有窮,要么改需求,要么找專業人士,要么自己研究
上一篇:《FFmpeg開發筆記(六):ffmpeg解碼視頻並使用SDL同步時間顯示播放》
下一篇:敬請期待
本篇解碼音頻,包括從mp3等文件中抽取音頻流的pcm,從視頻文件中抽取音頻流的pcm。
本文章篇幅相對較長,碼字作圖不易,請各位讀者且行且珍惜。
音頻的幾個關鍵因素請查看:《SDL開發筆記(二):音頻基礎介紹、使用SDL播放音頻》
導入原始文件,設置好數據類型、升到、采樣率
CSDN:https://download.csdn.net/download/qq21497936/12888731
QQ群:1047134658(點擊“文件”搜索“audacity”,群內與博文同步更新)
ffmpeg解碼音頻轉碼基本流程如下:
使用ffmpeg對應的庫,都需要進行注冊,可以注冊子項也可以注冊全部。
打開文件,根據文件名信息獲取對應的ffmpeg全局上下文。
一定要探測流信息,拿到流編碼的編碼格式,不探測流信息則其流編碼器拿到的編碼類型可能為空,后續進行數據轉換的時候就無法知曉原始格式,導致錯誤。
依據流的格式查找解碼器,軟解碼還是硬解碼是在此處決定的,但是特別注意是否支持硬件,需要自己查找本地的硬件解碼器對應的標識,並查詢其是否支持。普遍操作是,枚舉支持文件后綴解碼的所有解碼器進行查找,查找到了就是可以硬解了(此處,不做過多的討論,對應硬解碼后續會有文章進行進一步研究)。
(注意:解碼時查找解碼器,編碼時查找編碼器,兩者函數不同,不要弄錯了,否則后續能打開但是數據是錯的)
打開獲取到的解碼器。
此處特別注意,基本上解碼的數據都是pcm格式,pcm格式也分很多種,若8位整形,無符號8為整形,32位浮點,帶P和不帶P的,帶P的數據真存儲為LRLRLRLR不帶P的為LLLLRRRR,還有單通道、雙通道和多通道,通道又涉及到了聲道的定位枚舉,所以pcm原始數據也多種多樣,對齊進行重弄采樣使其輸出的pcm格式參數特點一致。
重采樣結構體設置好后,需要設置生效。
數據包是封裝在容器中的一個數據包。
拿取封裝的一個packet后,判斷packet數據的類型進行送往解碼器解碼。
一個包可能存在多組數據,老的api獲取的是第一個,新的api分開后,可以循環獲取,直至獲取不到跳轉“步驟十二”
使用沖殘陽函數結合轉換結構體對編碼的數據進行轉換,拿到重采樣后的音頻原始數據。
拿到了原始數據自行處理。
繼續執行“步驟八”,若步驟八獲取不到數據則執行“步驟十二”
此處要單獨列出是因為,其實很多網上和開發者的代碼:
在進入循環解碼前進行了av_new_packet,循環中未av_free_packet,造成內存溢出;
在進入循環解碼前進行了av_new_packet,循環中進行av_free_pakcet,那么一次new對應無數次free,在編碼器上是不符合前后一一對應規范的。
查看源代碼,其實可以發現av_read_frame時,自動進行了av_new_packet(),那么其實對於packet,只需要進行一次av_packet_alloc()即可,解碼完后av_free_packet。
執行完后,返回執行“步驟八:獲取一幀packet”,一次循環結束。
全部解碼完成后,按照申請順序,反向依次進行對應資源的釋放。
關閉之前打開的解碼/編碼器。
關閉文件上下文后,要對之前申請的變量按照申請的順序,依次釋放。
與視頻解碼通用變量請參照博文《FFmpeg開發筆記(四):ffmpeg解碼的基本流程詳解》中的“ffmpeg解碼相關變量”。
重采樣的結構體,最關鍵的是幾個參數,輸入的采樣頻率、通道布局、數據格式,輸出的采樣頻率、通道布局、數據格式。
與視頻解碼通用函數原型請參照博文《FFmpeg開發筆記(四):ffmpeg解碼的基本流程詳解》中的"ffmpeg解碼相關函數原型。
struct SwrContext *swr_alloc_set_opts(struct SwrContext *s,
int64_t out_ch_layout,
enum AVSampleFormat out_sample_fmt,
int out_sample_rate,
int64_t in_ch_layout,
enum AVSampleFormat in_sample_fmt,
int in_sample_rate,
int log_offset,
void *log_ctx);
分配並設置重采樣的結構體上下文。
- 參數一:輸入需要設置的重采樣結構體,如果為空,則會由此函數內部進行分配。
- 參數二:輸出的通道布局(轉換后的)
- 參數三:輸出的樣本格式(轉換后的)
帶P和不帶P,關系到了AVFrame中的data的數據排列,不帶P,則是LRLRLRLRLR排列,帶P則是LLLLLRRRRR排列,若是雙通道則帶P則意味着data[0]全是L,data[1]全是R(注意:這是采樣點不是字節),PCM播放器播放的文件需要的是LRLRLRLR的。 - 參數四:輸出的采樣率(轉換后的)
- 參數五:輸入的通道布局(轉換前的)
- 參數六:輸入的樣本格式(轉換前的)
- 參數七:輸入的采樣率(轉換前的)
- 參數八:日志等級,忽略直接0
- 參數九:日志,忽略直接0
int swr_init(struct SwrContext *s);
初始化采樣器,使采樣器生效。
void swr_free(struct SwrContext **s);
釋放給定的SwrContext並將指針設置為NULL。
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
將原始分組數據發送給解碼器。
在內部,此調用將復制相關的AVCodeContext字段,這些字段可以影響每個數據包的解碼,並在實際解碼數據包時應用這些字段。(例如AVCodeContext.skip_frame,這可能會指示解碼器丟棄使用此函數發送的數據包所包含的幀。)
這個函數可以理解為ffmpeg為多線程准備的,將解碼數據幀包送入編碼器理解為一個線程,將從編碼器獲取解碼后的數據理解為一個線程。
- 參數一:編解碼器上下文
- 參數二:avpkt輸入的AVPacket。通常,這將是一個單一的視頻幀,或幾個完整的音頻幀。數據包的所有權歸調用者所有,解碼器不會寫入數據包。解碼器可以創建對分組數據的引用(如果分組沒有被引用計數,則復制它)。與舊的API不同,數據包總是被完全消耗掉,如果它包含多個幀(例如某些音頻編解碼器),則需要在發送新數據包之前多次調用avcodec_receive_frame()。它可以是NULL(或者數據設置為NULL且大小設置為0的AVPacket);在這種情況下,它被認為是一個刷新包,它發出流結束的信號。發送第一個刷新包將返回成功。后續的是不必要的,將返回AVERROR ou EOF。如果解碼器仍有幀緩沖,它將在發送刷新包后返回它們。
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
從解碼器返回解碼輸出數據。這個函數可以理解為ffmpeg為多線程准備的,將解碼數據幀包送入編碼器理解為一個線程,將從編碼器獲取解碼后的數據理解為一個線程。
- 參數一:編解碼器上下文
- 參數二:這將被設置為參考計數的視頻或音頻解碼器分配的幀(取決於解碼器類型)。請注意,函數在執行任何其他操作之前總是調用av_frame_unref(frame),自己釋放frame,只有最后一幀不釋放。
void FFmpegManager::testDecodeAudio()
{
QString fileName = "test/1.avi";
// QString fileName = "test/1.mp4";
// QString fileName = "E:/testFile2/1.mp3";
QString outFileName = "E:/1.pcm";
// ffmpeg相關變量預先定義與分配
AVFormatContext *pAVFormatContext = 0; // ffmpeg的全局上下文,所有ffmpeg操作都需要
AVCodecContext *pAVCodecContext = 0; // ffmpeg編碼上下文
AVCodec *pAVCodec = 0; // ffmpeg編碼器
AVPacket *pAVPacket = 0; // ffmpag單幀數據包
AVFrame *pAVFrame = 0; // ffmpeg單幀緩存
QFile file(outFileName); // Qt文件操作
int ret = 0; // 函數執行結果
int audioIndex = -1; // 音頻流所在的序號
int numBytes = 0;
pAVFormatContext = avformat_alloc_context(); // 分配
pAVPacket = av_packet_alloc(); // 分配
pAVFrame = av_frame_alloc(); // 分配
if(!pAVFormatContext || !pAVPacket || !pAVFrame)
{
LOG << "Failed to alloc";
goto END;
}
// 步驟一:注冊所有容器和編解碼器(也可以只注冊一類,如注冊容器、注冊編碼器等)
av_register_all();
// 步驟二:打開文件(ffmpeg成功則返回0)
LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
// ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
if(ret)
{
LOG << "Failed";
goto END;
}
// 步驟三:探測流媒體信息
ret = avformat_find_stream_info(pAVFormatContext, 0);
if(ret < 0)
{
LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
goto END;
}
LOG << "視頻文件包含流信息的數量:" << pAVFormatContext->nb_streams;
// 步驟四:提取流信息,提取視頻信息
for(int index = 0; index < pAVFormatContext->nb_streams; index++)
{
pAVCodecContext = pAVFormatContext->streams[index]->codec;
switch (pAVCodecContext->codec_type)
{
case AVMEDIA_TYPE_UNKNOWN:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_UNKNOWN";
break;
case AVMEDIA_TYPE_VIDEO:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_VIDEO";
break;
case AVMEDIA_TYPE_AUDIO:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_AUDIO";
audioIndex = index;
break;
case AVMEDIA_TYPE_DATA:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_DATA";
break;
case AVMEDIA_TYPE_SUBTITLE:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_SUBTITLE";
break;
case AVMEDIA_TYPE_ATTACHMENT:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_ATTACHMENT";
break;
case AVMEDIA_TYPE_NB:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_NB";
break;
default:
break;
}
// 已經找打視頻品流
if(audioIndex != -1)
{
break;
}
}
if(audioIndex == -1 || !pAVCodecContext)
{
LOG << "Failed to find video stream";
goto END;
}
// 步驟五:對找到的音頻流尋解碼器
pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
if(!pAVCodec)
{
LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
<< pAVCodecContext->codec_id;
goto END;
}
#if 0
pAVCodecContext = avcodec_alloc_context3(pAVCodec);
// 填充CodecContext信息
if (avcodec_parameters_to_context(pAVCodecContext,
pAVFormatContext->streams[audioIndex]->codecpar) < 0)
{
printf("Failed to copy codec parameters to decoder context!\n");
goto END;
}
#endif
// 步驟六:打開解碼器
ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
if(ret)
{
LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
goto END;
}
// 打印
LOG << "解碼器名稱:" <<pAVCodec->name
<< "通道數:" << pAVCodecContext->channels
<< "采樣率:" << pAVCodecContext->sample_rate
<< "采樣格式:" << pAVCodecContext->sample_fmt;
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
// 步驟七:讀取一幀數據的數據包
while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
{
if(pAVPacket->stream_index == audioIndex)
{
// 步驟八:將封裝包發往解碼器
ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
if(ret)
{
LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
break;
}
// 步驟九:從解碼器循環拿取數據幀
while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
{
// for(int index = 0; index < pAVFrame->linesize[0]; index++)
// {
// 入坑一;字節交錯錯誤,單條音軌是好的,雙軌存入文件,使用pcm的軟件播放,則默認是LRLRLRLR的方式(采樣點交錯)
// file.write((const char *)(pAVFrame->data[0] + index), 1);
// file.write((const char *)(pAVFrame->data[1] + index), 1);
// }
// 入坑一;字節交錯錯誤,單條音軌是好的,雙軌存入文件,使用pcm的軟件播放,則默認是LRLRLRLR的方式(采樣點交錯)
// file.write((const char *)(pAVFrame->data[0], pAVFrame->linesize[0]);
// file.write((const char *)(pAVFrame->data[1], pAVFrame->linesize[0]);
// 輸出為2, S16P格式是2字節
numBytes = av_get_bytes_per_sample(pAVCodecContext->sample_fmt);
// LOG << "numBytes =" << numBytes;
/*
P表示Planar(平面),其數據格式排列方式為 (特別記住,該處是以點nb_samples采樣點來交錯,不是以字節交錯):
LLLLLLRRRRRRLLLLLLRRRRRRLLLLLLRRRRRRL...(每個LLLLLLRRRRRR為一個音頻幀)
而不帶P的數據格式(即交錯排列)排列方式為:
LRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRLRL...(每個LR為一個音頻樣本)
*/
// 使用命令行提取pcm ffmpeg.exe -i 1.mp3 -f s16le -ar 44100 -ac 2 -acodec pcm_s16le D:/2.pcm
for (int index = 0; index < pAVFrame->nb_samples; index++)
{
for (int channel = 0; channel < pAVCodecContext->channels; channel++) // 交錯的方式寫入, 大部分float的格式輸出
{
file.write((char *)pAVFrame->data[channel] + numBytes * index, numBytes);
}
}
av_free_packet(pAVPacket);
}
}
}
file.close();
END:
LOG << "釋放回收資源";
if(pAVFrame)
{
av_frame_free(&pAVFrame);
pAVFrame = 0;
LOG << "av_frame_free(pAVFrame)";
}
if(pAVPacket)
{
av_free_packet(pAVPacket);
pAVPacket = 0;
LOG << "av_free_packet(pAVPacket)";
}
if(pAVCodecContext)
{
avcodec_close(pAVCodecContext);
pAVCodecContext = 0;
LOG << "avcodec_close(pAVCodecContext);";
}
if(pAVFormatContext)
{
avformat_close_input(&pAVFormatContext);
avformat_free_context(pAVFormatContext);
pAVFormatContext = 0;
LOG << "avformat_free_context(pAVFormatContext)";
}
}
void FFmpegManager::testDecodeAudioForPcm()
{
// QString fileName = "test/1.avi";
QString fileName = "E:/testFile/3.mp4";
// QString fileName = "E:/testFile2/1.mp3";
QString outFileName = "D:/1.pcm";
AVFormatContext *pAVFormatContext = 0; // ffmpeg的全局上下文,所有ffmpeg操作都需要
AVCodecContext *pAVCodecContext = 0; // ffmpeg編碼上下文
AVCodec *pAVCodec = 0; // ffmpeg編碼器
AVPacket *pAVPacket = 0; // ffmpag單幀數據包
AVFrame *pAVFrame = 0; // ffmpeg單幀緩存
SwrContext *pSwrContext = 0; // ffmpeg音頻轉碼
QFile file(outFileName); // Qt文件操作
int ret = 0; // 函數執行結果
int audioIndex = -1; // 音頻流所在的序號
int numBytes = 0;
uint8_t * outData[2] = {0};
int dstNbSamples = 0; // 解碼目標的采樣率
int outChannel = 0; // 重采樣后輸出的通道
AVSampleFormat outFormat = AV_SAMPLE_FMT_NONE; // 重采樣后輸出的格式
int outSampleRate = 0; // 重采樣后輸出的采樣率
pAVFormatContext = avformat_alloc_context(); // 分配
pAVPacket = av_packet_alloc(); // 分配
pAVFrame = av_frame_alloc(); // 分配
if(!pAVFormatContext || !pAVPacket || !pAVFrame)
{
LOG << "Failed to alloc";
goto END;
}
// 步驟一:注冊所有容器和編解碼器(也可以只注冊一類,如注冊容器、注冊編碼器等)
av_register_all();
// 步驟二:打開文件(ffmpeg成功則返回0)
LOG << "文件:" << fileName << ",是否存在:" << QFile::exists(fileName);
// ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), pAVInputFormat, 0);
ret = avformat_open_input(&pAVFormatContext, fileName.toUtf8().data(), 0, 0);
if(ret)
{
LOG << "Failed";
goto END;
}
// 步驟三:探測流媒體信息
ret = avformat_find_stream_info(pAVFormatContext, 0);
if(ret < 0)
{
LOG << "Failed to avformat_find_stream_info(pAVCodecContext, 0)";
goto END;
}
LOG << "視頻文件包含流信息的數量:" << pAVFormatContext->nb_streams;
// 步驟四:提取流信息,提取視頻信息
for(int index = 0; index < pAVFormatContext->nb_streams; index++)
{
pAVCodecContext = pAVFormatContext->streams[index]->codec;
switch (pAVCodecContext->codec_type)
{
case AVMEDIA_TYPE_UNKNOWN:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_UNKNOWN";
break;
case AVMEDIA_TYPE_VIDEO:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_VIDEO";
break;
case AVMEDIA_TYPE_AUDIO:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_AUDIO";
audioIndex = index;
break;
case AVMEDIA_TYPE_DATA:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_DATA";
break;
case AVMEDIA_TYPE_SUBTITLE:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_SUBTITLE";
break;
case AVMEDIA_TYPE_ATTACHMENT:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_ATTACHMENT";
break;
case AVMEDIA_TYPE_NB:
LOG << "流序號:" << index << "類型為:" << "AVMEDIA_TYPE_NB";
break;
default:
break;
}
// 已經找打視頻品流
if(audioIndex != -1)
{
break;
}
}
if(audioIndex == -1 || !pAVCodecContext)
{
LOG << "Failed to find video stream";
goto END;
}
// 步驟五:對找到的音頻流尋解碼器
pAVCodec = avcodec_find_decoder(pAVCodecContext->codec_id);
if(!pAVCodec)
{
LOG << "Fialed to avcodec_find_decoder(pAVCodecContext->codec_id):"
<< pAVCodecContext->codec_id;
goto END;
}
#if 0
pAVCodecContext = avcodec_alloc_context3(pAVCodec);
// 填充CodecContext信息
if (avcodec_parameters_to_context(pAVCodecContext,
pAVFormatContext->streams[audioIndex]->codecpar) < 0)
{
printf("Failed to copy codec parameters to decoder context!\n");
goto END;
}
#endif
// 步驟六:打開解碼器
ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
if(ret)
{
LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, pAVDictionary)";
goto END;
}
// 打印
LOG << "解碼器名稱:" <<pAVCodec->name << endl
<< "通道數:" << pAVCodecContext->channels << endl
<< "通道布局:" << av_get_default_channel_layout(pAVCodecContext->channels) << endl
<< "采樣率:" << pAVCodecContext->sample_rate << endl
<< "采樣格式:" << pAVCodecContext->sample_fmt;
#if 1
outChannel = 2;
outSampleRate = 44100;
outFormat = AV_SAMPLE_FMT_S16P;
#endif
#if 0
outChannel = 2;
outSampleRate = 48000;
outFormat = AV_SAMPLE_FMT_FLTP;
#endif
LOG << "to" << endl
<< "通道數:" << outChannel << endl
<< "通道布局:" << av_get_default_channel_layout(outChannel) << endl
<< "采樣率:" << outSampleRate << endl
<< "采樣格式:" << outFormat;
// 步驟七:獲取音頻轉碼器並設置采樣參數初始化
// 入坑二:通道布局與通道數據的枚舉值是不同的,需要轉換
pSwrContext = swr_alloc_set_opts(0, // 輸入為空,則會分配
av_get_default_channel_layout(outChannel),
outFormat, // 輸出的采樣頻率
outSampleRate, // 輸出的格式
av_get_default_channel_layout(pAVCodecContext->channels),
pAVCodecContext->sample_fmt, // 輸入的格式
pAVCodecContext->sample_rate, // 輸入的采樣率
0,
0);
ret = swr_init(pSwrContext);
if(ret < 0)
{
LOG << "Failed to swr_init(pSwrContext);";
goto END;
}
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
outData[0] = (uint8_t *)av_malloc(1152 * 8);
outData[1] = (uint8_t *)av_malloc(1152 * 8);
// 步驟七:讀取一幀數據的數據包
while(av_read_frame(pAVFormatContext, pAVPacket) >= 0)
{
if(pAVPacket->stream_index == audioIndex)
{
// 步驟八:將封裝包發往解碼器
ret = avcodec_send_packet(pAVCodecContext, pAVPacket);
if(ret)
{
LOG << "Failed to avcodec_send_packet(pAVCodecContext, pAVPacket) ,ret =" << ret;
break;
}
// 步驟九:從解碼器循環拿取數據幀
while(!avcodec_receive_frame(pAVCodecContext, pAVFrame))
{
// nb_samples並不是每個包都相同,遇見過第一個包為47,第二個包開始為1152的
// LOG << pAVFrame->nb_samples;
// 步驟十:獲取每個采樣點的字節大小
numBytes = av_get_bytes_per_sample(outFormat);
// 步驟十一:修改采樣率參數后,需要重新獲取采樣點的樣本個數
dstNbSamples = av_rescale_rnd(pAVFrame->nb_samples,
outSampleRate,
pAVCodecContext->sample_rate,
AV_ROUND_ZERO);
// 步驟十二:重采樣
swr_convert(pSwrContext,
outData,
dstNbSamples,
(const uint8_t **)pAVFrame->data,
pAVFrame->nb_samples);
// 第一次顯示
static bool show = true;
if(show)
{
LOG << numBytes << pAVFrame->nb_samples << "to" << dstNbSamples;
show = false;
}
// 步驟十四:使用LRLRLRLRLRL(采樣點為單位,采樣點有幾個字節,交替存儲到文件,可使用pcm播放器播放)
for (int index = 0; index < dstNbSamples; index++)
{
for (int channel = 0; channel < pAVCodecContext->channels; channel++) // 交錯的方式寫入, 大部分float的格式輸出
{
// 用於原始文件jinxin跟對比
// file.write((char *)pAVFrame->data[channel] + numBytes * index, numBytes);
file.write((char *)outData[channel] + numBytes * index, numBytes);
}
}
av_free_packet(pAVPacket);
}
}
}
file.close();
END:
LOG << "釋放回收資源";
if(outData[0] && outData[1])
{
av_free(outData[0]);
av_free(outData[1]);
outData[0] = 0;
outData[1] = 0;
LOG << "av_free(outData[0])";
LOG << "av_free(outData[1])";
}
if(pSwrContext)
{
swr_free(&pSwrContext);
pSwrContext = 0;
}
if(pAVFrame)
{
av_frame_free(&pAVFrame);
pAVFrame = 0;
LOG << "av_frame_free(pAVFrame)";
}
if(pAVPacket)
{
av_free_packet(pAVPacket);
pAVPacket = 0;
LOG << "av_free_packet(pAVPacket)";
}
if(pAVCodecContext)
{
avcodec_close(pAVCodecContext);
pAVCodecContext = 0;
LOG << "avcodec_close(pAVCodecContext);";
}
if(pAVFormatContext)
{
avformat_close_input(&pAVFormatContext);
avformat_free_context(pAVFormatContext);
pAVFormatContext = 0;
LOG << "avformat_free_context(pAVFormatContext)";
}
}
對應工程模板v1.3.0:增加解碼音頻裸存pcmDemo
對應工程模板v1.3.1:增加解碼音頻重采樣存pcmDemo
存文件存錯了,入坑一;字節交錯錯誤,單條音軌是好的,雙軌存入文件,使用pcm的軟件播放,則默認是LRLRLRLR的方式(采樣點交錯)。
分析音頻文件如下:
通道布局與通道數據的枚舉值是不同的,需要轉換
重采樣之后,采樣率不同了,那么對應的時間分片的數據包是相同的,那么很明顯,采樣率低了,則數據應該減少,時間是一樣長的,問題就處在轉換函數需要計算一次采樣率變了之后的實際采樣點,關系到其輸出的音頻采樣點數據,否則長了還好說,短了的話,存入更多就是錯誤數據,自然就出現聲音不對。
解碼mp4封裝時,獲取到的第一個AVFrame的nb_samples不同,第一幀尾32,本想做動態分布,結果踩坑.
在最前面開辟認為的最大緩存空間,如下:
上一篇:《FFmpeg開發筆記(六):ffmpeg解碼視頻並使用SDL同步時間顯示播放》
下一篇:敬請期待