feat: video reconnect (wip egl error 3003)
This commit is contained in:
+77
-23
@@ -4,13 +4,12 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "types.h"
|
||||
|
||||
#include "config.h"
|
||||
#include "config_file.h"
|
||||
#include "file.h"
|
||||
#include "forge.h"
|
||||
#include "midi.h"
|
||||
#include "project.h"
|
||||
@@ -22,11 +21,11 @@
|
||||
#include "video.h"
|
||||
#include "window.h"
|
||||
|
||||
static Parameters init_params;
|
||||
static SharedContext *context;
|
||||
static ShaderProgram program;
|
||||
static Window *window_output;
|
||||
static Window *window_monitor;
|
||||
static VideoCaptureArray inputs;
|
||||
static Timer timer;
|
||||
static MidiDevice midi;
|
||||
static bool trace_midi;
|
||||
@@ -66,9 +65,6 @@ static void init_context(const Parameters *params) {
|
||||
params->auto_random_cycle, params->base_tempo, params->load_state);
|
||||
|
||||
memset(context->input_resolutions, 0, sizeof(context->input_resolutions));
|
||||
memset(context->input_formats, 0, sizeof(context->input_formats));
|
||||
memset(context->input_fps, 0, sizeof(context->input_fps));
|
||||
memset(context->input_swap, 0, sizeof(context->input_swap));
|
||||
}
|
||||
|
||||
static void free_context() { shared_close_context(context); }
|
||||
@@ -79,36 +75,79 @@ static void reload_shader(unsigned int i) {
|
||||
|
||||
#ifdef VIDEO_IN
|
||||
static void init_inputs(const StringArray *video_in, unsigned int video_size) {
|
||||
inputs.length = video_in->length;
|
||||
context->inputs.length = video_in->length;
|
||||
|
||||
for (unsigned int i = 0; i < video_in->length; i++) {
|
||||
video_init(&inputs.values[i], video_in->values[i], video_size);
|
||||
video_init(&context->inputs.values[i], video_in->values[i], video_size);
|
||||
|
||||
if (!inputs.values[i].error) {
|
||||
context->input_resolutions[i][0] = inputs.values[i].width;
|
||||
context->input_resolutions[i][1] = inputs.values[i].height;
|
||||
context->input_formats[i] = inputs.values[i].pixelformat;
|
||||
if (!context->inputs.values[i].error) {
|
||||
context->input_resolutions[i][0] = context->inputs.values[i].width;
|
||||
context->input_resolutions[i][1] = context->inputs.values[i].height;
|
||||
context->inputs.values[i].needs_reload = false;
|
||||
context->inputs.values[i].disconnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool start_video_captures(unsigned int video_count, bool trace_fps) {
|
||||
for (unsigned int i = 0; i < video_count; i++) {
|
||||
if (!inputs.values[i].error &&
|
||||
!video_background_read(&inputs.values[i], context, i, trace_fps)) {
|
||||
return false;
|
||||
static bool reconnect_video_captures(const StringArray *video_in,
|
||||
unsigned int video_size, bool trace_fps) {
|
||||
for (unsigned int i = 0; i < video_in->length; i++) {
|
||||
if (context->inputs.values[i].disconnected) {
|
||||
video_init(&context->inputs.values[i], video_in->values[i], video_size);
|
||||
|
||||
if (!context->inputs.values[i].error) {
|
||||
context->input_resolutions[i][0] = context->inputs.values[i].width;
|
||||
context->input_resolutions[i][1] = context->inputs.values[i].height;
|
||||
|
||||
if (!video_background_read(&context->inputs.values[i], context, i,
|
||||
trace_fps)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context->inputs.values[i].needs_reload = true;
|
||||
context->inputs.values[i].disconnected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool start_video_captures(unsigned int video_count, bool trace_fps) {
|
||||
pid_t pid;
|
||||
for (unsigned int i = 0; i < video_count; i++) {
|
||||
if (!context->inputs.values[i].error &&
|
||||
!video_background_read(&context->inputs.values[i], context, i,
|
||||
trace_fps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
log_error("Could not create subprocess");
|
||||
return false;
|
||||
}
|
||||
if (pid == 0) {
|
||||
return true;
|
||||
}
|
||||
log_info("background reconnect acquisition started (pid: %d)", pid);
|
||||
while (!context->stop) {
|
||||
sleep(1);
|
||||
if (!reconnect_video_captures(&init_params.video_in, init_params.video_size,
|
||||
init_params.trace_fps)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
static void free_video_captures(unsigned int video_count) {
|
||||
for (unsigned int i = 0; i < video_count; i++) {
|
||||
shaders_free_input(&program, &inputs.values[i]);
|
||||
shaders_free_input(&program, i);
|
||||
|
||||
video_free(&inputs.values[i]);
|
||||
video_free(&context->inputs.values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
static void error_callback(int error, const char *description) {
|
||||
@@ -140,6 +179,8 @@ static void midi_callback(unsigned char code, unsigned char value) {
|
||||
}
|
||||
|
||||
static bool init(const Parameters *params) {
|
||||
init_params = *params;
|
||||
|
||||
project_init(&project, params->project_path, params->config_file);
|
||||
|
||||
if (project.error) {
|
||||
@@ -200,7 +241,7 @@ static bool init(const Parameters *params) {
|
||||
}
|
||||
|
||||
#ifdef VIDEO_IN
|
||||
shaders_link_inputs(&program, &project, &inputs);
|
||||
shaders_link_inputs(&program, &project, &context->inputs);
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
if (program.error) {
|
||||
@@ -221,11 +262,20 @@ static bool should_close() {
|
||||
(window_monitor != NULL && window_should_close(window_monitor));
|
||||
}
|
||||
|
||||
static void loop(bool hr, bool trace_fps) {
|
||||
static bool loop(bool hr, bool trace_fps) {
|
||||
if (hr) {
|
||||
project_reload(&project, reload_shader);
|
||||
}
|
||||
|
||||
#ifdef VIDEO_IN
|
||||
for (unsigned int i = 0; i < context->inputs.length; i++) {
|
||||
if (context->inputs.values[i].needs_reload) {
|
||||
shaders_relink_input(&program, &project, &context->inputs, i);
|
||||
context->inputs.values[i].needs_reload = false;
|
||||
}
|
||||
}
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
compute_fps(trace_fps);
|
||||
|
||||
context->time = window_get_time();
|
||||
@@ -248,6 +298,8 @@ static void loop(bool hr, bool trace_fps) {
|
||||
}
|
||||
|
||||
window_events();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void shutdown(const Parameters *params) {
|
||||
@@ -288,8 +340,10 @@ void forge_run(const Parameters *params) {
|
||||
}
|
||||
|
||||
while (!should_close()) {
|
||||
loop(params->hot_reload, params->trace_fps);
|
||||
if (!loop(params->hot_reload, params->trace_fps)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
shutdown(params);
|
||||
}
|
||||
}
|
||||
|
||||
+82
-35
@@ -39,6 +39,20 @@ bool check_glerror(ShaderProgram *program) {
|
||||
return code > 0;
|
||||
}
|
||||
|
||||
bool check_eglerror(ShaderProgram *program, const char *context) {
|
||||
unsigned int code;
|
||||
|
||||
code = eglGetError();
|
||||
|
||||
if (code > 0 && code != EGL_SUCCESS) {
|
||||
log_warn("(%s) EGL Error: %04x", context, code);
|
||||
program->error = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void init_gl(ShaderProgram *program) {
|
||||
gladLoadGL(glfwGetProcAddress);
|
||||
|
||||
@@ -88,7 +102,15 @@ static void rebind_textures(const ShaderProgram *program) {
|
||||
|
||||
#ifdef VIDEO_IN
|
||||
static void link_input_to_texture(ShaderProgram *program, VideoCapture *input,
|
||||
unsigned int texture_index, bool swap) {
|
||||
unsigned int input_index,
|
||||
unsigned int texture_index, bool swap,
|
||||
bool reload) {
|
||||
if (reload) {
|
||||
glDeleteTextures(1, program->textures + texture_index);
|
||||
|
||||
glGenTextures(1, program->textures + texture_index);
|
||||
}
|
||||
|
||||
EGLImageKHR dma_image;
|
||||
|
||||
dma_image = EGL_NO_IMAGE_KHR;
|
||||
@@ -111,15 +133,14 @@ static void link_input_to_texture(ShaderProgram *program, VideoCapture *input,
|
||||
dma_image = eglCreateImageKHR(program->egl_display, EGL_NO_CONTEXT,
|
||||
EGL_LINUX_DMA_BUF_EXT, NULL, attrib_list);
|
||||
|
||||
if (swap) {
|
||||
input->dma_image_swap = dma_image;
|
||||
} else {
|
||||
input->dma_image = dma_image;
|
||||
if (check_eglerror(program, "eglCreateImageKHR")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dma_image == EGL_NO_IMAGE_KHR) {
|
||||
log_error("(%s) eglCreateImageKHR failed %04x", input->name, eglGetError());
|
||||
return;
|
||||
if (swap) {
|
||||
program->dma_images_swap[input_index] = dma_image;
|
||||
} else {
|
||||
program->dma_images[input_index] = dma_image;
|
||||
}
|
||||
|
||||
glActiveTexture(GL_TEXTURE0 + texture_index);
|
||||
@@ -130,30 +151,35 @@ static void link_input_to_texture(ShaderProgram *program, VideoCapture *input,
|
||||
GL_UNSIGNED_BYTE, 0);
|
||||
|
||||
// https://registry.khronos.org/OpenGL/extensions/EXT/EXT_EGL_image_storage.txt
|
||||
glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, input->dma_image, NULL);
|
||||
glEGLImageTargetTexStorageEXT(GL_TEXTURE_2D, dma_image, NULL);
|
||||
|
||||
if (check_eglerror(program, "glEGLImageTargetTexStorageEXT")) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_info("Texture %d linked to %s", texture_index, input->name);
|
||||
}
|
||||
|
||||
static void init_input(ShaderProgram *program, const ConfigFile *config,
|
||||
VideoCaptureArray *inputs) {
|
||||
VideoCaptureArray *inputs, unsigned int i, bool reload) {
|
||||
unsigned int tex_i;
|
||||
char name[STR_LEN];
|
||||
|
||||
for (unsigned int i = 0; i < program->in_count; i++) {
|
||||
if (i < inputs->length && !inputs->values[i].error) {
|
||||
snprintf(name, STR_LEN, "IN_%d_OUT", i + 1);
|
||||
if (i < inputs->length && !inputs->values[i].error) {
|
||||
snprintf(name, STR_LEN, "IN_%d_OUT", i + 1);
|
||||
tex_i = config_file_get_int(config, name, 0);
|
||||
link_input_to_texture(program, &inputs->values[i], i, tex_i, false, reload);
|
||||
if (inputs->values[i].with_swap) {
|
||||
snprintf(name, STR_LEN, "IN_%d_SWAP_OUT", i + 1);
|
||||
tex_i = config_file_get_int(config, name, 0);
|
||||
link_input_to_texture(program, &inputs->values[i], tex_i, false);
|
||||
if (inputs->values[i].with_swap) {
|
||||
snprintf(name, STR_LEN, "IN_%d_SWAP_OUT", i + 1);
|
||||
tex_i = config_file_get_int(config, name, 0);
|
||||
link_input_to_texture(program, &inputs->values[i], tex_i, true);
|
||||
}
|
||||
} else {
|
||||
log_warn("Cannot link input %d", i + 1);
|
||||
link_input_to_texture(program, &inputs->values[i], i, tex_i, true,
|
||||
reload);
|
||||
}
|
||||
} else {
|
||||
log_warn("Cannot link input %d", i + 1);
|
||||
}
|
||||
|
||||
check_glerror(program);
|
||||
}
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
@@ -426,8 +452,10 @@ static void update_viewport(ShaderProgram *program,
|
||||
// clean and resize all textures
|
||||
for (unsigned int i = 0; i < program->tex_count; i++) {
|
||||
glActiveTexture(GL_TEXTURE0 + i);
|
||||
// TODO dont remap input textures
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, context->tex_resolution[0],
|
||||
context->tex_resolution[1], 0, GL_RGB, GL_UNSIGNED_BYTE, 0);
|
||||
check_glerror(program);
|
||||
}
|
||||
program->last_resolution[0] = context->resolution[0];
|
||||
program->last_resolution[1] = context->resolution[1];
|
||||
@@ -507,11 +535,11 @@ static void use_program(const ShaderProgram *program, int i, bool output,
|
||||
write_uniform_2f(program->iinres_locations[i * program->in_count + j],
|
||||
&context->input_resolutions[j]);
|
||||
write_uniform_1i(program->iinfmt_locations[i * program->in_count + j],
|
||||
context->input_formats[j]);
|
||||
context->inputs.values[j].pixelformat);
|
||||
write_uniform_1i(program->iinfps_locations[i * program->in_count + j],
|
||||
context->input_fps[j]);
|
||||
context->inputs.values[j].fps);
|
||||
write_uniform_1i(program->iinswap_locations[i * program->in_count + j],
|
||||
context->input_swap[j] ? 1 : 0);
|
||||
context->inputs.values[j].swap ? 1 : 0);
|
||||
}
|
||||
|
||||
// set seeds uniforms
|
||||
@@ -581,6 +609,11 @@ void shaders_init(ShaderProgram *program, const Project *project,
|
||||
program->active_count = project->state_config.midi_active_counts.length;
|
||||
program->midi_lengths.length = 0;
|
||||
|
||||
#ifdef VIDEO_IN
|
||||
memset(program->dma_images, 0, sizeof(program->dma_images));
|
||||
memset(program->dma_images_swap, 0, sizeof(program->dma_images_swap));
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
init_gl(program);
|
||||
|
||||
if (check_glerror(program)) {
|
||||
@@ -625,13 +658,19 @@ void shaders_init(ShaderProgram *program, const Project *project,
|
||||
}
|
||||
}
|
||||
|
||||
void shaders_relink_input(ShaderProgram *program, const Project *project,
|
||||
VideoCaptureArray *inputs, unsigned int i) {
|
||||
#ifdef VIDEO_IN
|
||||
// shaders_free_input(program, i);
|
||||
init_input(program, &project->config, inputs, i, true);
|
||||
#endif /* VIDEO_IN */
|
||||
}
|
||||
|
||||
void shaders_link_inputs(ShaderProgram *program, const Project *project,
|
||||
VideoCaptureArray *inputs) {
|
||||
#ifdef VIDEO_IN
|
||||
init_input(program, &project->config, inputs);
|
||||
|
||||
if (check_glerror(program)) {
|
||||
return;
|
||||
for (unsigned int i = 0; i < program->in_count; i++) {
|
||||
init_input(program, &project->config, inputs, i, false);
|
||||
}
|
||||
#endif /* VIDEO_IN */
|
||||
}
|
||||
@@ -688,14 +727,22 @@ void shaders_free_window(const ShaderProgram *program, bool secondary) {
|
||||
glDeleteVertexArrays(1, &program->vertex_array[secondary ? 1 : 0]);
|
||||
}
|
||||
|
||||
void shaders_free_input(const ShaderProgram *program,
|
||||
const VideoCapture *input) {
|
||||
void shaders_free_input(ShaderProgram *program, unsigned int input_index) {
|
||||
#ifdef VIDEO_IN
|
||||
if (!input->error && input->dma_image != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(program->egl_display, input->dma_image);
|
||||
if (program->dma_images[input_index] != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(program->egl_display, program->dma_images[input_index]);
|
||||
|
||||
program->dma_images[input_index] = EGL_NO_IMAGE_KHR;
|
||||
|
||||
check_eglerror(program, "eglDestroyImageKHR");
|
||||
}
|
||||
if (!input->error && input->dma_image_swap != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(program->egl_display, input->dma_image_swap);
|
||||
if (program->dma_images_swap[input_index] != EGL_NO_IMAGE_KHR) {
|
||||
eglDestroyImageKHR(program->egl_display,
|
||||
program->dma_images_swap[input_index]);
|
||||
|
||||
program->dma_images_swap[input_index] = EGL_NO_IMAGE_KHR;
|
||||
|
||||
check_eglerror(program, "eglDestroyImageKHR");
|
||||
}
|
||||
#endif /* VIDEO_IN */
|
||||
}
|
||||
}
|
||||
|
||||
+5
-3
@@ -6,6 +6,9 @@
|
||||
void shaders_init(ShaderProgram *program, const Project *project,
|
||||
const SharedContext *context, bool rebind);
|
||||
|
||||
void shaders_relink_input(ShaderProgram *program, const Project *project,
|
||||
VideoCaptureArray *inputs, unsigned int i);
|
||||
|
||||
void shaders_link_inputs(ShaderProgram *program, const Project *project,
|
||||
VideoCaptureArray *inputs);
|
||||
|
||||
@@ -19,7 +22,6 @@ void shaders_free(const ShaderProgram *program);
|
||||
|
||||
void shaders_free_window(const ShaderProgram *program, bool secondary);
|
||||
|
||||
void shaders_free_input(const ShaderProgram *program,
|
||||
const VideoCapture *input);
|
||||
void shaders_free_input(ShaderProgram *program, unsigned int input_index);
|
||||
|
||||
#endif /* SHADERS_H */
|
||||
#endif /* SHADERS_H */
|
||||
|
||||
+12
-7
@@ -130,6 +130,8 @@ typedef struct ShaderProgram {
|
||||
unsigned int in_count;
|
||||
#ifdef VIDEO_IN
|
||||
EGLDisplay egl_display;
|
||||
EGLImageKHR dma_images[MAX_VIDEO];
|
||||
EGLImageKHR dma_images_swap[MAX_VIDEO];
|
||||
#endif /* VIDEO_IN */
|
||||
} ShaderProgram;
|
||||
|
||||
@@ -138,7 +140,11 @@ typedef struct ShaderProgram {
|
||||
typedef struct VideoCapture {
|
||||
char name[STR_LEN];
|
||||
bool error;
|
||||
bool disconnected;
|
||||
bool needs_reload;
|
||||
bool with_swap;
|
||||
bool swap;
|
||||
unsigned int fps;
|
||||
int fd;
|
||||
int exp_fd;
|
||||
int exp_fd_swap;
|
||||
@@ -149,12 +155,13 @@ typedef struct VideoCapture {
|
||||
#ifdef VIDEO_IN
|
||||
struct v4l2_buffer buf;
|
||||
struct v4l2_buffer buf_swap;
|
||||
EGLImageKHR dma_image;
|
||||
EGLImageKHR dma_image_swap;
|
||||
#endif /* VIDEO_IN */
|
||||
} VideoCapture;
|
||||
|
||||
typedef ARRAY(VideoCaptureArray, VideoCapture);
|
||||
typedef struct VideoCaptureArray {
|
||||
VideoCapture values[MAX_VIDEO];
|
||||
unsigned int length;
|
||||
} VideoCaptureArray;
|
||||
|
||||
// window.c
|
||||
|
||||
@@ -193,9 +200,7 @@ typedef struct SharedContext {
|
||||
unsigned int auto_random_cycle;
|
||||
unsigned int seeds[MAX_FRAG];
|
||||
unsigned int fps;
|
||||
unsigned int input_formats[MAX_VIDEO];
|
||||
unsigned int input_fps[MAX_VIDEO];
|
||||
bool input_swap[MAX_VIDEO];
|
||||
VideoCaptureArray inputs;
|
||||
bool stop;
|
||||
} SharedContext;
|
||||
|
||||
@@ -277,4 +282,4 @@ typedef struct Project {
|
||||
File fragment_shaders[MAX_FRAG][MAX_SUB_FILE + 1];
|
||||
} Project;
|
||||
|
||||
#endif /* TYPES_H */
|
||||
#endif /* TYPES_H */
|
||||
|
||||
+16
-14
@@ -1,6 +1,5 @@
|
||||
#ifdef VIDEO_IN
|
||||
|
||||
#include <bsd/string.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/videodev2.h>
|
||||
@@ -286,22 +285,25 @@ static void close_stream(const VideoCapture *video_capture) {
|
||||
ioctl(video_capture->fd, VIDIOC_STREAMOFF, &buf_type);
|
||||
}
|
||||
|
||||
static unsigned int read_video(const VideoCapture *video_capture, bool swap) {
|
||||
int result;
|
||||
static unsigned int read_video(VideoCapture *video_capture) {
|
||||
unsigned int result;
|
||||
struct v4l2_capability cap;
|
||||
|
||||
result = 0;
|
||||
|
||||
if ((swap || !video_capture->with_swap) &&
|
||||
if ((video_capture->swap || !video_capture->with_swap) &&
|
||||
ioctl(video_capture->fd, VIDIOC_DQBUF, &video_capture->buf) != -1) {
|
||||
ioctl(video_capture->fd, VIDIOC_QBUF, &video_capture->buf);
|
||||
|
||||
result = 1;
|
||||
} else if (!swap && video_capture->with_swap &&
|
||||
} else if (!video_capture->swap && video_capture->with_swap &&
|
||||
ioctl(video_capture->fd, VIDIOC_DQBUF, &video_capture->buf_swap) !=
|
||||
-1) {
|
||||
ioctl(video_capture->fd, VIDIOC_QBUF, &video_capture->buf_swap);
|
||||
|
||||
result = 2;
|
||||
} else if (ioctl(video_capture->fd, VIDIOC_QUERYCAP, &cap) == -1) {
|
||||
video_capture->error = true;
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -361,18 +363,18 @@ bool video_background_read(VideoCapture *video_capture, SharedContext *context,
|
||||
pid);
|
||||
timer_init(&timer, 30);
|
||||
|
||||
while (!context->stop) {
|
||||
video_result = read_video(video_capture, context->input_swap[input_index]);
|
||||
while (!context->stop && !video_capture->error) {
|
||||
video_result = read_video(video_capture);
|
||||
if (video_result > 0 && timer_inc(&timer)) {
|
||||
fps = timer_reset(&timer);
|
||||
|
||||
context->input_fps[input_index] = (unsigned int)round(fps);
|
||||
context->inputs.values[input_index].fps = (unsigned int)round(fps);
|
||||
if (trace_fps) {
|
||||
log_trace("(%s) %.2ffps", video_capture->name, fps);
|
||||
}
|
||||
}
|
||||
if (video_result > 0) {
|
||||
context->input_swap[input_index] = video_result == 2;
|
||||
video_capture->swap = video_result == 2;
|
||||
}
|
||||
}
|
||||
if (context->stop) {
|
||||
@@ -381,15 +383,15 @@ bool video_background_read(VideoCapture *video_capture, SharedContext *context,
|
||||
} else {
|
||||
log_info("(%s) background acquisition stopped after error (pid: %d)",
|
||||
video_capture->name, pid);
|
||||
video_capture->disconnected = true;
|
||||
video_capture->pixelformat = 0;
|
||||
video_free(video_capture);
|
||||
}
|
||||
exit(context->stop ? EXIT_SUCCESS : EXIT_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
void video_free(const VideoCapture *video_capture) {
|
||||
if (!video_capture->error) {
|
||||
close_stream(video_capture);
|
||||
}
|
||||
close_stream(video_capture);
|
||||
if (video_capture->exp_fd != -1) {
|
||||
close(video_capture->exp_fd);
|
||||
}
|
||||
@@ -401,4 +403,4 @@ void video_free(const VideoCapture *video_capture) {
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* VIDEO_IN */
|
||||
#endif /* VIDEO_IN */
|
||||
|
||||
Reference in New Issue
Block a user