前言

实时音频采集作为音频存储、处理的基础,无论在直播、通讯、音乐制作等领域中都扮演着重要的角色。通过选择合适的麦克风、采样率以及压缩算法等技术手段,可以达到高质量音频采集的目标。今天就详细的介绍一下各个平台的音频采集。


一、iOS端的音频采集有两种方式,一种是使用AVFoundation提供的AVAudioRecorder类进行录音,另一种是使用Core Audio框架中的Audio Unit实现音频采集。

下面详细介绍一下基于Core Audio框架的音频采集实现。

// 建立Audio Session
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
[session setMode:AVAudioSessionModeMeasurement error:nil];
[session setActive:YES error:nil];
// 创建Audio Unit对象
AudioComponentDescription desc;
desc.componentType = kAudioUnitType_Output;
desc.componentSubType = kAudioUnitSubType_RemoteIO;
desc.componentManufacturer = kAudioUnitManufacturer_Apple;
desc.componentFlags = 0;
desc.componentFlagsMask = 0;
AudioComponent inputComponent = AudioComponentFindNext(NULL, &desc);
AudioUnit audioUnit;
AudioComponentInstanceNew(inputComponent, &audioUnit);
// 创建Audio Unit后需要对其进行配置,包括格式、采样率、音频输入和输出回调等参数的设置
UInt32 flag = 1;
AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag)
);
AudioStreamBasicDescription audioFormat;
audioFormat.mSampleRate = 44100;
audioFormat.mFormatID = kAudioFormatLinearPCM;
audioFormat.mFormatFlags = kAudioFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked;
audioFormat.mFramesPerPacket = 1;
audioFormat.mChannelsPerFrame = 2;
audioFormat.mBytesPerFrame = 4;
audioFormat.mBytesPerPacket = 4;
audioFormat.mBitsPerChannel = 16;
AudioUnitSetProperty(audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &audioFormat, sizeof(audioFormat)); AURenderCallbackStruct callback;
callback.inputProc = audioInputCallback;
callback.inputProcRefCon = (__bridge void *)(self); AudioUnitSetProperty(audioUnit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Global, 0, &callback, sizeof(callback));
// 启动Audio
Unit AudioOutputUnitStart(audioUnit);
// audioInputCallback函数为数据回调函数,在每次有新的音频数据时会被调用。可以在这个函数中获取音频数据,进行后续处理或传输。
static OSStatus audioInputCallback(void *inRefCon, AudioUnitRenderActionFlags *ioActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { AudioBuffer buffer; buffer.mNumberChannels = 2; buffer.mDataByteSize = inNumberFrames * 4; buffer.mData = malloc(inNumberFrames * 4); AudioBufferList bufferList; bufferList.mNumberBuffers = 1; bufferList.mBuffers[0] = buffer; AudioUnitRender(audioUnit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, &bufferList); // 获取采集到的音频数据,进行后续处理或传输 free(bufferList.mBuffers[0].mData); return noErr;})

总的来说,iOS端的音频采集可以基于AVFoundation提供的AVAudioRecorder类进行简单的录音操作,也可以使用Core Audio框架中的Audio Unit实现低延时、高质量的音频采集和处理,具体实现方式需要根据应用场景和需求进行选择。

二、Android平台上实现音频采集

大致可以分为以下几个步骤:

1. 获取音频设备 在Android的Audio系统中,音频设备是通过AudioManager来获取的。可以通过AudioManager的getDevices() 方法获取所有音频设备列表,或者通过getDevicesForStream() 方法获取指定音频流对应的音频设备列表。

2. 配置音频采集参数 根据需求配置音频采集参数,包括采样频率、采样位数、声道数等。可以通过AudioRecord类来实现配置。AudioRecord是Android平台上的录音类,它可以通过构造函数来传入采集参数,例如:

int audioSource = MediaRecorder.AudioSource.MIC;
int sampleRateInHz = 44100;
int channelConfig = AudioFormat.CHANNEL_IN_MONO;
int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int bufferSizeInBytes = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
AudioRecord audioRecord = new AudioRecord(audioSource, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes); 

3. 采集音频数据 调用AudioRecord的 read()方法来采集音频数据。read()方法有多个重载,在本文中我们使用其中一个比较简单的重载,每次读取一定长度的数据,并将其存储到一个字节数组中。例如:

byte[] audioData = new byte[blockSizeInBytes];
audioRecord.read(audioData, 0, blockSizeInBytes); 

需要注意的是,这里采集到的是PCM格式的音频数据,需要根据采集参数进行相应的解析。

4. 停止音频采集 在不需要采集音频数据时,需要调用AudioRecord的stop() 方法及release() 方法来停止音频采集并释放相应的资源。例如:

audioRecord.stop();
audioRecord.release();

以上就是Android平台上实现音频采集的简单介绍。需要注意的是,音频采集过程中需要申请权限,并且考虑到设备的不同,配置参数和实现方式会有所不同。

三、Windows端音频采集

在Windows平台上进行音频采集需要使用Windows Multimedia API (MME)或者Windows Core Audio API。这里我们以Windows Core Audio API为例,介绍一下Windows端音频采集的代码实现。

1. 初始化设备 初始化设备需要使用MMDevice API,首先需要创建一个IMMDeviceEnumerator接口,并使用GetDefaultAudioEndpoint()方法获取默认音频输入设备。

IMMDeviceEnumerator* deviceEnumerator = nullptr;
IMMDevice* audioDevice = nullptr;
HRESULT hr = CoCreateInstance( __uuidof(MMDeviceEnumerator), nullptr, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&deviceEnumerator) );
if (FAILED(hr)) { fprintf(stderr, "ERROR: CoCreateInstance failed.\n"); return EXIT_FAILURE;
}
hr = deviceEnumerator->GetDefaultAudioEndpoint(eCapture, eCommunications, &audioDevice);
if (FAILED(hr)) { fprintf(stderr, "ERROR: GetDefaultAudioEndpoint failed.\n"); deviceEnumerator->Release(); return EXIT_FAILURE;
}
deviceEnumerator->Release();

2. 获取音频格式 接着需要获取音频输入设备当前支持的音频格式。我们可以使用 IAudioClient 接口来获取它所支持的格式,包括采样率、位深、通道数等。

IAudioClient* audioClient = nullptr;
IAudioCaptureClient* captureClient = nullptr;
WAVEFORMATEX* pwfx = nullptr;
hr = audioDevice->Activate( __uuidof(IAudioClient), CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&audioClient) );
if (FAILED(hr)) { fprintf(stderr, "ERROR: Activate failed.\n"); audioDevice->Release(); return EXIT_FAILURE;
}
// Get the default device format.
hr = audioClient->GetMixFormat(&pwfx);
if (FAILED(hr)) { fprintf(stderr, "ERROR: GetMixFormat failed.\n"); audioClient->Release(); audioDevice->Release(); return EXIT_FAILURE;
}
// Ensure the format support.
hr = audioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_LOOPBACK, REFTIMES_PER_SEC, 0, pwfx, nullptr );
if (FAILED(hr)) { fprintf(stderr, "ERROR: Initialize failed.\n"); audioClient->Release(); audioDevice->Release(); CoTaskMemFree(pwfx); return EXIT_FAILURE;
}
UINT32 bufferFrameCount = 0;
hr = audioClient->GetBufferSize(&bufferFrameCount);
if (FAILED(hr)) { fprintf(stderr, "ERROR: GetBufferSize failed.\n"); audioClient->Release(); audioDevice->Release(); CoTaskMemFree(pwfx); return EXIT_FAILURE;
}

3. 创建采样线程 在初始化完设备后,我们需要创建一个子线程并在其中不断地循环读取数据。这里我们使用IAudioCaptureClient接口的GetBuffer() 方法来获取音频数据,并使用ReleaseBuffer() 方法通知系统该缓冲区已满。

DWORD taskThreadId;
HANDLE taskThreadHandle = CreateThread( nullptr, 0, AudioCaptureThread, reinterpret_cast<void*>(audioClient), 0, &taskThreadId );
if (taskThreadHandle == nullptr) { fprintf(stderr, "ERROR: CreateThread failed.\n"); audioClient->Release(); audioDevice->Release(); CoTaskMemFree(pwfx); return EXIT_FAILURE;
}

在子线程中,我们循环读取采样缓冲区中的音频数据:

DWORD WINAPI AudioCaptureThread(LPVOID lpParameter) { IAudioClient* audioClient = reinterpret_cast<IAudioClient*>(lpParameter); IAudioCaptureClient* captureClient = nullptr; WAVEFORMATEX* pwfx = nullptr; UINT32 bufferFrameCount = 0; DWORD flags = 0; BYTE* pData = nullptr; DWORD n = 0; // Get the capture client. HRESULT hr = audioClient->GetService( __uuidof(IAudioCaptureClient), reinterpret_cast<void**>(&captureClient) ); if (FAILED(hr)) { fprintf(stderr, "ERROR: GetService failed.\n"); return EXIT_FAILURE; } // Get the buffer size. hr = audioClient->GetBufferSize(&bufferFrameCount); if (FAILED(hr)) { fprintf(stderr, "ERROR: GetBufferSize failed.\n"); captureClient->Release(); return EXIT_FAILURE; } // Get the default device format. hr = audioClient->GetMixFormat(&pwfx); if (FAILED(hr)) { fprintf(stderr, "ERROR: GetMixFormat failed.\n"); captureClient->Release(); return EXIT_FAILURE; } // Start capturing. hr = audioClient->Start(); if (FAILED(hr)) { fprintf(stderr, "ERROR: Start failed.\n"); captureClient->Release(); return EXIT_FAILURE; } // Capture loop. while (g_audioCapturing) { // Get the next packet. hr = captureClient->GetBuffer(&pData, &n, &flags, nullptr, nullptr); if (FAILED(hr)) { fprintf(stderr, "ERROR: GetBuffer failed.\n"); captureClient->Release(); return EXIT_FAILURE; } if (flags & AUDCLNT_BUFFERFLAGS_SILENT) { memset(pData, 0, n); } // Do something with the audio data. // ... // Release the packet. hr = captureClient->ReleaseBuffer(n); if (FAILED(hr)) { fprintf(stderr, "ERROR: ReleaseBuffer failed.\n"); captureClient->Release(); return EXIT_FAILURE; } } // Stop capturing. hr = audioClient->Stop(); if (FAILED(hr)) { fprintf(stderr, "ERROR: Stop failed.\n"); captureClient->Release(); return EXIT_FAILURE; } captureClient->Release(); return EXIT_SUCCESS;
}

4. 停止采集 最后,在程序退出时需要调用IAudioClient接口的 Stop() 方法来停止采集,并释放资源。

// Stop capturing.
g_audioCapturing = false;
WaitForSingleObject(taskThreadHandle, INFINITE);
HRESULT hr = audioClient->Stop();
if (FAILED(hr)) { fprintf(stderr, "ERROR: Stop failed.\n");
}
audioClient->Release();
audioDevice->Release();
CoTaskMemFree(pwfx); 

实现window端的音频采集,除了 Win32 API 之外,还可以使用第三方库进行音频采集。目前比较流行的音频采集库包括 OpenAL、ASIO、PortAudio 等。

至此,我们分别在iOS、Android、Windows平台实现是了音频采集,有关音频相关的常用参数可以查看 实时音视频的那些事儿(一)

实时音视频的那些事儿(二)—— 音频采集相关推荐

  1. 移动端实时音视频详解(二):处理

    移动端实时音视频详解(二):处理 视频或者音频完成采集之后得到原始数据,为了增强一些现场效果或者加上一些额外的效果,我们一般会在将其编码压缩前进行处理,比如打上时间戳或者公司 Logo 的水印,祛斑美 ...

  2. 从零开始学习音视频编程技术(二) 音频格式讲解

    从零开始学习音视频编程技术(二) 音频格式讲解 原文地址:http://blog.yundiantech.com/?log=blog&id=5 1. 音频简介 前面我们说过视频有一个每秒钟采集 ...

  3. LiveVideoStack线上分享第四季(十二):实时音视频抗丢包的实践

    12月26日 19:30,LiveVideoStack线上分享第四季,第十二期,我们邀请到了好视通 音视频开发工程师 何永德分享"好视通"如何通过FEC.NACK .带宽自适应等技 ...

  4. 实时音视频助力在线教育风口

    正文字数:6185  阅读时长:9分钟 TRTC(Tencent Realtime Communication)全称是腾讯实时音视频,是在腾讯云上以SDK和REST API的方式提供售卖的云服务.腾讯 ...

  5. 实时音视频的超级风口,开发者的机会在哪里?

    2021年初因为Elon Musk"带货"而走红的音频社交App Clubhouse,又以肉眼可见的速度跌落神坛,下载量从2月的960 万/月跌至4月的92万/月.不过在5月,Cl ...

  6. 实时音视频聊天中超低延迟架构的思考与技术实践

    1.前言 从直播在线上抓娃娃,不断变化的是玩法的创新,始终不变的是对超低延迟的苛求.实时架构是超低延迟的基石,如何在信源编码.信道编码和实时传输整个链条来构建实时架构?在实时架构的基础之上,如果通过优 ...

  7. 顶级技术大咖,揭秘实时音视频开发的超级风口

    2021年初因为Elon Musk"带货"而走红的音频社交App Clubhouse,又以肉眼可见的速度跌落神坛,下载量从2月的960 万/月跌至4月的92万/月.不过在5月,Cl ...

  8. 实时音视频直播带货中影响用户体验的Bug根因

    VOL 131 05 2020-06 今天距2021年209天 这是ITester软件测试小栈第131次推文 点击上方蓝字"ITester软件测试小栈"关注我,每周一.三.五早上  ...

  9. Android音视频学习系列(五) — 掌握音频基础知识并使用AudioTrack、OpenSL ES渲染PCM数据

    系列文章 Android音视频学习系列(一) - JNI从入门到精通 Android音视频学习系列(二) - 交叉编译动态库.静态库的入门 Android音视频学习系列(三) - Shell脚本入门 ...

最新文章

  1. 英特尔开源版HE-Transformer,对于隐私数据AI终于上手了!
  2. python extended,python list中的append 与 extended 的区别
  3. 数据结构pta选择判断复习
  4. 10 MM配置-主数据-定义物料类型
  5. 4015-基于递归的折半查找(C++)
  6. 余世维《职业经理人常犯的错误》[文字版]
  7. 基于matlab的神经网络设计,深度神经网络代码matlab
  8. 算法笔记练习 题解合集
  9. 一例智能网卡(mellanox)的网卡故障分析
  10. 遥感影像计算植被覆盖度FVC
  11. 2014 Unity 璀璨星空夜
  12. 梦幻西游战斗中服务器维护,梦幻西游10月22日维护公告 连续战斗自动问题修复...
  13. python实现3d扫描_三维激光扫描建模仪(基于树莓派)
  14. \u202a 神奇的控制字符
  15. 中国休闲食品行业发展现状及前景分析,市场集中度低「图」
  16. Jenkins构建项目poll SCM参数怎么配置
  17. C#实现的 写字板 程序
  18. python爬去新浪微博_Python 超简单爬取新浪微博数据 (高级版)
  19. 2.软件工程—E-R图
  20. NProgress在Vue中的使用

热门文章

  1. 客户需要增加注音、繁体输入法,手写输入法
  2. C# dsoframer.ocx 控件注册
  3. Android蓝牙配对时不弹框
  4. 计算机协会活动创意,【社团活动】创意科技让生活更美好——首届创意·科技文化节开幕式...
  5. 【云周刊】第144期:阿里云海外征战记:跻身全球前三,只用了两年半
  6. 从MySQL开发规范处看创业
  7. np.power()简要介绍
  8. UE4中的Collision Presets理解
  9. ERROR 1840 (HY000) @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empt
  10. 回购国内股份,改回中国国籍,93岁的李嘉诚葫芦里卖的什么药?