#cpp #windows #аудио #mmdevice
Я разрабатываю приложение для аудиоустройства, работающего по стандарту usb audio. Одной из задач является выбор формата воспроизведения: количество бит, частота дискретизации. Для задания формата, используя Windows Core Audio API, я написал следующий c++ код: bool CPropPage::SetMode(int freq, int bits) { static const int channels = 2; PROPVARIANT varName; IMMDevice* pDev = NULL; IPropertyStore *pProps = NULL; bool result = false; if (!m_pEnumerator) return false; HRESULT hr = m_pEnumerator->GetDevice(m_devguid.c_str(), &pDev); if (hr != S_OK) return false; hr = pDev->OpenPropertyStore(STGM_WRITE, &pProps); if (hr != S_OK) goto exit; PropVariantInit(&varName); varName.vt = VT_BLOB; varName.blob.cbSize = sizeof(WAVEFORMATEXTENSIBLE); int alignment = channels * bits / 8; WAVEFORMATEXTENSIBLE format = { { WAVE_FORMAT_EXTENSIBLE, channels, freq, freq * alignment, alignment, bits, (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) }, {bits}, SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT, KSDATAFORMAT_SUBTYPE_PCM }; varName.blob.pBlobData = (BYTE*)&format; LogEntry("SetMode:%d %d\n", freq, bits); hr = pProps->SetValue( PKEY_AudioEngine_DeviceFormat, varName); if (hr != S_OK) goto exit; LogEntry("SetMode ok\n"); LogAudioEngineFormat(pDev, &format.Format); result = true; exit: if (pDev) pDev->Release(); if (pProps) pProps->Release(); return result; } По сути, работа функции сводится к заданию свойства PKEY_AudioEngine_DeviceFormat объекта IMMDevice. Примечание. m_pEnumerator - указатель на объект COM-класса IMMDeviceEnumerator. m_devguid - GUID устройства. Результат работы функции: формат задается, и это видно в Панель управления -> Звук -> Свойства. Однако при этом невозможно воспроизвести ни один звук. Windows Media Player сообщает, что данный формат не поддерживается. (Задаваемый формат: 44100 Гц, 2 канала, 16 бит). При этом, если тот же самый формат задать через Панель управления -> Звук -> Свойства, все звуки, аудиофайлы воспроизводятся успешно. Что я делаю не так?
Ответы
Ответ 1
У аудиоустройства, помимо задаваемого PKEY_AudioEngine_DeviceFormat формата вывода звука, есть внутренний формат обработки звука. (См. Device Formats ) Этот формат может иметь параметры, отличающиеся от формата вывода (обычно, он использует 32-битные IEEE Float сэмплы, независимо от формата сэмплов в выводе), но число каналов и частота дискретизации у них должны быть одинаковыми, иначе при попытке воспроизведения звука будет выводится ошибка. Таким образом, помимо установки PKEY_AudioEngine_DeviceFormat, нужно установить значения частоты дискретизации и числа каналов, соответствующие новому формату, в свойстве внутреннего формата (при этом не трогая остальные его поля!) Microsoft не предоставляют документированный API для установки внутреннего формата, видимо потому что они не хотят, чтобы программы без ведома пользователя его меняли. Ключи реестра также не документированы. Если воспользоваться Process Monitor и отследить изменения реестра при переключении формата в панели управления, можно выявить, какие свойства изменяются. (Если также иметь установленный WinDBG, можно увидеть по стеку, что служба Аудио вызывает метод SetDeviceFormat недокументированного интерфейса IPolicyConfig. Наверное, можно попробовать тоже его вызывать, но непонятно, что именно передавать в параметрах...) Итак, исследования показали, что внутренний формат хранится в следующих свойствах: Windows 7: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render{...}\Properties {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0 Windows 10: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Render{...}\Properties {e4870e26-3cc5-4cd2-ba46-ca0a9a70ed04},0 {3d6e1656-2e50-4c4c-8d85-d0acae3c6c68},2 {3d6e1656-2e50-4c4c-8d85-d0acae3c6c68},3 {624f56de-fd24-473e-814a-de40aacaed16},3 Вспомогательная функция для изменения свойства аудиоформата: #define INITGUID #include#include #include #include #include #include #include #include #include #pragma comment(lib, "winmm.lib") #pragma comment(lib, "strmiids.lib") #pragma comment(lib, "quartz.lib") #pragma comment(lib, "uuid.lib") //property key для свойств, представляющих внутренний формат аудио DEFINE_PROPERTYKEY(PKEY_InternalAudioFormat, 0xe4870e26, 0x3cc5, 0x4cd2, 0xba, 0x46, 0xca, 0xa, 0x9a, 0x70, 0xed, 0x4, 0); DEFINE_PROPERTYKEY(PKEY_InternalAudioFormat2, 0x3d6e1656, 0x2e50, 0x4c4c, 0x8d, 0x85, 0xd0, 0xac, 0xae, 0x3c, 0x6c, 0x68, 2); DEFINE_PROPERTYKEY(PKEY_InternalAudioFormat3, 0x3d6e1656, 0x2e50, 0x4c4c, 0x8d, 0x85, 0xd0, 0xac, 0xae, 0x3c, 0x6c, 0x68, 3); DEFINE_PROPERTYKEY(PKEY_InternalAudioFormat4, 0x624f56de, 0xfd24, 0x473e, 0x81, 0x4a, 0xde, 0x40, 0xaa, 0xca, 0xed, 0x16, 3); /* Функция SetFormatProperty: Изменение свойства формата аудио в объекте IPropertyStore, свзянном с AudioEndpoint Параметры: propertyStore - объект IPropertyStore, в котором необходимо изменить свойство PROPERTYKEY - идентификатор свойства freq - частота дискретизации (Гц) channels - число каналов bitsPerSample - битов на сэмпл (0 - не указано) */ bool SetFormatProperty(IPropertyStore* propertyStore,PROPERTYKEY pkey,int freq,int channels,int bitsPerSample){ HRESULT hr; PROPVARIANT format; PropVariantInit(&format); WAVEFORMATEXTENSIBLE wext; hr = propertyStore->GetValue(pkey, &format);//получаем текущее значение свойства if(FAILED(hr)) {printf("Can't get property: HRESULT 0x%x\n",hr);return false;} if(format.blob.cbSize!=sizeof(WAVEFORMATEXTENSIBLE)){ return false; } memcpy_s(&wext,sizeof(wext),format.blob.pBlobData,sizeof(wext));//копируем значение в структуру wext.Format.nChannels=channels;//число каналов wext.Format.nSamplesPerSec=freq;//частота дискретизации int new_bits=wext.Format.wBitsPerSample;//новое число бит на сэмпл int new_valid_bits=wext.Samples.wValidBitsPerSample;//новое число используемых бит на сэмпл if(bitsPerSample!=0){//если указано bitsPerSample, изменяем число бит new_bits=bitsPerSample; new_valid_bits=bitsPerSample; } PROPVARIANT varName={0};//переменная для нового значения свойства PropVariantInit(&varName); varName.vt = VT_BLOB; varName.blob.cbSize = sizeof(WAVEFORMATEXTENSIBLE); int alignment = wext.Format.nChannels * new_bits / 8;//вычисляем размер блока WAVEFORMATEXTENSIBLE fmt = { //заполняем параметры формата { WAVE_FORMAT_EXTENSIBLE, wext.Format.nChannels, wext.Format.nSamplesPerSec, wext.Format.nSamplesPerSec * alignment, alignment, new_bits, (sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX)) }, {new_valid_bits}, wext.dwChannelMask, wext.SubFormat }; varName.blob.pBlobData = (BYTE*)&fmt; hr = propertyStore->SetValue(pkey, varName);//устанавливаем значение свойства if (hr != S_OK)printf("SetValue failed. HRESULT: 0x%x\n",(int)hr); PropVariantClear(&format); return (SUCCEEDED(hr)); } Пример кода для установки формата для устройства с указанным ID (запускать с правами администратора): int _tmain(int argc, _TCHAR* argv[]) { DWORD dwMode = STGM_READWRITE;//режим доступа для Property store: Чтение и запись wchar_t DeviceId[]=L"{0.0.0.00000000}.{ba946f3e-7cb9-4abf-a84c-a71bd014eb1c}";//ID нужного устройства setlocale(LC_ALL,"Russian"); CoInitialize(NULL); TCHAR* pstr=NULL; DWORD state; IMMDeviceEnumerator *deviceEnumerator = NULL; IMMDeviceCollection *deviceCollection = NULL; IMMDevice* dev=NULL; IPropertyStore *propertyStore=NULL; WAVEFORMATEXTENSIBLE wext; bool res; //Create Device Enumerator HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&deviceEnumerator)); if(FAILED(hr)) {printf("CoCreateInstance fail: HRESULT 0x%x",hr);goto End;} hr = deviceEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &deviceCollection); if(FAILED(hr)) {printf("EnumAudioEndpoints fail: HRESULT 0x%x\n",hr);goto End;} UINT deviceCount; hr = deviceCollection->GetCount(&deviceCount); if(FAILED(hr)) {printf("GetCount fail: HRESULT 0x%x\n",hr);goto End;} //находим нужное устройство for (UINT DeviceIndex = 0 ; DeviceIndex < deviceCount; DeviceIndex++) { deviceCollection->Item(DeviceIndex,&dev); if(FAILED(hr)) {printf("Item fail: 0x%x\n",hr);goto Next;} dev->GetId(&pstr); if(FAILED(hr)) {printf("Can't get device ID: HRESULT 0x%x\n",hr);goto Next;} if(wcscmp(pstr,DeviceId)==0) { //устанавливаем свойства... hr = dev->OpenPropertyStore(dwMode, &propertyStore); if(FAILED(hr)) {printf("Can't open property store: HRESULT 0x%x\n",hr);goto Next;} //формат вывода: заменяем частоту, число каналов и битность res=SetFormatProperty(propertyStore,PKEY_AudioEngine_DeviceFormat,44100,2,16); if(!res){printf("Can't set PKEY_AudioEngine_DeviceFormat!\n");goto Next;} //внутренный формат: заменяем только частоту и число каналов SetFormatProperty(propertyStore,PKEY_InternalAudioFormat,44100,2,0); SetFormatProperty(propertyStore,PKEY_InternalAudioFormat2,44100,2,0); SetFormatProperty(propertyStore,PKEY_InternalAudioFormat3,44100,2,0); SetFormatProperty(propertyStore,PKEY_InternalAudioFormat4,44100,2,0); } /* ****************** */ Next: if(dev!=NULL){dev->Release();dev=NULL;} if(propertyStore!=NULL){propertyStore->Release();propertyStore=NULL;} }//end for End:if(dev!=NULL){dev->Release();dev=NULL;} if(propertyStore!=NULL){propertyStore->Release();propertyStore=NULL;} if(deviceEnumerator!=NULL){deviceEnumerator->Release();deviceEnumerator=NULL;} if(deviceCollection!=NULL){deviceCollection->Release();deviceCollection=NULL;} CoUninitialize(); system("PAUSE"); return 0; }
Комментариев нет:
Отправить комментарий