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(); // 显式注册所有编解码器// 步骤二:设置设备输入格式未dshowpAVInputFormat = 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 1QString 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;
#elsestd::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格式,这个demoQString 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