mirror of
https://github.com/Harshit-Dhanwalkar/AsciiCam.git
synced 2026-06-09 10:25:12 +02:00
Organise project
This commit is contained in:
parent
c766d23028
commit
02926a2623
13 changed files with 398 additions and 288 deletions
28
Makefile
Normal file
28
Makefile
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -O2 -Iinclude
|
||||
LDFLAGS = -lm
|
||||
|
||||
SRCDIR = src
|
||||
INCDIR = include
|
||||
OBJDIR = obj
|
||||
BINDIR = .
|
||||
|
||||
SOURCES = $(wildcard $(SRCDIR)/*.c)
|
||||
OBJECTS = $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SOURCES))
|
||||
TARGET = $(BINDIR)/webcam_ascii
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJECTS)
|
||||
$(CC) $^ -o $@ $(LDFLAGS)
|
||||
|
||||
$(OBJDIR)/%.o: $(SRCDIR)/%.c | $(OBJDIR)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(OBJDIR):
|
||||
mkdir -p $(OBJDIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(OBJDIR) $(TARGET)
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
# AsciiCam
|
||||
Ascii video output from your webcam in your terminal.
|
||||
|
||||

|
||||

|
||||
|
||||
## TODO
|
||||
|
||||
- [x] Adjust width and height of capturing frame.
|
||||
- [ ] Add feature to record and save it in popular video formats like mp4 and gif.
|
||||
- [ ] Add feature to record and save it in popular video formats like `.mp4`, `.mov` and `.gif`.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 MiB |
BIN
assets/output.gif
Normal file
BIN
assets/output.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
15
include/ascii.h
Normal file
15
include/ascii.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef ASCII_H
|
||||
#define ASCII_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ASCII_CHARS " .:-=+*#%@"
|
||||
|
||||
// Convert YUYV raw data to grayscale
|
||||
void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height);
|
||||
|
||||
// grayscale to ASCII output grid
|
||||
char *grayscale_to_ascii(const uint8_t *gray, int src_w, int src_h,
|
||||
int dst_w, int dst_h);
|
||||
|
||||
#endif
|
||||
30
include/capture.h
Normal file
30
include/capture.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#ifndef CAPTURE_H
|
||||
#define CAPTURE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <linux/videodev2.h>
|
||||
|
||||
typedef struct {
|
||||
int fd;
|
||||
int width;
|
||||
int height;
|
||||
void *buffer;
|
||||
struct v4l2_buffer buf_info;
|
||||
} webcam_t;
|
||||
|
||||
// Initialize webcam
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height);
|
||||
|
||||
// Wait for frame to be ready
|
||||
int webcam_wait_frame(webcam_t *cam, int timeout_ms);
|
||||
|
||||
// Capture frame, dequeue buffer, fill grayscale output buffer
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer);
|
||||
|
||||
// Re‑queue buffer
|
||||
int webcam_requeue_buffer(webcam_t *cam);
|
||||
|
||||
// Stop streaming and clean up resources
|
||||
void webcam_cleanup(webcam_t *cam);
|
||||
|
||||
#endif
|
||||
11
include/timing.h
Normal file
11
include/timing.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#ifndef TIMING_H
|
||||
#define TIMING_H
|
||||
|
||||
#include <time.h>
|
||||
|
||||
// Initialize framerate control
|
||||
void timing_init(int fps);
|
||||
|
||||
void timing_sleep(struct timespec *start_time);
|
||||
|
||||
#endif
|
||||
47
src/ascii.c
Normal file
47
src/ascii.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#include "ascii.h"
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void yuyv_to_gray(const uint8_t *yuyv, uint8_t *gray, int width, int height) {
|
||||
for (int i = 0, j = 0; i < width * height * 2; i += 2, j++) {
|
||||
gray[j] = yuyv[i];
|
||||
}
|
||||
}
|
||||
|
||||
char *grayscale_to_ascii(const uint8_t *gray, int src_w, int src_h,
|
||||
int dst_w, int dst_h) {
|
||||
// Allocate output string: each row has dst_w chars + newline, plus null terminator
|
||||
char *output = malloc(dst_w * dst_h + dst_h + 1);
|
||||
if (!output) return NULL;
|
||||
|
||||
double block_w = (double)src_w / dst_w;
|
||||
double block_h = (double)src_h / dst_h;
|
||||
const char *ascii = ASCII_CHARS;
|
||||
int ascii_len = strlen(ascii);
|
||||
|
||||
int out_idx = 0;
|
||||
for (int y = 0; y < dst_h; y++) {
|
||||
for (int x = 0; x < dst_w; x++) {
|
||||
long total = 0;
|
||||
int count = 0;
|
||||
int y_start = (int)(y * block_h);
|
||||
int y_end = (int)((y + 1) * block_h);
|
||||
int x_start = (int)(x * block_w);
|
||||
int x_end = (int)((x + 1) * block_w);
|
||||
|
||||
for (int sy = y_start; sy < y_end && sy < src_h; sy++) {
|
||||
for (int sx = x_start; sx < x_end && sx < src_w; sx++) {
|
||||
total += gray[sy * src_w + sx];
|
||||
count++;
|
||||
}
|
||||
}
|
||||
unsigned char avg = (count > 0) ? (total / count) : 0;
|
||||
int idx = avg * (ascii_len - 1) / 255;
|
||||
output[out_idx++] = ascii[idx];
|
||||
}
|
||||
output[out_idx++] = '\n';
|
||||
}
|
||||
output[out_idx] = '\0';
|
||||
return output;
|
||||
}
|
||||
133
src/capture.c
Normal file
133
src/capture.c
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "capture.h"
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int webcam_init(webcam_t *cam, const char *device, int width, int height) {
|
||||
// Open device (non‑blocking for select usage)
|
||||
cam->fd = open(device, O_RDWR | O_NONBLOCK);
|
||||
if (cam->fd < 0) return -1;
|
||||
|
||||
// Set format
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = width;
|
||||
fmt.fmt.pix.height = height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Read back actual resolution
|
||||
cam->width = fmt.fmt.pix.width;
|
||||
cam->height = fmt.fmt.pix.height;
|
||||
|
||||
// Request one mmap buffer
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Query buffer info
|
||||
struct v4l2_buffer buf = {0};
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = 0;
|
||||
|
||||
if (ioctl(cam->fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// mmap
|
||||
cam->buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, cam->fd, buf.m.offset);
|
||||
if (cam->buffer == MAP_FAILED) {
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Store buffer info for later munmap and requeue
|
||||
cam->buf_info = buf;
|
||||
|
||||
// Queue the buffer
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Start streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
if (ioctl(cam->fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
munmap(cam->buffer, buf.length);
|
||||
close(cam->fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_wait_frame(webcam_t *cam, int timeout_ms) {
|
||||
fd_set fds;
|
||||
struct timeval tv;
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(cam->fd, &fds);
|
||||
tv.tv_sec = timeout_ms / 1000;
|
||||
tv.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
|
||||
int ret = select(cam->fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (ret <= 0) return -1; // timeout or error
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_capture_frame(webcam_t *cam, uint8_t *gray_buffer) {
|
||||
// Dequeue buffer
|
||||
struct v4l2_buffer buf = cam->buf_info;
|
||||
if (ioctl(cam->fd, VIDIOC_DQBUF, &buf) < 0) return -1;
|
||||
|
||||
// Convert YUYV -> grayscale (Y component)
|
||||
uint8_t *yuyv = (uint8_t *)cam->buffer;
|
||||
for (int i = 0, j = 0; i < cam->width * cam->height * 2; i += 2, j++) {
|
||||
gray_buffer[j] = yuyv[i];
|
||||
}
|
||||
|
||||
// Store updated buffer info for requeue
|
||||
cam->buf_info = buf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int webcam_requeue_buffer(webcam_t *cam) {
|
||||
if (ioctl(cam->fd, VIDIOC_QBUF, &cam->buf_info) < 0) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void webcam_cleanup(webcam_t *cam) {
|
||||
if (cam->fd >= 0) {
|
||||
// Stop streaming
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
ioctl(cam->fd, VIDIOC_STREAMOFF, &type);
|
||||
// Unmap and close
|
||||
if (cam->buffer != MAP_FAILED)
|
||||
munmap(cam->buffer, cam->buf_info.length);
|
||||
close(cam->fd);
|
||||
}
|
||||
cam->fd = -1;
|
||||
cam->buffer = MAP_FAILED;
|
||||
}
|
||||
110
src/main.c
Normal file
110
src/main.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#include "capture.h"
|
||||
#include "ascii.h"
|
||||
#include "timing.h"
|
||||
#include <getopt.h>
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Defaults
|
||||
#define DEFAULT_ASCII_WIDTH 80
|
||||
#define DEFAULT_ASCII_HEIGHT 40
|
||||
#define DEFAULT_CAPTURE_WIDTH 160
|
||||
#define DEFAULT_CAPTURE_HEIGHT 120
|
||||
#define DEFAULT_FPS 20
|
||||
|
||||
volatile sig_atomic_t keep_running = 1;
|
||||
void handle_signal(int sig) { (void)sig; keep_running = 0; }
|
||||
|
||||
void print_usage(char *prog) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-d <device>] [-W <width>] [-H <height>] [-f <fps>]\n",
|
||||
prog);
|
||||
fprintf(stderr, " -d <device> : video device (default: /dev/video0)\n");
|
||||
fprintf(stderr, " -W <width> : ASCII output width (default: %d)\n", DEFAULT_ASCII_WIDTH);
|
||||
fprintf(stderr, " -H <height> : ASCII output height (default: %d)\n", DEFAULT_ASCII_HEIGHT);
|
||||
fprintf(stderr, " -f <fps> : target framerate (default: %d)\n", DEFAULT_FPS);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
signal(SIGINT, handle_signal);
|
||||
|
||||
// Config
|
||||
char *device = "/dev/video0";
|
||||
int ascii_w = DEFAULT_ASCII_WIDTH;
|
||||
int ascii_h = DEFAULT_ASCII_HEIGHT;
|
||||
int fps = DEFAULT_FPS;
|
||||
|
||||
int opt;
|
||||
while ((opt = getopt(argc, argv, "d:W:H:f:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd': device = optarg; break;
|
||||
case 'W': ascii_w = atoi(optarg); if (ascii_w <= 0) ascii_w = DEFAULT_ASCII_WIDTH; break;
|
||||
case 'H': ascii_h = atoi(optarg); if (ascii_h <= 0) ascii_h = DEFAULT_ASCII_HEIGHT; break;
|
||||
case 'f': fps = atoi(optarg); if (fps <= 0) fps = DEFAULT_FPS; break;
|
||||
default: print_usage(argv[0]); return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture resolution (fixed for simplicity, could also be made configurable)
|
||||
int cap_w = DEFAULT_CAPTURE_WIDTH;
|
||||
int cap_h = DEFAULT_CAPTURE_HEIGHT;
|
||||
|
||||
timing_init(fps);
|
||||
|
||||
webcam_t cam = { .fd = -1, .buffer = MAP_FAILED };
|
||||
if (webcam_init(&cam, device, cap_w, cap_h) < 0) {
|
||||
perror("webcam_init");
|
||||
return 1;
|
||||
}
|
||||
printf("Webcam opened: %s, capture resolution %dx%d\n", device, cam.width, cam.height);
|
||||
|
||||
uint8_t *gray = malloc(cam.width * cam.height);
|
||||
if (!gray) {
|
||||
perror("malloc gray");
|
||||
webcam_cleanup(&cam);
|
||||
return 1;
|
||||
}
|
||||
|
||||
printf("Starting ASCII stream (%dx%d), target %d fps. Press Ctrl+C to stop.\n",
|
||||
ascii_w, ascii_h, fps);
|
||||
|
||||
struct timespec start_time;
|
||||
while (keep_running) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
|
||||
if (webcam_wait_frame(&cam, 1000) < 0) {
|
||||
// timeout or error, just continue
|
||||
continue;
|
||||
}
|
||||
|
||||
if (webcam_capture_frame(&cam, gray) < 0) {
|
||||
perror("capture_frame");
|
||||
break;
|
||||
}
|
||||
|
||||
char *ascii_art = grayscale_to_ascii(gray, cam.width, cam.height,
|
||||
ascii_w, ascii_h);
|
||||
if (ascii_art) {
|
||||
printf("\033[2J\033[H"); // clear screen, home cursor
|
||||
fputs(ascii_art, stdout);
|
||||
fflush(stdout);
|
||||
free(ascii_art);
|
||||
}
|
||||
|
||||
if (webcam_requeue_buffer(&cam) < 0) {
|
||||
perror("requeue_buffer");
|
||||
break;
|
||||
}
|
||||
|
||||
timing_sleep(&start_time);
|
||||
}
|
||||
|
||||
printf("\nStopping...\n");
|
||||
free(gray);
|
||||
webcam_cleanup(&cam);
|
||||
return 0;
|
||||
}
|
||||
22
src/timing.c
Normal file
22
src/timing.c
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#include "timing.h"
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
|
||||
static long frame_duration_ns = 0; // nanoseconds per frame
|
||||
|
||||
void timing_init(int fps) {
|
||||
frame_duration_ns = 1000000000L / fps;
|
||||
}
|
||||
|
||||
void timing_sleep(struct timespec *start_time) {
|
||||
struct timespec end_time;
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
|
||||
long elapsed_ns = (end_time.tv_sec - start_time->tv_sec) * 1000000000L +
|
||||
(end_time.tv_nsec - start_time->tv_nsec);
|
||||
long sleep_ns = frame_duration_ns - elapsed_ns;
|
||||
if (sleep_ns > 0) {
|
||||
struct timespec ts = { sleep_ns / 1000000000L, sleep_ns % 1000000000L };
|
||||
while (nanosleep(&ts, &ts) == -1 && errno == EINTR);
|
||||
}
|
||||
}
|
||||
286
webcam.c
286
webcam.c
|
|
@ -1,286 +0,0 @@
|
|||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <linux/videodev2.h>
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Default configuration values
|
||||
#define DEFAULT_ASCII_WIDTH 80
|
||||
#define DEFAULT_ASCII_HEIGHT 40
|
||||
#define DEFAULT_CAPTURE_WIDTH 160
|
||||
#define DEFAULT_CAPTURE_HEIGHT 120
|
||||
#define DEFAULT_FPS 20
|
||||
|
||||
#define ASCII_CHARS " .:-=+*#%@"
|
||||
#define CLEAR_SCREEN "\033[2J\033[H"
|
||||
|
||||
volatile sig_atomic_t keep_running = 1;
|
||||
|
||||
void handle_signal(int sig) { keep_running = 0; }
|
||||
|
||||
void print_usage(char *prog_name) {
|
||||
fprintf(stderr,
|
||||
"Usage: %s [-d <device>] [-W <width>] [-H <height>] [-f <fps>]\n",
|
||||
prog_name);
|
||||
fprintf(stderr, " -d <device> : Video device path (default: /dev/video0)\n");
|
||||
fprintf(stderr, " -W <width> : ASCII output width (default: %d)\n",
|
||||
DEFAULT_ASCII_WIDTH);
|
||||
fprintf(stderr, " -H <height> : ASCII output height (default: %d)\n",
|
||||
DEFAULT_ASCII_HEIGHT);
|
||||
fprintf(stderr, " -f <fps> : Target framerate (default: %d)\n",
|
||||
DEFAULT_FPS);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
signal(SIGINT, handle_signal);
|
||||
|
||||
// Configuration Variables
|
||||
char *device_path = "/dev/video0";
|
||||
int ascii_width = DEFAULT_ASCII_WIDTH;
|
||||
int ascii_height = DEFAULT_ASCII_HEIGHT;
|
||||
int target_fps = DEFAULT_FPS;
|
||||
long frame_duration_ns = 1000000000L / target_fps;
|
||||
int capture_width = DEFAULT_CAPTURE_WIDTH;
|
||||
int capture_height = DEFAULT_CAPTURE_HEIGHT;
|
||||
|
||||
// Resource Tracking for Cleanup
|
||||
int fd = -1;
|
||||
void *buffer = MAP_FAILED;
|
||||
unsigned char *gray_buffer = NULL;
|
||||
struct v4l2_buffer buf = {0};
|
||||
enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
|
||||
// Command-Line Argument Parsing
|
||||
int c;
|
||||
while ((c = getopt(argc, argv, "d:W:H:f:")) != -1) {
|
||||
switch (c) {
|
||||
case 'd':
|
||||
device_path = optarg;
|
||||
break;
|
||||
case 'W':
|
||||
ascii_width = atoi(optarg);
|
||||
if (ascii_width <= 0)
|
||||
ascii_width = DEFAULT_ASCII_WIDTH;
|
||||
break;
|
||||
case 'H':
|
||||
ascii_height = atoi(optarg);
|
||||
if (ascii_height <= 0)
|
||||
ascii_height = DEFAULT_ASCII_HEIGHT;
|
||||
break;
|
||||
case 'f':
|
||||
target_fps = atoi(optarg);
|
||||
if (target_fps <= 0)
|
||||
target_fps = DEFAULT_FPS;
|
||||
frame_duration_ns = 1000000000L / target_fps;
|
||||
break;
|
||||
default:
|
||||
print_usage(argv[0]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// V4L2 Setup
|
||||
fd = open(device_path, O_RDWR | O_NONBLOCK);
|
||||
if (fd < 0) {
|
||||
perror("Error opening video device");
|
||||
goto cleanup;
|
||||
}
|
||||
printf("Webcam opened successfully: %s\n", device_path);
|
||||
|
||||
// Check capabilities (omitted for brevity, assume capture capability)
|
||||
|
||||
// Set format
|
||||
struct v4l2_format fmt = {0};
|
||||
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
fmt.fmt.pix.width = capture_width;
|
||||
fmt.fmt.pix.height = capture_height;
|
||||
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
|
||||
fmt.fmt.pix.field = V4L2_FIELD_NONE;
|
||||
|
||||
if (ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
|
||||
perror("Setting format failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Format Negotiation (Read back the actual accepted size)
|
||||
capture_width = fmt.fmt.pix.width;
|
||||
capture_height = fmt.fmt.pix.height;
|
||||
printf("Capture resolution set to: %dx%d\n", capture_width, capture_height);
|
||||
|
||||
// Request buffers
|
||||
struct v4l2_requestbuffers req = {0};
|
||||
req.count = 1;
|
||||
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
req.memory = V4L2_MEMORY_MMAP;
|
||||
|
||||
if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0) {
|
||||
perror("Requesting buffers failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Map the buffer
|
||||
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
||||
buf.memory = V4L2_MEMORY_MMAP;
|
||||
buf.index = 0;
|
||||
|
||||
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) < 0) {
|
||||
perror("Querying buffer failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
buffer = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
|
||||
buf.m.offset);
|
||||
if (buffer == MAP_FAILED) {
|
||||
perror("Memory mapping failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Queue the buffer
|
||||
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
perror("Queue buffer failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Start streaming
|
||||
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
|
||||
perror("Start streaming failed");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
printf("Starting ASCII webcam stream... Press Ctrl+C to stop\n");
|
||||
|
||||
// Create buffer for grayscale conversion
|
||||
gray_buffer = malloc(capture_width * capture_height);
|
||||
if (gray_buffer == NULL) {
|
||||
perror("Memory allocation failed for gray_buffer");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Main Streaming Loop
|
||||
struct timespec start_time, end_time;
|
||||
|
||||
while (keep_running) {
|
||||
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||
|
||||
fd_set fds;
|
||||
struct timeval tv = {0};
|
||||
|
||||
FD_ZERO(&fds);
|
||||
FD_SET(fd, &fds);
|
||||
tv.tv_sec = 1; // Shorter select timeout
|
||||
|
||||
int r = select(fd + 1, &fds, NULL, NULL, &tv);
|
||||
if (r < 0) {
|
||||
if (errno == EINTR)
|
||||
continue; // Handle interrupted select
|
||||
perror("Select failed");
|
||||
break;
|
||||
}
|
||||
|
||||
if (r == 0) {
|
||||
fprintf(stderr, "Select timeout on frame availability\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Dequeue buffer
|
||||
if (ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
|
||||
perror("Dequeue buffer failed");
|
||||
break;
|
||||
}
|
||||
|
||||
// Convert YUYV to grayscale (Y component only)
|
||||
uint8_t *yuyv_data = (uint8_t *)buffer;
|
||||
for (int i = 0, j = 0; i < capture_width * capture_height * 2;
|
||||
i += 2, j++) {
|
||||
gray_buffer[j] = yuyv_data[i];
|
||||
}
|
||||
|
||||
// Clear screen and move cursor to top-left
|
||||
printf(CLEAR_SCREEN);
|
||||
|
||||
// Averaging Resampling and ASCII Conversion
|
||||
for (int y = 0; y < ascii_height; y++) {
|
||||
for (int x = 0; x < ascii_width; x++) {
|
||||
double block_width = (double)capture_width / ascii_width;
|
||||
double block_height = (double)capture_height / ascii_height;
|
||||
long long total_brightness = 0;
|
||||
int pixel_count = 0;
|
||||
|
||||
// Iterate over the source block that corresponds to one ASCII character
|
||||
for (int src_y = (int)(y * block_height);
|
||||
src_y < (int)((y + 1) * block_height); src_y++) {
|
||||
for (int src_x = (int)(x * block_width);
|
||||
src_x < (int)((x + 1) * block_width); src_x++) {
|
||||
|
||||
// Bounds check
|
||||
if (src_x < capture_width && src_y < capture_height) {
|
||||
int idx = src_y * capture_width + src_x;
|
||||
total_brightness += gray_buffer[idx];
|
||||
pixel_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned char avg_pixel = 0;
|
||||
if (pixel_count > 0) {
|
||||
avg_pixel = total_brightness / pixel_count;
|
||||
}
|
||||
|
||||
// Map brightness (0-255) to ASCII character set
|
||||
int ascii_len = strlen(ASCII_CHARS);
|
||||
int ascii_idx = avg_pixel * (ascii_len - 1) / 255;
|
||||
putchar(ASCII_CHARS[ascii_idx]);
|
||||
}
|
||||
putchar('\n');
|
||||
}
|
||||
fflush(stdout);
|
||||
|
||||
// Re-queue buffer
|
||||
if (ioctl(fd, VIDIOC_QBUF, &buf) < 0) {
|
||||
perror("Re-queue buffer failed");
|
||||
break;
|
||||
}
|
||||
|
||||
// Precise Frame Rate Control
|
||||
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||
long elapsed_ns = (end_time.tv_sec - start_time.tv_sec) * 1000000000L +
|
||||
(end_time.tv_nsec - start_time.tv_nsec);
|
||||
long sleep_ns = frame_duration_ns - elapsed_ns;
|
||||
|
||||
if (sleep_ns > 0) {
|
||||
struct timespec ts = {0, 0};
|
||||
ts.tv_sec = sleep_ns / 1000000000L;
|
||||
ts.tv_nsec = sleep_ns % 1000000000L;
|
||||
// Use nanosleep with loop to handle interruptions
|
||||
while (nanosleep(&ts, &ts) == -1 && errno == EINTR)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
printf("\nStopping...\n");
|
||||
// Stop streaming
|
||||
ioctl(fd, VIDIOC_STREAMOFF, &type);
|
||||
|
||||
// Centralized Cleanup
|
||||
cleanup:
|
||||
if (gray_buffer != NULL) {
|
||||
free(gray_buffer);
|
||||
}
|
||||
if (buffer != MAP_FAILED) {
|
||||
// buf.length is still valid from the initial VIDIOC_QUERYBUF call
|
||||
munmap(buffer, buf.length);
|
||||
}
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
|
||||
return (fd < 0 || buffer == MAP_FAILED || gray_buffer == NULL) ? 1 : 0;
|
||||
}
|
||||
BIN
webcam_ascii
Executable file
BIN
webcam_ascii
Executable file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue