mswasapi_reader.cpp 12.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
mswasapi_reader.cpp

mediastreamer2 library - modular sound and video processing and streaming
Windows Audio Session API sound card plugin for mediastreamer2
Copyright (C) 2010-2013 Belledonne Communications, Grenoble, France

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
20
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 22 23 24
*/


#include "mediastreamer2/mscommon.h"
25
#include "mediastreamer2/msticker.h"
26 27 28
#include "mswasapi_reader.h"


Ghislain MARY's avatar
Ghislain MARY committed
29
#define REFTIME_250MS 2500000
30 31 32 33 34 35 36 37 38 39 40 41 42

#define RELEASE_CLIENT(client) \
	if (client != NULL) { \
		client->Release(); \
		client = NULL; \
	}
#define FREE_PTR(ptr) \
	if (ptr != NULL) { \
		CoTaskMemFree((LPVOID)ptr); \
		ptr = NULL; \
	}


43 44 45
bool MSWASAPIReader::smInstantiated = false;


46 47
MSWASAPIReader::MSWASAPIReader(MSFilter *filter)
	: mAudioClient(NULL), mAudioCaptureClient(NULL), mVolumeControler(NULL), mBufferFrameCount(0), mIsInitialized(false), mIsActivated(false), mIsStarted(false), mFilter(filter)
48
{
49
	mTickerSynchronizer = ms_ticker_synchronizer_new();
50
#ifdef MS2_WINDOWS_UNIVERSAL
Ghislain MARY's avatar
Ghislain MARY committed
51
	mActivationEvent = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
52 53 54 55 56
	if (!mActivationEvent) {
		ms_error("Could not create activation event of the MSWASAPI audio input interface [%i]", GetLastError());
		return;
	}
#endif
57 58 59 60 61
}

MSWASAPIReader::~MSWASAPIReader()
{
	RELEASE_CLIENT(mAudioClient);
62
#ifdef MS2_WINDOWS_PHONE
63
	FREE_PTR(mCaptureId);
64 65 66 67 68 69
#endif
#ifdef MS2_WINDOWS_UNIVERSAL
	if (mActivationEvent != INVALID_HANDLE_VALUE) {
		CloseHandle(mActivationEvent);
		mActivationEvent = INVALID_HANDLE_VALUE;
	}
70
#endif
71
	ms_ticker_synchronizer_destroy(mTickerSynchronizer);
72 73 74 75 76
	smInstantiated = false;
}


void MSWASAPIReader::init(LPCWSTR id)
77 78 79
{
	HRESULT result;
	WAVEFORMATEX *pWfx = NULL;
80 81 82
#if defined(MS2_WINDOWS_PHONE) || defined(MS2_WINDOWS_UNIVERSAL)
	AudioClientProperties properties = { 0 };
#endif
83

84 85
#if defined(MS2_WINDOWS_UNIVERSAL)
	ComPtr<IActivateAudioInterfaceAsyncOperation> asyncOp;
86
	mCaptureId = ref new Platform::String(id);
87 88 89 90 91 92 93 94
	if (mCaptureId == nullptr) {
		ms_error("Could not get the CaptureID of the MSWASAPI audio input interface");
		goto error;
	}
	if (smInstantiated) {
		ms_error("An MSWASAPIReader is already instantiated. A second one can not be created.");
		goto error;
	}
Ghislain MARY's avatar
Ghislain MARY committed
95
	result = ActivateAudioInterfaceAsync(mCaptureId->Data(), IID_IAudioClient2, NULL, this, &asyncOp);
96 97
	REPORT_ERROR("Could not activate the MSWASAPI audio input interface [%i]", result);
	WaitForSingleObjectEx(mActivationEvent, INFINITE, FALSE);
Ghislain MARY's avatar
Ghislain MARY committed
98
	if (mAudioClient == NULL) {
99 100 101 102
		ms_error("Could not create the MSWASAPI audio input interface client");
		goto error;
	}
#elif defined(MS2_WINDOWS_PHONE)
103 104 105 106 107
	mCaptureId = GetDefaultAudioCaptureId(Communications);
	if (mCaptureId == NULL) {
		ms_error("Could not get the CaptureId of the MSWASAPI audio input interface");
		goto error;
	}
108 109 110

	if (smInstantiated) {
		ms_error("An MSWASAPIReader is already instantiated. A second one can not be created.");
111
		goto error;
112 113
	}

114
	result = ActivateAudioInterface(mCaptureId, IID_IAudioClient2, (void **)&mAudioClient);
Ghislain MARY's avatar
Ghislain MARY committed
115
	REPORT_ERROR("Could not activate the MSWASAPI audio input interface [%x]", result);
116 117 118
#else
	IMMDeviceEnumerator *pEnumerator = NULL;
	IMMDevice *pDevice = NULL;
119 120 121
#ifdef ENABLE_MICROSOFT_STORE_APP
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
122
	CoInitialize(NULL);
123
#endif
Julien Wadel's avatar
Julien Wadel committed
124
	result = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
125 126 127
	REPORT_ERROR("mswasapi: Could not create an instance of the device enumerator", result);
	mCaptureId = id;
	result = pEnumerator->GetDevice(mCaptureId, &pDevice);
128 129
	SAFE_RELEASE(pEnumerator);
	REPORT_ERROR("mswasapi: Could not get the capture device", result);
130
	result = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&mAudioClient);
131 132
	SAFE_RELEASE(pDevice);
	REPORT_ERROR("mswasapi: Could not activate the capture device", result);
133 134 135 136 137 138 139
#endif
#if defined(MS2_WINDOWS_PHONE) || defined(MS2_WINDOWS_UNIVERSAL)
	properties.cbSize = sizeof(AudioClientProperties);
	properties.bIsOffload = false;
	properties.eCategory = AudioCategory_Communications;
	result = mAudioClient->SetClientProperties(&properties);
	REPORT_ERROR("Could not set properties of the MSWASAPI audio input interface [%x]", result);
140
#endif
141
	result = mAudioClient->GetMixFormat(&pWfx);
Ghislain MARY's avatar
Ghislain MARY committed
142
	REPORT_ERROR("Could not get the mix format of the MSWASAPI audio input interface [%x]", result);
143 144
	mRate = pWfx->nSamplesPerSec;
	mNChannels = pWfx->nChannels;
Julien Wadel's avatar
Julien Wadel committed
145
	mNBlockAlign = pWfx->nBlockAlign;
146 147
	FREE_PTR(pWfx);
	mIsInitialized = true;
148
	smInstantiated = true;
149
	activate();
Ghislain MARY's avatar
Ghislain MARY committed
150
	return;
151 152

error:
153 154 155
	// Initialize the frame rate and the number of channels to be able to generate silence.
	mRate = 8000;
	mNChannels = 1;
Julien Wadel's avatar
Julien Wadel committed
156
	mNBlockAlign = 16 * 1 / 8;
157 158 159 160 161 162
	return;
}

int MSWASAPIReader::activate()
{
	HRESULT result;
Ghislain MARY's avatar
Ghislain MARY committed
163
	REFERENCE_TIME requestedDuration = REFTIME_250MS;
164 165 166
	WAVEFORMATPCMEX proposedWfx;
	WAVEFORMATEX *pUsedWfx = NULL;
	WAVEFORMATEX *pSupportedWfx = NULL;
167
	DWORD flags = 0;
168 169 170

	if (!mIsInitialized) goto error;

171
#if defined( MS2_WINDOWS_PHONE )
172 173 174
	flags = AUDCLNT_SESSIONFLAGS_EXPIREWHENUNOWNED | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDE | AUDCLNT_SESSIONFLAGS_DISPLAY_HIDEWHENEXPIRED;
#endif

175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
	proposedWfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
	proposedWfx.Format.nChannels = (WORD)mNChannels;
	proposedWfx.Format.nSamplesPerSec = mRate;
	proposedWfx.Format.wBitsPerSample = 16;
	proposedWfx.Format.nAvgBytesPerSec = mRate * mNChannels * proposedWfx.Format.wBitsPerSample / 8;
	proposedWfx.Format.nBlockAlign = (WORD)(proposedWfx.Format.wBitsPerSample * mNChannels / 8);
	proposedWfx.Format.cbSize = 22;
	proposedWfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
	proposedWfx.Samples.wValidBitsPerSample = proposedWfx.Format.wBitsPerSample;
	if (mNChannels == 1) {
		proposedWfx.dwChannelMask = SPEAKER_FRONT_CENTER;
	} else {
		proposedWfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
	}
	result = mAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX *)&proposedWfx, &pSupportedWfx);
	if (result == S_OK) {
		pUsedWfx = (WAVEFORMATEX *)&proposedWfx;
	} else if (result == S_FALSE) {
		pUsedWfx = pSupportedWfx;
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
195
		REPORT_ERROR("Audio format not supported by the MSWASAPI audio input interface [%x]", result);
196
	}
197
	
198
	result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, requestedDuration, 0, pUsedWfx, NULL);
199
	if ((result != S_OK) && (result != AUDCLNT_E_ALREADY_INITIALIZED)) {
200 201 202 203 204 205 206 207
		mAudioClient->Reset();
		result = mAudioClient->GetMixFormat(&pSupportedWfx);
		pUsedWfx = pSupportedWfx;
		result = mAudioClient->Initialize(AUDCLNT_SHAREMODE_SHARED, flags, requestedDuration, 0, pUsedWfx, NULL);
		if ((result != S_OK) && (result != AUDCLNT_E_ALREADY_INITIALIZED)) {
			mAudioClient->Reset();
			REPORT_ERROR("Could not initialize the MSWASAPI audio input interface [%x]", result);
		}
208
	}
Julien Wadel's avatar
Julien Wadel committed
209
	mNBlockAlign = pUsedWfx->nBlockAlign;
210
	result = mAudioClient->GetBufferSize(&mBufferFrameCount);
Ghislain MARY's avatar
Ghislain MARY committed
211
	REPORT_ERROR("Could not get buffer size for the MSWASAPI audio input interface [%x]", result);
212 213
	ms_message("MSWASAPI audio input interface buffer size: %i", mBufferFrameCount);
	result = mAudioClient->GetService(IID_IAudioCaptureClient, (void **)&mAudioCaptureClient);
Ghislain MARY's avatar
Ghislain MARY committed
214
	REPORT_ERROR("Could not get render service from the MSWASAPI audio input interface [%x]", result);
215 216
	result = mAudioClient->GetService(IID_ISimpleAudioVolume, (void **)&mVolumeControler);
	REPORT_ERROR("Could not get volume control service from the MSWASAPI audio input interface [%x]", result);
217
	mIsActivated = true;
218 219
	ms_message("Wasapi capture initialized at %i Hz, %i channels, with buffer size %i (%i ms), %i-bit frames are on %i bits", (int)mRate, (int)mNChannels,
		(int)mBufferFrameCount, (int)1000*mBufferFrameCount/(mNChannels*2* mRate), (int)pUsedWfx->wBitsPerSample, mNBlockAlign*8);
Julien Wadel's avatar
Julien Wadel committed
220
	FREE_PTR(pSupportedWfx);
221 222 223 224 225 226 227 228 229 230
	return 0;

error:
	FREE_PTR(pSupportedWfx);
	return -1;
}

int MSWASAPIReader::deactivate()
{
	RELEASE_CLIENT(mAudioCaptureClient);
231
	RELEASE_CLIENT(mVolumeControler);
232
	
233 234 235 236 237 238 239 240 241 242 243 244
	mIsActivated = false;
	return 0;
}

void MSWASAPIReader::start()
{
	HRESULT result;

	if (!isStarted() && mIsActivated) {
		mIsStarted = true;
		result = mAudioClient->Start();
		if (result != S_OK) {
Julien Wadel's avatar
Julien Wadel committed
245
			ms_error("Could not start capture on the MSWASAPI audio input interface [%x]", result);
246 247
		}
	}
248
	ms_ticker_set_synchronizer(mFilter->ticker, mTickerSynchronizer);
249 250 251 252 253 254 255 256 257 258
}

void MSWASAPIReader::stop()
{
	HRESULT result;

	if (isStarted() && mIsActivated) {
		mIsStarted = false;
		result = mAudioClient->Stop();
		if (result != S_OK) {
Julien Wadel's avatar
Julien Wadel committed
259
			ms_error("Could not stop capture on the MSWASAPI audio input interface [%x]", result);
260 261
		}
	}
262
	ms_ticker_set_synchronizer(mFilter->ticker, nullptr);
263 264
}

265
int MSWASAPIReader::feed(MSFilter *f)
266 267 268 269
{
	HRESULT result;
	DWORD flags;
	BYTE *pData;
270 271 272 273


	UINT32 numFramesAvailable;
	UINT32 numFramesInNextPacket = 0;
274
	UINT64 devicePosition;
275
	mblk_t *m;
Julien Wadel's avatar
Julien Wadel committed
276
	int bytesPerFrame = mNBlockAlign;
277

278 279
	if (isStarted()) {
		result = mAudioCaptureClient->GetNextPacketSize(&numFramesInNextPacket);
280 281 282
		while (numFramesInNextPacket != 0) {
			REPORT_ERROR("Could not get next packet size for the MSWASAPI audio input interface [%x]", result);

283
			result = mAudioCaptureClient->GetBuffer(&pData, &numFramesAvailable, &flags, &devicePosition, NULL);
Ghislain MARY's avatar
Ghislain MARY committed
284
			REPORT_ERROR("Could not get buffer from the MSWASAPI audio input interface [%x]", result);
285 286 287 288 289 290 291 292 293
			if (numFramesAvailable > 0) {
				m = allocb(numFramesAvailable * bytesPerFrame, 0);
				if (m == NULL) {
					ms_error("Could not allocate memory for the captured data from the MSWASAPI audio input interface");
					goto error;
				}
				
				if (flags & AUDCLNT_BUFFERFLAGS_SILENT) {
					memset(m->b_wptr, 0, numFramesAvailable * bytesPerFrame);
294
				} else {
295 296 297 298 299 300
					memcpy(m->b_wptr, pData, numFramesAvailable * bytesPerFrame);
				}
				result = mAudioCaptureClient->ReleaseBuffer(numFramesAvailable);
				REPORT_ERROR("Could not release buffer of the MSWASAPI audio input interface [%x]", result);

				m->b_wptr += numFramesAvailable * bytesPerFrame;
301
				ms_ticker_synchronizer_update(mTickerSynchronizer, devicePosition, (unsigned int)mRate);
302 303 304

				ms_queue_put(f->outputs[0], m);
				result = mAudioCaptureClient->GetNextPacketSize(&numFramesInNextPacket);
305
			}
306 307
			
		} 
308 309
	} else {
		silence(f);
310 311 312 313 314 315
	}
	return 0;

error:
	return -1;
}
316

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346
float MSWASAPIReader::getVolumeLevel() {
	HRESULT result;
	float volume;

	if (!mIsActivated) {
		ms_error("MSWASAPIReader::getVolumeLevel(): the MSWASAPIReader instance is not started");
		goto error;
	}
	result = mVolumeControler->GetMasterVolume(&volume);
	REPORT_ERROR("MSWASAPIReader::getVolumeLevel(): could not get the master volume [%x]", result);
	return volume;

error:
	return -1.0f;
}

void MSWASAPIReader::setVolumeLevel(float volume) {
	HRESULT result;

	if (!mIsActivated) {
		ms_error("MSWASAPIReader::setVolumeLevel(): the MSWASAPIReader instance is not started");
		goto error;
	}
	result = mVolumeControler->SetMasterVolume(volume, NULL);
	REPORT_ERROR("MSWASAPIReader::setVolumeLevel(): could not set the master volume [%x]", result);

error:
	return;
}

347 348 349 350 351 352 353 354 355 356 357 358
void MSWASAPIReader::silence(MSFilter *f)
{
	mblk_t *om;
	unsigned int bufsize;
	unsigned int nsamples;

	nsamples = (f->ticker->interval * mRate) / 1000;
	bufsize = nsamples * mNChannels * 2;
	om = allocb(bufsize, 0);
	memset(om->b_wptr, 0, bufsize);
	om->b_wptr += bufsize;
	ms_queue_put(f->outputs[0], om);
359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
}


#ifdef MS2_WINDOWS_UNIVERSAL
//
//  ActivateCompleted()
//
//  Callback implementation of ActivateAudioInterfaceAsync function.  This will be called on MTA thread
//  when results of the activation are available.
//
HRESULT MSWASAPIReader::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *operation)
{
	HRESULT hr = S_OK;
	HRESULT hrActivateResult = S_OK;
	ComPtr<IUnknown> audioInterface;

	hr = operation->GetActivateResult(&hrActivateResult, &audioInterface);
	if (FAILED(hr))	goto exit;
	hr = hrActivateResult;
	if (FAILED(hr)) goto exit;

	audioInterface.CopyTo(&mAudioClient);
Ghislain MARY's avatar
Ghislain MARY committed
381
	if (mAudioClient == NULL) {
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
		hr = E_NOINTERFACE;
		goto exit;
	}

exit:
	if (FAILED(hr))	{
		SAFE_RELEASE(mAudioClient);
		SAFE_RELEASE(mAudioCaptureClient);
	}

	SetEvent(mActivationEvent);
	return S_OK;
}


397
MSWASAPIReaderPtr MSWASAPIReaderNew(MSFilter *f)
398
{
399 400
	MSWASAPIReaderPtr r = new MSWASAPIReaderWrapper();
	r->reader = Make<MSWASAPIReader>(f);
401 402
	return r;
}
403 404 405 406 407 408
void MSWASAPIReaderDelete(MSWASAPIReaderPtr ptr)
{
	ptr->reader->setAsNotInstantiated();
	ptr->reader = nullptr;
	delete ptr;
}
409
#else
410
MSWASAPIReaderPtr MSWASAPIReaderNew(MSFilter *f)
411
{
412
	return (MSWASAPIReaderPtr) new MSWASAPIReader(f);
413
}
414 415 416 417
void MSWASAPIReaderDelete(MSWASAPIReaderPtr ptr)
{
	delete ptr;
}
418
#endif