#c #windows #winapi #audio #wasapi
#c #Windows #winapi #Аудио #wasapi
Вопрос:
Я пытаюсь воспроизвести шум через средство визуализации конечной точки звука по умолчанию, используя интерфейс WASPAI. Я пытаюсь использовать код, предоставленный Microsoft на этой странице: https://docs.microsoft.com/en-us/windows/win32/coreaudio/rendering-a-stream . Я хочу написать класс, который может генерировать шум для этого примера кода.
Я попытался записать целочисленные значения со знаком и без знака в буфер средства визуализации конечной точки звука по умолчанию и вижу, что значения записываются в буфер, но звук не воспроизводится.
Для начала я создал заголовок с необходимыми методами и генератором случайных чисел.
#pragma once
// RNG
#include <random>
template <typename T>
class Random {
public:
Random(T low, T high) : mLow(low), mHigh(high), function(std::mt19937_64(__rdtsc())) {};
T operator()() {
signed __int64 f = function();
return ((f % ((signed __int64) mHigh (signed __int64) mLow)) (signed __int64) mLow); }
private:
T mLow;
T mHigh;
std::mt19937_64 function;
};
class Noise_Gen {
public:
Noise_Gen() : nChannels(NULL), nSamplesPerSec(NULL), nAvgBytesPerSec(NULL), nByteAlign(NULL), wBitsPerSample(NULL),
wValidBitsPerSample(NULL), wSamplesPerBlock(NULL), dwChannelMask(NULL), rd(NULL) {};
~Noise_Gen() {
if(rd != NULL) {
delete rd;
}
};
HRESULT SetFormat(WAVEFORMATEX*);
HRESULT LoadData(UINT32 bufferFrameCount, BYTE* pData, DWORD* flags);
private:
void* rd;
// WAVEFORMATEX
WORD nChannels;
DWORD nSamplesPerSec;
DWORD nAvgBytesPerSec;
WORD nByteAlign;
WORD wBitsPerSample;
// WAVEFORMATEXTENSIBLE
WORD wValidBitsPerSample;
WORD wSamplesPerBlock;
DWORD dwChannelMask;
};
Затем я добавил определения:
// WASAPI
#include <Audiopolicy.h>
#include <Audioclient.h>
#include <time.h>
#include "Noise_Gen.h"
HRESULT Noise_Gen::SetFormat(WAVEFORMATEX* format) {
nChannels = format->nChannels;
nSamplesPerSec = format->nSamplesPerSec;
nAvgBytesPerSec = format->nAvgBytesPerSec;
nByteAlign = format->nBlockAlign;
wBitsPerSample = format->wBitsPerSample;
WORD wFormatTag = format->wFormatTag;
if(wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
WAVEFORMATEXTENSIBLE* pWFE = reinterpret_cast<WAVEFORMATEXTENSIBLE*>(format);
wValidBitsPerSample = pWFE->Samples.wValidBitsPerSample;
wSamplesPerBlock = pWFE->Samples.wSamplesPerBlock;
dwChannelMask = pWFE->dwChannelMask;
} else {
wValidBitsPerSample = wBitsPerSample;
}
double amplitude = std::pow(2.0, wValidBitsPerSample) - 1;
switch(wBitsPerSample / 8) {
case(1):
rd = new Random<unsigned __int8>(0.0, amplitude);
break;
case(2):
rd = new Random<unsigned __int16>(0.0, amplitude);
break;
case(3):
rd = new Random<unsigned __int32>(0.0, amplitude);
break;
case(4):
rd = new Random<signed __int32>(-amplitude, amplitude);
break;
case(5):
rd = new Random<unsigned __int64>(0.0, amplitude);
break;
case(6):
rd = new Random<unsigned __int64>(0.0, amplitude);
break;
case(7):
rd = new Random<unsigned __int64>(0.0, amplitude);
break;
case(8):
rd = new Random<unsigned __int64>(0.0, amplitude);
break;
default:
return E_NOTIMPL;
}
return S_OK;
}
// (The size of an audio frame = nChannels * wBitsPerSample)
HRESULT Noise_Gen::LoadData(UINT32 bufferFrameCount, BYTE* pData, DWORD* flags) {
for(UINT32 i = 0; i < nChannels *bufferFrameCount; i ) {
switch(wBitsPerSample / 8) {
case(1):
pData[i] = (((Random<unsigned __int8>*)rd)->operator()());
break;
case(2):{
unsigned __int16* pData2 = (unsigned __int16*) pData;
pData2[i] = (((Random<unsigned __int16>*)rd)->operator()());
break;
}
case(3): {
__int32 data = ((Random<unsigned __int32>*)rd)->operator()();
unsigned char* cp = (unsigned char*) (amp;data);
pData[(3 * i)] = cp[0];
pData[1 (3 * i)] = cp[1];
pData[2 (3 * i)] = cp[2];
break;
}
case(4):{
signed __int32* pData2 = (signed __int32*) pData;
pData2[i] = (((Random<signed __int32>*)rd)->operator()());
break;
}
case(5): {
__int64 data = ((Random<unsigned __int64>*)rd)->operator()();
unsigned char* cp = (unsigned char*) amp;data;
pData[(5 * i)] = cp[0];
pData[1 (5 * i)] = cp[1];
pData[2 (5 * i)] = cp[2];
pData[3 (5 * i)] = cp[3];
pData[4 (5 * i)] = cp[4];
break;
}
case(6): {
__int64 data = ((Random<unsigned __int64>*)rd)->operator()();
unsigned char* cp = (unsigned char*) amp;data;
pData[(6 * i)] = cp[0];
pData[1 (6 * i)] = cp[1];
pData[2 (6 * i)] = cp[2];
pData[3 (6 * i)] = cp[3];
pData[4 (6 * i)] = cp[4];
pData[5 (6 * i)] = cp[5];
break;
}
case(7): {
__int64 data = ((Random<unsigned __int64>*)rd)->operator()();
unsigned char* cp = (unsigned char*) amp;data;
pData[(7 * i)] = cp[0];
pData[1 (7 * i)] = cp[1];
pData[2 (7 * i)] = cp[2];
pData[3 (7 * i)] = cp[3];
pData[4 (7 * i)] = cp[4];
pData[5 (7 * i)] = cp[5];
pData[6 (7 * i)] = cp[6];
break;
}
case(8): {
unsigned __int64* pData2 = (unsigned __int64*) pData;
pData2[i] = (((Random<unsigned __int64>*)rd)->operator()());
break;
}
default:
// For stopping playback
(*flags) = AUDCLNT_BUFFERFLAGS_SILENT;
return E_NOTIMPL;
}
}
return S_OK;
}
Затем я добавил свой класс в шаблон, предоставленный Microsoft, и напечатал средство визуализации конечной точки звука по умолчанию на консоли.
#include <InitGuid.h>
#include <iostream>
#include <Windows.h>
#include <dshow.h>
// Windows multimedia device
#include <Mmdeviceapi.h>
#include <Functiondiscoverykeys_devpkey.h>
// WASAPI
#include <Audiopolicy.h>
#include <Audioclient.h>
#include "Noise_Gen.h"
//-----------------------------------------------------------
// Play an audio stream on the default audio rendering
// device. The PlayAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data to the
// rendering device. The inner loop runs every 1/2 second.
//-----------------------------------------------------------
// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
#define EXIT_ON_ERROR(hres)
if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)
if ((punk) != NULL)
{ (punk)->Release(); (punk) = NULL; }
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
HRESULT PlayAudioStream(Noise_Gen* pMySource) {
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
REFERENCE_TIME hnsActualDuration;
IMMDeviceEnumerator* pEnumerator = NULL;
IMMDevice* pDevice = NULL;
IAudioClient* pAudioClient = NULL;
IAudioRenderClient* pRenderClient = NULL;
WAVEFORMATEX* pwfx = NULL;
UINT32 bufferFrameCount;
UINT32 numFramesAvailable;
UINT32 numFramesPadding;
BYTE* pData;
DWORD flags = 0;
IPropertyStore* pPropertyStore = NULL;
PROPVARIANT name;
hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL,
CLSCTX_ALL, IID_IMMDeviceEnumerator,
(void**) amp;pEnumerator);
EXIT_ON_ERROR(hr);
hr = pEnumerator->GetDefaultAudioEndpoint(
eRender, eConsole, amp;pDevice);
hr = pDevice->OpenPropertyStore(STGM_READ, amp;pPropertyStore);
PropVariantInit(amp;name);
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, amp;name);
printf("%S", name.pwszVal);
printf("n");
EXIT_ON_ERROR(hr);
hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL,
NULL, (void**) amp;pAudioClient);
EXIT_ON_ERROR(hr);
hr = pAudioClient->GetMixFormat(amp;pwfx);
EXIT_ON_ERROR(hr);
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
0, hnsRequestedDuration,
0, pwfx, NULL);
EXIT_ON_ERROR(hr);
// Tell the audio source which format to use.
hr = pMySource->SetFormat(pwfx);
EXIT_ON_ERROR(hr);
// Get the actual size of the allocated buffer.
hr = pAudioClient->GetBufferSize(amp;bufferFrameCount);
EXIT_ON_ERROR(hr);
hr = pAudioClient->GetService(IID_IAudioRenderClient,
(void**) amp;pRenderClient);
EXIT_ON_ERROR(hr);
// Grab the entire buffer for the initial fill operation.
hr = pRenderClient->GetBuffer(bufferFrameCount, amp;pData);
EXIT_ON_ERROR(hr);
// Load the initial data into the shared buffer.
hr = pMySource->LoadData(bufferFrameCount, pData, amp;flags);
EXIT_ON_ERROR(hr);
hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
EXIT_ON_ERROR(hr);
// Calculate the actual duration of the allocated buffer.
hnsActualDuration = (double) REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
hr = pAudioClient->Start(); // Start playing.
EXIT_ON_ERROR(hr);
// Each loop fills about half of the shared buffer.
while(flags != AUDCLNT_BUFFERFLAGS_SILENT) {
// Sleep for half the buffer duration.
Sleep((DWORD) (hnsActualDuration / REFTIMES_PER_MILLISEC / 2));
// See how much buffer space is available.
hr = pAudioClient->GetCurrentPadding(amp;numFramesPadding);
EXIT_ON_ERROR(hr);
numFramesAvailable = bufferFrameCount - numFramesPadding;
// Grab all the available space in the shared buffer.
hr = pRenderClient->GetBuffer(numFramesAvailable, amp;pData);
EXIT_ON_ERROR(hr);
// Get next 1/2-second of data from the audio source.
hr = pMySource->LoadData(numFramesAvailable, pData, amp;flags);
EXIT_ON_ERROR(hr);
hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
EXIT_ON_ERROR(hr);
}
// Wait for last data in buffer to play before stopping.
Sleep((DWORD) (hnsActualDuration / REFTIMES_PER_MILLISEC / 2));
hr = pAudioClient->Stop(); // Stop playing.
EXIT_ON_ERROR(hr);
Exit:
CoTaskMemFree(pwfx);
SAFE_RELEASE(pEnumerator);
SAFE_RELEASE(pDevice);
SAFE_RELEASE(pAudioClient);
SAFE_RELEASE(pRenderClient);
return hr;
}
int main() {
HRESULT hr = CoInitialize(nullptr);
if(FAILED(hr)) { return hr; }
Noise_Gen* ng = new Noise_Gen();
PlayAudioStream(ng);
delete ng;
CoUninitialize();
}
Средство визуализации конечной точки звука по умолчанию в моей системе использует 32-разрядные значения, поэтому код начинался с записи в буфер 32-разрядных значений без знака. Затем я попытался использовать значения со знаком, которые можно увидеть в приведенном выше коде. В обоих этих случаях звук не воспроизводился. Я проверил содержимое буфера во время отладки, и они действительно меняются. Я напечатал средство визуализации конечной точки звука по умолчанию на консоль, и это динамик моей системы. Windows даже показывает мое приложение в микшере громкости, но звук не отображается даже при максимальной громкости. Затем я проверил время ожидания, чтобы убедиться, что он находится в спящем режиме, поэтому система имеет доступ к буферу, и он переходит в спящий режим в течение 500 мс между записями в буфер.
Обновление: я обнаружил, что использую подформат KSDATAFORMAT_SUBTYPE_IEEEE_FLOAT, и попытался передать плавающие значения буфера в диапазоне от амплитуды до амплитуды, от 0 до амплитуды, от -1 до 1 и от 0 до 1.
Чего мне не хватает?
Ответ №1:
Ваш код распределения случайных чисел работает некорректно для форматов с плавающей запятой (который, насколько я знаю, в основном всегда будет форматом микширования в общем режиме).
Это неправильно даже для целых чисел. Я предполагаю, что вы хотели написать
((f % ((signed __int64) mHigh - (signed __int64) mLow)) (signed __int64) mLow);
(обратите внимание на минус),
но вам все равно не следует использовать необработанный модуль, потому что он слегка смещен.
Для форматов с плавающей запятой вы всегда используете диапазон от -1 до 1.
Я адаптировал ваш код для использования std ::uniform_real_distribution, и я слышу шум, воспроизводимый в моих динамиках.
#include <cstdio>
#include <Windows.h>
// Windows multimedia device
#include <Mmdeviceapi.h>
#include <Functiondiscoverykeys_devpkey.h>
// WASAPI
#include <Audiopolicy.h>
#include <Audioclient.h>
#include <random>
class Noise_Gen {
public:
Noise_Gen() : format(), engine(__rdtsc()), float_dist(-1.f, 1.f) {};
void SetFormat(WAVEFORMATEX* wfex) {
if(wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
format = *reinterpret_cast<WAVEFORMATEXTENSIBLE*>(wfex);
} else {
format.Format = *wfex;
format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
INIT_WAVEFORMATEX_GUID(amp;format.SubFormat, wfex->wFormatTag);
format.Samples.wValidBitsPerSample = format.Format.wBitsPerSample;
format.dwChannelMask = 0;
}
}
// (The size of an audio frame = nChannels * wBitsPerSample)
void FillBuffer(UINT32 bufferFrameCount, BYTE* pData, DWORD* flags) {
const UINT16 formatTag = EXTRACT_WAVEFORMATEX_ID(amp;format.SubFormat);
if(formatTag == WAVE_FORMAT_IEEE_FLOAT) {
float* fData = (float*)pData;
for(UINT32 i = 0; i < format.Format.nChannels * bufferFrameCount; i ) {
fData[i] = float_dist(engine);
}
} else if(formatTag == WAVE_FORMAT_PCM) {
using rndT = decltype(engine)::result_type;
UINT32 iterations = format.Format.nBlockAlign * bufferFrameCount / sizeof(rndT);
UINT32 leftoverBytes = format.Format.nBlockAlign * bufferFrameCount % sizeof(rndT);
rndT* iData = (rndT*)pData;
UINT32 i = 0;
for(; i < iterations; i ) {
iData[i] = engine();
}
if(leftoverBytes != 0) {
rndT lastRnd = engine();
BYTE* pLastBytes = pData i * sizeof(rndT);
for(UINT32 j = 0; j < leftoverBytes; j) {
pLastBytes[j] = lastRnd >> (j * 8) amp; 0xFF;
}
}
} else {
//memset(pData, 0, wfex.Format.nBlockAlign * bufferFrameCount);
*flags = AUDCLNT_BUFFERFLAGS_SILENT;
}
}
private:
WAVEFORMATEXTENSIBLE format;
std::mt19937_64 engine;
std::uniform_real_distribution<float> float_dist;
};
// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC 10000000ll
#define REFTIMES_PER_MILLISEC 10000
#define EXIT_ON_ERROR(hres)
if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)
if ((punk) != NULL)
{ (punk)->Release(); (punk) = NULL; }
HRESULT PlayAudioStream(Noise_Gen* pMySource) {
HRESULT hr;
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
REFERENCE_TIME hnsActualDuration;
IMMDeviceEnumerator* pEnumerator = NULL;
IPropertyStore* pPropertyStore = NULL;
IMMDevice* pDevice = NULL;
IAudioClient* pAudioClient = NULL;
IAudioRenderClient* pRenderClient = NULL;
WAVEFORMATEX* pwfx = NULL;
UINT32 bufferFrameCount;
BYTE* pData;
DWORD flags = 0;
PROPVARIANT name;
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
CLSCTX_ALL, IID_PPV_ARGS(amp;pEnumerator));
EXIT_ON_ERROR(hr);
hr = pEnumerator->GetDefaultAudioEndpoint(
eRender, eConsole, amp;pDevice);
EXIT_ON_ERROR(hr);
hr = pDevice->OpenPropertyStore(STGM_READ, amp;pPropertyStore);
EXIT_ON_ERROR(hr);
PropVariantInit(amp;name);
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, amp;name);
EXIT_ON_ERROR(hr);
printf("%S", name.pwszVal);
printf("n");
hr = pDevice->Activate(__uuidof(pAudioClient), CLSCTX_ALL,
NULL, (void**) amp;pAudioClient);
EXIT_ON_ERROR(hr);
hr = pAudioClient->GetMixFormat(amp;pwfx);
EXIT_ON_ERROR(hr);
hr = pAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED,
0, hnsRequestedDuration,
0, pwfx, NULL);
EXIT_ON_ERROR(hr);
// Tell the audio source which format to use.
pMySource->SetFormat(pwfx);
// Get the actual size of the allocated buffer.
hr = pAudioClient->GetBufferSize(amp;bufferFrameCount);
EXIT_ON_ERROR(hr);
hr = pAudioClient->GetService(IID_PPV_ARGS(amp;pRenderClient));
EXIT_ON_ERROR(hr);
// Grab the entire buffer for the initial fill operation.
hr = pRenderClient->GetBuffer(bufferFrameCount, amp;pData);
EXIT_ON_ERROR(hr);
// Load the initial data into the shared buffer.
pMySource->FillBuffer(bufferFrameCount, pData, amp;flags);
hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
EXIT_ON_ERROR(hr);
// Calculate the actual duration of the allocated buffer.
hnsActualDuration = REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
hr = pAudioClient->Start(); // Start playing.
EXIT_ON_ERROR(hr);
// Each loop fills about half of the shared buffer.
DWORD sleepTime;
while(flags != AUDCLNT_BUFFERFLAGS_SILENT) {
// Sleep for half the buffer duration.
sleepTime = (DWORD) (hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
if(sleepTime != 0)
Sleep(sleepTime);
// See how much buffer space is available.
UINT32 numFramesPadding;
hr = pAudioClient->GetCurrentPadding(amp;numFramesPadding);
EXIT_ON_ERROR(hr);
UINT32 numFramesAvailable = bufferFrameCount - numFramesPadding;
// Grab all the available space in the shared buffer.
hr = pRenderClient->GetBuffer(numFramesAvailable, amp;pData);
EXIT_ON_ERROR(hr);
// Get next 1/2-second of data from the audio source.
pMySource->FillBuffer(numFramesAvailable, pData, amp;flags);
hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
EXIT_ON_ERROR(hr);
}
// Wait for last data in buffer to play before stopping.
sleepTime = (DWORD) (hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
if(sleepTime != 0)
Sleep(sleepTime);
hr = pAudioClient->Stop(); // Stop playing.
EXIT_ON_ERROR(hr);
Exit:
CoTaskMemFree(pwfx);
SAFE_RELEASE(pRenderClient);
SAFE_RELEASE(pAudioClient);
SAFE_RELEASE(pDevice);
SAFE_RELEASE(pPropertyStore); // you forgot to free the property store
SAFE_RELEASE(pEnumerator);
return hr;
}
int main() {
HRESULT hr = CoInitialize(nullptr);
if(FAILED(hr)) { return hr; }
Noise_Gen ng;
PlayAudioStream(amp;ng);
CoUninitialize();
}
Комментарии:
1. Спасибо за ответ. Мне не хватало информации: «Для форматов с плавающей запятой вы всегда используете диапазон от -1 до 1». В итоге я использовал
std::bind(std::uniform_real_distribution<>(low, high), std::default_random_engine())
для случайных чисел.2. Возможно, я ошибаюсь, но я не думаю, что это заполняет весь циклический буфер. Когда
numFramesAvailable
размер меньше полного размера буфера,FillBuffer
будет переходить обратно к индексу0
pData
в случаеWAVE_FORMAT_IEEE_FLOAT
.3. pData будет указывать в буфер с правильным смещением уже тогда, когда мы получим его из IAudioRenderClient::GetBuffer, поэтому мы всегда должны начинать заполнять новые данные со смещением 0 из pData
4. @TheOm Это странно, поскольку я обнаружил, что это не относится к моей собственной реализации. Меня беспокоит то, что характер белого шума звука скрывает разрывы.
5. @TheOm
GetBuffer
не обновляет адресpData
в моем случае, и мне нужно самому отслеживать положение буфера.