| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593 | 
							- #include "enum-wasapi.hpp"
 
- #include <obs-module.h>
 
- #include <util/platform.h>
 
- #include <util/windows/HRError.hpp>
 
- #include <util/windows/ComPtr.hpp>
 
- #include <util/windows/WinHandle.hpp>
 
- #include <util/windows/CoTaskMemPtr.hpp>
 
- #include <util/threading.h>
 
- using namespace std;
 
- #define OPT_DEVICE_ID         "device_id"
 
- #define OPT_USE_DEVICE_TIMING "use_device_timing"
 
- static void GetWASAPIDefaults(obs_data_t *settings);
 
- #define KSAUDIO_SPEAKER_4POINT1 (KSAUDIO_SPEAKER_QUAD|SPEAKER_LOW_FREQUENCY)
 
- #define KSAUDIO_SPEAKER_2POINT1 (KSAUDIO_SPEAKER_STEREO|SPEAKER_LOW_FREQUENCY)
 
- class WASAPISource {
 
- 	ComPtr<IMMDevice>           device;
 
- 	ComPtr<IAudioClient>        client;
 
- 	ComPtr<IAudioCaptureClient> capture;
 
- 	ComPtr<IAudioRenderClient>  render;
 
- 	obs_source_t                *source;
 
- 	string                      device_id;
 
- 	string                      device_name;
 
- 	bool                        isInputDevice;
 
- 	bool                        useDeviceTiming = false;
 
- 	bool                        isDefaultDevice = false;
 
- 	bool                        reconnecting = false;
 
- 	bool                        previouslyFailed = false;
 
- 	WinHandle                   reconnectThread;
 
- 	bool                        active = false;
 
- 	WinHandle                   captureThread;
 
- 	WinHandle                   stopSignal;
 
- 	WinHandle                   receiveSignal;
 
- 	speaker_layout              speakers;
 
- 	audio_format                format;
 
- 	uint32_t                    sampleRate;
 
- 	static DWORD WINAPI ReconnectThread(LPVOID param);
 
- 	static DWORD WINAPI CaptureThread(LPVOID param);
 
- 	bool ProcessCaptureData();
 
- 	inline void Start();
 
- 	inline void Stop();
 
- 	void Reconnect();
 
- 	bool InitDevice(IMMDeviceEnumerator *enumerator);
 
- 	void InitName();
 
- 	void InitClient();
 
- 	void InitRender();
 
- 	void InitFormat(WAVEFORMATEX *wfex);
 
- 	void InitCapture();
 
- 	void Initialize();
 
- 	bool TryInitialize();
 
- 	void UpdateSettings(obs_data_t *settings);
 
- public:
 
- 	WASAPISource(obs_data_t *settings, obs_source_t *source_, bool input);
 
- 	inline ~WASAPISource();
 
- 	void Update(obs_data_t *settings);
 
- };
 
- WASAPISource::WASAPISource(obs_data_t *settings, obs_source_t *source_,
 
- 		bool input)
 
- 	: source          (source_),
 
- 	  isInputDevice   (input)
 
- {
 
- 	UpdateSettings(settings);
 
- 	stopSignal = CreateEvent(nullptr, true, false, nullptr);
 
- 	if (!stopSignal.Valid())
 
- 		throw "Could not create stop signal";
 
- 	receiveSignal = CreateEvent(nullptr, false, false, nullptr);
 
- 	if (!receiveSignal.Valid())
 
- 		throw "Could not create receive signal";
 
- 	Start();
 
- }
 
- inline void WASAPISource::Start()
 
- {
 
- 	if (!TryInitialize()) {
 
- 		blog(LOG_INFO, "[WASAPISource::WASAPISource] "
 
- 		               "Device '%s' not found.  Waiting for device",
 
- 		               device_id.c_str());
 
- 		Reconnect();
 
- 	}
 
- }
 
- inline void WASAPISource::Stop()
 
- {
 
- 	SetEvent(stopSignal);
 
- 	if (active) {
 
- 		blog(LOG_INFO, "WASAPI: Device '%s' Terminated",
 
- 				device_name.c_str());
 
- 		WaitForSingleObject(captureThread, INFINITE);
 
- 	}
 
- 	if (reconnecting)
 
- 		WaitForSingleObject(reconnectThread, INFINITE);
 
- 	ResetEvent(stopSignal);
 
- }
 
- inline WASAPISource::~WASAPISource()
 
- {
 
- 	Stop();
 
- }
 
- void WASAPISource::UpdateSettings(obs_data_t *settings)
 
- {
 
- 	device_id       = obs_data_get_string(settings, OPT_DEVICE_ID);
 
- 	useDeviceTiming = obs_data_get_bool(settings, OPT_USE_DEVICE_TIMING);
 
- 	isDefaultDevice = _strcmpi(device_id.c_str(), "default") == 0;
 
- }
 
- void WASAPISource::Update(obs_data_t *settings)
 
- {
 
- 	string newDevice = obs_data_get_string(settings, OPT_DEVICE_ID);
 
- 	bool restart = newDevice.compare(device_id) != 0;
 
- 	if (restart)
 
- 		Stop();
 
- 	UpdateSettings(settings);
 
- 	if (restart)
 
- 		Start();
 
- }
 
- bool WASAPISource::InitDevice(IMMDeviceEnumerator *enumerator)
 
- {
 
- 	HRESULT res;
 
- 	if (isDefaultDevice) {
 
- 		res = enumerator->GetDefaultAudioEndpoint(
 
- 				isInputDevice ? eCapture        : eRender,
 
- 				isInputDevice ? eCommunications : eConsole,
 
- 				device.Assign());
 
- 	} else {
 
- 		wchar_t *w_id;
 
- 		os_utf8_to_wcs_ptr(device_id.c_str(), device_id.size(), &w_id);
 
- 		res = enumerator->GetDevice(w_id, device.Assign());
 
- 		bfree(w_id);
 
- 	}
 
- 	return SUCCEEDED(res);
 
- }
 
- #define BUFFER_TIME_100NS (5*10000000)
 
- void WASAPISource::InitClient()
 
- {
 
- 	CoTaskMemPtr<WAVEFORMATEX> wfex;
 
- 	HRESULT                    res;
 
- 	DWORD                      flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
 
- 	res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
 
- 			nullptr, (void**)client.Assign());
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to activate client context", res);
 
- 	res = client->GetMixFormat(&wfex);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get mix format", res);
 
- 	InitFormat(wfex);
 
- 	if (!isInputDevice)
 
- 		flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
 
- 	res = client->Initialize(
 
- 			AUDCLNT_SHAREMODE_SHARED, flags,
 
- 			BUFFER_TIME_100NS, 0, wfex, nullptr);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get initialize audio client", res);
 
- }
 
- void WASAPISource::InitRender()
 
- {
 
- 	CoTaskMemPtr<WAVEFORMATEX> wfex;
 
- 	HRESULT                    res;
 
- 	LPBYTE                     buffer;
 
- 	UINT32                     frames;
 
- 	ComPtr<IAudioClient>       client;
 
- 	res = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL,
 
- 			nullptr, (void**)client.Assign());
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to activate client context", res);
 
- 	res = client->GetMixFormat(&wfex);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get mix format", res);
 
- 	res = client->Initialize(
 
- 			AUDCLNT_SHAREMODE_SHARED, 0,
 
- 			BUFFER_TIME_100NS, 0, wfex, nullptr);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get initialize audio client", res);
 
- 	/* Silent loopback fix. Prevents audio stream from stopping and */
 
- 	/* messing up timestamps and other weird glitches during silence */
 
- 	/* by playing a silent sample all over again. */
 
- 	res = client->GetBufferSize(&frames);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get buffer size", res);
 
- 	res = client->GetService(__uuidof(IAudioRenderClient),
 
- 		(void**)render.Assign());
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get render client", res);
 
- 	res = render->GetBuffer(frames, &buffer);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to get buffer", res);
 
- 	memset(buffer, 0, frames*wfex->nBlockAlign);
 
- 	render->ReleaseBuffer(frames, 0);
 
- }
 
- static speaker_layout ConvertSpeakerLayout(DWORD layout, WORD channels)
 
- {
 
- 	switch (layout) {
 
- 	case KSAUDIO_SPEAKER_QUAD:             return SPEAKERS_QUAD;
 
- 	case KSAUDIO_SPEAKER_2POINT1:          return SPEAKERS_2POINT1;
 
- 	case KSAUDIO_SPEAKER_4POINT1:          return SPEAKERS_4POINT1;
 
- 	case KSAUDIO_SPEAKER_SURROUND:         return SPEAKERS_SURROUND;
 
- 	case KSAUDIO_SPEAKER_5POINT1:          return SPEAKERS_5POINT1;
 
- 	case KSAUDIO_SPEAKER_5POINT1_SURROUND: return SPEAKERS_5POINT1_SURROUND;
 
- 	case KSAUDIO_SPEAKER_7POINT1:          return SPEAKERS_7POINT1;
 
- 	case KSAUDIO_SPEAKER_7POINT1_SURROUND: return SPEAKERS_7POINT1_SURROUND;
 
- 	}
 
- 	return (speaker_layout)channels;
 
- }
 
- void WASAPISource::InitFormat(WAVEFORMATEX *wfex)
 
- {
 
- 	DWORD layout = 0;
 
- 	if (wfex->wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
 
- 		WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)wfex;
 
- 		layout = ext->dwChannelMask;
 
- 	}
 
- 	/* WASAPI is always float */
 
- 	sampleRate = wfex->nSamplesPerSec;
 
- 	format     = AUDIO_FORMAT_FLOAT;
 
- 	speakers   = ConvertSpeakerLayout(layout, wfex->nChannels);
 
- }
 
- void WASAPISource::InitCapture()
 
- {
 
- 	HRESULT res = client->GetService(__uuidof(IAudioCaptureClient),
 
- 			(void**)capture.Assign());
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to create capture context", res);
 
- 	res = client->SetEventHandle(receiveSignal);
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to set event handle", res);
 
- 	captureThread = CreateThread(nullptr, 0,
 
- 			WASAPISource::CaptureThread, this,
 
- 			0, nullptr);
 
- 	if (!captureThread.Valid())
 
- 		throw "Failed to create capture thread";
 
- 	client->Start();
 
- 	active = true;
 
- 	blog(LOG_INFO, "WASAPI: Device '%s' initialized", device_name.c_str());
 
- }
 
- void WASAPISource::Initialize()
 
- {
 
- 	ComPtr<IMMDeviceEnumerator> enumerator;
 
- 	HRESULT res;
 
- 	res = CoCreateInstance(__uuidof(MMDeviceEnumerator),
 
- 			nullptr, CLSCTX_ALL,
 
- 			__uuidof(IMMDeviceEnumerator),
 
- 			(void**)enumerator.Assign());
 
- 	if (FAILED(res))
 
- 		throw HRError("Failed to create enumerator", res);
 
- 	if (!InitDevice(enumerator))
 
- 		return;
 
- 	device_name = GetDeviceName(device);
 
- 	InitClient();
 
- 	if (!isInputDevice) InitRender();
 
- 	InitCapture();
 
- }
 
- bool WASAPISource::TryInitialize()
 
- {
 
- 	try {
 
- 		Initialize();
 
- 	} catch (HRError error) {
 
- 		if (previouslyFailed)
 
- 			return active;
 
- 		blog(LOG_WARNING, "[WASAPISource::TryInitialize]:[%s] %s: %lX",
 
- 				device_name.empty() ?
 
- 					device_id.c_str() : device_name.c_str(),
 
- 				error.str, error.hr);
 
- 	} catch (const char *error) {
 
- 		if (previouslyFailed)
 
- 			return active;
 
- 		blog(LOG_WARNING, "[WASAPISource::TryInitialize]:[%s] %s",
 
- 				device_name.empty() ?
 
- 					device_id.c_str() : device_name.c_str(),
 
- 				error);
 
- 	}
 
- 	previouslyFailed = !active;
 
- 	return active;
 
- }
 
- void WASAPISource::Reconnect()
 
- {
 
- 	reconnecting = true;
 
- 	reconnectThread = CreateThread(nullptr, 0,
 
- 			WASAPISource::ReconnectThread, this,
 
- 			0, nullptr);
 
- 	if (!reconnectThread.Valid())
 
- 		blog(LOG_WARNING, "[WASAPISource::Reconnect] "
 
- 		                "Failed to intiialize reconnect thread: %lu",
 
- 		                 GetLastError());
 
- }
 
- static inline bool WaitForSignal(HANDLE handle, DWORD time)
 
- {
 
- 	return WaitForSingleObject(handle, time) != WAIT_TIMEOUT;
 
- }
 
- #define RECONNECT_INTERVAL 3000
 
- DWORD WINAPI WASAPISource::ReconnectThread(LPVOID param)
 
- {
 
- 	WASAPISource *source = (WASAPISource*)param;
 
- 	os_set_thread_name("win-wasapi: reconnect thread");
 
- 	while (!WaitForSignal(source->stopSignal, RECONNECT_INTERVAL)) {
 
- 		if (source->TryInitialize())
 
- 			break;
 
- 	}
 
- 	source->reconnectThread = nullptr;
 
- 	source->reconnecting = false;
 
- 	return 0;
 
- }
 
- bool WASAPISource::ProcessCaptureData()
 
- {
 
- 	HRESULT res;
 
- 	LPBYTE  buffer;
 
- 	UINT32  frames;
 
- 	DWORD   flags;
 
- 	UINT64  pos, ts;
 
- 	UINT    captureSize = 0;
 
- 	while (true) {
 
- 		res = capture->GetNextPacketSize(&captureSize);
 
- 		if (FAILED(res)) {
 
- 			if (res != AUDCLNT_E_DEVICE_INVALIDATED)
 
- 				blog(LOG_WARNING,
 
- 						"[WASAPISource::GetCaptureData]"
 
- 						" capture->GetNextPacketSize"
 
- 						" failed: %lX", res);
 
- 			return false;
 
- 		}
 
- 		if (!captureSize)
 
- 			break;
 
- 		res = capture->GetBuffer(&buffer, &frames, &flags, &pos, &ts);
 
- 		if (FAILED(res)) {
 
- 			if (res != AUDCLNT_E_DEVICE_INVALIDATED)
 
- 				blog(LOG_WARNING,
 
- 						"[WASAPISource::GetCaptureData]"
 
- 						" capture->GetBuffer"
 
- 						" failed: %lX", res);
 
- 			return false;
 
- 		}
 
- 		obs_source_audio data = {};
 
- 		data.data[0]          = (const uint8_t*)buffer;
 
- 		data.frames           = (uint32_t)frames;
 
- 		data.speakers         = speakers;
 
- 		data.samples_per_sec  = sampleRate;
 
- 		data.format           = format;
 
- 		data.timestamp        = useDeviceTiming ?
 
- 			ts*100 : os_gettime_ns();
 
- 		obs_source_output_audio(source, &data);
 
- 		capture->ReleaseBuffer(frames);
 
- 	}
 
- 	return true;
 
- }
 
- static inline bool WaitForCaptureSignal(DWORD numSignals, const HANDLE *signals,
 
- 		DWORD duration)
 
- {
 
- 	DWORD ret;
 
- 	ret = WaitForMultipleObjects(numSignals, signals, false, duration);
 
- 	return ret == WAIT_OBJECT_0 || ret == WAIT_TIMEOUT;
 
- }
 
- DWORD WINAPI WASAPISource::CaptureThread(LPVOID param)
 
- {
 
- 	WASAPISource *source   = (WASAPISource*)param;
 
- 	bool         reconnect = false;
 
- 	/* Output devices don't signal, so just make it check every 10 ms */
 
- 	DWORD        dur       = source->isInputDevice ? INFINITE : 10;
 
- 	HANDLE sigs[2] = {
 
- 		source->receiveSignal,
 
- 		source->stopSignal
 
- 	};
 
- 	os_set_thread_name("win-wasapi: capture thread");
 
- 	while (WaitForCaptureSignal(2, sigs, dur)) {
 
- 		if (!source->ProcessCaptureData()) {
 
- 			reconnect = true;
 
- 			break;
 
- 		}
 
- 	}
 
- 	source->client->Stop();
 
- 	source->captureThread = nullptr;
 
- 	source->active        = false;
 
- 	if (reconnect) {
 
- 		blog(LOG_INFO, "Device '%s' invalidated.  Retrying",
 
- 				source->device_name.c_str());
 
- 		source->Reconnect();
 
- 	}
 
- 	return 0;
 
- }
 
- /* ------------------------------------------------------------------------- */
 
- static const char *GetWASAPIInputName(void)
 
- {
 
- 	return obs_module_text("AudioInput");
 
- }
 
- static const char *GetWASAPIOutputName(void)
 
- {
 
- 	return obs_module_text("AudioOutput");
 
- }
 
- static void GetWASAPIDefaults(obs_data_t *settings)
 
- {
 
- 	obs_data_set_default_string(settings, OPT_DEVICE_ID, "default");
 
- 	obs_data_set_default_bool(settings, OPT_USE_DEVICE_TIMING, true);
 
- }
 
- static void *CreateWASAPISource(obs_data_t *settings, obs_source_t *source,
 
- 		bool input)
 
- {
 
- 	try {
 
- 		return new WASAPISource(settings, source, input);
 
- 	} catch (const char *error) {
 
- 		blog(LOG_ERROR, "[CreateWASAPISource] %s", error);
 
- 	}
 
- 	return nullptr;
 
- }
 
- static void *CreateWASAPIInput(obs_data_t *settings, obs_source_t *source)
 
- {
 
- 	return CreateWASAPISource(settings, source, true);
 
- }
 
- static void *CreateWASAPIOutput(obs_data_t *settings, obs_source_t *source)
 
- {
 
- 	return CreateWASAPISource(settings, source, false);
 
- }
 
- static void DestroyWASAPISource(void *obj)
 
- {
 
- 	delete static_cast<WASAPISource*>(obj);
 
- }
 
- static void UpdateWASAPISource(void *obj, obs_data_t *settings)
 
- {
 
- 	static_cast<WASAPISource*>(obj)->Update(settings);
 
- }
 
- static obs_properties_t *GetWASAPIProperties(bool input)
 
- {
 
- 	obs_properties_t *props = obs_properties_create();
 
- 	vector<AudioDeviceInfo> devices;
 
- 	/* TODO: translate */
 
- 	obs_property_t *device_prop = obs_properties_add_list(props,
 
- 			OPT_DEVICE_ID, obs_module_text("Device"),
 
- 			OBS_COMBO_TYPE_LIST, OBS_COMBO_FORMAT_STRING);
 
- 	GetWASAPIAudioDevices(devices, input);
 
- 	if (devices.size())
 
- 		obs_property_list_add_string(device_prop,
 
- 				obs_module_text("Default"), "default");
 
- 	for (size_t i = 0; i < devices.size(); i++) {
 
- 		AudioDeviceInfo &device = devices[i];
 
- 		obs_property_list_add_string(device_prop,
 
- 				device.name.c_str(), device.id.c_str());
 
- 	}
 
- 	obs_properties_add_bool(props, OPT_USE_DEVICE_TIMING,
 
- 			obs_module_text("UseDeviceTiming"));
 
- 	return props;
 
- }
 
- static obs_properties_t *GetWASAPIPropertiesInput(void *)
 
- {
 
- 	return GetWASAPIProperties(true);
 
- }
 
- static obs_properties_t *GetWASAPIPropertiesOutput(void *)
 
- {
 
- 	return GetWASAPIProperties(false);
 
- }
 
- void RegisterWASAPIInput()
 
- {
 
- 	obs_source_info info = {};
 
- 	info.id              = "wasapi_input_capture";
 
- 	info.type            = OBS_SOURCE_TYPE_INPUT;
 
- 	info.output_flags    = OBS_SOURCE_AUDIO;
 
- 	info.get_name        = GetWASAPIInputName;
 
- 	info.create          = CreateWASAPIInput;
 
- 	info.destroy         = DestroyWASAPISource;
 
- 	info.update          = UpdateWASAPISource;
 
- 	info.get_defaults    = GetWASAPIDefaults;
 
- 	info.get_properties  = GetWASAPIPropertiesInput;
 
- 	obs_register_source(&info);
 
- }
 
- void RegisterWASAPIOutput()
 
- {
 
- 	obs_source_info info = {};
 
- 	info.id              = "wasapi_output_capture";
 
- 	info.type            = OBS_SOURCE_TYPE_INPUT;
 
- 	info.output_flags    = OBS_SOURCE_AUDIO;
 
- 	info.get_name        = GetWASAPIOutputName;
 
- 	info.create          = CreateWASAPIOutput;
 
- 	info.destroy         = DestroyWASAPISource;
 
- 	info.update          = UpdateWASAPISource;
 
- 	info.get_defaults    = GetWASAPIDefaults;
 
- 	info.get_properties  = GetWASAPIPropertiesOutput;
 
- 	obs_register_source(&info);
 
- }
 
 
  |