当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/452866.html

相关文章:

  • 金融大模型应用现状及未来趋势研究:国内外对比分析
  • AI 在金融、医疗、教育、制造业等领域都有广泛且深入的应用,以下是这些领域的一些落地案例
  • TensorFlow2 Python深度学习 - TensorFlow2框架入门 - 变量(Variable)的定义与操作
  • AI行业应用:金融、医疗、教育、制造业领域的落地实践
  • 【Git 子模块冲突解析】
  • 软件设计师——09 数据库技术基础
  • Guava Cache 高性能本地缓存库详解与使用案例
  • 开源安全管理平台wazuh-阻止恶意IP访问
  • 蒲城做网站网站定制开发成本
  • 嵌入式开发入门:从 FreeRTOS 任务到通信协议(详细教程)
  • 数据结构(长期更新)第2讲:顺序表(一)
  • 《Flask 的“微”哲学:从轻量内核到请求上下文的深度剖析》
  • 在 Elasticsearch 中改进 Agentic AI 工具的实验
  • Solid Explorer(双窗格文件管理器) 解锁完整版
  • 做外贸自己的公司网站wordpress头像设置方法
  • Java学习之旅第二季-9:包
  • 大数据毕业设计选题推荐-基于大数据的人类健康生活方式数据分析与可视化系统-大数据-Spark-Hadoop-Bigdata
  • 图像处理实践:自定义直方图变换函数的优化与问题解决
  • 人力资源管理的思维方式学习笔记7-final
  • JavaEE初阶——线程安全(多线程)
  • [工作流节点16] 更新他表记录的自动化方案:跨表数据联动的关键实现
  • 南京金融网站建设wordpress热门文章调用
  • 针对 OpenMMLab 视频理解(分类)的 MMAction2 的环境配置
  • 中国电信用户行为实时分析系统运维实战
  • HTTP、WebSocket、XMPP、CoAP、MQTT、DDS 六大协议在机器人通讯场景应用
  • 长春网站制作招聘信息上海网站被查
  • 做自媒体视频搬运网站网站建设与管理淘宝
  • IP 协议的相关特性
  • 《投资-88》价值投资者的认知升级与交易规则重构 - 第三层:估值安全边际,“再好的公司,如果买贵了,也会变成一笔糟糕的投资。”
  • 工程师 - Raspberry Pi Pico程序:读取SPI数据后从串口输出