mswinrtcap.cpp 15.3 KB
Newer Older
1
/*
2
mswinrtcap.cpp
3 4 5

mediastreamer2 library - modular sound and video processing and streaming
Windows Audio Session API sound card plugin for mediastreamer2
6
Copyright (C) 2010-2015 Belledonne Communications, Grenoble, France
7 8 9 10 11 12 13 14 15 16 17 18 19

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 "mswinrtcap.h"
25

26 27
using namespace Microsoft::WRL;
using namespace Windows::Foundation;
Ghislain MARY's avatar
Ghislain MARY committed
28 29
using namespace Windows::Devices::Enumeration;
using namespace Windows::Storage;
30
using namespace libmswinrtvid;
31 32


33
bool MSWinRTCap::smInstantiated = false;
34
bctbx_list_t *MSWinRTCap::smCameras = NULL;
35 36


37 38 39
MSWinRTCapHelper::MSWinRTCapHelper() :
	mRotationKey({ 0xC380465D, 0x2271, 0x428C,{ 0x9B, 0x83, 0xEC, 0xEA, 0x3B, 0x4A, 0x85, 0xC1 } }),
	mDeviceOrientation(0), mAllocator(NULL)
40
{
41 42 43
	mInitializationCompleted = CreateEventEx(NULL, L"Local\\MSWinRTCapInitialization", 0, EVENT_ALL_ACCESS);
	if (!mInitializationCompleted) {
		ms_error("[MSWinRTCap] Could not create initialization event [%i]", GetLastError());
44 45
		return;
	}
46
	mStartCompleted = CreateEventEx(NULL, L"Local\\MSWinRTCapStart", 0, EVENT_ALL_ACCESS);
47
	if (!mStartCompleted) {
48
		ms_error("[MSWinRTCap] Could not create start event [%i]", GetLastError());
49 50
		return;
	}
51
	mStopCompleted = CreateEventEx(NULL, L"Local\\MSWinRTCapStop", 0, EVENT_ALL_ACCESS);
52
	if (!mStopCompleted) {
53
		ms_error("[MSWinRTCap] Could not create stop event [%i]", GetLastError());
54 55
		return;
	}
56

57 58 59
	ms_mutex_init(&mMutex, NULL);
	mAllocator = ms_yuv_buf_allocator_new();
	ms_queue_init(&mSamplesQueue);
60 61
}

62
MSWinRTCapHelper::~MSWinRTCapHelper()
63
{
64 65 66
	if (mCapture.Get() != nullptr) {
		mCapture->Failed -= mMediaCaptureFailedEventRegistrationToken;
	}
67 68 69 70 71 72 73 74
	if (mStopCompleted) {
		CloseHandle(mStopCompleted);
		mStopCompleted = NULL;
	}
	if (mStartCompleted) {
		CloseHandle(mStartCompleted);
		mStartCompleted = NULL;
	}
75 76 77
	if (mInitializationCompleted) {
		CloseHandle(mInitializationCompleted);
		mInitializationCompleted = NULL;
78
	}
79 80 81 82
	if (mAllocator != NULL) {
		ms_yuv_buf_allocator_free(mAllocator);
		mAllocator = NULL;
	}
Ghislain MARY's avatar
Ghislain MARY committed
83
	mEncodingProfile = nullptr;
84 85 86
	ms_mutex_destroy(&mMutex);
}

87 88 89 90
void MSWinRTCapHelper::OnCaptureFailed(MediaCapture^ sender, MediaCaptureFailedEventArgs^ errorEventArgs)
{
	errorEventArgs->Message;
}
91

92
bool MSWinRTCapHelper::Initialize(Platform::String^ DeviceId)
93
{
94
	bool isInitialized = false;
Ghislain MARY's avatar
Ghislain MARY committed
95
	mCapture = ref new MediaCapture();
96
	mMediaCaptureFailedEventRegistrationToken = mCapture->Failed += ref new MediaCaptureFailedEventHandler(this, &MSWinRTCapHelper::OnCaptureFailed);
Ghislain MARY's avatar
Ghislain MARY committed
97
	MediaCaptureInitializationSettings^ initSettings = ref new MediaCaptureInitializationSettings();
Ghislain MARY's avatar
Ghislain MARY committed
98
	initSettings->MediaCategory = MediaCategory::Communications;
99
	initSettings->VideoDeviceId = DeviceId;
Ghislain MARY's avatar
Ghislain MARY committed
100 101
	initSettings->StreamingCaptureMode = StreamingCaptureMode::Video;
	IAsyncAction^ initAction = mCapture->InitializeAsync(initSettings);
102
	initAction->Completed = ref new AsyncActionCompletedHandler([this, &isInitialized](IAsyncAction^ asyncInfo, Windows::Foundation::AsyncStatus asyncStatus) {
Ghislain MARY's avatar
Ghislain MARY committed
103
		switch (asyncStatus) {
104
		case Windows::Foundation::AsyncStatus::Completed:
Ghislain MARY's avatar
Ghislain MARY committed
105
			ms_message("[MSWinRTCap] InitializeAsync completed");
106
			isInitialized = true;
Ghislain MARY's avatar
Ghislain MARY committed
107
			break;
108
		case Windows::Foundation::AsyncStatus::Canceled:
Ghislain MARY's avatar
Ghislain MARY committed
109 110
			ms_warning("[MSWinRTCap] InitializeAsync has been canceled");
			break;
111
		case Windows::Foundation::AsyncStatus::Error:
Ghislain MARY's avatar
Ghislain MARY committed
112 113 114 115 116
			ms_error("[MSWinRTCap] InitializeAsync failed [0x%x]", asyncInfo->ErrorCode);
			break;
		default:
			break;
		}
117
		SetEvent(mInitializationCompleted);
Ghislain MARY's avatar
Ghislain MARY committed
118 119
	});

120
	WaitForSingleObjectEx(mInitializationCompleted, INFINITE, FALSE);
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
	return isInitialized;
}

bool MSWinRTCapHelper::StartCapture(MediaEncodingProfile^ EncodingProfile)
{
	bool isStarted = false;
	mEncodingProfile = EncodingProfile;
	MakeAndInitialize<MSWinRTMediaSink>(&mMediaSink, EncodingProfile->Video);
	static_cast<MSWinRTMediaSink *>(mMediaSink.Get())->SetCaptureFilter(this);
	ComPtr<IInspectable> spInspectable;
	HRESULT hr = mMediaSink.As(&spInspectable);
	if (FAILED(hr)) return false;
	IMediaExtension^ mediaExtension = safe_cast<IMediaExtension^>(reinterpret_cast<Object^>(spInspectable.Get()));
	IAsyncAction^ action = mCapture->StartRecordToCustomSinkAsync(EncodingProfile, mediaExtension);
	action->Completed = ref new AsyncActionCompletedHandler([this, &isStarted](IAsyncAction^ asyncAction, Windows::Foundation::AsyncStatus asyncStatus) {
		IAsyncAction^ capturePropertiesAction = nullptr;
		IMediaEncodingProperties^ props = nullptr;
		if (asyncStatus == Windows::Foundation::AsyncStatus::Completed) {
			ms_message("[MSWinRTCap] StartRecordToCustomSinkAsync completed");
			isStarted = true;
		} else {
			ms_error("[MSWinRTCap] StartRecordToCustomSinkAsync failed");
		}
		SetEvent(mStartCompleted);
	});
	WaitForSingleObjectEx(mStartCompleted, INFINITE, FALSE);
	return isStarted;
}

void MSWinRTCapHelper::StopCapture()
{
	static_cast<MSWinRTMediaSink *>(mMediaSink.Get())->SetCaptureFilter(nullptr);
	IAsyncAction^ action = mCapture->StopRecordAsync();
	action->Completed = ref new AsyncActionCompletedHandler([this](IAsyncAction^ asyncAction, Windows::Foundation::AsyncStatus asyncStatus) {
		static_cast<MSWinRTMediaSink *>(mMediaSink.Get())->Shutdown();
		mMediaSink = nullptr;
		if (asyncStatus == Windows::Foundation::AsyncStatus::Completed) {
			ms_message("[MSWinRTCap] StopRecordAsync completed");
		}
		else {
			ms_error("[MSWinRTCap] StopRecordAsync failed");
		}
		SetEvent(mStopCompleted);
	});
	WaitForSingleObjectEx(mStopCompleted, INFINITE, FALSE);
}

void MSWinRTCapHelper::OnSampleAvailable(BYTE *buf, DWORD bufLen, LONGLONG presentationTime)
{
	mblk_t *m;
	uint32_t timestamp = (uint32_t)((presentationTime / 10000LL) * 90LL);

	int w = mEncodingProfile->Video->Width;
	int h = mEncodingProfile->Video->Height;
	if ((mDeviceOrientation % 180) == 90) {
		w = mEncodingProfile->Video->Height;
		h = mEncodingProfile->Video->Width;
	}
	uint8_t *y = (uint8_t *)buf;
	uint8_t *cbcr = (uint8_t *)(buf + w * h);
	m = copy_ycbcrbiplanar_to_true_yuv_with_rotation(mAllocator, y, cbcr, mDeviceOrientation, w, h, mEncodingProfile->Video->Width, mEncodingProfile->Video->Width, TRUE);
	mblk_set_timestamp_info(m, timestamp);

	ms_mutex_lock(&mMutex);
	ms_queue_put(&mSamplesQueue, m);
	ms_mutex_unlock(&mMutex);
}

mblk_t * MSWinRTCapHelper::GetSample()
{
	ms_mutex_lock(&mMutex);
	mblk_t *m = ms_queue_get(&mSamplesQueue);
	ms_mutex_unlock(&mMutex);
	return m;
}

MSVideoSize MSWinRTCapHelper::SelectBestVideoSize(MSVideoSize vs)
{
	if ((CaptureDevice == nullptr) || (CaptureDevice->VideoDeviceController == nullptr)) {
		return vs;
	}

	MSVideoSize requestedSize;
	MSVideoSize bestFoundSize;
	MSVideoSize minSize = { 65536, 65536 };
	bestFoundSize.width = bestFoundSize.height = 0;
	requestedSize.width = vs.width;
	requestedSize.height = vs.height;

	IVectorView<IMediaEncodingProperties^>^ props = mCapture->VideoDeviceController->GetAvailableMediaStreamProperties(MediaStreamType::VideoRecord);
	for (unsigned int i = 0; i < props->Size; i++) {
		IMediaEncodingProperties^ encodingProp = props->GetAt(i);
		if (encodingProp->Type == L"Video") {
			IVideoEncodingProperties^ videoProp = static_cast<IVideoEncodingProperties^>(encodingProp);
			String^ subtype = videoProp->Subtype;
			if ((subtype == L"NV12") || (subtype == L"Unknown")) {
				MSVideoSize currentSize;
				currentSize.width = (int)videoProp->Width;
				currentSize.height = (int)videoProp->Height;
				ms_message("[MSWinRTCap] Seeing video size %ix%i", currentSize.width, currentSize.height);
				if (ms_video_size_greater_than(requestedSize, currentSize)) {
					if (ms_video_size_greater_than(currentSize, bestFoundSize)) {
						bestFoundSize = currentSize;
					}
				}
				if (ms_video_size_greater_than(minSize, currentSize)) {
					minSize = currentSize;
				}
			}
		}
	}

	if ((bestFoundSize.width == 0) && bestFoundSize.height == 0) {
234 235
		ms_warning("[MSWinRTCap] This camera does not support our video size, use requested size");
		return vs;
236 237 238 239 240 241 242 243
	}

	ms_message("[MSWinRTCap] Best video size is %ix%i", bestFoundSize.width, bestFoundSize.height);
	return bestFoundSize;
}


MSWinRTCap::MSWinRTCap()
Ghislain MARY's avatar
Ghislain MARY committed
244
	: mIsInitialized(false), mIsActivated(false), mIsStarted(false), mFps(15), mStartTime(0)
245 246
{
	if (smInstantiated) {
247
		ms_error("[MSWinRTCap] A video capture filter is already instantiated. A second one can not be created.");
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
		return;
	}

	mVideoSize.width = MS_VIDEO_SIZE_CIF_W;
	mVideoSize.height = MS_VIDEO_SIZE_CIF_H;
	mHelper = ref new MSWinRTCapHelper();
	smInstantiated = true;
}

MSWinRTCap::~MSWinRTCap()
{
	stop();
	deactivate();
	smInstantiated = false;
}


void MSWinRTCap::initialize()
{
	mIsInitialized = mHelper->Initialize(mDeviceId);
268 269 270 271
}

int MSWinRTCap::activate()
{
272
	if (!mIsInitialized) initialize();
273 274

	ms_average_fps_init(&mAvgFps, "[MSWinRTCap] fps=%f");
275
	ms_video_init_framerate_controller(&mFpsControl, mFps);
276 277 278 279
	configure();
	applyVideoSize();
	applyFps();
	mIsActivated = true;
280
	return 0;
281 282
}

283
int MSWinRTCap::deactivate()
284 285
{
	mIsActivated = false;
286
	mIsInitialized = false;
287
	return 0;
288 289
}

290
void MSWinRTCap::start()
291 292
{
	if (!mIsStarted && mIsActivated) {
293
		mIsStarted = mHelper->StartCapture(mEncodingProfile);
294
	}
295 296
}

297
void MSWinRTCap::stop()
298 299 300 301
{
	mblk_t *m;

	if (!mIsStarted) return;
302
	mHelper->StopCapture();
303 304

	// Free the samples that have not been sent yet
305
	while ((m = mHelper->GetSample()) != NULL) {
306 307
		freemsg(m);
	}
Ghislain MARY's avatar
Ghislain MARY committed
308
	mIsStarted = false;
309 310
}

311
int MSWinRTCap::feed(MSFilter *f)
312
{
313 314 315 316 317 318 319 320
	if (ms_video_capture_new_frame(&mFpsControl, f->ticker->time)) {
		mblk_t *im;
		
		// Send queued samples
		while ((im = mHelper->GetSample()) != NULL) {
			ms_queue_put(f->outputs[0], im);
			ms_average_fps_update(&mAvgFps, (uint32_t)f->ticker->time);
		}
321
	}
322 323 324 325

	return 0;
}

326

327
void MSWinRTCap::setFps(float fps)
328
{
329
	mFps = fps;
330 331
	ms_average_fps_init(&mAvgFps, "[MSWinRTCap] fps=%f");
	ms_video_init_framerate_controller(&mFpsControl, fps);
332
	applyFps();
333 334
}

335 336 337 338 339
float MSWinRTCap::getAverageFps()
{
	return ms_average_fps_get(&mAvgFps);
}

340
MSVideoSize MSWinRTCap::getVideoSize()
341 342
{
	MSVideoSize vs;
Ghislain MARY's avatar
Ghislain MARY committed
343
	if ((mHelper->DeviceOrientation % 180) == 90) {
344 345
		vs.width = mVideoSize.height;
		vs.height = mVideoSize.width;
346
	} else {
347
		vs = mVideoSize;
348
	}
349 350 351
	return vs;
}

352
void MSWinRTCap::setVideoSize(MSVideoSize vs)
353
{
354
	selectBestVideoSize(vs);
355
	applyVideoSize();
356 357
}

358 359
void MSWinRTCap::selectBestVideoSize(MSVideoSize vs)
{
360
	mVideoSize = mHelper->SelectBestVideoSize(vs);
361 362
}

363
void MSWinRTCap::setDeviceOrientation(int degrees)
364
{
365
	if (mFront) {
Ghislain MARY's avatar
Ghislain MARY committed
366
		mHelper->DeviceOrientation = degrees % 360;
367
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
368
		mHelper->DeviceOrientation = (360 - degrees) % 360;
369
	}
370 371
}

372

373
void MSWinRTCap::applyFps()
374
{
375
	if (mEncodingProfile != nullptr) {
376
		mEncodingProfile->Video->FrameRate->Numerator = (unsigned int)mFps;
377
		mEncodingProfile->Video->FrameRate->Denominator = 1;
378 379 380
	}
}

381
void MSWinRTCap::applyVideoSize()
382
{
383
	if (mEncodingProfile != nullptr) {
384
		MSVideoSize vs = mVideoSize;
385 386
		mEncodingProfile->Video->Width = vs.width;
		mEncodingProfile->Video->Height = vs.height;
387 388
		mEncodingProfile->Video->PixelAspectRatio->Numerator = 1;
		mEncodingProfile->Video->PixelAspectRatio->Denominator = 1;
Ghislain MARY's avatar
Ghislain MARY committed
389
	}
390 391
}

392
void MSWinRTCap::configure()
393
{
394 395 396
	mEncodingProfile = ref new MediaEncodingProfile();
	mEncodingProfile->Audio = nullptr;
	mEncodingProfile->Container = nullptr;
397
	MSVideoSize vs = mVideoSize;
398
	mEncodingProfile->Video = VideoEncodingProperties::CreateUncompressed(MediaEncodingSubtypes::Nv12, vs.width, vs.height);
399 400
}

401
void MSWinRTCap::addCamera(MSWebCamManager *manager, MSWebCamDesc *desc, DeviceInformation^ DeviceInfo)
Ghislain MARY's avatar
Ghislain MARY committed
402
{
403 404
	char *idStr = NULL;
	char *nameStr = NULL;
Ghislain MARY's avatar
Ghislain MARY committed
405
	size_t returnlen;
406
	size_t inputlen = wcslen(DeviceInfo->Name->Data()) + 1;
407 408
	nameStr = (char *)ms_malloc(inputlen);
	if (!nameStr || wcstombs_s(&returnlen, nameStr, inputlen, DeviceInfo->Name->Data(), inputlen) != 0) {
Ghislain MARY's avatar
Ghislain MARY committed
409 410 411
		ms_error("MSWinRTCap: Cannot convert webcam name to multi-byte string.");
		goto error;
	}
412 413 414 415 416 417 418 419
	const wchar_t *id = DeviceInfo->Id->Data();
	inputlen = wcslen(id) + 1;
	idStr = (char *)ms_malloc(inputlen);
	if (!idStr || wcstombs_s(&returnlen, idStr, inputlen, DeviceInfo->Id->Data(), inputlen) != 0) {
		ms_error("MSWinRTCap: Cannot convert webcam id to multi-byte string.");
		goto error;
	}
	char *name = bctbx_strdup_printf("%s--%s", nameStr, idStr);
Ghislain MARY's avatar
Ghislain MARY committed
420 421

	MSWebCam *cam = ms_web_cam_new(desc);
422
	cam->name = name;
Ghislain MARY's avatar
Ghislain MARY committed
423 424 425 426 427
	WinRTWebcam *winrtwebcam = new WinRTWebcam();
	winrtwebcam->id_vector = new std::vector<wchar_t>(wcslen(id) + 1);
	wcscpy_s(&winrtwebcam->id_vector->front(), winrtwebcam->id_vector->size(), id);
	winrtwebcam->id = &winrtwebcam->id_vector->front();
	cam->data = winrtwebcam;
428 429 430 431
	if (DeviceInfo->EnclosureLocation != nullptr) {
		if (DeviceInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Front) {
			winrtwebcam->external = FALSE;
			winrtwebcam->front = TRUE;
432
			smCameras = bctbx_list_append(smCameras, cam);
433 434 435 436 437 438 439 440
		} else {
			if (DeviceInfo->EnclosureLocation->Panel == Windows::Devices::Enumeration::Panel::Unknown) {
				winrtwebcam->external = TRUE;
				winrtwebcam->front = TRUE;
			} else {
				winrtwebcam->external = FALSE;
				winrtwebcam->front = FALSE;
			}
441
			smCameras = bctbx_list_prepend(smCameras, cam);
442
		}
443
	} else {
444 445
		winrtwebcam->external = TRUE;
		winrtwebcam->front = TRUE;
446
		smCameras = bctbx_list_prepend(smCameras, cam);
447
	}
Ghislain MARY's avatar
Ghislain MARY committed
448 449

error:
450 451 452 453 454 455
	if (nameStr) {
		ms_free(nameStr);
	}
	if (idStr) {
		ms_free(idStr);
	}
Ghislain MARY's avatar
Ghislain MARY committed
456
}
457

458 459
void MSWinRTCap::registerCameras(MSWebCamManager *manager)
{
460
	if (bctbx_list_size(smCameras) == 0) {
461 462
		ms_warning("[MSWinRTCap] No camera detected!");
	}
463 464
	for (size_t i = 0; i < bctbx_list_size(smCameras); i++) {
		ms_web_cam_manager_prepend_cam(manager, (MSWebCam *)bctbx_list_nth_data(smCameras, (int)i));
465
	}
466
	bctbx_list_free(smCameras);
467
	smCameras = NULL;
468 469
}

470
void MSWinRTCap::detectCameras(MSWebCamManager *manager, MSWebCamDesc *desc)
471
{
Ghislain MARY's avatar
Ghislain MARY committed
472 473 474 475 476 477
	HANDLE eventCompleted = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
	if (!eventCompleted) {
		ms_error("[MSWinRTCap] Could not create camera detection event [%i]", GetLastError());
		return;
	}
	IAsyncOperation<DeviceInformationCollection^>^ enumOperation = DeviceInformation::FindAllAsync(DeviceClass::VideoCapture);
478 479 480
	enumOperation->Completed = ref new AsyncOperationCompletedHandler<DeviceInformationCollection^>(
		[manager, desc, eventCompleted](IAsyncOperation<DeviceInformationCollection^>^ asyncOperation, Windows::Foundation::AsyncStatus asyncStatus) {
		if (asyncStatus == Windows::Foundation::AsyncStatus::Completed) {
Ghislain MARY's avatar
Ghislain MARY committed
481 482 483 484 485 486 487
			DeviceInformationCollection^ DeviceInfoCollection = asyncOperation->GetResults();
			if ((DeviceInfoCollection == nullptr) || (DeviceInfoCollection->Size == 0)) {
				ms_error("[MSWinRTCap] No webcam found");
			}
			else {
				try {
					for (unsigned int i = 0; i < DeviceInfoCollection->Size; i++) {
488
						addCamera(manager, desc, DeviceInfoCollection->GetAt(i));
Ghislain MARY's avatar
Ghislain MARY committed
489
					}
490
					registerCameras(manager);
Ghislain MARY's avatar
Ghislain MARY committed
491 492 493 494 495 496 497 498 499 500 501
				}
				catch (Platform::Exception^ e) {
					ms_error("[MSWinRTCap] Error of webcam detection");
				}
			}
		} else {
			ms_error("[MSWinRTCap] Cannot enumerate webcams");
		}
		SetEvent(eventCompleted);
	});
	WaitForSingleObjectEx(eventCompleted, INFINITE, FALSE);
502
}