Хорошо, оказывается, нет никакого надежного способа вывести из потока петлевого что ничего не воспроизводится на соответствующем устройстве вывода. Тем не менее, можно запросить сеансы аудио на самом устройстве вывода и для каждого сеанса, проверить, что это состояние активно. Также можно получать уведомления, когда сеансы добавляются/удаляются с устройства и получать уведомление при активации сеанса (de). Таким образом, вы можете запустить поток loopback, когда первый сеанс активируется на устройстве и останавливает его, когда последний отключается.
Вот как это сделать:
- Реализовать IAudioSessionNotification и IAudioSessionEvents
- Настройка АПС нити, STA не будет работать
- Получить IMMDevice интерфейс вывода вы хотите контролировать
- Из IMMDevice получите IAudioSessionManager2
- От IAudioSessionManager2, получите IAudioSessionEnumerator
- От IAudioSessionEnumerator, перечисление IAudioSessionControls
- Это список начальных сеансов. Он будет оставаться постоянным в течение срока службы счетчика.
- От IAudioSessionControl используйте GetState, чтобы увидеть, что первоначально активный
- Использование IAudioSessionControl :: RegisterAudioSessionNotification, чтобы получить уведомление о (де) активации начальных сессий
- Использование IAudioSessionManager2 :: RegisterSessionNotification зарегистрировать свое пользовательское уведомление сеанса
- Это будет вызвано для любых дополнительных сеансов, созданных после первоначального перечисления
- От IAudioSessionNotification :: OnSessionCreated, используйте IAudioSessionControl :: RegisterAudioSessionNotification, чтобы получить уведомление о (де) активации дополнительных сессий
- Если какой-либо сеанс изначально активен, запустить поток обратной петли
- Когда последний (начальный или дополнительный) сеанс получает deactivaed или отключен, остановка циклический поток
- И рестарта конечно, когда любой сеанс снова активирует
Ниже приведен рабочий пример. Обратите внимание, что я не:
- ли какой-либо тщательной обработки ошибок, кроме печати, что случилось
- На самом деле создан поток обратной петли. Пример только печатает, когда что-либо (де) активируется.
- Выполняйте любую учетную запись COM-интерфейсов! Образец - это утечка интерфейсов. Чтобы правильно использовать этот материал, вы, вероятно, захотите сохранить объекты SessionNotifications/SessionEvents в некоторой поточно-безопасной коллекции и отменить регистрацию/выпуск, поскольку сеансы будут уничтожены.
#include <windows.h>
#include <atlbase.h>
#include <atlcomcli.h>
#include <mmdeviceapi.h>
#include <audioclient.h>
#include <audiopolicy.h>
#include <iostream>
#include <vector>
#include <string>
#define VERIFY(expr) do { \
if(FAILED(hr = (expr))) { \
std::cout << "Error: " << #expr << ": " << hr << "\n"; \
goto error; \
} \
} while(0)
static volatile ULONG sessionId = 0;
class SessionEvents : public IAudioSessionEvents {
private:
LONG rc;
ULONG id;
~SessionEvents() {}
public:
SessionEvents(ULONG id) :
id(id), rc(1) {}
ULONG STDMETHODCALLTYPE AddRef() {
return InterlockedIncrement(&rc);
}
ULONG STDMETHODCALLTYPE Release() {
ULONG rc = InterlockedDecrement(&this->rc);
if(rc == 0) {
delete this;
}
return rc;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) {
if(IID_IUnknown == riid) {
AddRef();
*ppv = static_cast<IUnknown*>(this);
return S_OK;
}
else if(__uuidof(IAudioSessionEvents) == riid) {
AddRef();
*ppv = static_cast<IAudioSessionEvents*>(this);
return S_OK;
}
else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
HRESULT STDMETHODCALLTYPE OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnChannelVolumeChanged(DWORD ChannelCount, float NewChannelVolumeArray[], DWORD ChangedChannel, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext) {
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnStateChanged(AudioSessionState NewState) {
switch(NewState) {
case AudioSessionStateInactive: std::wcout << L"AudioSessionStateInactive session # " << id << L"\n"; break;
case AudioSessionStateActive: std::wcout << L"AudioSessionStateActive session # " << id << L"\n"; break;
case AudioSessionStateExpired: std::wcout << L"AudioSessionStateExpired session # " << id << L"\n"; break;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) {
std::wcout << L"OnSessionDisconnected session # " << id << L"\n";
return S_OK;
}
};
class SessionNotification : public IAudioSessionNotification {
private:
LONG rc;
~SessionNotification() {}
public:
SessionNotification() : rc(1) {}
ULONG STDMETHODCALLTYPE AddRef() {
return InterlockedIncrement(&rc);
}
ULONG STDMETHODCALLTYPE Release() {
ULONG rc = InterlockedDecrement(&this->rc);
if(rc == 0)
delete this;
return rc;
}
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) {
if(IID_IUnknown == riid) {
AddRef();
*ppv = static_cast<IUnknown*>(this);
return S_OK;
}
else if(__uuidof(IAudioSessionNotification) == riid) {
AddRef();
*ppv = static_cast<IAudioSessionNotification*>(this);
return S_OK;
}
else {
*ppv = nullptr;
return E_NOINTERFACE;
}
}
HRESULT OnSessionCreated(IAudioSessionControl *newSession) {
HRESULT hr = S_OK;
if(newSession) {
newSession->AddRef();
CComHeapPtr<WCHAR> name;
ULONG id = InterlockedIncrement(&sessionId);
VERIFY(newSession->GetDisplayName(&name));
std::wcout << L"created session # " << id << L": " << name.m_pData << L"\n";
VERIFY(newSession->RegisterAudioSessionNotification(new SessionEvents(id)));
}
error:
return hr;
}
};
int main(int argc, char** argv) {
HRESULT hr = S_OK;
VERIFY(CoInitializeEx(nullptr, COINIT_MULTITHREADED));
{
int count;
std::wstring line;
CComPtr<IMMDevice> defaultOutput;
CComPtr<IMMDeviceEnumerator> devices;
CComPtr<IAudioSessionManager2> manager;
CComPtr<IAudioSessionEnumerator> sessions;
SessionNotification* notification = new SessionNotification;
VERIFY(devices.CoCreateInstance(__uuidof(MMDeviceEnumerator)));
VERIFY(devices->GetDefaultAudioEndpoint(eRender, eMultimedia, &defaultOutput));
VERIFY(defaultOutput->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, nullptr, reinterpret_cast<void**>(&manager)));
VERIFY(manager->RegisterSessionNotification(notification));
VERIFY(manager->GetSessionEnumerator(&sessions));
VERIFY(sessions->GetCount(&count));
std::wcout << L"Initial sessions: " << count << L"\n";
for(int s = 0; s < count; s++) {
AudioSessionState state;
CComHeapPtr<WCHAR> name;
CComPtr<IAudioSessionControl> control;
VERIFY(sessions->GetSession(s, &control));
VERIFY(control->GetDisplayName(&name));
VERIFY(control->GetState(&state));
std::wcout << L"Initial session name: " << name.m_pData << L", active = " << (state == AudioSessionStateActive) << L"\n";
VERIFY(control->RegisterAudioSessionNotification(new SessionEvents(InterlockedIncrement(&sessionId))));
}
std::wcout << L"Press return to exit...\n";
std::getline(std::wcin, line);
VERIFY(manager->UnregisterSessionNotification(notification));
}
error:
CoUninitialize();
return 0;
}
Не можете ли вы просто проверить тишину? Например. остановить поток после X секунд записи нулей (или что-нибудь ниже некоторого порога?) –
Конечно, это возможно. Но не изящный и не может быть надежным методом. Иногда в некоторых композициях (особенно в современном композиторе) у нас довольно длинные паузы (см. John Cage 4'33 ").))) – DotNetter