ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
webcam_mediafoundation.c
Go to the documentation of this file.
1
7#ifdef _WIN32
8
9#define COBJMACROS
10#include "video/webcam/webcam.h"
11#include "common.h"
12#include "util/time.h"
13#include "util/overflow.h"
14#include <mfapi.h>
15#include <mfidl.h>
16#include <mfreadwrite.h>
17#include <mferror.h>
18
19// Windows Media Foundation webcam implementation
20// Note: MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING and
21// MF_SOURCE_READER_ENABLE_VIDEO_PROCESSING are defined in mfreadwrite.h
22
23struct webcam_context_t {
24 IMFMediaSource *device;
25 IMFSourceReader *reader;
26 int width;
27 int height;
28 BOOL mf_initialized;
29 BOOL com_initialized;
30};
31
32static HRESULT enumerate_devices_and_print(void) {
33 IMFAttributes *attr = NULL;
34 IMFActivate **devices = NULL;
35 UINT32 count = 0;
36 HRESULT hr;
37
38 // Create attribute store for device enumeration
39 hr = MFCreateAttributes(&attr, 1);
40 if (FAILED(hr)) {
41 return SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to create MF attributes: 0x%08x", hr);
42 }
43
44 // Set the device type to video capture
45 hr =
46 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
47 if (FAILED(hr)) {
48 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to set MF device type: 0x%08x", hr);
49 goto cleanup;
50 }
51
52 // Enumerate video capture devices
53 hr = MFEnumDeviceSources(attr, &devices, &count);
54 if (FAILED(hr)) {
55 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to enumerate MF devices: 0x%08x", hr);
56 goto cleanup;
57 }
58
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;
63
64 hr = IMFActivate_GetAllocatedString(devices[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName, &nameLength);
65 if (SUCCEEDED(hr) && friendlyName) {
66 // Convert wide string to multibyte for logging
67 int len = WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, NULL, 0, NULL, NULL);
68 if (len > 0) {
69 char *mbName = SAFE_MALLOC((size_t)len, void *);
70 if (mbName && WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, mbName, len, NULL, NULL)) {
71 log_info(" Device %d: %s", i, mbName);
72 }
73 SAFE_FREE(mbName);
74 }
75 CoTaskMemFree(friendlyName);
76 } else {
77 log_info(" Device %d: <Unknown Name>", i);
78 }
79 }
80
81cleanup:
82 if (devices) {
83 for (UINT32 i = 0; i < count; i++) {
84 if (devices[i]) {
85 IMFActivate_Release(devices[i]);
86 }
87 }
88 CoTaskMemFree((void *)devices);
89 }
90 if (attr) {
91 IMFAttributes_Release(attr);
92 }
93
94 return hr;
95}
96
97asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index) {
98 log_info("Opening Windows webcam with Media Foundation, device index %d", device_index);
99
100 webcam_context_t *cam;
102
103 // Initialize all fields
104 cam->device = NULL;
105 cam->reader = NULL;
106 cam->width = 0;
107 cam->height = 0;
108 cam->mf_initialized = FALSE;
109 cam->com_initialized = FALSE;
110
111 HRESULT hr;
112 IMFAttributes *attr = NULL;
113 IMFActivate **devices = NULL;
114 UINT32 count = 0;
115 int result = -1;
116
117 // Initialize COM
118 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
119 if (SUCCEEDED(hr)) {
120 cam->com_initialized = TRUE;
121 } else if (hr != RPC_E_CHANGED_MODE) {
122 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to initialize COM: 0x%08x", hr);
123 goto error;
124 }
125
126 // Initialize Media Foundation
127 hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
128 if (FAILED(hr)) {
129 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to startup Media Foundation: 0x%08x", hr);
130 goto error;
131 }
132 cam->mf_initialized = TRUE;
133
134 // Enumerate and print all devices
135 enumerate_devices_and_print();
136
137 // Create attribute store for device enumeration
138 hr = MFCreateAttributes(&attr, 1);
139 if (FAILED(hr)) {
140 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to create MF attributes: 0x%08x", hr);
141 goto error;
142 }
143
144 hr =
145 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
146 if (FAILED(hr)) {
147 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to set MF device type: 0x%08x", hr);
148 goto error;
149 }
150
151 // Enumerate devices
152 hr = MFEnumDeviceSources(attr, &devices, &count);
153 if (FAILED(hr)) {
154 SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to enumerate MF devices: 0x%08x", hr);
155 goto error;
156 }
157
158 if (count == 0) {
159 SET_ERRNO(ERROR_WEBCAM, "No video capture devices found");
160 hr = E_FAIL;
161 goto error;
162 }
163
164 if (device_index >= count) {
165 SET_ERRNO(ERROR_WEBCAM, "Device index %d out of range (0-%d)", device_index, count - 1);
166 hr = E_FAIL;
167 goto error;
168 }
169
170 // Create media source for the specified device
171 hr = IMFActivate_ActivateObject(devices[device_index], &IID_IMFMediaSource, (void **)&cam->device);
172 log_info("IMFActivate_ActivateObject returned: 0x%08x", hr);
173
174 if (FAILED(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");
179 result = ERROR_WEBCAM_IN_USE;
180 goto error;
181 }
182
183 // Create source reader with GPU-accelerated video processing (Windows 8+)
184 // This enables automatic YUV->RGB conversion with hardware acceleration
185 IMFAttributes *readerAttrs = NULL;
186 hr = MFCreateAttributes(&readerAttrs, 1);
187 if (FAILED(hr)) {
188 log_error("Failed to create reader attributes: 0x%08x", hr);
189 result = ERROR_WEBCAM;
190 goto error;
191 }
192
193 // Enable advanced video processing for GPU-accelerated YUV->RGB conversion (Windows 8+)
194 // This is the recommended attribute for webcam capture with format conversion
195 hr = IMFAttributes_SetUINT32(readerAttrs, &MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, TRUE);
196 if (FAILED(hr)) {
197 log_warn("Failed to set advanced video processing attribute: 0x%08x", hr);
198 }
199
200 hr = MFCreateSourceReaderFromMediaSource(cam->device, readerAttrs, &cam->reader);
201 log_info("MFCreateSourceReaderFromMediaSource returned: 0x%08x, readerAttrs=%p", hr, (void *)readerAttrs);
202
203 if (readerAttrs) {
204 IMFAttributes_Release(readerAttrs);
205 }
206
207 if (FAILED(hr)) {
208 log_error("CRITICAL: Failed to create MF source reader: 0x%08x", hr);
209 result = ERROR_WEBCAM_IN_USE;
210 goto error;
211 }
212
213 // IMPORTANT: Select the video stream first before configuring
214 hr = IMFSourceReader_SetStreamSelection(cam->reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS, FALSE);
215 log_info("SetStreamSelection (deselect all) returned: 0x%08x", hr);
216 if (FAILED(hr)) {
217 log_warn("Failed to deselect all streams: 0x%08x", hr);
218 }
219
220 hr = IMFSourceReader_SetStreamSelection(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, TRUE);
221 log_info("SetStreamSelection (select video) returned: 0x%08x", hr);
222 if (FAILED(hr)) {
223 log_error("Failed to select video stream: 0x%08x", hr);
224 goto error;
225 }
226
227 // Request RGB32 output format (BGRA) at 640x480 resolution
228 // Combined with MF_SOURCE_READER_ENABLE_ADVANCED_VIDEO_PROCESSING, this enables
229 // GPU-accelerated YUV->RGB conversion
230 IMFMediaType *rgbType = NULL;
231 hr = MFCreateMediaType(&rgbType);
232 if (SUCCEEDED(hr)) {
233 IMFMediaType_SetGUID(rgbType, &MF_MT_MAJOR_TYPE, &MFMediaType_Video);
234 IMFMediaType_SetGUID(rgbType, &MF_MT_SUBTYPE, &MFVideoFormat_RGB32);
235
236 // Request 640x480 resolution (we only need 480x270 max for ascii-chat)
237 // This dramatically reduces pixel copy overhead (307,200 vs 8,294,400 pixels)
238 UINT64 frameSize = ((UINT64)640 << 32) | (UINT64)480;
239 hr = IMFMediaType_SetUINT64(rgbType, &MF_MT_FRAME_SIZE, frameSize);
240 if (FAILED(hr)) {
241 log_warn("Could not set frame size to 640x480: 0x%08x", hr);
242 }
243
244 // Use partial type - let MF fill in other details
245 hr = IMFSourceReader_SetCurrentMediaType(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, NULL, rgbType);
246 IMFMediaType_Release(rgbType);
247
248 if (SUCCEEDED(hr)) {
249 log_info("Successfully requested RGB32 output format at 640x480");
250 } else {
251 log_warn("Could not set RGB32 format: 0x%08x, will use native format", hr);
252 // Don't fail - just use whatever format the camera provides
253 }
254 }
255
256 // Get actual media type and dimensions
257 IMFMediaType *currentType = NULL;
258 hr = IMFSourceReader_GetCurrentMediaType(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM, &currentType);
259 if (SUCCEEDED(hr)) {
260 UINT64 frameSize = 0;
261 hr = IMFMediaType_GetUINT64(currentType, &MF_MT_FRAME_SIZE, &frameSize);
262 if (SUCCEEDED(hr)) {
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);
266 } else {
267 // Default resolution if we can't get the actual size
268 cam->width = 640;
269 cam->height = 480;
270 log_warn("Could not get frame size, using default 640x480");
271 }
272 IMFMediaType_Release(currentType);
273 } else {
274 cam->width = 640;
275 cam->height = 480;
276 log_warn("Could not get media type, using default 640x480");
277 }
278
279 DWORD streamIndex, flags;
280 LONGLONG timestamp;
281 IMFSample *sample = NULL;
282
283 hr = IMFSourceReader_ReadSample(cam->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
284 0, // Regular synchronous read
285 &streamIndex, &flags, &timestamp, &sample);
286
287 if (FAILED(hr)) {
288 log_error("CRITICAL: Failed to read test frame during initialization: 0x%08x", hr);
289 result = ERROR_WEBCAM_IN_USE;
290 goto error;
291 }
292
293 // Clean up the test sample if we got one
294 if (sample) {
295 IMFSample_Release(sample);
296 }
297
298 // Cleanup enumeration resources on success
299 if (devices) {
300 for (UINT32 i = 0; i < count; i++) {
301 if (devices[i]) {
302 IMFActivate_Release(devices[i]);
303 }
304 }
305 CoTaskMemFree((void *)devices);
306 devices = NULL;
307 }
308 if (attr) {
309 IMFAttributes_Release(attr);
310 attr = NULL;
311 }
312
313 *ctx = cam;
314 return ASCIICHAT_OK;
315
316error:
317 // Only cleanup devices and attr if they haven't been cleaned up yet
318 if (devices) {
319 for (UINT32 i = 0; i < count; i++) {
320 if (devices[i]) {
321 IMFActivate_Release(devices[i]);
322 }
323 }
324 CoTaskMemFree((void *)devices);
325 }
326 if (attr) {
327 IMFAttributes_Release(attr);
328 }
329 if (cam->reader) {
330 IMFSourceReader_Release(cam->reader);
331 }
332 if (cam->device) {
333 IMFMediaSource_Release(cam->device);
334 }
335 if (cam->mf_initialized) {
336 MFShutdown();
337 }
338 if (cam->com_initialized) {
339 CoUninitialize();
340 }
341 if (cam) {
342 SAFE_FREE(cam);
343 }
344 return result;
345}
346
348 if (ctx && ctx->reader) {
349 // Flush to cancel any pending ReadSample operations
350 // This interrupts blocking synchronous reads in other threads
351 HRESULT hr = IMFSourceReader_Flush(ctx->reader, (DWORD)MF_SOURCE_READER_ALL_STREAMS);
352 if (FAILED(hr)) {
353 log_warn("IMFSourceReader_Flush failed: 0x%08lx", hr);
354 } else {
355 log_debug("Flushed webcam source reader");
356 }
357 }
358}
359
361 if (ctx) {
362 if (ctx->reader) {
363 // Flush before release to ensure no pending operations
365 IMFSourceReader_Release(ctx->reader);
366 ctx->reader = NULL;
367 }
368 if (ctx->device) {
369 IMFMediaSource_Release(ctx->device);
370 ctx->device = NULL;
371 }
372 if (ctx->mf_initialized) {
373 MFShutdown();
374 ctx->mf_initialized = FALSE;
375 }
376 if (ctx->com_initialized) {
377 CoUninitialize();
378 ctx->com_initialized = FALSE;
379 }
380 SAFE_FREE(ctx);
381 log_debug("Windows Media Foundation webcam closed");
382 }
383}
384
386 if (!ctx || !ctx->reader) {
387 return NULL;
388 }
389
390 HRESULT hr;
391 IMFSample *sample = NULL;
392 DWORD streamIndex, flags;
393 LONGLONG timestamp;
394
395 // Timing diagnostic: measure ReadSample duration
396 LARGE_INTEGER freq, start, end;
397 QueryPerformanceFrequency(&freq);
398 QueryPerformanceCounter(&start);
399
400 // Read a sample from the source reader
401 // Use 0 for synchronous blocking read (DRAIN flag was wrong - that's for EOF)
402 hr = IMFSourceReader_ReadSample(ctx->reader, (DWORD)MF_SOURCE_READER_FIRST_VIDEO_STREAM,
403 0, // Regular synchronous read
404 &streamIndex, &flags, &timestamp, &sample);
405
406 QueryPerformanceCounter(&end);
407 double elapsed_ms = ((double)(end.QuadPart - start.QuadPart)) * 1000.0 / (double)freq.QuadPart;
408
409 char duration_str[32];
410 format_duration_ms(elapsed_ms, duration_str, sizeof(duration_str));
411 log_info("ReadSample took %s (hr=0x%08x, flags=0x%08x, sample=%p)", duration_str, hr, flags, sample);
412
413 // Check for stream tick or other non-data flags
414 if (SUCCEEDED(hr) && (flags & MF_SOURCE_READERF_STREAMTICK)) {
415 log_info("Received stream tick, no sample yet");
416 return NULL;
417 }
418
419 if (SUCCEEDED(hr) && (flags & MF_SOURCE_READERF_ENDOFSTREAM)) {
420 log_warn("End of stream reached");
421 return NULL;
422 }
423
424 if (FAILED(hr)) {
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");
432
433 // Return NULL - device is likely in use
434 return NULL;
435 }
436
437 if (!sample) {
438 static int null_count = 0;
439 null_count++;
440 if (null_count <= 10) {
441 // This is normal when using DRAIN flag - it means no sample ready yet
442 if (null_count == 1) {
443 log_info("No sample available yet (this is normal during startup)");
444 }
445 } else if (null_count > 50) {
446 // Too many consecutive NULL samples - likely exclusive access issue
447 log_error("Too many consecutive NULL samples (%d) - device likely in use", null_count);
448 }
449 return NULL;
450 }
451
452 // Get the media buffer from the sample
453 IMFMediaBuffer *buffer = NULL;
454 hr = IMFSample_ConvertToContiguousBuffer(sample, &buffer);
455 if (FAILED(hr)) {
456 static int buffer_fail_count = 0;
457 buffer_fail_count++;
458 log_debug("Failed to get contiguous buffer: 0x%08x (failure #%d)", hr, buffer_fail_count);
459
460 if (buffer_fail_count > 20) {
461 log_error("CRITICAL: Failed to get media buffer %d times - webcam likely in use", buffer_fail_count);
462 }
463
464 IMFSample_Release(sample);
465 return NULL;
466 }
467
468 // Lock the buffer and get the data
469 BYTE *bufferData = NULL;
470 DWORD bufferLength = 0;
471 hr = IMFMediaBuffer_Lock(buffer, &bufferData, NULL, &bufferLength);
472 if (FAILED(hr)) {
473 static int lock_fail_count = 0;
474 lock_fail_count++;
475 log_debug("Failed to lock MF buffer: 0x%08x (failure #%d)", hr, lock_fail_count);
476
477 if (lock_fail_count > 20) {
478 log_error("CRITICAL: Failed to lock media buffer %d times - webcam likely in use", lock_fail_count);
479 }
480
481 IMFMediaBuffer_Release(buffer);
482 IMFSample_Release(sample);
483 return NULL;
484 }
485
486 // Use cached frame dimensions from context (no need to query media type every frame)
487 UINT32 width = (UINT32)ctx->width;
488 UINT32 height = (UINT32)ctx->height;
489
490 // CRITICAL: Check for integer overflow before multiplication (prevents heap corruption)
491 // Check pixel count overflow
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);
498 return NULL;
499 }
500
501 // Check pixel buffer size overflow
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);
508 return NULL;
509 }
510
511 // Create image_t structure
512 image_t *img = SAFE_MALLOC(sizeof(image_t), image_t *);
513 img->w = (int)(unsigned int)width;
514 img->h = (int)(unsigned int)height;
515 // Use SIMD-aligned allocation for optimal NEON/AVX performance with vld3q_u8
516 img->pixels = SAFE_MALLOC_SIMD(pixel_buffer_size, rgb_pixel_t *);
517
518 // Copy RGB32 data (BGRA order in Media Foundation)
519 // Media Foundation converts YUV->RGB32 via GPU-accelerated pipeline
520 // RGB32 format is 4 bytes per pixel: B, G, R, A (we ignore alpha)
521
522 LARGE_INTEGER copy_start, copy_end;
523 QueryPerformanceCounter(&copy_start);
524
525 UINT32 pixel_count = width * height;
526 BYTE *src = bufferData;
527 rgb_pixel_t *dst = img->pixels;
528
529 // Optimized pixel copy - process 4 pixels at a time
530 UINT32 i;
531 for (i = 0; i + 4 <= pixel_count; i += 4) {
532 // Pixel 0
533 dst[0].b = src[0];
534 dst[0].g = src[1];
535 dst[0].r = src[2];
536 // Pixel 1
537 dst[1].b = src[4];
538 dst[1].g = src[5];
539 dst[1].r = src[6];
540 // Pixel 2
541 dst[2].b = src[8];
542 dst[2].g = src[9];
543 dst[2].r = src[10];
544 // Pixel 3
545 dst[3].b = src[12];
546 dst[3].g = src[13];
547 dst[3].r = src[14];
548
549 src += 16; // 4 pixels * 4 bytes
550 dst += 4;
551 }
552
553 // Handle remaining pixels
554 for (; i < pixel_count; i++) {
555 dst->b = src[0];
556 dst->g = src[1];
557 dst->r = src[2];
558 src += 4;
559 dst++;
560 }
561
562 QueryPerformanceCounter(&copy_end);
563 double copy_ms = ((double)(copy_end.QuadPart - copy_start.QuadPart)) * 1000.0 / (double)freq.QuadPart;
564
565 char copy_duration_str[32];
566 format_duration_ms(copy_ms, copy_duration_str, sizeof(copy_duration_str));
567 log_info("Pixel copy took %s (%u pixels)", copy_duration_str, pixel_count);
568
569 // Unlock and cleanup
570 IMFMediaBuffer_Unlock(buffer);
571 IMFMediaBuffer_Release(buffer);
572 IMFSample_Release(sample);
573
574 return img;
575}
576
577asciichat_error_t webcam_get_dimensions(webcam_context_t *ctx, int *width, int *height) {
578 if (!ctx || !width || !height) {
579 return SET_ERRNO(ERROR_INVALID_PARAM, "webcam_get_dimensions: invalid parameters");
580 }
581
582 *width = ctx->width;
583 *height = ctx->height;
584 return ASCIICHAT_OK;
585}
586
587asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count) {
588 if (!out_devices || !out_count) {
589 return SET_ERRNO(ERROR_INVALID_PARAM, "webcam_list_devices: invalid parameters");
590 }
591
592 *out_devices = NULL;
593 *out_count = 0;
594
595 IMFAttributes *attr = NULL;
596 IMFActivate **mf_devices = NULL;
597 UINT32 mf_count = 0;
598 HRESULT hr;
600 BOOL com_initialized = FALSE;
601 BOOL mf_initialized = FALSE;
602
603 // Initialize COM
604 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
605 if (SUCCEEDED(hr)) {
606 com_initialized = TRUE;
607 } else if (hr != RPC_E_CHANGED_MODE) {
608 return SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to initialize COM: 0x%08x", hr);
609 }
610
611 // Initialize Media Foundation
612 hr = MFStartup(MF_VERSION, MFSTARTUP_NOSOCKET);
613 if (FAILED(hr)) {
614 result = SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to startup Media Foundation: 0x%08x", hr);
615 goto cleanup;
616 }
617 mf_initialized = TRUE;
618
619 // Create attribute store for device enumeration
620 hr = MFCreateAttributes(&attr, 1);
621 if (FAILED(hr)) {
622 result = SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to create MF attributes: 0x%08x", hr);
623 goto cleanup;
624 }
625
626 // Set the device type to video capture
627 hr =
628 IMFAttributes_SetGUID(attr, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, &MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
629 if (FAILED(hr)) {
630 result = SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to set MF device type: 0x%08x", hr);
631 goto cleanup;
632 }
633
634 // Enumerate video capture devices
635 hr = MFEnumDeviceSources(attr, &mf_devices, &mf_count);
636 if (FAILED(hr)) {
637 result = SET_ERRNO_SYS(ERROR_WEBCAM, "Failed to enumerate MF devices: 0x%08x", hr);
638 goto cleanup;
639 }
640
641 if (mf_count == 0) {
642 // No devices found - not an error, just return empty list
643 goto cleanup;
644 }
645
646 // Allocate output array
648 if (!devices) {
649 result = SET_ERRNO(ERROR_MEMORY, "Failed to allocate device info array");
650 goto cleanup;
651 }
652
653 // Populate device info
654 for (UINT32 i = 0; i < mf_count; i++) {
655 devices[i].index = i;
656
657 LPWSTR friendlyName = NULL;
658 UINT32 nameLength = 0;
659
660 hr = IMFActivate_GetAllocatedString(mf_devices[i], &MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, &friendlyName,
661 &nameLength);
662 if (SUCCEEDED(hr) && friendlyName) {
663 // Convert wide string to UTF-8
664 int len = WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, NULL, 0, NULL, NULL);
665 if (len > 0 && len < WEBCAM_DEVICE_NAME_MAX) {
666 WideCharToMultiByte(CP_UTF8, 0, friendlyName, -1, devices[i].name, WEBCAM_DEVICE_NAME_MAX, NULL, NULL);
667 } else {
668 SAFE_STRNCPY(devices[i].name, "<Unknown>", WEBCAM_DEVICE_NAME_MAX);
669 }
670 CoTaskMemFree(friendlyName);
671 } else {
672 SAFE_STRNCPY(devices[i].name, "<Unknown>", WEBCAM_DEVICE_NAME_MAX);
673 }
674 }
675
676 *out_devices = devices;
677 *out_count = mf_count;
678
679cleanup:
680 if (mf_devices) {
681 for (UINT32 i = 0; i < mf_count; i++) {
682 if (mf_devices[i]) {
683 IMFActivate_Release(mf_devices[i]);
684 }
685 }
686 CoTaskMemFree((void *)mf_devices);
687 }
688 if (attr) {
689 IMFAttributes_Release(attr);
690 }
691 if (mf_initialized) {
692 MFShutdown();
693 }
694 if (com_initialized) {
695 CoUninitialize();
696 }
697
698 return result;
699}
700
702 SAFE_FREE(devices);
703}
704
705#endif
#define SAFE_MALLOC_SIMD(size, cast)
Definition common.h:308
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_CALLOC(count, size, cast)
Definition common.h:218
#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)
Definition error_codes.h:46
@ ERROR_WEBCAM_IN_USE
Definition error_codes.h:62
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
@ ERROR_WEBCAM
Definition error_codes.h:61
#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.
Definition time.c:269
asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index)
Initialize webcam context for advanced operations.
Definition webcam.c:306
asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count)
Enumerate available webcam devices.
Definition webcam.c:336
void webcam_free_device_list(webcam_device_info_t *devices)
Free device list returned by webcam_list_devices()
Definition webcam.c:344
void webcam_flush_context(webcam_context_t *ctx)
Flush/interrupt pending read operations on webcam context.
Definition webcam.c:318
struct webcam_context_t webcam_context_t
Opaque webcam context structure.
Definition webcam.h:153
asciichat_error_t webcam_get_dimensions(webcam_context_t *ctx, int *width, int *height)
Get webcam frame dimensions.
Definition webcam.c:329
#define WEBCAM_DEVICE_NAME_MAX
Maximum length of webcam device name.
Definition webcam.h:77
image_t * webcam_read_context(webcam_context_t *ctx)
Capture a frame from webcam context.
Definition webcam.c:323
void webcam_cleanup_context(webcam_context_t *ctx)
Clean up webcam context and release resources.
Definition webcam.c:313
✅ Safe Integer Arithmetic and Overflow Detection
Image structure.
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.
Definition webcam.h:89
unsigned int index
Device index (use with webcam_init)
Definition webcam.h:90
⏱️ High-precision timing utilities using sokol_time.h and uthash