FFmpeg開發筆記(十二):ffmpeg音頻處理、采集麥克風音頻錄音為WAV - 指南
若該文為原創文章,轉載請注明原文出處
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/152651085
各位讀者,知識無窮而人力有窮,要么改需求,要么找專業人士,要么自己研究
長沙紅胖子Qt(長沙創微智科)博文大全:開發技術集合(包含Qt實用技術、樹莓派、三維、OpenCV、OpenGL、ffmpeg、OSG、單片機、軟硬結合等等)持續更新中…
FFmpeg和SDL開發專欄(點擊傳送門)
上一篇:《FFmpeg開發筆記(十一):ffmpeg移植到海思HI35xx平臺之將ffmpeg庫引入到sample的demo中》
下一篇:敬請期待…
前言
??ffmpeg采集音頻是一塊很重要也復雜的,本篇描述了錄音麥克風為pcm封裝成wav。
使用ffmpeg命令行獲取設備列表
??ffmpeg 會列出所有可用的視頻和音頻設備。其中音頻設備會在 “DirectShow audio devices"”標題下顯示
ffmpeg -list_devices true -f dshow -i dummy

??發現輸出的是亂碼(中文在cmd上的輸出),
chcp 56001
??65001 是 UTF-8 編碼的代碼頁

??以上2個沒有麥(系統自己的,沒插入麥),使用usb的:

音頻
音頻源(Audio Source)
??音頻源指FFmpeg獲取聲音數據的來源,是錄制的 “起點”。常見的音頻源主要分為兩類:
- 硬件音頻源:直接從計算機硬件設備采集聲音,如麥克風(通過聲卡輸入接口)、線路輸入(Line-In,用于連接外部設備如錄音機、吉他等)。在不同操作系統中,硬件音頻源的標識方式不同,例如 Windows 下通過 “麥克風陣列”“線路輸入” 等設備名識別,Linux 下通過 ALSA(Advanced Linux Sound Architecture)或 PulseAudio 的設備節點(如hw:0,0)標識,macOS 則依賴 Core Audio 架構的設備 ID。
- 軟件音頻源:從系統內部或其他軟件中捕獲音頻,如錄制瀏覽器播放的音樂、視頻會議的聲音等。這種場景需要依賴 “虛擬音頻設備”(如 Windows 的 “立體聲混音”、macOS 的 Soundflower、Linux 的 PulseAudio Loopback),將系統輸出的音頻重新作為輸入源提供給 FFmpeg。
音頻編解碼器(Audio Codec)
??編解碼器(Codec,即 Coder-Decoder)是處理音頻數據的核心組件,負責將原始音頻采樣數據壓縮(編碼)為特定格式的文件,或解壓(解碼)為可播放的原始數據。在錄制場景中,我們主要關注 “編碼器”,它直接影響錄制文件的體積、音質與兼容性。
??FFmpeg 支持幾乎所有主流音頻編碼器,常見的包括:
- PCM(脈沖編碼調制):無壓縮編碼,直接存儲原始音頻采樣數據,音質最佳但文件體積極大(例如 44.1kHz、16 位、立體聲的 PCM 音頻,每分鐘約 10MB),常見于 WAV 格式文件。
- MP3(MPEG-1 Audio Layer III):有損壓縮編碼,通過舍棄人耳不敏感的音頻頻段實現高壓縮比,是目前最普及的音頻格式之一,比特率通常在 128-320kbps 之間(320kbps 接近無損音質)。
- AAC(Advanced Audio Coding):有損壓縮編碼,性能優于 MP3,在相同比特率下音質更優,廣泛用于 MP4、MOV 等視頻文件及流媒體場景(如 YouTube、抖音)。
- FLAC(Free Lossless Audio Codec):無損壓縮編碼,在保留原始音質的前提下壓縮文件體積(壓縮比約 1:2),適合對音質要求極高的場景(如音樂制作、無損音樂收藏)。
音頻采樣參數
??采樣參數決定了音頻的 “精度” 與 “范圍”,是影響音質的核心指標,主要包括以下三個:
- 采樣率(Sample Rate):單位時間內對音頻信號的采樣次數,單位為赫茲(Hz)。采樣率越高,越能還原高頻聲音,音質越細膩。常見的采樣率有: 44.1kHz:CD 音質的標準采樣率,能覆蓋人耳可聽范圍(20Hz-20kHz); 48kHz:專業音頻制作與視頻配套音頻的常用采樣率; 96kHz/192kHz:高解析度音頻(Hi-Res)的采樣率,適合高端音頻設備。
- 采樣位深(Sample Bit Depth):每個采樣點用多少位二進制數表示,決定了音頻的動態范圍(即最大音量與最小音量的差值)。位深越大,動態范圍越廣,聲音的層次感越強。常見的位深有 16 位(CD 標準,動態范圍約 96dB)、24 位(專業制作標準,動態范圍約 144dB)。
- 聲道數(Channels):音頻信號的通道數量,決定了聲音的空間感。常見的聲道模式有: 單聲道(Mono,1 聲道):適合語音錄制(如 podcasts、語音備忘錄); 立體聲(Stereo,2 聲道):左右聲道分別傳輸不同信號,營造空間感,適合音樂、影視音頻; 多聲道(如 5.1、7.1 聲道):用于環繞聲系統,常見于電影音頻。
容器格式(Container Format)
??容器格式(也稱封裝格式)是用于存儲音頻、視頻、字幕等多種數據流的文件格式,它不負責數據壓縮,僅規定數據的存儲結構。在音頻錄制中,容器格式需與編碼器匹配,常見的組合如下:
- WAV + PCM:無壓縮音頻的標準組合,兼容性強但體積大;
- MP3 + MP3:有損音頻的主流組合,僅支持 MP3 編碼;
- MP4 + AAC:視頻配套音頻或純音頻的常用組合,兼容性好且體積小;
- FLAC + FLAC:無損音頻的主流組合,保留音質的同時壓縮體積。
錄音流程
??FFmpeg 通過 “輸入設備→采集原始壓縮數據包AVPacket→封裝Wav”三個步驟,將音頻源的信號轉化為目標音頻文件,具體流程如下:
步驟一:設備探測與選擇
??FFmpeg 首先通過操作系統的音頻接口(如 Windows 的 DirectSound、Linux 的 ALSA、macOS 的 Core Audio)探測可用的音頻輸入設備,用戶通過命令行參數指定要使用的設備(如-f dshow -i audio=“麥克風陣列”)。
步驟二:音頻采集與原始壓縮數據獲取AVPacket
??選定設備后,FFmpeg 按照指定的采樣參數(采樣率、位深、聲道數)從設備中讀取原始 PCM 壓縮數據。這一步是 “無損” 的,數據直接來自硬件或虛擬設備的輸出。
步驟三:pcm壓縮數據支持.wav格式封裝,直接存為目標文件
??編碼后的音頻數據流會被寫入指定的容器格式(如 MP3、MP4)中,同時生成文件頭、索引等元數據,最終形成可播放的音頻文件。
??注意:本篇沒有重采樣和壓縮,直接存儲的WAV+PCM。
Demo源碼
void FFmpegManager::testCaptureAudio()
{
// 命令行,查看本地可用的音頻設備列表
// linux : ffmpeg -list_devices true -f alsa -i dummy
//
// windows: ffmpeg -list_devices true -f dshow -i dummy
// Windows 系統下通過 DirectShow 接口訪問音頻設備的場景。
// "麥克風 (Realtek(R) Audio)"
// "麥克風 (USB Audio Device)" 使用本設備
// "立體聲混音 (Realtek(R) Audio)"
//
// windows錄制音頻測試: ffmpeg -f dshow -i audio="麥克風 (USB Audio Device)" output.wav
//
// ffmpeg相關變量預先定義與分配
AVFormatContext *pAVFormatContext = 0; // ffmpeg的全局上下文,所有ffmpeg操作都需要
AVInputFormat * pAVInputFormat = 0; // ffmpeg輸入類型格式
AVStream *pAVStream = 0; // ffmpeg流信息
AVCodecParameters *pAVCodecParameters; // ffmpeg解碼器參數
AVCodecContext *pAVCodecContext = 0; // ffmpeg編碼上下文
AVCodec *pAVCodec = 0; // ffmpeg編碼器
AVPacket *pAVPacket = 0; // ffmpag單幀數據包
int ret = 0; // 函數執行結果
int audioIndex = -1; // 音頻流所在的序號
// 步驟一: 注冊ffmpeg所有組件
av_register_all(); // 初始化所有組件(只使用這個,找不到dshow)
avdevice_register_all(); // 顯示注冊所有設備
avcodec_register_all(); // 顯式注冊所有編解碼器
// 步驟二:設置設備輸入格式未dshow
pAVInputFormat = av_find_input_format("dshow");
if(!pAVInputFormat)
{
LOG << "Failed to av_find_input_format(\"dshow\")";
return;
}
{
#if 0
// 探測輸入設備代碼: 使用代碼探測所有設備并輸出
// AVInputFormat * pAVInputFormat = av_find_input_format("dshow");
AVDeviceInfoList * pAVDeviceInfoList = 0;
ret = avdevice_list_input_sources(pAVInputFormat, 0, 0, &pAVDeviceInfoList);
if(ret < 0)
{
char err_buf[1024];
av_strerror(ret, err_buf, sizeof(err_buf));
LOG << QSTRING("無法列出設備: ") << QSTRING(err_buf) << QSTRING("(錯誤代碼:") << ret << ")";
return;
}
for(int index = 0; index < pAVDeviceInfoList->nb_devices; index++)
{
std::string deviceName = pAVDeviceInfoList->devices[index]->device_name;
LOG << QString(deviceName.data());
}
avdevice_free_list_devices(&pAVDeviceInfoList);
#endif
}
// 步驟三: 設置輸入設備
#if 1
QString deviceStr = QSTRING("audio=%1").arg(QSTRING("麥克風 (USB Audio Device)"));
// 步驟四: 打開輸入設備
ret = avformat_open_input(&pAVFormatContext, deviceStr.toUtf8().constData(), pAVInputFormat, 0);
if(ret < 0)
{
LOG << "Failed to open avformat_open_input:" << deviceStr;
return;
}
LOG << "Suceed to open avformat_open_input:" << deviceStr;
#else
std::string deviceName = "麥克風 (USB Audio Device)";
std::string deviceArg = "audio=" + deviceName;
// 步驟四: 打開輸入設備
ret = avformat_open_input(&pAVFormatContext, deviceArg.c_str(), pAVInputFormat, 0);
if(ret < 0)
{
LOG << "Failed to open avformat_open_input:" << QString(deviceName.data());
return;
}
#endif
// 步驟五: 查找流信息, 提取音頻
for(int index = 0; index < pAVFormatContext->nb_streams; index++)
{
pAVCodecContext = pAVFormatContext->streams[index]->codec;
pAVStream = pAVFormatContext->streams[index];
switch (pAVCodecContext->codec_type)
{
case AVMEDIA_TYPE_UNKNOWN:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_UNKNOWN").arg(index);
break;
case AVMEDIA_TYPE_VIDEO:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_VIDEO").arg(index);
break;
case AVMEDIA_TYPE_AUDIO:
audioIndex = index;
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_AUDIO").arg(index);
break;
case AVMEDIA_TYPE_DATA:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_DATA").arg(index);
break;
case AVMEDIA_TYPE_SUBTITLE:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_SUBTITLE").arg(index);
break;
case AVMEDIA_TYPE_ATTACHMENT:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_ATTACHMENT").arg(index);
break;
case AVMEDIA_TYPE_NB:
LOG << QSTRING("流序號: %1 類型為: AVMEDIA_TYPE_NB").arg(index);
break;
default:
break;
}
// 已經找打視頻品流
if(audioIndex != -1)
{
break;
}
}
if(audioIndex == -1 || !pAVCodecContext)
{
LOG << "Failed to find video stream";
return;
}
LOG << "Succeed to find audio stream";
// 步驟六:獲取解碼器參數
pAVCodecParameters = pAVFormatContext->streams[audioIndex]->codecpar;
// 步驟七:查找解碼器
LOG << "AVCodecID" << pAVCodecParameters->codec_id << pAVCodecContext->codec_id;
LOG << "AV_CODEC_ID_PCM_S16LE =" << AV_CODEC_ID_PCM_S16LE;
pAVCodec = avcodec_find_decoder(pAVCodecParameters->codec_id);
if(!pAVCodec)
{
LOG << "Failed to avcodec_find_decoder(pAVCodecContext->codec_id):"
<< pAVCodecContext->codec_id;
return;
}
// 步驟八: 打開解碼器
ret = avcodec_open2(pAVCodecContext, pAVCodec, NULL);
if(ret < 0)
{
LOG << "Failed to avcodec_open2(pAVCodecContext, pAVCodec, NULL);";
return;
}
#if 1
// 打印音頻信息
LOG << QSTRING("音頻信息 采樣率: %1Hz 聲道數: %2 采樣格式: %3")
.arg(pAVCodecContext->sample_rate)
.arg(pAVCodecContext->channels)
.arg(av_get_sample_fmt_name(pAVCodecContext->sample_fmt));
#endif
// 只能wav格式,這個demo
QString fileName = "1.wav";
// QString fileName = "1.aac"; // 錄制無法播放
// 步驟九: 創建輸出上下文
AVFormatContext *pAVFormatContextOut = 0;
ret = avformat_alloc_output_context2(&pAVFormatContextOut, 0, 0, fileName.toUtf8().data());
if(ret < 0)
{
LOG << QSTRING("無法創建輸出上下文");
return;
}
// 步驟十: 創建輸出流
AVStream *pAVStreamOut = 0; // ffmpeg流信息(輸出)
pAVStreamOut = avformat_new_stream(pAVFormatContextOut, 0);
if(!pAVStreamOut)
{
LOG << QSTRING("無法創建輸出流");
return;
}
// 步驟十:復制編碼器信息
ret = avcodec_parameters_copy(pAVStreamOut->codecpar, pAVCodecParameters);
if(ret < 0)
{
LOG << QSTRING("復制編碼器失敗");
return;
}
#if 0
// 步驟十一: 設置輸出編碼器參數(想要別的就修改)
pAVStreamOut->codecpar->codec_id = AV_CODEC_ID_PCM_S16LE;
pAVStreamOut->codecpar->sample_rate = 44100;
// pAVStreamOut->codecpar->sample_rate = 22050; // 聲音會變得慢低,時間翻倍,
// pAVStreamOut->codecpar->sample_rate = 88200; // 生意會變快尖,時間減半
pAVStreamOut->codecpar->channels = 2;
// pAVStreamOut->codecpar->channels = 1; // 聲音會變的慢低,時間翻倍
// pAVStreamOut->codecpar->channels = 4; // 聲音會變的快尖,時間減半
#endif
// 步驟十二: 創建輸出文件
ret = avio_open(&pAVFormatContextOut->pb, fileName.toUtf8().data(), AVIO_FLAG_WRITE);
if(ret < 0)
{
LOG << QSTRING("無法開輸出文件");
return;
}
LOG;
// 步驟十三:寫入頭文件
ret = avformat_write_header(pAVFormatContextOut, 0);
LOG;
if(ret < 0)
{
LOG << QSTRING("寫入頭文件失敗");
return;
}
LOG;
// 步驟十四:錄制循環
LOG << QSTRING("開始錄制...");
int frames = 0;
pAVPacket = av_packet_alloc();
QElapsedTimer elapsedTimer;
elapsedTimer.start();
while(elapsedTimer.elapsed() < 10 * 1000)
{
LOG;
// 步驟十五:讀取一幀音頻數據
ret = av_read_frame(pAVFormatContext, pAVPacket);
LOG;
if(ret < 0)
{
LOG << QSTRING("讀取數據包失敗");
if(ret == AVERROR_EOF)
{
break;
}
return;
}
// 步驟十六:是音頻流則進行時間戳調整
if(pAVPacket->stream_index == audioIndex)
{
// 調整時間戳
pAVPacket->stream_index = 0;
av_packet_rescale_ts(pAVPacket,
pAVFormatContext->streams[audioIndex]->time_base,
pAVStreamOut->time_base);
pAVPacket->pos = -1;
// 寫入數據包
ret = av_interleaved_write_frame(pAVFormatContextOut, pAVPacket);
if (ret < 0)
{
LOG << QSTRING("寫入數據包失敗:") << ret;
break;
}
frames++;
}
av_packet_unref(pAVPacket);
}
// 步驟十五: 寫入文件尾巴
av_write_trailer(pAVFormatContextOut);
av_packet_free(&pAVPacket);
avformat_close_input(&pAVFormatContext);
avio_closep(&pAVFormatContextOut->pb);
avformat_free_context(pAVFormatContext);
LOG << QSTRING("錄制完成! 已保存到") << fileName;
LOG << QSTRING("共寫入 %1 個音頻幀").arg(frames);
}
工程模板v1.6.0

入坑
入坑一:ffmpeg命令行輸出中文設備亂碼
問題

原因
??在 Windows 的命令提示符(CMD)中使用 ffmpeg 時出現中文亂碼,通常是由于編碼不匹配導致的,測試改成uft-8即可。
解決
chcp 65001

入坑二:ffmpeg代碼獲取設備失敗
問題
??Ffmpeg代碼獲取失敗,為0。
原因
??代碼exe獲取設備在win10上是需要管理員權限的,無效;
??打印錯誤代碼:


??ENOSYS(錯誤碼 40)本質上是 FFmpeg 告訴你:“我不認識這個設備類型,因為編譯時沒加支持”。解決的核心是確保 FFmpeg 包含對應平臺的設備模塊,并在代碼中使用正確的設備格式。
思考
??可能是編譯的時候沒編譯dshow進去?但是通過dshow去獲取輸入接口又是可以,只是拿列表不行。
??查找編譯參數:
ffmpeg -buildconf

??Windows:尋找 --enable-dshow(DirectShow 設備支持),Linux:尋找 --enable-alsa(ALSA 音頻設備)或 --enable-v4l2(視頻設備)。
??沒有編譯進去,那就是。
解決
??未解決,不深究,有興趣讀者可以深入嘗試并交流結果。
入坑三:ffmpeg打開音頻獲取解碼器失敗
問題
??找不到編碼器。

嘗試一:列出設備
ffmpeg -f dshow -i audio="麥克風 (USB Audio Device)" -v debug -t 1 NUL


嘗試二:切換msvc版本
??嘗試切換版本后,打開設備都失敗:
??列出設備也失敗,可能就不是版本問題,應該要能列出來。
??使用ffmpeg錄音測試,發現msvc版本的ffmpeg(預編譯)可以錄音,而mingw32的ffmpeg(自編譯)不可以錄音:
??錄音后打開,發現正常錄制了,下面的是mingw32都無法錄音:
??所以懷疑還是版本問題,再次切換至msvc,上面的問題有可能是編碼問題?
??搭建號環境后,再次ffmpeg命令行錄音并播放測試:
??確認沒有問題,庫是沒問題的,查看代碼運行:
??無法列出設備:
??編碼切換可以打開設備,也有數據流,但是拿不到解碼器還是:

??定位解碼器:
ffmpeg -codecs


??是有的,但是無法通過設備去拿到這個id?
??id是對的,鬧烏龍,以為65535是越界,但是更加奇怪,都已經有這個id了,為什么打開其編碼器是失敗的呢?
??測試獲取其編碼器和解碼器都失敗:

??就好像根本找不到這個一樣。
ffmpeg -encoders

ffmpeg -decoders

??原地蒙了?沒注冊,試了下,是沒注冊:
??測試是否可以獲取設備列表了:
??還是不行,查閱細化:
??功能未實現。
解決方式
??注冊即可`
// 步驟一: 注冊ffmpeg所有組件
av_register_all(); // 初始化所有組件(只使用這個,找不到dshow)
avdevice_register_all(); // 顯示注冊所有設備
avcodec_register_all(); // 顯式注冊所有編解碼器
上一篇:《FFmpeg開發筆記(十一):ffmpeg移植到海思HI35xx平臺之將ffmpeg庫引入到sample的demo中》
下一篇:敬請期待…
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/152651085

浙公網安備 33010602011771號