diff --git a/Makefile.in b/Makefile.in index 37c587e32..6dad28628 100644 --- a/Makefile.in +++ b/Makefile.in @@ -490,6 +490,10 @@ libavcodec: @LIBAVCODEC_DECOMPRESS_LIB_TARGET@ @LIBAVCODEC_COMPRESS_LIB_TARGET@ mkdir -p lib/ultragrid $(LINKER) $(LDFLAGS) -shared -Wl,-soname,vidcap_testcard.so.@video_capture_abi_version@ $^ @TESTCARD_LIB@ -o $@ +@SWMIX_LIB_TARGET@: @GL_COMMON_OBJ@ @SWMIX_OBJ@ src/host.o + mkdir -p lib/ultragrid + $(LINKER) $(LDFLAGS) -shared -Wl,-soname,vidcap_swmix.so.@video_capture_abi_version@ $^ @SWMIX_LIB@ -o $@ + @TESTCARD2_LIB_TARGET@: @TESTCARD_COMMON@ @TESTCARD2_OBJ@ mkdir -p lib/ultragrid $(LINKER) $(LDFLAGS) -shared -Wl,-soname,vidcap_testcard2.so.@video_capture_abi_version@ $^ @TESTCARD2_LIB@ -o $@ diff --git a/configure.ac b/configure.ac index 6378b76dd..c3782c673 100644 --- a/configure.ac +++ b/configure.ac @@ -1263,7 +1263,7 @@ AC_SUBST(TESTCARD2_LIB) # ------------------------------------------------------------------------------------------------- # OpenGL stuff - +# ------------------------------------------------------------------------------------------------- GL_INC= GL_OBJ= GL_LIB= @@ -1444,10 +1444,75 @@ AC_SUBST(GL_INC) AC_SUBST(GL_LIB) AC_SUBST(GL_OBJ) +# ------------------------------------------------------------------------------------------------- +# These tests are shared for OpenGL processing +# ------------------------------------------------------------------------------------------------- +SAVED_LIBS=$LIBS + +AC_CHECK_HEADER(GL/glx.h, FOUND_GLX_H=yes) +AC_CHECK_HEADER(GL/gl.h, FOUND_GL_H=yes) +AC_CHECK_LIB(GL, glBindTexture) +AC_CHECK_LIB(GL, glXCreateNewContext, FOUND_GLX_L=yes) +AC_CHECK_LIB(X11, XCreateWindow) + +LIBS=$SAVED_LIBS + +OPENGL=no + +# Linux +if test "$ac_cv_lib_X11_XCreateWindow" = yes -a $FOUND_GLEW = yes -a "$FOUND_GLX_L" = yes -a "$FOUND_GLX_H" = yes \ + -a "$FOUND_GL_H" = yes -a $ac_cv_lib_GL_glBindTexture = yes -a "$system" = Linux +then + OPENGL=yes + OPENGL_LIB="-lGLEW -lGL -lX11" +fi + +# Mac +if test "$system" = MacOSX +then + OPENGL=yes + OPENGL_LIB="-framework OpenGL -framework Cocoa" +fi + +# ------------------------------------------------------------------------------------------------- +# SW Mix Stuff +# ------------------------------------------------------------------------------------------------- +SWMIX_INC= +SWMIX_LIB= +SWMIX_OBJ= +swmix=no + +AC_ARG_ENABLE(swmix, + AS_HELP_STRING([--disable-swmix], [disable SW mix (default is auto)]), + [swmix_req=$enableval], + [swmix_req=auto] + ) + +if test $swmix_req != no -a $OPENGL = yes +then + swmix=yes + SWMIX_LIB="$OPENGL_LIB" + SWMIX_OBJ="$SWMIX_OBJ src/video_capture/swmix.o" + AC_SUBST(SWMIX_LIB_TARGET, "lib/ultragrid/vidcap_swmix.so.$video_capture_abi_version") + LIB_TARGETS="$LIB_TARGETS $SWMIX_LIB_TARGET" + LIB_OBJS="$LIB_OBJS $SWMIX_OBJ" + AC_DEFINE([HAVE_SWMIX], [1], [Build SW mix capture]) + DEFINE_GL +fi + +if test $swmix_req = yes -a $swmix = no; then + AC_MSG_ERROR([SW mix was not found (OpenGL libraries missing)]); +fi + +LIB_MODULES="$LIB_MODULES $SWMIX_LIB" + +AC_SUBST(SWMIX_INC) +AC_SUBST(SWMIX_LIB) +AC_SUBST(SWMIX_OBJ) # ------------------------------------------------------------------------------------------------- # Screen capture stuff - +# ------------------------------------------------------------------------------------------------- SCREEN_CAP_INC= SCREEN_CAP_OBJ= SCREEN_CAP_LIB= @@ -1589,6 +1654,7 @@ AC_SUBST(FASTDXT_OBJ) # ------------------------------------------------------------------------------------------------- # GLSL DXT +# ------------------------------------------------------------------------------------------------- rtdxt=no AC_ARG_ENABLE(rtdxt, @@ -1596,14 +1662,6 @@ AC_ARG_ENABLE(rtdxt, [rtdxt_req=$enableval], [rtdxt_req=auto]) -SAVED_LIBS=$LIBS - -AC_CHECK_HEADER(GL/glx.h, FOUND_GLX_H=yes) -AC_CHECK_HEADER(GL/gl.h, FOUND_GL_H=yes) -AC_CHECK_LIB(GL, glXCreateNewContext, FOUND_GLX_L=yes) -AC_CHECK_LIB(X11, XCreateWindow) - -LIBS=$SAVED_LIBS AC_DEFINE([USE_PBO_DXT_ENCODER], [1], [We want to use OpenGL pixel buffer objects]) # We probably want this only if there is need to maximalize bandwidth @@ -1611,23 +1669,10 @@ AC_DEFINE([USE_PBO_DXT_ENCODER], [1], [We want to use OpenGL pixel buffer object #AC_DEFINE([USE_PBO_DXT_ECODER], [1], [We want to use OpenGL pixel buffer objects]) # Linux -if test $rtdxt_req != no -a "$ac_cv_lib_X11_XCreateWindow" = yes -a $FOUND_GLEW = yes -a "$FOUND_GLX_L" = yes -a "$FOUND_GLX_H" = yes \ - -a "$FOUND_GL_H" = yes -a "$system" = Linux +if test $rtdxt_req != no -a $OPENGL = yes then - RTDXT_LIB=" -lGLEW -lGL -lX11" rtdxt=yes -fi - -# Mac -if test $rtdxt_req != no -a "$system" = MacOSX -then - RTDXT_LIB="-framework OpenGL -framework Cocoa" - rtdxt=yes -fi - -# common -if test $rtdxt = yes -then + RTDXT_LIB="$OPENGL_LIB" AC_DEFINE([HAVE_DXT_GLSL], [1], [Build with DXT_GLSL support]) RTDXT_COMMON_OBJ="$RTDXT_COMMON_OBJ dxt_compress/dxt_util.o" RTDXT_COMMON_HEADERS="$RTDXT_COMMON_HEADERS dxt_compress/dxt_glsl.h" @@ -2188,6 +2233,7 @@ AC_SUBST(COREAUDIO_OBJ) # ------------------------------------------------------------------------------------------------- # Scale Stuff +# ------------------------------------------------------------------------------------------------- SCALE_INC= SCALE_LIB= SCALE_OBJ= @@ -2199,38 +2245,10 @@ AC_ARG_ENABLE(scale, [scale_req=auto] ) -SAVED_LIBS=$LIBS -AC_CHECK_LIB(GL, glBindTexture) -LIBS=$SAVED_LIBS - -case $host_os in - *darwin*) - if test $scale_req != auto - then - scale=yes - SCALE_LIB="$SCALE_LIB -framework OpenGL -framework Cocoa" - else - scale=no - fi - ;; - *mingw32*) - scale=no - ;; - *) - if test $scale_req != no -a $FOUND_GLEW = yes \ - -a $ac_cv_header_GL_glx_h = yes -a $ac_cv_header_X11_Xlib_h = yes -a \ - $ac_cv_lib_GL_glBindTexture = yes - then - scale=yes - SCALE_LIB="$SCALE_LIB -lGL -lGLEW" - else - scale=no - fi - ;; -esac - -if test $scale = yes +if test $scale_req != no -a $OPENGL = yes then + scale=yes + SCALE_LIB="$SCALE_LIB $OPENGL_LIB" SCALE_OBJ="$SCALE_OBJ src/vo_postprocess/scale.o" AC_SUBST(SCALE_LIB_TARGET, "lib/ultragrid/vo_pp_scale.so.$vo_pp_abi_version") LIB_TARGETS="$LIB_TARGETS $SCALE_LIB_TARGET" @@ -2509,8 +2527,9 @@ RESULT=\ Quicktime ................... $quicktime SAGE ........................ $sage SDL ......................... $sdl - screen capture .............. $screen_cap + Screen Capture .............. $screen_cap V4L2 ........................ $v4l2 + SW Video Mix ................ $swmix Portaudio ................... $portaudio ALSA ........................ $alsa diff --git a/src/gl_context.c b/src/gl_context.c index 6eb3687f5..fcbc35e8a 100644 --- a/src/gl_context.c +++ b/src/gl_context.c @@ -75,6 +75,7 @@ void gl_context_make_current(struct gl_context *context) { #ifdef HAVE_MACOSX // TODO + abort(); #else if(context) { glx_make_current(context->context); diff --git a/src/gl_context.h b/src/gl_context.h index 7c03fdfce..e64169920 100644 --- a/src/gl_context.h +++ b/src/gl_context.h @@ -1,6 +1,32 @@ #ifndef _GL_CONTEXT_H_ #define _GL_CONTEXT_H_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#include "config_win32.h" +#include "config_unix.h" +#endif // HAVE_CONFIG_H + +#ifdef HAVE_LINUX +#include +#else +#include +#endif /* HAVE_LINUX */ + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#if defined HAVE_MACOSX && OS_VERSION_MAJOR < 11 +#define glGenFramebuffers glGenFramebuffersEXT +#define glBindFramebuffer glBindFramebufferEXT +#define GL_FRAMEBUFFER GL_FRAMEBUFFER_EXT +#define glFramebufferTexture2D glFramebufferTexture2DEXT +#define glDeleteFramebuffers glDeleteFramebuffersEXT +#define GL_FRAMEBUFFER_COMPLETE GL_FRAMEBUFFER_COMPLETE_EXT +#define glCheckFramebufferStatus glCheckFramebufferStatusEXT +#endif + struct gl_context { int legacy:1; void *context; @@ -15,6 +41,51 @@ struct gl_context { bool init_gl_context(struct gl_context *context, int which); void gl_context_make_current(struct gl_context *context); void destroy_gl_context(struct gl_context *context); +static void gl_check_error(); + +static void gl_check_error() +{ + GLenum msg; + msg=glGetError(); + int flag = 1; + switch(msg){ + case GL_NO_ERROR: + flag = 0; + printf("No error\n"); + break; + case GL_INVALID_ENUM: + fprintf(stderr, "GL_INVALID_ENUM\n"); + break; + case GL_INVALID_VALUE: + fprintf(stderr, "GL_INVALID_VALUE\n"); + break; + case GL_INVALID_OPERATION: + fprintf(stderr, "GL_INVALID_OPERATION\n"); + break; + case GL_STACK_OVERFLOW: + fprintf(stderr, "GL_STACK_OVERFLOW\n"); + break; + case GL_STACK_UNDERFLOW: + fprintf(stderr, "GL_STACK_UNDERFLOW\n"); + break; + case GL_OUT_OF_MEMORY: + fprintf(stderr, "GL_OUT_OF_MEMORY\n"); + break; + case 1286: + fprintf(stderr, "INVALID_FRAMEBUFFER_OPERATION_EXT\n"); + break; + default: + fprintf(stderr, "wft mate? Unknown GL ERROR: %d\n", msg); + break; + } + if(flag) + abort(); +} + + +#ifdef __cplusplus +} +#endif // __cplusplus #endif /* _GL_CONTEXT_H_ */ diff --git a/src/host.h b/src/host.h index 5b732c47d..5d2716bb9 100644 --- a/src/host.h +++ b/src/host.h @@ -47,6 +47,10 @@ #ifndef __host_h #define __host_h +#ifdef __cplusplus +extern "C" { +#endif + extern int uv_argc; extern char **uv_argv; @@ -85,4 +89,8 @@ void destroy_decoder(struct vcodec_state *video_decoder_state); // if not NULL, data should be exported extern char *export_dir; +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/video.c b/src/video.c index 183df0d3b..6b4c71522 100644 --- a/src/video.c +++ b/src/video.c @@ -322,3 +322,21 @@ double compute_fps(int fps, int fpsd, int fd, int fi) return res; } +struct video_frame *vf_get_copy(struct video_frame *original) { + struct video_frame *frame_copy; + + frame_copy = (struct video_frame *) malloc(sizeof(struct video_frame)); + memcpy(frame_copy, original, sizeof(struct video_frame)); + + frame_copy->tiles = (struct tile *) malloc(sizeof(struct tile) * frame_copy->tile_count); + memcpy(frame_copy->tiles, original->tiles, sizeof(struct tile) * frame_copy->tile_count); + + for(int i = 0; i < (int) frame_copy->tile_count; ++i) { + frame_copy->tiles[i].data = (char *) malloc(frame_copy->tiles[i].data_len); + memcpy(frame_copy->tiles[i].data, original->tiles[i].data, + frame_copy->tiles[i].data_len); + } + + return frame_copy; +} + diff --git a/src/video.h b/src/video.h index f2dfbbd14..d8aa88e7a 100644 --- a/src/video.h +++ b/src/video.h @@ -175,6 +175,7 @@ void vf_free(struct video_frame *buf); */ void vf_free_data(struct video_frame *buf); struct tile * vf_get_tile(struct video_frame *buf, int pos); +struct video_frame * vf_get_copy(struct video_frame *frame); int video_desc_eq(struct video_desc, struct video_desc); int video_desc_eq_excl_param(struct video_desc a, struct video_desc b, unsigned int excluded_params); struct video_desc video_desc_from_frame(struct video_frame *frame); diff --git a/src/video_capture.c b/src/video_capture.c index bc60d4ed0..1801c1531 100644 --- a/src/video_capture.c +++ b/src/video_capture.c @@ -67,6 +67,7 @@ #include "video_capture/null.h" #include "video_capture/quicktime.h" #include "video_capture/screen.h" +#include "video_capture/swmix.h" #include "video_capture/testcard.h" #include "video_capture/testcard2.h" #include "video_capture/v4l2.h" diff --git a/src/video_capture.h b/src/video_capture.h index 8679dcd9d..c82fa8bbc 100644 --- a/src/video_capture.h +++ b/src/video_capture.h @@ -56,6 +56,10 @@ #ifndef _VIDEO_CAPTURE_H_ #define _VIDEO_CAPTURE_H_ +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + #define VIDCAP_FLAG_AUDIO_EMBEDDED (1<<1) #define VIDCAP_FLAG_AUDIO_AESEBU (1<<2) #define VIDCAP_FLAG_AUDIO_ANALOG (1<<3) @@ -105,6 +109,9 @@ void vidcap_done(struct vidcap *state); void vidcap_finish(struct vidcap *state); struct video_frame *vidcap_grab(struct vidcap *state, struct audio_frame **audio); +#ifdef __cplusplus +} +#endif // __cplusplus #endif // _VIDEO_CAPTURE_H_ diff --git a/src/video_capture/swmix.cpp b/src/video_capture/swmix.cpp new file mode 100644 index 000000000..36e8c144b --- /dev/null +++ b/src/video_capture/swmix.cpp @@ -0,0 +1,833 @@ +/* + * FILE: swmix.c + * AUTHOR: Martin Pulec + * + * Copyright (c) 2012 CESNET z.s.p.o. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by CESNET z.s.p.o. + * + * 4. Neither the name of the CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ +#ifdef HAVE_CONFIG_H +#include "config.h" +#include "config_unix.h" +#include "config_win32.h" +#endif // HAVE_CONFIG_H + +#include "debug.h" +#include "gl_context.h" +#include "host.h" +#include "video_codec.h" +#include "video_capture.h" + +#include "tv.h" + +#include "video_capture/swmix.h" +#include "audio/audio.h" + +#include +#include +#include + +#include "video_display.h" +#include "video.h" + +using namespace std; + +/* prototypes of functions defined in this module */ +static void show_help(void); +static void *master_worker(void *arg); +static void *slave_worker(void *arg); + +static void show_help() +{ + printf("SW Mix capture\n"); + printf("Usage\n"); + printf("\t-t swmix:::[:]##[#....]\n"); + printf("\t\twhere is a complete configuration string of device " + "involved in an SW mix device\n"); + printf("\t\t widht of resulting video\n"); + printf("\t\t height of resulting video\n"); + printf("\t\t FPS of resulting video\n"); + printf("\t\t codec of resulting video, may be one of RGBA, " + "RGB or UYVY (optional, default RGBA)\n"); +} + +struct state_slave { + pthread_t thread_id; + bool should_exit; + struct vidcap *device; + pthread_mutex_t lock; + + struct video_frame *captured_frame; + struct video_frame *done_frame; +}; + +struct vidcap_swmix_state { + struct state_slave *slaves; + int devices_cnt; + struct gl_context gl_context; + + GLuint tex_output; + GLuint tex_output_uyvy; + GLuint fbo; + GLuint fbo_uyvy; + + struct video_frame *frame; + char *network_buffer; + char *completed_buffer; + queue free_buffer_queue; + pthread_cond_t free_buffer_queue_not_empty_cv; + + int frames; + struct timeval t, t0; + + pthread_mutex_t lock; + pthread_cond_t frame_ready_cv; + pthread_cond_t frame_sent_cv; + + pthread_t master_thread_id; + bool should_exit; +}; + + +struct vidcap_type * +vidcap_swmix_probe(void) +{ + struct vidcap_type* vt; + + vt = (struct vidcap_type *) malloc(sizeof(struct vidcap_type)); + if (vt != NULL) { + vt->id = VIDCAP_SWMIX_ID; + vt->name = "swmix"; + vt->description = "SW mix video capture"; + } + return vt; +} + +struct slave_data { + struct video_frame *current_frame; + struct video_desc saved_desc; + float posX[4]; + float posY[4]; + GLuint texture; + double x, y, width, height; // in 1x1 unit space + double fb_aspect; +}; + +static struct slave_data *init_slave_data(vidcap_swmix_state *s) { + struct slave_data *slaves_data = (struct slave_data *) + calloc(s->devices_cnt, sizeof(struct slave_data)); + + // arrangement + // we want to have least MxN, where N <= M + 1 + double m = (-1.0 + sqrt(1.0 + 4.0 * s->devices_cnt)) / 2.0; + m = ceil(m); + int n = (s->devices_cnt + m - 1) / ((int) m); + + for(int i = 0; i < s->devices_cnt; ++i) { + glGenTextures(1, &(slaves_data[i].texture)); + glBindTexture(GL_TEXTURE_2D, slaves_data[i].texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + slaves_data[i].width = 1.0 / (int) m; + slaves_data[i].height = 1.0 / n; + slaves_data[i].x = (i % (int) m) * slaves_data[i].width; + slaves_data[i].y = (i / (int) m) * slaves_data[i].height; + slaves_data[i].fb_aspect = (double) s->frame->tiles[0].width / + s->frame->tiles[0].height; + } + + return slaves_data; +} + +static void destroy_slave_data(struct slave_data *data, int count) { + for(int i = 0; i < count; ++i) { + glDeleteTextures(1, &data[i].texture); + } + free(data); +} + +static void reconfigure_slave_rendering(struct slave_data *s, struct video_desc desc) +{ + glBindTexture(GL_TEXTURE_2D, s->texture); + switch (desc.color_spec) { + case RGBA: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width, desc.height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + break; + case RGB: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width, desc.height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + break; + case UYVY: + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width / 2, desc.height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + break; + default: + fprintf(stderr, "SW mix: Unsupported color spec.\n"); + exit_uv(1); + } + + double video_aspect = (double) desc.width / desc.height; + double fb_aspect = (double) s->fb_aspect * s->width / s->height; + double width = s->width; + double height = s->height; + double x = s->x; + double y = s->y; + + if(video_aspect > fb_aspect) { + height = width / video_aspect * s->fb_aspect; + y += (s->height - height) / 2; + } else { + width = height * video_aspect / s->fb_aspect; + x += (s->width - width) / 2; + } + + // left top + s->posX[0] = -1.0 + 2.0 * x; + s->posY[0] = -1.0 + 2.0 * y; + + // right top + s->posX[1] = -1.0 + 2.0 * x + 2.0 * width; + s->posY[1] = -1.0 + 2.0 * y; + + // right bottom + s->posX[2] = -1.0 + 2.0 * x + 2.0 * width; + s->posY[2] = -1.0 + 2.0 * y + 2.0 * height; + + // left bottom + s->posX[3] = -1.0 + 2.0 * x; + s->posY[3] = -1.0 + 2.0 * y + 2.0 * height; +} + +// source code for a shader unit +#define STRINGIFY(A) #A +static const char * fprogram_from_uyvy = STRINGIFY( +uniform sampler2D image; +uniform float imageWidth; +void main() +{ + vec4 yuv; + yuv.rgba = texture2D(image, gl_TexCoord[0].xy).grba; + if(gl_TexCoord[0].x * imageWidth / 2.0 - floor(gl_TexCoord[0].x * imageWidth / 2.0) > 0.5) + yuv.r = yuv.a; + yuv.r = 1.1643 * (yuv.r - 0.0625); + yuv.g = yuv.g - 0.5; + yuv.b = yuv.b - 0.5; + gl_FragColor.r = yuv.r + 1.7926 * yuv.b; + gl_FragColor.g = yuv.r - 0.2132 * yuv.g - 0.5328 * yuv.b; + gl_FragColor.b = yuv.r + 2.1124 * yuv.g; +}); + +static const char * fprogram_to_uyvy = STRINGIFY( +uniform sampler2D image; +uniform float imageWidth; +void main() +{ + vec4 rgba1; + vec4 rgba2; + vec4 yuv1; + vec4 yuv2; + vec2 coor1; + vec2 coor2; + float U; + float V; + coor1 = gl_TexCoord[0].xy - vec2(1.0 / (imageWidth * 2.0), 0.0); + coor2 = gl_TexCoord[0].xy + vec2(1.0 / (imageWidth * 2.0), 0.0); + rgba1 = texture2D(image, coor1); + rgba2 = texture2D(image, coor2); + yuv1.x = 1.0/16.0 + (rgba1.r * 0.2126 + rgba1.g * 0.7152 + rgba1.b * 0.0722) * 0.8588; + yuv1.y = 0.5 + (-rgba1.r * 0.1145 - rgba1.g * 0.3854 + rgba1.b * 0.5) * 0.8784; + yuv1.z = 0.5 + (rgba1.r * 0.5 - rgba1.g * 0.4541 - rgba1.b * 0.0458) * 0.8784; + yuv2.x = 1.0/16.0 + (rgba2.r * 0.2126 + rgba2.g * 0.7152 + rgba2.b * 0.0722) * 0.8588; + yuv2.y = 0.5 + (-rgba2.r * 0.1145 - rgba2.g * 0.3854 + rgba2.b * 0.5) * 0.8784; + yuv2.z = 0.5 + (rgba2.r * 0.5 - rgba2.g * 0.4541 - rgba2.b * 0.0458) * 0.8784; + U = mix(yuv1.y, yuv2.y, 0.5); + V = mix(yuv1.z, yuv2.z, 0.5); + gl_FragColor = vec4(U,yuv1.x, V, yuv2.x); +}); + +static const char * vprogram = STRINGIFY( +void main() { + gl_TexCoord[0] = gl_MultiTexCoord0; + gl_Position = ftransform(); +}); + +static GLuint get_shader(const char *vprogram, const char *fprogram) { + char log[32768]; + GLuint vhandle, fhandle; + GLuint phandle; + + phandle = glCreateProgram(); + vhandle = glCreateShader(GL_VERTEX_SHADER); + fhandle = glCreateShader(GL_FRAGMENT_SHADER); + + /* compile */ + /* fragmemt */ + glShaderSource(fhandle, 1, &fprogram, NULL); + glCompileShader(fhandle); + /* Print compile log */ + glGetShaderInfoLog(fhandle,32768,NULL,log); + printf("Compile Log: %s\n", log); + /* vertex */ + glShaderSource(vhandle, 1, &vprogram, NULL); + glCompileShader(vhandle); + /* Print compile log */ + glGetShaderInfoLog(vhandle,32768,NULL,log); + printf("Compile Log: %s\n", log); + + /* attach and link */ + glAttachShader(phandle, vhandle); + glAttachShader(phandle, fhandle); + glLinkProgram(phandle); + + printf("Program compilation/link status: "); + gl_check_error(); + + glGetProgramInfoLog(phandle, 32768, NULL, (GLchar*)log); + if ( strlen(log) > 0 ) + printf("Link Log: %s\n", log); + + // mark shaders for deletion when program is deleted + glDeleteShader(vhandle); + glDeleteShader(fhandle); + + return phandle; +} + +static void *master_worker(void *arg) +{ + struct vidcap_swmix_state *s = (struct vidcap_swmix_state *) arg; + struct timeval t0; + GLuint from_uyvy, to_uyvy; + + gettimeofday(&t0, NULL); + + gl_context_make_current(&s->gl_context); + glEnable(GL_TEXTURE_2D); + from_uyvy = get_shader(vprogram, fprogram_from_uyvy); + to_uyvy = get_shader(vprogram, fprogram_to_uyvy); + assert(from_uyvy != 0); + assert(to_uyvy != 0); + + glUseProgram(to_uyvy); + glUniform1i(glGetUniformLocation(from_uyvy, "image"), 0); + glUniform1f(glGetUniformLocation(from_uyvy, "imageWidth"), + (GLfloat) s->frame->tiles[0].width); + glUseProgram(0); + + const GLcharARB *VProgram, *FProgram; + + struct slave_data *slaves_data = init_slave_data(s); + + while(1) { + pthread_mutex_lock(&s->lock); + if(s->should_exit) { + pthread_mutex_unlock(&s->lock); + break; + } + pthread_mutex_unlock(&s->lock); + + char *current_buffer = NULL; + + pthread_mutex_lock(&s->lock); + while(s->free_buffer_queue.empty()) { + pthread_cond_wait(&s->free_buffer_queue_not_empty_cv, + &s->lock); + } + current_buffer = s->free_buffer_queue.front(); + s->free_buffer_queue.pop(); + pthread_mutex_unlock(&s->lock); + + // "capture" frames + for(int i = 0; i < s->devices_cnt; ++i) { + pthread_mutex_lock(&s->slaves[i].lock); + vf_free_data(s->slaves[i].done_frame); + s->slaves[i].done_frame = slaves_data[i].current_frame; + slaves_data[i].current_frame = NULL; + if(s->slaves[i].captured_frame) { + slaves_data[i].current_frame = + s->slaves[i].captured_frame; + s->slaves[i].captured_frame = NULL; + } else if(s->slaves[i].done_frame) { + slaves_data[i].current_frame = + s->slaves[i].done_frame; + s->slaves[i].done_frame = NULL; + } + pthread_mutex_unlock(&s->slaves[i].lock); + } + + // check for mode change + for(int i = 0; i < s->devices_cnt; ++i) { + if(slaves_data[i].current_frame) { + struct video_desc desc = video_desc_from_frame(slaves_data[i].current_frame); + if(!video_desc_eq(desc, slaves_data[i].saved_desc)) { + reconfigure_slave_rendering(&slaves_data[i], desc); + slaves_data[i].saved_desc = desc; + } + } + } + + // draw + glBindFramebuffer(GL_FRAMEBUFFER, s->fbo); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, s->tex_output, 0); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + + glViewport(0, 0, s->frame->tiles[0].width, s->frame->tiles[0].height); + + for(int i = 0; i < s->devices_cnt; ++i) { + if(slaves_data[i].current_frame) { + glBindTexture(GL_TEXTURE_2D, slaves_data[i].texture); + int width = slaves_data[i].current_frame->tiles[0].width; + GLenum format; + switch(slaves_data[i].current_frame->color_spec) { + case UYVY: + width /= 2; + format = GL_RGBA; + break; + case RGB: + format = GL_RGB; + break; + case RGBA: + format = GL_RGBA; + break; + } + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, + width, + slaves_data[i].current_frame->tiles[0].height, + format, GL_UNSIGNED_BYTE, + slaves_data[i].current_frame->tiles[0].data); + + if(slaves_data[i].current_frame->color_spec == UYVY) { + glUseProgram(from_uyvy); + GLuint image_id = glGetUniformLocation(from_uyvy, "image"); + glUniform1i(glGetUniformLocation(from_uyvy, "image"), 0); + glUniform1f(glGetUniformLocation(from_uyvy, "imageWidth"), + (GLfloat) slaves_data[i].current_frame->tiles[0].width); + } + + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); glVertex2f(slaves_data[i].posX[0], + slaves_data[i].posY[0]); + glTexCoord2f(1.0, 0.0); glVertex2f(slaves_data[i].posX[1], + slaves_data[i].posY[1]); + glTexCoord2f(1.0, 1.0); glVertex2f(slaves_data[i].posX[2], + slaves_data[i].posY[2]); + glTexCoord2f(0.0, 1.0); glVertex2f(slaves_data[i].posX[3], + slaves_data[i].posY[3]); + glEnd(); + glUseProgram(0); + } + } + + // read back + glBindTexture(GL_TEXTURE_2D, s->tex_output); + int width = s->frame->tiles[0].width; + GLenum format = GL_RGBA; + if(s->frame->color_spec == UYVY) { + glBindFramebuffer(GL_FRAMEBUFFER, s->fbo_uyvy); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, + GL_TEXTURE_2D, s->tex_output_uyvy, 0); + glViewport(0, 0, s->frame->tiles[0].width / 2, s->frame->tiles[0].height); + glUseProgram(to_uyvy); + glBegin(GL_QUADS); + glTexCoord2f(0.0, 0.0); glVertex2f(-1.0, -1.0); + glTexCoord2f(1.0, 0.0); glVertex2f(1.0, -1.0); + glTexCoord2f(1.0, 1.0); glVertex2f(1.0, 1.0); + glTexCoord2f(0.0, 1.0); glVertex2f(-1.0, 1.0); + glEnd(); + glUseProgram(0); + width /= 2; + glBindTexture(GL_TEXTURE_2D, s->tex_output_uyvy); + } else if (s->frame->color_spec == RGB) { + format = GL_RGB; + } + + glReadPixels(0, 0, width, + s->frame->tiles[0].height, + format, GL_UNSIGNED_BYTE, + current_buffer); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, 0); + + // wait until next frame time is due + double sec; + struct timeval t; + do { + gettimeofday(&t, NULL); + sec = tv_diff(t, t0); + } while(sec < 1.0 / s->frame->fps); + t0 = t; + + pthread_mutex_lock(&s->lock); + while(s->completed_buffer != NULL) { + pthread_cond_wait(&s->frame_sent_cv, &s->lock); + } + s->completed_buffer = current_buffer; + pthread_cond_signal(&s->frame_ready_cv); + pthread_mutex_unlock(&s->lock); + + } + + destroy_slave_data(slaves_data, s->devices_cnt); + + glDeleteProgram(from_uyvy); + glDeleteProgram(to_uyvy); + glDisable(GL_TEXTURE_2D); + gl_context_make_current(NULL); + + return NULL; +} + +static void *slave_worker(void *arg) +{ + struct state_slave *s = (struct state_slave *) arg; + + while(!s->should_exit) { + struct video_frame *frame; + struct audio_frame *unused_af; + + frame = vidcap_grab(s->device, &unused_af); + if(frame) { + struct video_frame *frame_copy = vf_get_copy(frame); + pthread_mutex_lock(&s->lock); + if(s->captured_frame) { + vf_free_data(s->captured_frame); + } + s->captured_frame = frame_copy; + pthread_mutex_unlock(&s->lock); + } + } + + return NULL; +} + +void * +vidcap_swmix_init(char *init_fmt, unsigned int flags) +{ + struct vidcap_swmix_state *s; + char *save_ptr = NULL; + char *item; + char *parse_string; + char *tmp; + int i; + struct video_desc desc; + GLenum format; + + printf("vidcap_swmix_init\n"); + + s = new vidcap_swmix_state; + if(s == NULL) { + printf("Unable to allocate swmix capture state\n"); + return NULL; + } + + s->frames = 0; + gettimeofday(&s->t0, NULL); + + if(!init_fmt || strcmp(init_fmt, "help") == 0) { + show_help(); + return NULL; + } + + memset(&desc, 0, sizeof(desc)); + desc.tile_count = 1; + desc.color_spec = RGBA; + + int token_nr = 0; + tmp = parse_string = strdup(init_fmt); + while((item = strtok_r(tmp, ":#", &save_ptr))) { + bool found = false; + switch (token_nr) { + case 0: + desc.width = atoi(item); + break; + case 1: + desc.height = atoi(item); + break; + case 2: + desc.fps = atof(item); + break; + case 3: + for (int i = 0; codec_info[i].name != NULL; i++) { + if (strcmp(item, codec_info[i].name) == 0) { + desc.color_spec = codec_info[i].codec; + found = true; + } + } + if(!found) { + fprintf(stderr, "Unrecognized color spec string: %s\n", item); + return NULL; + } + break; + } + tmp = NULL; + token_nr += 1; + } + free(parse_string); + + if(desc.width * desc.height * desc.fps == 0.0) { + show_help(); + return NULL; + } + + if(desc.color_spec != RGBA && desc.color_spec != RGB && desc.color_spec != UYVY) { + fprintf(stderr, "Unsupported output codec.\n"); + return NULL; + } + + s->devices_cnt = -1; + tmp = parse_string = strdup(init_fmt); + while(strtok_r(tmp, "#", &save_ptr)) { + s->devices_cnt++; + tmp = NULL; + } + free(parse_string); + + s->slaves = (struct state_slave *) calloc(s->devices_cnt, sizeof(struct state_slave)); + i = 0; + parse_string = strdup(init_fmt); + strtok_r(parse_string, "#", &save_ptr); // drop first part + while((item = strtok_r(NULL, "#", &save_ptr))) { + char *device; + char *config = strdup(item); + char *device_cfg = NULL; + device = config; + if(strchr(config, ':')) { + char *delim = strchr(config, ':'); + *delim = '\0'; + device_cfg = delim + 1; + } + + s->slaves[i].device = initialize_video_capture(device, + device_cfg, 0); + free(config); + if(!s->slaves[i].device) { + fprintf(stderr, "[swmix] Unable to initialize device %d.\n", i); + goto error; + } + ++i; + } + free(parse_string); + s->frame = vf_alloc_desc(desc); + + s->should_exit = false; + s->completed_buffer = NULL; + s->network_buffer = NULL; + + pthread_mutex_init(&s->lock, NULL); + pthread_cond_init(&s->frame_ready_cv, NULL); + pthread_cond_init(&s->frame_sent_cv, NULL); + pthread_cond_init(&s->free_buffer_queue_not_empty_cv, NULL); + + if(!init_gl_context(&s->gl_context, GL_CONTEXT_LEGACY)) { + fprintf(stderr, "[swmix] Unable to initialize OpenGL context.\n"); + return NULL; + } + + gl_context_make_current(&s->gl_context); + + format = GL_RGBA; + if(desc.color_spec == RGB) { + format = GL_RGB; + } + glGenTextures(1, &s->tex_output); + glBindTexture(GL_TEXTURE_2D, s->tex_output); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, format, desc.width, desc.height, + 0, format, GL_UNSIGNED_BYTE, NULL); + + glGenTextures(1, &s->tex_output_uyvy); + glBindTexture(GL_TEXTURE_2D, s->tex_output_uyvy); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, desc.width / 2, desc.height, + 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + + glGenFramebuffers(1, &s->fbo); + glGenFramebuffers(1, &s->fbo_uyvy); + + gl_context_make_current(NULL); + + for(int i = 0; i < s->devices_cnt; ++i) { + pthread_mutex_init(&(s->slaves[i].lock), NULL); + } + + s->frame->tiles[0].data_len = vc_get_linesize(s->frame->tiles[0].width, + s->frame->color_spec) * s->frame->tiles[0].height; + for(int i = 0; i < 3; ++i) { + char *buffer = (char *) malloc(s->frame->tiles[0].data_len); + s->free_buffer_queue.push(buffer); + } + + pthread_create(&s->master_thread_id, NULL, master_worker, (void *) s); + + for(int i = 0; i < s->devices_cnt; ++i) { + pthread_create(&(s->slaves[i].thread_id), NULL, slave_worker, (void *) &s->slaves[i]); + } + + return s; + +error: + if(s->slaves) { + int i; + for (i = 0u; i < s->devices_cnt; ++i) { + if(s->slaves[i].device) { + vidcap_done(s->slaves[i].device); + } + } + free(s->slaves); + } + free(s); + return NULL; +} + +void +vidcap_swmix_finish(void *state) +{ + struct vidcap_swmix_state *s = (struct vidcap_swmix_state *) state; + +} + +void +vidcap_swmix_done(void *state) +{ + struct vidcap_swmix_state *s = (struct vidcap_swmix_state *) state; + + assert(s != NULL); + + for(int i = 0; i < s->devices_cnt; ++i) { + s->slaves[i].should_exit = true; + } + + for(int i = 0; i < s->devices_cnt; ++i) { + pthread_join(s->slaves[i].thread_id, NULL); + } + + // wait for master thread to finish + pthread_mutex_lock(&s->lock); + s->should_exit = true; + if(s->network_buffer) { + s->free_buffer_queue.push(s->network_buffer); + s->network_buffer = NULL; + pthread_cond_signal(&s->free_buffer_queue_not_empty_cv); + } + if(s->completed_buffer) { + s->free_buffer_queue.push(s->completed_buffer); + s->completed_buffer = NULL; + pthread_cond_signal(&s->frame_sent_cv); + } + pthread_mutex_unlock(&s->lock); + pthread_join(s->master_thread_id, NULL); + + if(s->completed_buffer) + free(s->completed_buffer); + while(!s->free_buffer_queue.empty()) { + free(s->free_buffer_queue.front()); + s->free_buffer_queue.pop(); + } + + for (int i = 0; i < s->devices_cnt; ++i) { + vidcap_done(s->slaves[i].device); + pthread_mutex_destroy(&s->slaves[i].lock); + vf_free_data(s->slaves[i].captured_frame); + vf_free_data(s->slaves[i].done_frame); + } + free(s->slaves); + + vf_free(s->frame); + + gl_context_make_current(&s->gl_context); + + glDeleteTextures(1, &s->tex_output); + glDeleteTextures(1, &s->tex_output_uyvy); + glDeleteFramebuffers(1, &s->fbo); + glDeleteFramebuffers(1, &s->fbo_uyvy); + + gl_context_make_current(NULL); + destroy_gl_context(&s->gl_context); + + pthread_mutex_destroy(&s->lock); + pthread_cond_destroy(&s->frame_ready_cv); + pthread_cond_destroy(&s->frame_sent_cv); + pthread_cond_destroy(&s->free_buffer_queue_not_empty_cv); + + delete s; +} + +struct video_frame * +vidcap_swmix_grab(void *state, struct audio_frame **audio) +{ + struct vidcap_swmix_state *s = (struct vidcap_swmix_state *) state; + + *audio = NULL; + + pthread_mutex_lock(&s->lock); + while(s->completed_buffer == NULL) { + pthread_cond_wait(&s->frame_ready_cv, &s->lock); + } + if(s->network_buffer) { + s->free_buffer_queue.push(s->network_buffer); + pthread_cond_signal(&s->free_buffer_queue_not_empty_cv); + } + s->network_buffer = s->completed_buffer; + s->completed_buffer = NULL; + pthread_cond_signal(&s->frame_sent_cv); + pthread_mutex_unlock(&s->lock); + + s->frame->tiles[0].data = s->network_buffer; + + s->frames++; + gettimeofday(&s->t, NULL); + double seconds = tv_diff(s->t, s->t0); + if (seconds >= 5) { + float fps = s->frames / seconds; + fprintf(stderr, "[swmix cap.] %d frames in %g seconds = %g FPS\n", s->frames, seconds, fps); + s->t0 = s->t; + s->frames = 0; + } + + return s->frame; +} + diff --git a/src/video_capture/swmix.h b/src/video_capture/swmix.h new file mode 100644 index 000000000..780089687 --- /dev/null +++ b/src/video_capture/swmix.h @@ -0,0 +1,67 @@ +/* + * FILE: swmix.h + * AUTHORS: Martin Benes + * Lukas Hejtmanek + * Petr Holub + * Milos Liska + * Jiri Matela + * Dalibor Matura <255899@mail.muni.cz> + * Ian Wesley-Smith + * + * Copyright (c) 2005-2010 CESNET z.s.p.o. + * + * Redistribution and use in source and binary forms, with or without + * modification, is permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * + * This product includes software developed by CESNET z.s.p.o. + * + * 4. Neither the name of the CESNET nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, + * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#define VIDCAP_SWMIX_ID 0x03244b4a + +#ifdef __cplusplus +extern "C" { +#endif + +struct audio_frame; +struct vidcap_type; +struct video_frame; + +struct vidcap_type *vidcap_swmix_probe(void); +void *vidcap_swmix_init(char *fmt, unsigned int flags); +void vidcap_swmix_finish(void *state); +void vidcap_swmix_done(void *state); +struct video_frame *vidcap_swmix_grab(void *state, struct audio_frame **audio); + +#ifdef __cplusplus +} +#endif + diff --git a/src/video_capture/testcard.c b/src/video_capture/testcard.c index 99e1168a7..765d005c3 100644 --- a/src/video_capture/testcard.c +++ b/src/video_capture/testcard.c @@ -287,6 +287,7 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) unsigned int rect_size = COL_NUM; codec_t codec = RGBA; int aligned_x; + char *save_ptr = NULL; if (fmt == NULL || strcmp(fmt, "help") == 0) { printf("testcard options:\n"); @@ -307,21 +308,21 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) char *tmp; - tmp = strtok(fmt, ":"); + tmp = strtok_r(fmt, ":", &save_ptr); if (!tmp) { fprintf(stderr, "Wrong format for testcard '%s'\n", fmt); free(s); return NULL; } vf_get_tile(s->frame, 0)->width = atoi(tmp); - tmp = strtok(NULL, ":"); + tmp = strtok_r(NULL, ":", &save_ptr); if (!tmp) { fprintf(stderr, "Wrong format for testcard '%s'\n", fmt); free(s); return NULL; } vf_get_tile(s->frame, 0)->height = atoi(tmp); - tmp = strtok(NULL, ":"); + tmp = strtok_r(NULL, ":", &save_ptr); if (!tmp) { free(s); fprintf(stderr, "Wrong format for testcard '%s'\n", fmt); @@ -330,7 +331,7 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) s->frame->fps = atof(tmp); - tmp = strtok(NULL, ":"); + tmp = strtok_r(NULL, ":", &save_ptr); if (!tmp) { free(s); fprintf(stderr, "Wrong format for testcard '%s'\n", fmt); @@ -368,7 +369,7 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) s->frame->interlacing = PROGRESSIVE; s->size = aligned_x * vf_get_tile(s->frame, 0)->height * bpp; - filename = strtok(NULL, ":"); + filename = strtok_r(NULL, ":", &save_ptr); if (filename && strcmp(filename, "p") != 0 && strncmp(filename, "s=", 2ul) != 0 && strcmp(filename, "i") != 0 @@ -399,7 +400,7 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) } fclose(in); - tmp = strtok(NULL, ":"); + tmp = strtok_r(NULL, ":", &save_ptr); memcpy(s->data + s->size, s->data, s->size); vf_get_tile(s->frame, 0)->data = s->data; @@ -492,7 +493,7 @@ void *vidcap_testcard_init(char *fmt, unsigned int flags) } else if (strcmp(tmp, "still") == 0) { s->still_image = TRUE; } - tmp = strtok(NULL, ":"); + tmp = strtok_r(NULL, ":", &save_ptr); }