ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
webcam.c
Go to the documentation of this file.
1
8#include <stdio.h>
9
10#include "webcam.h"
11#include "common.h"
12#include "options/options.h"
13#include "video/image.h"
14#include "platform/string.h"
15
16static webcam_context_t *global_webcam_ctx = NULL;
17
18asciichat_error_t webcam_init(unsigned short int webcam_index) {
19 // Check if test pattern mode is enabled
20 if (GET_OPTION(test_pattern)) {
21 log_info("Test pattern mode enabled - not opening real webcam");
22 log_info("Test pattern resolution: 1280x720");
23 return ASCIICHAT_OK;
24 }
25
26#ifdef __linux__
27 log_info("Initializing webcam with V4L2 (Linux)");
28 log_info("Attempting to open webcam with index %d using V4L2 (Linux)...", webcam_index);
29#elif defined(__APPLE__)
30 log_info("Initializing webcam with AVFoundation (macOS)");
31 log_info("Attempting to open webcam with index %d using AVFoundation (macOS)...", webcam_index);
32#elif defined(_WIN32)
33 log_info("Initializing webcam with Media Foundation (Windows)");
34 log_info("Attempting to open webcam with index %d using Media Foundation (Windows)...", webcam_index);
35#else
36 log_info("Initializing webcam with Unknown platform");
37 log_info("Attempting to open webcam with index %d using Unknown platform...", webcam_index);
38#endif
39
40 asciichat_error_t result = webcam_init_context(&global_webcam_ctx, webcam_index);
41 if (result != ASCIICHAT_OK) {
42 SET_ERRNO(result, "Failed to connect to webcam (error code: %d)", result);
43 return result;
44 }
45
46 // Get image dimensions
47 int width, height;
48 if (webcam_get_dimensions(global_webcam_ctx, &width, &height) == ASCIICHAT_OK) {
49 log_info("Webcam opened successfully! Resolution: %dx%d", width, height);
50 } else {
51 SET_ERRNO(ERROR_WEBCAM, "Webcam opened but failed to get dimensions");
52 }
53
54 return result;
55}
56
58 // Check if test pattern mode is enabled
59 if (GET_OPTION(test_pattern)) {
60 // Use cached test pattern for 60 FPS performance
61 // Generate once on first call, reuse for subsequent calls
62 static image_t *cached_pattern = NULL;
63 static int frame_counter = 0;
64
65 if (!cached_pattern) {
66 // Generate a colorful test pattern ONCE on first call
67 cached_pattern = image_new(1280, 720);
68 if (!cached_pattern) {
69 SET_ERRNO(ERROR_MEMORY, "Failed to allocate test pattern frame");
70 return NULL;
71 }
72
73 // Generate a simple color bar pattern (FAST - no per-pixel calculations)
74 for (int y = 0; y < cached_pattern->h; y++) {
75 for (int x = 0; x < cached_pattern->w; x++) {
76 rgb_pixel_t *pixel = &cached_pattern->pixels[y * cached_pattern->w + x];
77
78 // Simple color bars (8 vertical sections)
79 int grid_x = x / 160;
80
81 // Base pattern: color bars (no floating-point math)
82 switch (grid_x) {
83 case 0: // Red
84 pixel->r = 255;
85 pixel->g = 0;
86 pixel->b = 0;
87 break;
88 case 1: // Green
89 pixel->r = 0;
90 pixel->g = 255;
91 pixel->b = 0;
92 break;
93 case 2: // Blue
94 pixel->r = 0;
95 pixel->g = 0;
96 pixel->b = 255;
97 break;
98 case 3: // Yellow
99 pixel->r = 255;
100 pixel->g = 255;
101 pixel->b = 0;
102 break;
103 case 4: // Cyan
104 pixel->r = 0;
105 pixel->g = 255;
106 pixel->b = 255;
107 break;
108 case 5: // Magenta
109 pixel->r = 255;
110 pixel->g = 0;
111 pixel->b = 255;
112 break;
113 case 6: // White
114 pixel->r = 255;
115 pixel->g = 255;
116 pixel->b = 255;
117 break;
118 case 7: // Gray gradient
119 default: {
120 uint8_t gray = (uint8_t)((y * 255) / cached_pattern->h);
121 pixel->r = gray;
122 pixel->g = gray;
123 pixel->b = gray;
124 break;
125 }
126 }
127
128 // Add grid lines for visual separation
129 if (x % 160 == 0 || y % 120 == 0) {
130 pixel->r = 0;
131 pixel->g = 0;
132 pixel->b = 0;
133 }
134 }
135 }
136 }
137
138 frame_counter++;
139
140 // Clone the cached pattern for each call (caller owns and frees it)
141 // This is MUCH faster than regenerating the pattern each time
142 image_t *test_frame = image_new(cached_pattern->w, cached_pattern->h);
143 if (!test_frame) {
144 SET_ERRNO(ERROR_MEMORY, "Failed to allocate test pattern frame");
145 return NULL;
146 }
147
148 // Fast memcpy instead of per-pixel loop
149 memcpy(test_frame->pixels, cached_pattern->pixels,
150 (size_t)cached_pattern->w * cached_pattern->h * sizeof(rgb_pixel_t));
151
152 // Apply horizontal flip if requested (same as real webcam)
153 if (GET_OPTION(webcam_flip)) {
154 for (int y = 0; y < test_frame->h; y++) {
155 for (int x = 0; x < test_frame->w / 2; x++) {
156 rgb_pixel_t temp = test_frame->pixels[y * test_frame->w + x];
157 test_frame->pixels[y * test_frame->w + x] = test_frame->pixels[y * test_frame->w + (test_frame->w - 1 - x)];
158 test_frame->pixels[y * test_frame->w + (test_frame->w - 1 - x)] = temp;
159 }
160 }
161 }
162
163 return test_frame;
164 }
165
166 if (!global_webcam_ctx) {
167 SET_ERRNO(ERROR_WEBCAM, "Webcam not initialized - global_webcam_ctx is NULL");
168 return NULL;
169 }
170
171 image_t *frame = webcam_read_context(global_webcam_ctx);
172
173 if (!frame) {
174 return NULL;
175 }
176
177 // Apply horizontal flip if requested
178 if (GET_OPTION(webcam_flip) && frame->w > 1) {
179 // Flip the image horizontally - optimized for large images
180 // Process entire rows to improve cache locality
181 rgb_pixel_t *left = frame->pixels;
182 rgb_pixel_t *right = frame->pixels + frame->w - 1;
183
184 for (int y = 0; y < frame->h; y++) {
185 rgb_pixel_t *row_left = left;
186 rgb_pixel_t *row_right = right;
187
188 // Swap pixels from both ends moving inward
189 for (int x = 0; x < frame->w / 2; x++) {
190 rgb_pixel_t temp = *row_left;
191 *row_left++ = *row_right;
192 *row_right-- = temp;
193 }
194
195 // Move to next row
196 left += frame->w;
197 right += frame->w;
198 }
199 }
200
201 return frame;
202}
203
204void webcam_cleanup(void) {
205 if (GET_OPTION(test_pattern)) {
206 log_info("Test pattern mode - no webcam resources to release");
207 return;
208 }
209
210 if (global_webcam_ctx) {
211 webcam_cleanup_context(global_webcam_ctx);
212 global_webcam_ctx = NULL;
213 // log_info("Webcam resources released");
214 } else {
215 log_info("Webcam was not opened, nothing to release");
216 }
217}
218
219void webcam_flush(void) {
220 if (GET_OPTION(test_pattern)) {
221 return; // Test pattern doesn't need flushing
222 }
223
224 if (global_webcam_ctx) {
225 webcam_flush_context(global_webcam_ctx);
226 }
227}
228
230 // Platform-specific error messages and troubleshooting help
231#ifdef __linux__
232 safe_fprintf(stderr, "\n");
233
234 if (error_code == ERROR_WEBCAM) {
235 safe_fprintf(stderr, "Webcam initialization failed on Linux.\n\n");
236 safe_fprintf(stderr, "Common solutions:\n");
237 safe_fprintf(stderr, " 1. Check if a camera is connected:\n");
238 safe_fprintf(stderr, " ls /dev/video*\n\n");
239 safe_fprintf(stderr, " 2. If no camera is available, use test pattern mode:\n");
240 safe_fprintf(stderr, " ascii-chat client --test-pattern\n\n");
241 safe_fprintf(stderr, " 3. Install V4L2 drivers if needed:\n");
242 safe_fprintf(stderr, " sudo apt-get install v4l-utils\n");
243 } else if (error_code == ERROR_WEBCAM_PERMISSION) {
244 safe_fprintf(stderr, "Camera permission denied.\n\n");
245 safe_fprintf(stderr, "Fix permissions with:\n");
246 safe_fprintf(stderr, " sudo usermod -a -G video $USER\n");
247 safe_fprintf(stderr, "Then log out and log back in for changes to take effect.\n");
248 } else if (error_code == ERROR_WEBCAM_IN_USE) {
249 safe_fprintf(stderr, "Camera is already in use by another application.\n\n");
250 safe_fprintf(stderr, "Try closing other camera apps or use test pattern mode:\n");
251 safe_fprintf(stderr, " ascii-chat client --test-pattern\n");
252 } else {
253 safe_fprintf(stderr, "Webcam error on Linux.\n\n");
254 safe_fprintf(stderr, "General troubleshooting:\n");
255 safe_fprintf(stderr, "* Check camera: ls /dev/video*\n");
256 safe_fprintf(stderr, "* Check permissions: groups | grep video\n");
257 safe_fprintf(stderr, "* Use test pattern: ascii-chat client --test-pattern\n");
258 }
259 (void)fflush(stderr);
260#elif defined(__APPLE__)
261 (void)error_code;
262 safe_fprintf(stderr, "\n");
263 safe_fprintf(stderr, "On macOS, you may need to grant camera permissions:\n");
264 safe_fprintf(stderr,
265 "* Say \"yes\" to the popup about system camera access that you see when running this program for the "
266 "first time.\n");
268 stderr, "* If you said \"no\" to the popup, go to System Preferences > Security & Privacy > Privacy > Camera.\n");
269 safe_fprintf(stderr,
270 " Now flip the switch next to your terminal application in that privacy list to allow ascii-chat to "
271 "access your camera.\n");
272 safe_fprintf(stderr, " Then just run this program again.\n");
273 (void)fflush(stderr);
274#elif defined(_WIN32)
276 // Device is in use by another application - this is a fatal error on Windows
277 safe_fprintf(stderr, "\n");
278 safe_fprintf(stderr, "Webcam is already in use by another application.\n");
279 safe_fprintf(stderr, "Windows allows only one application to access the webcam at a time.\n");
280 safe_fprintf(stderr, "\n");
281 safe_fprintf(stderr, "To use ascii-chat with multiple clients, try these alternatives:\n");
282 safe_fprintf(stderr, " --test-pattern Generate a colorful test pattern instead of using webcam\n");
283 safe_fprintf(stderr, " --file VIDEO.mp4 Use a video file as input (to be implemented)\n");
284 safe_fprintf(stderr, "\n");
285 safe_fprintf(stderr, "Example: ascii-chat client --test-pattern\n");
286 (void)fflush(stderr);
287 } else {
288 // Other webcam errors - general failure
289 safe_fprintf(stderr, "\n");
290 safe_fprintf(stderr, "On Windows, this might be because:\n");
291 safe_fprintf(stderr, "* Camera permissions are not granted\n");
292 safe_fprintf(stderr, "* Camera driver issues\n");
293 safe_fprintf(stderr, "* No webcam device found\n");
294 (void)fflush(stderr);
295 }
296#else
297 // Unknown platform
298 (void)error_code;
299 safe_fprintf(stderr, "\nWebcam initialization failed on unsupported platform.\n");
300 (void)fflush(stderr);
301#endif
302}
303
304// Fallback implementations for unsupported platforms
305#if !defined(__linux__) && !defined(__APPLE__) && !defined(_WIN32)
306asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index) {
307 (void)ctx;
308 (void)device_index;
309 SET_ERRNO(ERROR_WEBCAM, "Webcam platform not supported on this system");
310 return ERROR_WEBCAM;
311}
312
314 (void)ctx;
315 log_warn("Webcam cleanup called on unsupported platform");
316}
317
319 (void)ctx;
320 // No-op on unsupported platforms
321}
322
324 (void)ctx;
325 SET_ERRNO(ERROR_WEBCAM, "Webcam read not supported on this platform");
326 return NULL;
327}
328
330 (void)ctx;
331 (void)width;
332 (void)height;
333 return SET_ERRNO(ERROR_WEBCAM, "Webcam get dimensions not supported on this platform");
334}
335
336asciichat_error_t webcam_list_devices(webcam_device_info_t **out_devices, unsigned int *out_count) {
337 if (out_devices)
338 *out_devices = NULL;
339 if (out_count)
340 *out_count = 0;
341 return SET_ERRNO(ERROR_WEBCAM, "Webcam device enumeration not supported on this platform");
342}
343
345 (void)devices;
346 // No-op on unsupported platforms
347}
348#endif
asciichat_error_t error_code
unsigned char uint8_t
Definition common.h:56
#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_WEBCAM_PERMISSION
Definition error_codes.h:63
@ ERROR_WEBCAM
Definition error_codes.h:61
#define log_warn(...)
Log a WARN message.
#define log_info(...)
Log an INFO message.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
Definition options.h:644
int safe_fprintf(FILE *stream, const char *format,...)
Safe version of fprintf.
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
Definition video/image.c:36
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
asciichat_error_t webcam_init(unsigned short int webcam_index)
Initialize global webcam interface.
Definition webcam.c:18
void webcam_flush_context(webcam_context_t *ctx)
Flush/interrupt pending read operations on webcam context.
Definition webcam.c:318
image_t * webcam_read(void)
Capture a frame from global webcam.
Definition webcam.c:57
void webcam_print_init_error_help(asciichat_error_t error_code)
Print helpful error diagnostics for webcam initialization failures.
Definition webcam.c:229
void webcam_flush(void)
Flush/interrupt any pending webcam read operations.
Definition webcam.c:219
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
void webcam_cleanup(void)
Clean up global webcam interface.
Definition webcam.c:204
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
⚙️ Command-line options parsing and configuration management for ascii-chat
Platform-independent safe string functions.
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
Image Data Structures and Operations.