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

posted @ 2025-11-04 15:29  yangykaifa  阅读(75)  评论(0)    收藏  举报