diff options
Diffstat (limited to 'main.c')
-rw-r--r-- | main.c | 361 |
1 files changed, 329 insertions, 32 deletions
@@ -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; } |