Skip to content

Commit

Permalink
Update new version
Browse files Browse the repository at this point in the history
* Add function to render video from camera directly on a 2D panel
* Add function to generate ambilight effect from camera/HDMI capture input
* Improve code by generating header files and splitting files
* Remove the need to enable audio or 2D graphics at compile time
* Support for Raspberry Pi Zero 2 W
* Update documentation
  • Loading branch information
tom-2015 committed Nov 3, 2021
1 parent 7e9e42b commit ed05e01
Show file tree
Hide file tree
Showing 122 changed files with 5,176 additions and 4,870 deletions.
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,9 @@ Temporary Items
#executable / objects:
*.o
*.elf
ws2812svr
ws2812svr

tests/

.vscode
.github
260 changes: 260 additions & 0 deletions 2D/camera.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
#include <ctype.h>
#include <linux/types.h>
#include <linux/v4l2-common.h>
#include <linux/v4l2-controls.h>
#include <linux/videodev2.h>
#include <linux/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>

#include "../ws2812svr.h"
#include "../jpghelper.h"

//renders camera output (requires USE_JPEG)
//camera <channel>,<camera_device>,<dst_x>,<dst_y>,<dst_width>,<dst_height>,<frame_count>,<delay>
void camera(thread_context* context, char* args) {
char device_name[MAX_VAL_LEN] = { 0 };
int channel = 0, frame_count=0, delay=10;
double dst_x = 0.0, dst_y = 0.0, dst_width = 0.0, dst_height = 0.0;

args = read_channel(args, &channel);

if (is_valid_2D_channel_number(channel)) {
channel_info * led_channel = get_channel(channel);

strcpy(device_name, "/dev/video0");

args = read_str(args, device_name, MAX_VAL_LEN);

dst_width = led_channel->width;
dst_height = led_channel->height;

args = read_double(args, &dst_x);
args = read_double(args, &dst_y);
args = read_double(args, &dst_width);
args = read_double(args, &dst_height);
args = read_int(args, &frame_count);
args = read_int(args, &delay);

if (debug) printf("camera %d\n", channel);


// 1. Open the device
int fd; // A file descriptor to the video device
fd = open(device_name, O_RDWR);
if (fd < 0) {
perror("Failed to open video device.");
close(fd);
return;
}

// 2. Ask the device if it can capture frames
struct v4l2_capability capability;
if (ioctl(fd, VIDIOC_QUERYCAP, &capability) < 0) {
// something went wrong... exit
perror("Failed to get device capabilities, VIDIOC_QUERYCAP");
close(fd);
return;
}

// 3. Set Image format
struct v4l2_format imageFormat;
imageFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
imageFormat.fmt.pix.width = dst_width;
imageFormat.fmt.pix.height = dst_height;
imageFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
imageFormat.fmt.pix.field = V4L2_FIELD_NONE;

// tell the device you are using this format
if (ioctl(fd, VIDIOC_S_FMT, &imageFormat) < 0) {
perror("Device could not set format, VIDIOC_S_FMT");
close(fd);
return;
}

// 4. Request Buffers from the device
struct v4l2_requestbuffers requestBuffer = { 0 };
requestBuffer.count = 1; // one request buffer
requestBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // request a buffer wich we an use for capturing frames
requestBuffer.memory = V4L2_MEMORY_MMAP;

if (ioctl(fd, VIDIOC_REQBUFS, &requestBuffer) < 0) {
perror("Could not request buffer from device, VIDIOC_REQBUFS");
close(fd);
return;
}

// 5. Quety the buffer to get raw data ie. ask for the you requested buffer
// and allocate memory for it
struct v4l2_buffer queryBuffer = { 0 };
queryBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
queryBuffer.memory = V4L2_MEMORY_MMAP;
queryBuffer.index = 0;
if (ioctl(fd, VIDIOC_QUERYBUF, &queryBuffer) < 0) {
perror("Device did not return the buffer information, VIDIOC_QUERYBUF");
close(fd);
return;
}

// use a pointer to point to the newly created buffer
// mmap() will map the memory address of the device to
// an address in memory
char* buffer = (char*)mmap(NULL, queryBuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, queryBuffer.m.offset);
memset(buffer, 0, queryBuffer.length);


// 6. Get a frame
// Create a new buffer type so the device knows whichbuffer we are talking about
struct v4l2_buffer bufferinfo;
memset(&bufferinfo, 0, sizeof(bufferinfo));
bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
bufferinfo.memory = V4L2_MEMORY_MMAP;
bufferinfo.index = 0;

// Activate streaming
int type = bufferinfo.type;
if (ioctl(fd, VIDIOC_STREAMON, &type) < 0) {
perror("Could not start streaming, VIDIOC_STREAMON");
close(fd);
return;
}

//initialize JPEG decompression object
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;

int row_stride; /* physical row width in output buffer */

// We set up the normal JPEG error routines, then override error_exit.
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
// Establish the setjmp return context for my_error_exit to use.
if (setjmp(jerr.setjmp_buffer)) {
// If we get here, the JPEG code has signaled an error. We need to clean up the JPEG object, close the input file, and return.
jpeg_destroy_decompress(&cinfo);
close(fd);
return;
}

jpeg_create_decompress(&cinfo);
struct jpeg_source_mgr* src = (struct jpeg_source_mgr*)(*cinfo.mem->alloc_small) ((j_common_ptr)&cinfo, JPOOL_PERMANENT, sizeof(struct jpeg_source_mgr));

src->init_source = jpg_init_source;
src->fill_input_buffer = jpg_fill_input_buffer;
src->skip_input_data = jpg_skip_input_data;
src->term_source = jpg_term_source;
src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
src->bytes_in_buffer = 0;
src->next_input_byte = (JOCTET*)buffer;
cinfo.src = src;

//create cairo objects
cairo_surface_t* src_surface=NULL;
cairo_t* cr = led_channel->cr;

cairo_save(cr);

unsigned int frame_number = 0;
const int cairo_pixel_size = 4; //number of bytes for each cairo pixel
unsigned char* surface_pixels = NULL;
unsigned int cairo_stride = 0;
double xScale = 1;
double yScale = 1;
unsigned int src_x = 0, src_y = 0;

while (!context->end_current_command && (frame_number < frame_count || frame_count == 0)) {
// Queue the buffer
if (ioctl(fd, VIDIOC_QBUF, &bufferinfo) < 0) {
perror("Could not queue buffer, VIDIOC_QBUF");
close(fd);
jpeg_destroy_decompress(&cinfo);
if (src_surface != NULL) cairo_surface_destroy(src_surface);
return;
}

if (debug) printf("Get Frame.\n");

usleep(delay * 1000);

// Dequeue the buffer
if (ioctl(fd, VIDIOC_DQBUF, &bufferinfo) < 0) {
perror("Could not dequeue the buffer, VIDIOC_DQBUF");
close(fd);
jpeg_destroy_decompress(&cinfo);
if (src_surface != NULL) cairo_surface_destroy(src_surface);
return;
}

//tell JPEG how many bytes received and the input buffer location
src->bytes_in_buffer = bufferinfo.bytesused;
src->next_input_byte = (JOCTET*)buffer;

if (src->next_input_byte[0] == 0xFF && src->next_input_byte[1] == 0xD8 && src->next_input_byte[2] == 0xFF) {

if (debug) printf("JPEG header OK\n");
//start read JPEG
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);

if (cinfo.image_width > 0 && cinfo.image_height > 0) {
if (src_surface == NULL) {
src_surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, cinfo.image_width, cinfo.image_height);
surface_pixels = cairo_image_surface_get_data(src_surface); //get pointer to pixel data in cairo
cairo_stride = cairo_image_surface_get_stride(src_surface); //number of bytes in 1 row of cairo pointer
xScale = dst_width / (float)cinfo.image_width;
yScale = dst_height / (float)cinfo.image_height;
cairo_scale(cr, xScale, yScale);
cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
}

if (debug) printf("JPG decompressed.\n");

JSAMPARRAY jpg_buffer; // Output row buffer
int jpg_row_stride = cinfo.output_width * cinfo.output_components; //number of bytes in 1 row of the jpg image
jpg_buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, jpg_row_stride, 1); //pointer to pixel data from jpg

unsigned int i;
unsigned int row_index = 0;
while (cinfo.output_scanline < cinfo.output_height) {
jpeg_read_scanlines(&cinfo, jpg_buffer, 1);
for (i = 0;i < cinfo.image_width;i++) {
unsigned char r, g, b;
r = jpg_buffer[0][i * cinfo.output_components];
g = jpg_buffer[0][i * cinfo.output_components + 1];
b = jpg_buffer[0][i * cinfo.output_components + 2];
if (cinfo.output_components == 1) { //grayscale image
g = r;
b = r;
}
surface_pixels[row_index * cairo_stride + i * cairo_pixel_size] = b;
surface_pixels[row_index * cairo_stride + i * cairo_pixel_size + 1] = g;
surface_pixels[row_index * cairo_stride + i * cairo_pixel_size + 2] = r;
//surface_pixels[row_index * cairo_stride + i * cairo_pixel_size + 3] = 0xFF; //alpha
}
row_index++;
}

//now paint the frame
cairo_surface_mark_dirty_rectangle(src_surface, 0, 0, cinfo.image_width, cinfo.image_height);
cairo_set_source_surface(cr, src_surface, (dst_x / xScale - src_x), (dst_y / yScale - src_y));
cairo_rectangle(cr, dst_x / xScale, dst_y / yScale, dst_width / xScale, dst_height / yScale);
cairo_fill(cr);

if (debug) printf("Rendering camera image.\n");
render_channel(channel);
}

jpeg_finish_decompress(&cinfo);
}
}

if (src_surface!=NULL) cairo_surface_destroy(src_surface);
cairo_restore(cr);
jpeg_destroy_decompress(&cinfo);

}else {
fprintf(stderr, ERROR_INVALID_2D_CHANNEL);
}
}
8 changes: 8 additions & 0 deletions 2D/camera.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

#ifndef _2D_CAMERA_H
#define _2D_CAMERA_H
#include "../ws2812svr.h"

void camera(thread_context* context, char* args);

#endif
10 changes: 6 additions & 4 deletions 2D/change_layer.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "change_layer.h"

//change_layer, changes the current cairo render object to a different layer
//change_layer <channel>,<layer_nr>
Expand All @@ -9,17 +10,18 @@ void change_layer(thread_context* context, char* args) {
if (is_valid_2D_channel_number(channel)) {
args = read_int(args, &layer_nr);
if (debug) printf("change_layer %d, %d.\n", channel, layer_nr);


channel_info * led_channel = get_channel(channel);
if (layer_nr != 0) {
layer_nr--;
if (layer_nr >=0 && layer_nr < CAIRO_MAX_LAYERS && led_channels[channel].layers[layer_nr].cr != NULL) {
led_channels[channel].cr = led_channels[channel].layers[layer_nr].cr;
if (layer_nr >=0 && layer_nr < CAIRO_MAX_LAYERS && led_channel->layers[layer_nr].cr != NULL) {
led_channel->cr = led_channel->layers[layer_nr].cr;
} else {
fprintf(stderr, "Invalid layer nr %d.\n", layer_nr + 1);
return;
}
} else {
led_channels[channel].cr = led_channels[channel].main_cr;
led_channel->cr = led_channel->main_cr;
}

} else {
Expand Down
7 changes: 7 additions & 0 deletions 2D/change_layer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef _2D_CHANGE_LAYER_H
#define _2D_CHANGE_LAYER_H
#include "../ws2812svr.h"

void change_layer(thread_context* context, char* args);

#endif
4 changes: 3 additions & 1 deletion 2D/circle.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#define _USE_MATH_DEFINES
#include <math.h>
#include "../ws2812svr.h"

//http://zetcode.com/gfx/cairo/basicdrawing/
//draw_circle
Expand Down Expand Up @@ -27,7 +28,8 @@ void draw_circle(thread_context* context, char* args) {
//cairo_pattern_set_filter(cairo_get_source(cr), CAIRO_FILTER_NEAREST);
if (debug) printf("draw_circle %d,%f,%f,%f,%d,%f,%d,%f,%f,%d\n", channel, x, y, radius, fill_color, border_width, border_color,start_angle,stop_angle,negative);

cairo_t* cr = led_channels[channel].cr;
channel_info * led_channel = get_channel(channel);
cairo_t* cr = led_channel->cr;


/*cairo_set_line_width(cr, 1);
Expand Down
7 changes: 7 additions & 0 deletions 2D/circle.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#ifndef _2D_CIRCLE_H
#define _2D_CIRCLE_H
#include "../ws2812svr.h"

void draw_circle(thread_context* context, char* args);

#endif
19 changes: 10 additions & 9 deletions 2D/cls.c
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include "cls.h"
//fills 2D channel with color
//cls <channel>,<color>
void cls(thread_context* context, char* args) {
Expand All @@ -7,10 +8,10 @@ void cls(thread_context* context, char* args) {

if (is_valid_2D_channel_number(channel)) {

channel_info * led_channel = get_channel(channel);
cairo_t* cr = led_channel->cr;

cairo_t* cr = led_channels[channel].cr;

if (cr == led_channels[channel].main_cr) color = 0;
if (cr == led_channel->main_cr) color = 0;

args = read_color_arg(args, &color, 4);

Expand All @@ -22,21 +23,21 @@ void cls(thread_context* context, char* args) {

cairo_restore(cr);

/*cairo_surface_flush(led_channels[channel].surface);
/*cairo_surface_flush(led_channel->surface);
if (color == 0) {
unsigned char* pixels = cairo_image_surface_get_data(led_channels[channel].surface); //get pointer to pixel data
memset(pixels, convert_cairo_color(color), led_channels[channel].surface_stride * sizeof(unsigned char) * led_channels[channel].height);
unsigned char* pixels = cairo_image_surface_get_data(led_channel->surface); //get pointer to pixel data
memset(pixels, convert_cairo_color(color), led_channel->surface_stride * sizeof(unsigned char) * led_channel->height);
}else {
unsigned int * pixels = (unsigned int *) cairo_image_surface_get_data(led_channels[channel].surface); //get pointer to pixel data
unsigned int* pixels_end = pixels + led_channels[channel].width * led_channels[channel].height;
unsigned int * pixels = (unsigned int *) cairo_image_surface_get_data(led_channel->surface); //get pointer to pixel data
unsigned int* pixels_end = pixels + led_channel->width * led_channel->height;
color = convert_cairo_color(color);
while (pixels != pixels_end) {
*pixels = color;
pixels++;
}
}
cairo_surface_mark_dirty(led_channels[channel].surface);*/
cairo_surface_mark_dirty(led_channel->surface);*/
} else {
fprintf(stderr, ERROR_INVALID_2D_CHANNEL);
}
Expand Down
Loading

0 comments on commit ed05e01

Please sign in to comment.