summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c361
1 files changed, 329 insertions, 32 deletions
diff --git a/main.c b/main.c
index 57e94c0..8dcebf8 100644
--- a/main.c
+++ b/main.c
@@ -7,13 +7,89 @@
#include <inttypes.h>
#include <errno.h>
#include <string.h>
+#include <SDL.h>
+#include <time.h>
+#include <stdbool.h>
+#include <GL/glcorearb.h>
#include "ds.h"
+#include "lib/stb_image_write.h"
typedef struct {
int32_t dev_idx;
char *name;
+ uint32_t input_idx;
+ struct v4l2_format best_format;
} Camera;
+/// macro trickery to avoid having to write every GL function multiple times
+#define gl_for_each_proc(do)\
+ do(DRAWARRAYS, DrawArrays)\
+ do(GENTEXTURES, GenTextures)\
+ do(DELETETEXTURES, DeleteTextures)\
+ do(GENERATEMIPMAP, GenerateMipmap)\
+ do(TEXIMAGE2D, TexImage2D)\
+ do(BINDTEXTURE, BindTexture)\
+ do(TEXPARAMETERI, TexParameteri)\
+ do(GETERROR, GetError)\
+ do(GETINTEGERV, GetIntegerv)\
+ do(ENABLE, Enable)\
+ do(DISABLE, Disable)\
+ do(BLENDFUNC, BlendFunc)\
+ do(VIEWPORT, Viewport)\
+ do(CLEARCOLOR, ClearColor)\
+ do(CLEAR, Clear)\
+ do(FINISH, Finish)\
+ do(CREATESHADER, CreateShader)\
+ do(DELETESHADER, DeleteShader)\
+ do(CREATEPROGRAM, CreateProgram)\
+ do(SHADERSOURCE, ShaderSource)\
+ do(GETSHADERIV, GetShaderiv)\
+ do(GETSHADERINFOLOG, GetShaderInfoLog)\
+ do(COMPILESHADER, CompileShader)\
+ do(CREATEPROGRAM, CreateProgram)\
+ do(DELETEPROGRAM, DeleteProgram)\
+ do(ATTACHSHADER, AttachShader)\
+ do(LINKPROGRAM, LinkProgram)\
+ do(GETPROGRAMIV, GetProgramiv)\
+ do(GETPROGRAMINFOLOG, GetProgramInfoLog)\
+ do(USEPROGRAM, UseProgram)\
+ do(GETATTRIBLOCATION, GetAttribLocation)\
+ do(GETUNIFORMLOCATION, GetUniformLocation)\
+ do(GENBUFFERS, GenBuffers)\
+ do(DELETEBUFFERS, DeleteBuffers)\
+ do(BINDBUFFER, BindBuffer)\
+ do(BUFFERDATA, BufferData)\
+ do(VERTEXATTRIBPOINTER, VertexAttribPointer)\
+ do(ENABLEVERTEXATTRIBARRAY, EnableVertexAttribArray)\
+ do(DISABLEVERTEXATTRIBARRAY, DisableVertexAttribArray)\
+ do(GENVERTEXARRAYS, GenVertexArrays)\
+ do(DELETEVERTEXARRAYS, DeleteVertexArrays)\
+ do(BINDVERTEXARRAY, BindVertexArray)\
+ do(ACTIVETEXTURE, ActiveTexture)\
+ do(UNIFORM1F, Uniform1f)\
+ do(UNIFORM2F, Uniform2f)\
+ do(UNIFORM3F, Uniform3f)\
+ do(UNIFORM4F, Uniform4f)\
+ do(UNIFORM1I, Uniform1i)\
+ do(UNIFORM2I, Uniform2i)\
+ do(UNIFORM3I, Uniform3i)\
+ do(UNIFORM4I, Uniform4i)\
+ do(UNIFORMMATRIX4FV, UniformMatrix4fv)\
+ do(DEBUGMESSAGECALLBACK, DebugMessageCallback)\
+ do(DEBUGMESSAGECONTROL, DebugMessageControl)\
+ do(PIXELSTOREI, PixelStorei)
+#define gl_define_proc(upper, lower) static PFNGL##upper##PROC gl_##lower;
+gl_for_each_proc(gl_define_proc)
+#undef gl_define_proc
+
+#if DEBUG
+static void APIENTRY gl_message_callback(GLenum source, GLenum type, unsigned int id, GLenum severity,
+ GLsizei length, const char *message, const void *userParam) {
+ (void)source; (void)type; (void)id; (void)length; (void)userParam;
+ if (severity == GL_DEBUG_SEVERITY_NOTIFICATION) return;
+ printf("Message from OpenGL: %s.\n", message);
+}
+#endif
static void debug_save_24bpp_bmp(const char *filename, const uint8_t *pixels, uint16_t width, uint16_t height) {
FILE *fp = fopen(filename, "wb");
@@ -69,6 +145,76 @@ char *a_sprintf(const char *fmt, ...) {
return str;
}
+// compile a GLSL shader
+GLuint gl_compile_shader(char error_buf[256], const char *code, GLenum shader_type) {
+ GLuint shader = gl_CreateShader(shader_type);
+ char header[128];
+ snprintf(header, sizeof header, "#version 130\n\
+#line 1\n");
+ const char *sources[2] = {
+ header,
+ code
+ };
+ gl_ShaderSource(shader, 2, sources, NULL);
+ gl_CompileShader(shader);
+ GLint status = 0;
+ gl_GetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (status == GL_FALSE) {
+ char log[1024] = {0};
+ gl_GetShaderInfoLog(shader, sizeof log - 1, NULL, log);
+ if (error_buf) {
+ snprintf(error_buf, 256, "Error compiling shader: %s", log);
+ } else {
+ printf("Error compiling shader: %s\n", log);
+ }
+ return 0;
+ }
+ return shader;
+}
+
+// link together GL shaders
+GLuint gl_link_program(char error_buf[256], GLuint *shaders, size_t count) {
+ GLuint program = gl_CreateProgram();
+ if (program) {
+ for (size_t i = 0; i < count; ++i) {
+ if (!shaders[i]) {
+ gl_DeleteProgram(program);
+ return 0;
+ }
+ gl_AttachShader(program, shaders[i]);
+ }
+ gl_LinkProgram(program);
+ GLint status = 0;
+ gl_GetProgramiv(program, GL_LINK_STATUS, &status);
+ if (status == GL_FALSE) {
+ char log[1024] = {0};
+ gl_GetProgramInfoLog(program, sizeof log - 1, NULL, log);
+ if (error_buf) {
+ snprintf(error_buf, 256, "Error linking shaders: %s", log);
+ } else {
+ printf("Error linking shaders: %s\n", log);
+ }
+ gl_DeleteProgram(program);
+ return 0;
+ }
+ }
+ return program;
+}
+
+GLuint gl_compile_and_link_shaders(char error_buf[256], const char *vshader_code, const char *fshader_code) {
+ GLuint shaders[2];
+ shaders[0] = gl_compile_shader(error_buf, vshader_code, GL_VERTEX_SHADER);
+ shaders[1] = gl_compile_shader(error_buf, fshader_code, GL_FRAGMENT_SHADER);
+ GLuint program = gl_link_program(error_buf, shaders, 2);
+ if (shaders[0]) gl_DeleteShader(shaders[0]);
+ if (shaders[1]) gl_DeleteShader(shaders[1]);
+ if (program) {
+ printf("Successfully linked program %u.\n", program);
+ }
+ return program;
+}
+
+
void get_cameras_from_device(int32_t dev_idx, int fd, Camera **cameras) {
struct v4l2_capability cap = {0};
v4l2_ioctl(fd, VIDIOC_QUERYCAP, &cap);
@@ -78,58 +224,56 @@ void get_cameras_from_device(int32_t dev_idx, int fd, Camera **cameras) {
input.index = input_idx;
if (v4l2_ioctl(fd, VIDIOC_ENUMINPUT, &input) == -1) break;
if (input.type != V4L2_INPUT_TYPE_CAMERA) continue;
- Camera *camera = arr_addp(*cameras);
- camera->name = a_sprintf(
- "%s %s %s %x %x %x", (const char *)cap.card, (const char *)cap.bus_info, (const char *)input.name,
- cap.device_caps, cap.capabilities, cap.version
- );
- printf("%s %s %s\n", (const char *)cap.card, (const char *)cap.bus_info, (const char *)input.name);
- struct v4l2_fmtdesc fmt = {0};
+ struct v4l2_fmtdesc fmtdesc = {0};
+ struct v4l2_format best_format = {0};
for (uint32_t fmt_idx = 0; ; fmt_idx++) {
- fmt.index = fmt_idx;
- fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- if (v4l2_ioctl(fd, VIDIOC_ENUM_FMT, &fmt) == -1) break;
- uint32_t fourcc[2] = {fmt.pixelformat, 0};
- printf(" - %s (%s)\n",fmt.description, (const char *)fourcc);
+ fmtdesc.index = fmt_idx;
+ fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (v4l2_ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == -1) break;
+ uint32_t fourcc[2] = {fmtdesc.pixelformat, 0};
+ printf(" - %s (%s)\n",fmtdesc.description, (const char *)fourcc);
struct v4l2_frmsizeenum frmsize = {0};
for (uint32_t frmsz_idx = 0; ; frmsz_idx++) {
frmsize.index = frmsz_idx;
- frmsize.pixel_format = fmt.pixelformat;
+ frmsize.pixel_format = fmtdesc.pixelformat;
if (v4l2_ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize) == -1) break;
if (frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE) {
printf(" - %ux%u\n", frmsize.discrete.width, frmsize.discrete.height);
-if (frmsize.discrete.width == 4096 && fmt.pixelformat == V4L2_PIX_FMT_BGR24) {
- uint32_t frame_width = frmsize.discrete.width;
- uint32_t frame_height = frmsize.discrete.height;
- if (v4l2_ioctl(fd, VIDIOC_S_INPUT, &input_idx) == -1) { perror("v4l2_ioctl VIDIOC_S_INPUT"); return; }
+ } else {
+ printf(" - %ux%u to %ux%u skip %ux%u\n", frmsize.stepwise.min_width, frmsize.stepwise.min_height,
+ frmsize.stepwise.max_width, frmsize.stepwise.max_height,
+ frmsize.stepwise.step_width, frmsize.stepwise.step_height);
+ }
+uint32_t frame_width = frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE ? frmsize.discrete.width : frmsize.stepwise.max_width;
+uint32_t frame_height = frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE ? frmsize.discrete.height : frmsize.stepwise.max_height;
+
+if (fmtdesc.pixelformat == V4L2_PIX_FMT_RGB24) {
struct v4l2_format format = {0};
format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
- format.fmt.pix.pixelformat = V4L2_PIX_FMT_BGR24;
format.fmt.pix.field = V4L2_FIELD_ANY;
+ format.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24;
format.fmt.pix.bytesperline = 3 * frame_width;
format.fmt.pix.sizeimage = format.fmt.pix.bytesperline * frame_height;
format.fmt.pix.width = frame_width;
format.fmt.pix.height = frame_height;
- if (v4l2_ioctl(fd, VIDIOC_S_FMT, &format) == -1) { perror("v4l2_ioctl VIDIOC_S_FMT"); return; }
- uint8_t *buffer = malloc(format.fmt.pix.sizeimage);
- size_t n = v4l2_read(fd, buffer, format.fmt.pix.sizeimage);
- if (n != format.fmt.pix.sizeimage) {
- perror("read"); return;
- }
- debug_save_24bpp_bmp("out.bmp", buffer, frame_width, frame_height);
- return;
+ if (frame_width > best_format.fmt.pix.width)
+ best_format = format;
}
- } else {
- printf(" - %ux%u to %ux%u skip %ux%u\n", frmsize.stepwise.min_width, frmsize.stepwise.min_height,
- frmsize.stepwise.max_width, frmsize.stepwise.max_height,
- frmsize.stepwise.step_width, frmsize.stepwise.step_height);
- }
}
}
+ Camera *camera = arr_addp(*cameras);
+ camera->best_format = best_format;
+ camera->name = a_sprintf(
+ "%s %s %s (%" PRIu32 "x%" PRIu32 ")", (const char *)cap.card, (const char *)cap.bus_info, (const char *)input.name,
+ best_format.fmt.pix.width, best_format.fmt.pix.height
+ );
+ camera->input_idx = input_idx;
+ camera->dev_idx = dev_idx;
}
}
int main() {
+ SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "1"); // if this program is sent a SIGTERM/SIGINT, don't turn it into a quit event
if (access("/dev", R_OK) != 0) {
fprintf(stderr, "Can't access /dev");
return EXIT_FAILURE;
@@ -144,7 +288,11 @@ int main() {
continue;
}
if (status) break;
- int fd = v4l2_open(devpath, O_RDWR);
+ int fd = v4l2_open(devpath, O_RDWR | O_NONBLOCK);
+ if (fd < 0) {
+ perror("v4l2_open");
+ continue;
+ }
get_cameras_from_device(dev_idx, fd, &cameras);
v4l2_close(fd);
}
@@ -152,5 +300,154 @@ int main() {
for (size_t i = 0; i < arr_len(cameras); i++) {
printf("[%zu] %s\n", i, cameras[i].name);
}
+ int choice = 0;
+ Camera *camera = &cameras[choice];
+ char devpath[32] = {0};
+ snprintf(devpath, sizeof devpath, "/dev/video%" PRId32, camera->dev_idx);
+ int fd = v4l2_open(devpath, O_RDWR);
+ if (fd < 0) {
+ perror("v4l2_open");
+ return EXIT_FAILURE;
+ }
+ if (v4l2_ioctl(fd, VIDIOC_S_INPUT, &camera->input_idx) != 0) {
+ perror("v4l2_ioctl");
+ return EXIT_FAILURE;
+ }
+ if (v4l2_ioctl(fd, VIDIOC_S_FMT, &camera->best_format) != 0) {
+ perror("v4l2_ioctl");
+ return EXIT_FAILURE;
+ }
+ SDL_Init(SDL_INIT_EVERYTHING);
+ SDL_Window *window = SDL_CreateWindow("camlet", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 1280, 720, SDL_WINDOW_OPENGL|SDL_WINDOW_SHOWN|SDL_WINDOW_RESIZABLE);
+ int gl_version_major = 3, gl_version_minor = 0;
+ #if DEBUG
+ gl_version_major = 4;
+ gl_version_minor = 3;
+ #endif
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, gl_version_major);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, gl_version_minor);
+ SDL_GLContext glctx = SDL_GL_CreateContext(window);
+ if (!glctx) {
+ printf("couldn't create GL context: %s\n", SDL_GetError());
+ }
+ SDL_GL_SetSwapInterval(1); // vsync
+ #define gl_get_proc(upper, lower) gl_##lower = (PFNGL##upper##PROC)SDL_GL_GetProcAddress("gl" #lower);
+ gl_for_each_proc(gl_get_proc);
+ #if DEBUG
+ {
+ GLint flags = 0;
+ gl_GetIntegerv(GL_CONTEXT_FLAGS, &flags);
+ gl_Enable(GL_DEBUG_OUTPUT);
+ gl_Enable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
+ if (flags & GL_CONTEXT_FLAG_DEBUG_BIT) {
+ // set up debug message callback
+ gl_DebugMessageCallback(gl_message_callback, NULL);
+ gl_DebugMessageControl(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
+ }
+ }
+ #endif
+ uint8_t *buf = calloc(1, camera->best_format.fmt.pix.sizeimage);
+ struct timespec ts = {0};
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ double last_time = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
+ GLuint texture = 0;
+ gl_GenTextures(1, &texture);
+ gl_BindTexture(GL_TEXTURE_2D, texture);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ gl_TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ const char *vshader_code = "attribute vec2 v_pos;\n\
+attribute vec2 v_tex_coord;\n\
+out vec2 tex_coord;\n\
+void main() {\n\
+ tex_coord = vec2(v_tex_coord.x, 1.0 - v_tex_coord.y);\n\
+ gl_Position = vec4(v_pos, 0.0, 1.0);\n\
+}\n\
+";
+ const char *fshader_code = "in vec4 color;\n\
+in vec2 tex_coord;\n\
+uniform sampler2D u_sampler;\n\
+void main() {\n\
+ vec4 tex_color = texture2D(u_sampler, tex_coord);\n\
+ gl_FragColor = vec4(tex_color.xyz, 1.0);\n\
+}\n\
+";
+ uint32_t frame_width = camera->best_format.fmt.pix.width;
+ uint32_t frame_height = camera->best_format.fmt.pix.height;
+ for (int align = 8; align >= 1; align >>= 1) {
+ if (frame_width % align == 0) {
+ gl_PixelStorei(GL_UNPACK_ALIGNMENT, align);
+ break;
+ }
+ }
+ char err[256] = {0};
+ GLuint program = gl_compile_and_link_shaders(err, vshader_code, fshader_code);
+ if (program == 0) return EXIT_FAILURE;
+ GLuint vbo = 0, vao = 0;
+ gl_GenBuffers(1, &vbo);
+ gl_GenVertexArrays(1, &vao);
+ typedef struct {
+ float pos[2];
+ float tex_coord[2];
+ } Vertex;
+ typedef struct {
+ Vertex v0;
+ Vertex v1;
+ Vertex v2;
+ } Triangle;
+ Triangle triangles[2] = {
+ { {{-1, -1}, {0, 0}}, {{1, 1}, {1, 1}}, {{-1, 1}, {0, 1}} },
+ { {{-1, -1}, {0, 0}}, {{1, -1}, {1, 0}}, {{1, 1}, {1, 1}} },
+ };
+ int ntriangles = sizeof triangles / sizeof triangles[0];
+ GLuint u_sampler = gl_GetUniformLocation(program, "u_sampler");
+ GLint v_pos = gl_GetAttribLocation(program, "v_pos");
+ GLint v_tex_coord = gl_GetAttribLocation(program, "v_tex_coord");
+ gl_BindBuffer(GL_ARRAY_BUFFER, vbo);
+ gl_BindVertexArray(vao);
+ gl_BufferData(GL_ARRAY_BUFFER, (GLsizeiptr)(ntriangles * sizeof(Triangle)), triangles, GL_STATIC_DRAW);
+ gl_VertexAttribPointer(v_pos, 2, GL_FLOAT, 0, sizeof(Vertex), (void *)offsetof(Vertex, pos));
+ gl_EnableVertexAttribArray(v_pos);
+ gl_VertexAttribPointer(v_tex_coord, 2, GL_FLOAT, 0, sizeof(Vertex), (void *)offsetof(Vertex, tex_coord));
+ gl_EnableVertexAttribArray(v_tex_coord);
+ while(true) {
+ SDL_Event event={0};
+ while (SDL_PollEvent(&event)) {
+ if (event.type == SDL_QUIT) goto quit;
+ if (event.type == SDL_KEYDOWN) switch (event.key.keysym.sym) {
+ case SDLK_SPACE: {
+ time_t t = time(NULL);
+ struct tm *tm = localtime(&t);
+ char name[256];
+ strftime(name, sizeof name, "%Y-%m-%d-%H-%M-%S.jpg", tm);
+ stbi_write_jpg(name, frame_width, frame_height, 3, buf, 90);
+ } break;
+ }
+ }
+ int window_width = 0, window_height = 0;
+ SDL_GetWindowSize(window, &window_width, &window_height);
+ gl_Viewport(0, 0, window_width, window_height);
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ double t = (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
+ //printf("%.1fms frame time\n",(t-last_time)*1000);
+ last_time = t; (void)last_time;
+ gl_ActiveTexture(GL_TEXTURE0);
+ gl_BindTexture(GL_TEXTURE_2D, texture);
+ if (v4l2_read(fd, buf, camera->best_format.fmt.pix.sizeimage) == camera->best_format.fmt.pix.sizeimage) {
+ gl_TexImage2D(GL_TEXTURE_2D, 0, GL_RGB, frame_width, frame_height, 0, GL_RGB, GL_UNSIGNED_BYTE, buf);
+ }
+ gl_UseProgram(program);
+ gl_BindBuffer(GL_ARRAY_BUFFER, vbo);
+ gl_BindVertexArray(vao);
+ gl_Uniform1i(u_sampler, 0);
+ gl_DrawArrays(GL_TRIANGLES, 0, (GLsizei)(3 * ntriangles));
+ gl_BindTexture(GL_TEXTURE_2D, 0);
+ SDL_GL_SwapWindow(window);
+ }
+ quit:
+ //debug_save_24bpp_bmp("out.bmp", buf, camera->best_format.fmt.pix.width, camera->best_format.fmt.pix.height);
+ v4l2_close(fd);
+ SDL_Quit();
return 0;
}