16#include <mfreadwrite.h>
24 IMFMediaSource *device;
25 IMFSourceReader *reader;
32static HRESULT enumerate_devices_and_print(
void) {
33 IMFAttributes *attr = NULL;
34 IMFActivate **devices = NULL;
39 hr = MFCreateAttributes(&attr, 1);
46 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
53 hr = MFEnumDeviceSources(attr, &devices, &count);
59 log_info(
"Found %d video capture device(s):", count);
60 for (UINT32 i = 0; i < count; i++) {
61 LPWSTR friendlyName = NULL;
62 UINT32 nameLength = 0;
64 hr = IMFActivate_GetAllocatedString(devices[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength);
65 if (SUCCEEDED(hr) && friendlyName) {
67 int len = WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, NULL, 0, NULL, NULL);
70 if (mbName && WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, mbName, len, NULL, NULL)) {
71 log_info(
" Device %d: %s", i, mbName);
75 CoTaskMemFree(friendlyName);
77 log_info(
" Device %d: <Unknown Name>", i);
83 for (UINT32 i = 0; i < count; i++) {
85 IMFActivate_Release(devices[i]);
88 CoTaskMemFree((
void *)devices);
91 IMFAttributes_Release(attr);
98 log_info(
"Opening Windows webcam with Media Foundation, device index %d", device_index);
108 cam->mf_initialized = FALSE;
109 cam->com_initialized = FALSE;
112 IMFAttributes *attr = NULL;
113 IMFActivate **devices = NULL;
118 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
120 cam->com_initialized = TRUE;
121 }
else if (hr != RPC_E_CHANGED_MODE) {
127 hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
132 cam->mf_initialized = TRUE;
135 enumerate_devices_and_print();
138 hr = MFCreateAttributes(&attr, 1);
145 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
152 hr = MFEnumDeviceSources(attr, &devices, &count);
164 if (device_index >= count) {
171 hr = IMFActivate_ActivateObject(devices[device_index], &IID_IMFMediaSource, (
void **)&cam->device);
172 log_info(
"IMFActivate_ActivateObject returned: 0x%08x", hr);
175 log_error(
"CRITICAL: Failed to activate MF device: 0x%08x", hr);
176 log_error(
" 0x80070005 = E_ACCESSDENIED (device in use)");
177 log_error(
" 0xc00d3704 = Device already in use");
178 log_error(
" 0xc00d3e85 = MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED");
185 IMFAttributes *readerAttrs = NULL;
186 hr = MFCreateAttributes(&readerAttrs, 1);
188 log_error(
"Failed to create reader attributes: 0x%08x", hr);
195 hr = IMFAttributes_SetUINT32(readerAttrs, &MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
197 log_warn(
"Failed to set advanced video processing attribute: 0x%08x", hr);
200 hr = MFCreateSourceReaderFromMediaSource(cam->device, readerAttrs, &cam->reader);
201 log_info(
"MFCreateSourceReaderFromMediaSource returned: 0x%08x, readerAttrs=%p", hr, (
void *)readerAttrs);
204 IMFAttributes_Release(readerAttrs);
208 log_error(
"CRITICAL: Failed to create MF source reader: 0x%08x", hr);
214 hr = IMFSourceReader_SetStreamSelection(cam->reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);
215 log_info(
"SetStreamSelection (deselect all) returned: 0x%08x", hr);
217 log_warn(
"Failed to deselect all streams: 0x%08x", hr);
220 hr = IMFSourceReader_SetStreamSelection(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE);
221 log_info(
"SetStreamSelection (select video) returned: 0x%08x", hr);
223 log_error(
"Failed to select video stream: 0x%08x", hr);
230 IMFMediaType *rgbType = NULL;
231 hr = MFCreateMediaType(&rgbType);
233 IMFMediaType_SetGUID(rgbType, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
234 IMFMediaType_SetGUID(rgbType, &MF_MT_SUBTYPE, &MFVideoFormat_RGB32);
238 UINT64 frameSize = ((UINT64)640 << 32) | (UINT64)480;
239 hr = IMFMediaType_SetUINT64(rgbType, &MF_MT_FRAME_SIZE, frameSize);
241 log_warn(
"Could not set frame size to 640x480: 0x%08x", hr);
245 hr = IMFSourceReader_SetCurrentMediaType(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, rgbType);
246 IMFMediaType_Release(rgbType);
249 log_info(
"Successfully requested RGB32 output format at 640x480");
251 log_warn(
"Could not set RGB32 format: 0x%08x, will use native format", hr);
257 IMFMediaType *currentType = NULL;
258 hr = IMFSourceReader_GetCurrentMediaType(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, ¤tType);
260 UINT64 frameSize = 0;
261 hr = IMFMediaType_GetUINT64(currentType, &MF_MT_FRAME_SIZE, &frameSize);
263 cam->width = (int)(frameSize >> 32);
264 cam->height = (int)(frameSize & 0xFFFFFFFF);
265 log_info(
"Media Foundation webcam opened: %dx%d", cam->width, cam->height);
270 log_warn(
"Could not get frame size, using default 640x480");
272 IMFMediaType_Release(currentType);
276 log_warn(
"Could not get media type, using default 640x480");
279 DWORD streamIndex, flags;
281 IMFSample *sample = NULL;
283 hr = IMFSourceReader_ReadSample(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
285 &streamIndex, &flags, ×tamp, &sample);
288 log_error(
"CRITICAL: Failed to read test frame during initialization: 0x%08x", hr);
295 IMFSample_Release(sample);
300 for (UINT32 i = 0; i < count; i++) {
302 IMFActivate_Release(devices[i]);
305 CoTaskMemFree((
void *)devices);
309 IMFAttributes_Release(attr);
319 for (UINT32 i = 0; i < count; i++) {
321 IMFActivate_Release(devices[i]);
324 CoTaskMemFree((
void *)devices);
327 IMFAttributes_Release(attr);
330 IMFSourceReader_Release(cam->reader);
333 IMFMediaSource_Release(cam->device);
335 if (cam->mf_initialized) {
338 if (cam->com_initialized) {
348 if (ctx && ctx->reader) {
351 HRESULT hr = IMFSourceReader_Flush(ctx->reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS);
353 log_warn(
"IMFSourceReader_Flush failed: 0x%08lx", hr);
355 log_debug(
"Flushed webcam source reader");
365 IMFSourceReader_Release(ctx->reader);
369 IMFMediaSource_Release(ctx->device);
372 if (ctx->mf_initialized) {
374 ctx->mf_initialized = FALSE;
376 if (ctx->com_initialized) {
378 ctx->com_initialized = FALSE;
381 log_debug(
"Windows Media Foundation webcam closed");
386 if (!ctx || !ctx->reader) {
391 IMFSample *sample = NULL;
392 DWORD streamIndex, flags;
396 LARGE_INTEGER freq, start, end;
397 QueryPerformanceFrequency(&freq);
398 QueryPerformanceCounter(&start);
402 hr = IMFSourceReader_ReadSample(ctx->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
404 &streamIndex, &flags, ×tamp, &sample);
406 QueryPerformanceCounter(&end);
407 double elapsed_ms = ((double)(end.QuadPart - start.QuadPart)) * 1000.0 / (
double)freq.QuadPart;
409 char duration_str[32];
411 log_info(
"ReadSample took %s (hr=0x%08x, flags=0x%08x, sample=%p)", duration_str, hr, flags, sample);
414 if (SUCCEEDED(hr) && (flags & MF_SOURCE_READERF_STREAMTICK)) {
415 log_info(
"Received stream tick, no sample yet");
419 if (SUCCEEDED(hr) && (flags & MF_SOURCE_READERF_ENDOFSTREAM)) {
425 log_error(
"CRITICAL: Failed to read MF sample on FIRST attempt: 0x%08x", hr);
426 log_error(
" 0x80070005 = E_ACCESSDENIED (device in use)");
427 log_error(
" 0xc00d3704 = Device already in use");
428 log_error(
" 0xc00d3e85 = MF_E_VIDEO_RECORDING_DEVICE_INVALIDATED");
429 log_error(
" 0x80004005 = E_FAIL (generic failure)");
430 log_error(
" 0xc00d36b2 = MF_E_INVALIDREQUEST");
431 log_error(
" 0xc00d36c4 = MF_E_HW_MFT_FAILED_START_STREAMING");
438 static int null_count = 0;
440 if (null_count <= 10) {
442 if (null_count == 1) {
443 log_info(
"No sample available yet (this is normal during startup)");
445 }
else if (null_count > 50) {
447 log_error(
"Too many consecutive NULL samples (%d) - device likely in use", null_count);
453 IMFMediaBuffer *buffer = NULL;
454 hr = IMFSample_ConvertToContiguousBuffer(sample, &buffer);
456 static int buffer_fail_count = 0;
458 log_debug(
"Failed to get contiguous buffer: 0x%08x (failure #%d)", hr, buffer_fail_count);
460 if (buffer_fail_count > 20) {
461 log_error(
"CRITICAL: Failed to get media buffer %d times - webcam likely in use", buffer_fail_count);
464 IMFSample_Release(sample);
469 BYTE *bufferData = NULL;
470 DWORD bufferLength = 0;
471 hr = IMFMediaBuffer_Lock(buffer, &bufferData, NULL, &bufferLength);
473 static int lock_fail_count = 0;
475 log_debug(
"Failed to lock MF buffer: 0x%08x (failure #%d)", hr, lock_fail_count);
477 if (lock_fail_count > 20) {
478 log_error(
"CRITICAL: Failed to lock media buffer %d times - webcam likely in use", lock_fail_count);
481 IMFMediaBuffer_Release(buffer);
482 IMFSample_Release(sample);
487 UINT32 width = (UINT32)ctx->width;
488 UINT32 height = (UINT32)ctx->height;
492 size_t pixel_count_check = 0;
493 if (checked_size_mul((
size_t)width, (
size_t)height, &pixel_count_check) !=
ASCIICHAT_OK) {
494 log_error(
"webcam_read_context: dimensions overflow: %u x %u", width, height);
495 IMFMediaBuffer_Unlock(buffer);
496 IMFMediaBuffer_Release(buffer);
497 IMFSample_Release(sample);
502 size_t pixel_buffer_size = 0;
503 if (checked_size_mul(pixel_count_check,
sizeof(rgb_pixel_t), &pixel_buffer_size) !=
ASCIICHAT_OK) {
504 log_error(
"webcam_read_context: pixel buffer overflow: %zu pixels", pixel_count_check);
505 IMFMediaBuffer_Unlock(buffer);
506 IMFMediaBuffer_Release(buffer);
507 IMFSample_Release(sample);
513 img->
w = (int)(
unsigned int)width;
514 img->
h = (int)(
unsigned int)height;
522 LARGE_INTEGER copy_start, copy_end;
523 QueryPerformanceCounter(©_start);
525 UINT32 pixel_count = width * height;
526 BYTE *src = bufferData;
527 rgb_pixel_t *dst = img->
pixels;
531 for (i = 0; i + 4 <= pixel_count; i += 4) {
554 for (; i < pixel_count; i++) {
562 QueryPerformanceCounter(©_end);
563 double copy_ms = ((double)(copy_end.QuadPart - copy_start.QuadPart)) * 1000.0 / (
double)freq.QuadPart;
565 char copy_duration_str[32];
567 log_info(
"Pixel copy took %s (%u pixels)", copy_duration_str, pixel_count);
570 IMFMediaBuffer_Unlock(buffer);
571 IMFMediaBuffer_Release(buffer);
572 IMFSample_Release(sample);
578 if (!ctx || !width || !height) {
583 *height = ctx->height;
588 if (!out_devices || !out_count) {
595 IMFAttributes *attr = NULL;
596 IMFActivate **mf_devices = NULL;
600 BOOL com_initialized = FALSE;
601 BOOL mf_initialized = FALSE;
604 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
606 com_initialized = TRUE;
607 }
else if (hr != RPC_E_CHANGED_MODE) {
612 hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
617 mf_initialized = TRUE;
620 hr = MFCreateAttributes(&attr, 1);
628 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
635 hr = MFEnumDeviceSources(attr, &mf_devices, &mf_count);
654 for (UINT32 i = 0; i < mf_count; i++) {
655 devices[i].
index = i;
657 LPWSTR friendlyName = NULL;
658 UINT32 nameLength = 0;
660 hr = IMFActivate_GetAllocatedString(mf_devices[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName,
662 if (SUCCEEDED(hr) && friendlyName) {
664 int len = WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, NULL, 0, NULL, NULL);
670 CoTaskMemFree(friendlyName);
676 *out_devices = devices;
677 *out_count = mf_count;
681 for (UINT32 i = 0; i < mf_count; i++) {
683 IMFActivate_Release(mf_devices[i]);
686 CoTaskMemFree((
void *)mf_devices);
689 IMFAttributes_Release(attr);
691 if (mf_initialized) {
694 if (com_initialized) {
#define SAFE_MALLOC_SIMD(size, cast)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MALLOC(size, cast)
#define SAFE_CALLOC(count, size, cast)
#define SET_ERRNO_SYS(code, context_msg,...)
Set error code with custom message and system error context.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
int format_duration_ms(double milliseconds, char *buffer, size_t buffer_size)
Format milliseconds as human-readable duration string.
asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index)
Initialize webcam context for advanced operations.
asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count)
Enumerate available webcam devices.
void webcam_free_device_list(webcam_device_info_t *devices)
Free device list returned by webcam_list_devices()
void webcam_flush_context(webcam_context_t *ctx)
Flush/interrupt pending read operations on webcam context.
struct webcam_context_t webcam_context_t
Opaque webcam context structure.
asciichat_error_t webcam_get_dimensions(webcam_context_t *ctx, int *width, int *height)
Get webcam frame dimensions.
#define WEBCAM_DEVICE_NAME_MAX
Maximum length of webcam device name.
image_t * webcam_read_context(webcam_context_t *ctx)
Capture a frame from webcam context.
void webcam_cleanup_context(webcam_context_t *ctx)
Clean up webcam context and release resources.
✅ Safe Integer Arithmetic and Overflow Detection
int w
Image width in pixels (must be > 0)
int h
Image height in pixels (must be > 0)
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
Webcam device information structure.
unsigned int index
Device index (use with webcam_init)
⏱️ High-precision timing utilities using sokol_time.h and uthash