音频采集 via FFmpeg
FFmpeg 简介
FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用 LGPL 或 GPL 许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频 / 视频编解码库 libavcodec,为了保证高可移植性和编解码质量,libavcodec 里很多 code 都是从头开发的。
FFmpeg 在 Linux 平台下开发,但它同样也可以在其它操作系统环境中编译运行,包括 Windows、Mac OS X 等。这个项目最早由 Fabrice Bellard 发起,2004 年至 2015 年间由 Michael Niedermayer 主要负责维护。许多 FFmpeg 的开发人员都来自 MPlayer 项目,而且当前 FFmpeg 也是放在 MPlayer 项目组的服务器上。项目的名称来自 MPEG 视频编码标准,前面的 “FF” 代表 “Fast Forward”。
FFmpeg 命令行采集音频
FFmpeg 提供了现成的程序用命令行的方式对音频进行采集。
- 首先需要枚举电脑上的音频采集设备:
>ffmpeg.exe -list_devices true -f dshow -i dummy
… …
[dshow @ 007bd020] DirectShow video devices (some may be both video and audio devices)
[dshow @ 007bd020] “USB Web Camera - HD"
[dshow @ 007bd020] Alternative name "@device_pnp_\\?\usb#vid_1bcf&pid_288e&mi_00#7&6c75a67&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global"
[dshow @ 003cd000] DirectShow audio devices
[dshow @ 003cd000] "Microphone (Realtek High Defini"
[dshow @ 003cd000] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\Microphone (Realtek High Defini“
在我的电脑上有两个采集设备,一个是用来采集视频的摄像头,一个是用来采集音频的麦克风“Microphone (Realtek High Defini ”(此处名称因为太长被截断)。
- 然后选择麦克风设备进行采集
>ffmpeg.exe -f dshow -i audio="Microphone (Realtek High Defini" d:\test.mp3
... ...
Guessed Channel Layout for Input Stream #0.0 : stereo
Input #0, dshow, from 'audio=Microphone (Realtek High Defini':
Duration: N/A, start: 38604.081000, bitrate: 1411 kb/s
Stream #0:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s
Stream mapping:
Stream #0:0 -> #0:0 (pcm_s16le (native) -> mp3 (libmp3lame))
Press [q] to stop, [?] for help
Output #0, mp3, to 'd:\test.mp3':
Metadata:
TSSE : Lavf57.71.100
Stream #0:0: Audio: mp3 (libmp3lame), 44100 Hz, stereo, s16p
Metadata:
encoder : Lavc57.89.100 libmp3lame
size=102kB time=00:00:06.48 bitrate=128.7kbits/s speed=2.13x ← 正在录制的音频信息,会实时变化
FFmpeg 在 Windows 上用的是 DirectShow 输入设备进行采集的,然后经由自己的 mp3 encoder 和 file writer 写到磁盘上。
FFmpeg API 采集音频
FFmpeg 还提供了完备的 API 对音频进行采集。
下面是使用 FFmpeg 采集并编码音频的流程图。使用该流程,可以编码 MP3、AAC、FLAC 等等各种 FFmpeg 支持的音频。
音频重采样
音频重采样 是转换已采样的音频数据的过程,比如当输入输出数据的采样率不一致时,或者声道数不一致时,就需要重采样。音频重采样主要步骤是进行抽取或插值。由于抽取可能产生混叠,插值可能产生镜像,因此需要在抽取前进行抗混叠滤波,在插值后进行抗镜像滤波。抗混叠滤波和抗镜像滤波都是使用低通滤波器实现。
FFmpeg 提供了重采样的 API,主要流程如下:
采集音频代码
以下是整个 FFmpeg 采集过程的概要代码,略去各个函数的具体实现和资源释放。
本文中的代码基于 FFmpeg 4.1。
hr = open_cap_device(AVMEDIA_TYPE_AUDIO, &cap_fmt_ctx, &cap_codec_ctx);
GOTO_IF_FAILED(hr);
hr = open_output_audio_file(out_file, cap_codec_ctx, &out_fmt_ctx, &enc_ctx);
GOTO_IF_FAILED(hr);
hr = init_resampler(cap_codec_ctx, enc_ctx, &resample_ctx);
GOTO_IF_FAILED(hr);
hr = init_fifo(&fifo, enc_ctx);
GOTO_IF_FAILED(hr);
hr = avformat_write_header(out_fmt_ctx, NULL);
GOTO_IF_FAILED(hr);
while (_kbhit() == 0) {
// Infinitely capture audio until a key input.
int finished = 0;
hr = audio_transcode( cap_fmt_ctx, cap_codec_ctx, out_fmt_ctx, enc_ctx,
fifo, resample_ctx, 0, &finished, false, true );
GOTO_IF_FAILED(hr);
if (finished)
break;
}
flush_encoder(out_fmt_ctx, enc_ctx);
hr = av_write_trailer(out_fmt_ctx);
GOTO_IF_FAILED(hr);
open_cap_device 函数
在 Windows 上 FFmpeg 使用 DirectShow 的设备进行采集,这里我们先枚举所有的设备,然后选用第一个成功初始化的设备。
int open_cap_device(
AVMediaType cap_type,
AVFormatContext **cap_fmt_ctx,
AVCodecContext **cap_codec_ctx,
AVDictionary** options = NULL)
{
RETURN_IF_NULL(cap_fmt_ctx);
RETURN_IF_NULL(cap_codec_ctx);
*cap_fmt_ctx = NULL;
*cap_codec_ctx = NULL;
int hr = -1;
std::string cap_device_name;
std::vector<std::wstring> cap_devices;
avdevice_register_all();
CoInitialize(NULL);
AVInputFormat* input_fmt = av_find_input_format("dshow");
GOTO_IF_NULL(input_fmt);
switch (cap_type) {
case AVMEDIA_TYPE_AUDIO:
cap_device_name = "audio=";
hr = enum_dshow_acap_devices(cap_devices);
break;
}
GOTO_IF_FAILED(hr);
cap_device_name += unicodeToUtf8(cap_devices[0].c_str());
hr = avformat_open_input(cap_fmt_ctx, cap_device_name.c_str(), input_fmt, options);
GOTO_IF_FAILED(hr);
hr = avformat_find_stream_info(*cap_fmt_ctx, NULL);
GOTO_IF_FAILED(hr);
for (unsigned int i = 0; i < (*cap_fmt_ctx)->nb_streams; i++) {
AVCodecParameters* codec_par = (*cap_fmt_ctx)->streams[i]->codecpar;
if (codec_par->codec_type == cap_type) {
av_dump_format(*cap_fmt_ctx, i, NULL, 0);
AVCodec* decoder = avcodec_find_decoder(codec_par->codec_id);
GOTO_IF_NULL(decoder);
*cap_codec_ctx = avcodec_alloc_context3(decoder)