调整IMFSourceReader数据块大小实现低延迟音频捕获
2025-01-25 18:52:08
调整 IMFSourceReader 的数据块大小
在使用 Windows Media Foundation (IMF) 进行音频录制时,IMFSourceReader::OnReadSample
方法返回的数据块大小以及调用频率,直接关系到音频数据的实时性和响应速度。开发者常常需要根据应用需求,调整这些参数来获得更好的音频处理效果。 默认情况下,IMFSourceReader
返回的音频块大小通常是基于硬件和格式的一些默认设置。 这会导致回调间隔固定,数据块延迟增大。
问题剖析
在实际开发中,我们遇到如下问题:
- 通过
IMFSourceReader
读取音频数据时,每个数据块的持续时间固定为 50 毫秒。 - 降低采样率会影响数据块的大小,但不会改变其持续时间。
- 尝试调整如
MF_SAUDIO_SAMPLES-PER-BLOCK
等参数并没有产生预期效果。 - 需要降低数据块延迟到 10 毫秒或更高频率的回调,实现更精细化的音频操作。
这个问题的本质在于 IMFSourceReader
对数据块的管理和输出机制。它的输出,并非严格按照开发者设置来返回数据块。系统对 IMFSourceReader
读取的数据流,进行缓冲管理。 而在 IMFSourceReader
的设置上,系统会根据设备和格式来进行默认缓冲配置,而不是开发者简单调整就能立即生效。要解决这一问题,需要对 IMFSourceReader
以及其底层的媒体管道机制有深入理解。
解决方案
1. 设置更小的采样率
尽管降低采样率本身不会直接减少数据块的持续时间, 但它能缩小每次返回的数据块的字节大小。较小的数据块配合后续的手段可以带来一些优化空间。 可以通过在 IMFMediaType
上设置 MF_MT_AUDIO_SAMPLES_PER_SECOND
来实现 。
操作步骤:
- 获取当前的
IMFMediaType
- 通过
SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, desiredSampleRate)
方法修改采样率 - 重新初始化
IMFSourceReader
使用更新后的媒体类型。
代码示例:
HRESULT SetAudioSampleRate(IMFMediaType* pMediaType, UINT32 sampleRate) {
HRESULT hr = S_OK;
hr = pMediaType->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, sampleRate);
return hr;
}
// Example Usage:
// 假设 pMediaType 为 IMFMediaType 的实例, newSampleRate 是新的采样率值 (例如, 24000).
// 需要在 IMFSourceReader 初始化之前,执行此操作。
HRESULT hr = SetAudioSampleRate(pMediaType, newSampleRate);
2. 设置音频设备缓存时长
IMFSourceReader 通常使用默认的音频设备缓冲区。通过调整这个缓冲时长可以影响 OnReadSample
回调的频率,通过更短的缓冲区时间能使得音频流更精细的呈现。可以使用 IPropertyStore
来控制音频捕获设备缓存属性 PKEY_AudioEngine_DeviceFormat
的 KSAUDIO_BUFFER_LENGTH
参数。 这涉及到底层 KSPROPERTY 机制。
操作步骤:
- 获取音频捕捉设备的 IKsControl 接口。
- 获取设备的 Property Store.
- 修改 Property Store 中的 KSAUDIO_BUFFER_LENGTH 参数。
代码示例:
HRESULT SetAudioBufferLength(IMMDevice* pDevice, DWORD bufferLengthMilliseconds) {
HRESULT hr = S_OK;
IKsControl* pKsControl = NULL;
IPropertyStore* pPropertyStore = NULL;
// 获取 IKsControl 接口.
hr = pDevice->Activate(__uuidof(IKsControl), CLSCTX_ALL, NULL, (void**)&pKsControl);
if(FAILED(hr))
goto done;
// Get the Property Store for the audio endpoint
hr = pDevice->OpenPropertyStore(STGM_READWRITE, &pPropertyStore);
if(FAILED(hr))
goto done;
// 创建一个新的 variant 用于储存时间数据,以十分之一毫秒为单位.
PROPVARIANT varValue;
PropVariantInit(&varValue);
varValue.vt = VT_UI8; //Unsigned 64-bit integer
ULONGLONG ullNanoseconds = (ULONGLONG)bufferLengthMilliseconds * 10000;
varValue.uhVal.QuadPart = ullNanoseconds;
// Set the buffer length to PKEY_AudioEngine_DeviceFormat property
PROPERTYKEY keyBufferLength = PKEY_AudioEngine_DeviceFormat;
hr = pPropertyStore->SetValue(keyBufferLength,varValue );
if (FAILED(hr)) {
goto done;
}
hr = pPropertyStore->Commit();
done:
if (pPropertyStore)
{
pPropertyStore->Release();
pPropertyStore = nullptr;
}
if (pKsControl)
{
pKsControl->Release();
pKsControl = nullptr;
}
return hr;
}
//Example usage
// 假设 pAudioCaptureDevice 是 IMMDevice 音频捕捉设备
DWORD desiredBufferLengthMilliseconds = 10; //设置缓冲区时长为 10 毫秒
HRESULT hr = SetAudioBufferLength(pAudioCaptureDevice,desiredBufferLengthMilliseconds);
注意: 设置过小的缓存值可能导致音频数据丢失,增加抖动或者卡顿。需根据实际情况,平衡延迟与稳定性。 另外这种修改影响系统级属性,注意做回滚操作以及异常情况处理,以及权限控制。
3.使用 WASAPI (Windows Audio Session API)
相对于 IMFSourceReader
, WASAPI 提供了更底层的音频访问。 WASAPI 可以提供精确到样本级的音频数据操作,这使得对数据块的处理有更多的掌控权,更低延迟。
操作步骤:
- 使用
IMMDeviceEnumerator
枚举音频设备,并选择指定的音频设备. - 激活
IAudioClient
并配置音频格式。 - 通过
IAudioCaptureClient
获取捕获的数据.
** 代码示例: (仅为演示, 具体细节需按需调整)**
// 参考 Microsoft 官方文档:https://learn.microsoft.com/en-us/windows/win32/coreaudio/capturing-a-stream
#include <mmdeviceapi.h>
#include <audioclient.h>
// 为了简化演示省略很多异常检查
void CaptureAudioStreamWASAPI(int sampleRate, int channelCount,int bufferLengthInMilliseconds) {
IMMDeviceEnumerator* pDeviceEnumerator = NULL;
IMMDevice* pDevice = NULL;
IAudioClient* pAudioClient = NULL;
IAudioCaptureClient* pCaptureClient = NULL;
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator),NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator),(void**)&pDeviceEnumerator);
hr = pDeviceEnumerator->GetDefaultAudioEndpoint(eCapture,eConsole,&pDevice);
hr = pDevice->Activate(__uuidof(IAudioClient), CLSCTX_ALL, NULL, (void**)&pAudioClient);
WAVEFORMATEX *pwfx=nullptr;
hr= pAudioClient->GetMixFormat(&pwfx);
WAVEFORMATEX waveFormat = {};
waveFormat.wFormatTag = WAVE_FORMAT_PCM;
waveFormat.nChannels = channelCount;
waveFormat.nSamplesPerSec =sampleRate;
waveFormat.wBitsPerSample = 16; // or 32
waveFormat.nBlockAlign = waveFormat.nChannels *(waveFormat.wBitsPerSample/8);
waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign;
REFERENCE_TIME bufferDurationInHundredNanoseconds = (REFERENCE_TIME)bufferLengthInMilliseconds * 10000; // Convert milliseconds to 100ns units
hr =pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,0,bufferDurationInHundredNanoseconds,0,&waveFormat, NULL);
hr=pAudioClient->GetBufferSize(&bufferSize);
hr = pAudioClient->GetService(__uuidof(IAudioCaptureClient),(void**)&pCaptureClient);
hr = pAudioClient->Start();
//在循环中,从CaptureClient中读取音频数据并发送, 这里只做了简单演示
}
安全建议:
- 在涉及操作系统音频配置的时候需要额外注意权限和操作,不当的配置可能会影响系统和其他音频应用的正常使用,记得处理异常以及提供适当的回滚机制
- 在使用 WASAPI 的时候 需要格外注意资源的分配和释放,例如线程资源。并且捕获和渲染,是相对复杂的流程,需要认真阅读 Microsoft 文档,谨慎操作。
上述方法各有优缺点,开发者需要根据自身实际情况,选择最适合的方案,或者将几种方案进行组合,以期获得最佳效果。