47#include <AppKit/NSApplication.h>
49#include <OpenGL/OpenGL.h>
50#include <OpenGL/glext.h>
51#include <Syphon/Syphon.h>
66#define MOD_NAME "[syphon cap.] "
67#define DEFAULT_MAX_QUEUE_SIZE 1
69static void usage(
bool full);
73#define NSEventTypeApplicationDefined NSApplicationDefined
79 "#define TEXCOORD gl_TexCoord[0]\n"
81 "#define TEXCOORD TEX0\n"
82 "#define texture2D texture\n"
86 "#define colorOut gl_FragColor\n"
88 "out vec4 colorOut;\n"
95 "uniform sampler2DRect image;\n"
96 "uniform float imageWidth; // is original image width, it means twice as wide as ours\n"
100 " vec4 rgba1, rgba2;\n"
101 " vec4 yuv1, yuv2;\n"
102 " vec2 coor1, coor2;\n"
105 " coor1 = TEXCOORD.xy - vec2(1.0 / (imageWidth * 2.0), 0.0);\n"
106 " coor2 = TEXCOORD.xy + vec2(1.0 / (imageWidth * 2.0), 0.0);\n"
108 " rgba1 = texture2DRect(image, coor1);\n"
109 " rgba2 = texture2DRect(image, coor2);\n"
111 " yuv1.x = 1.0/16.0 + (rgba1.r * 0.2126 + rgba1.g * 0.7152 + rgba1.b * 0.0722) * 0.8588; // Y\n"
112 " yuv1.y = 0.5 + (-rgba1.r * 0.1145 - rgba1.g * 0.3854 + rgba1.b * 0.5) * 0.8784;\n"
113 " yuv1.z = 0.5 + (rgba1.r * 0.5 - rgba1.g * 0.4541 - rgba1.b * 0.0458) * 0.8784;\n"
115 " yuv2.x = 1.0/16.0 + (rgba2.r * 0.2126 + rgba2.g * 0.7152 + rgba2.b * 0.0722) * 0.8588; // Y\n"
116 " yuv2.y = 0.5 + (-rgba2.r * 0.1145 - rgba2.g * 0.3854 + rgba2.b * 0.5) * 0.8784;\n"
117 " yuv2.z = 0.5 + (rgba2.r * 0.5 - rgba2.g * 0.4541 - rgba2.b * 0.0458) * 0.8784;\n"
119 " U = mix(yuv1.y, yuv2.y, 0.5);\n"
120 " V = mix(yuv1.z, yuv2.z, 0.5);\n"
122 " colorOut = vec4(U,yuv1.x, V, yuv2.x);\n"
167 glBindTexture(GL_TEXTURE_2D, s->
tex_id);
169 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB,
desc.
width,
desc.
height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL);
171 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
desc.
width / 2,
desc.
height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
174 glBindFramebufferEXT(GL_FRAMEBUFFER, s->
fbo_id);
175 glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, s->
tex_id, 0);
177 glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
179 glMatrixMode( GL_MODELVIEW );
182 glMatrixMode( GL_PROJECTION );
196 static char ret[1024];
197 NSDictionary *dict = [
client serverDescription];
198 snprintf(ret,
sizeof ret,
"app: %s name: %s",
199 [[dict objectForKey:
@"SyphonServerDescriptionAppNameKey"] UTF8String],
200 [[dict objectForKey:
@"SyphonServerDescriptionNameKey"] UTF8String]);
205 [[NSApplication sharedApplication] stop : nil];
206 NSEvent*
event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
207 location:NSMakePoint(0, 0)
215 [[NSApplication sharedApplication] postEvent:event atStart:YES];
218static void oneshot_init(CFRunLoopTimerRef timer,
void *context);
221 CFAbsoluteTime fireTime = CFAbsoluteTimeGetCurrent() + 0.1;
222 CFRunLoopTimerSetNextFireDate(s->
timer, fireTime);
223 CFRunLoopAddTimer(CFRunLoopGetCurrent(), s->
timer, kCFRunLoopCommonModes);
228 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
258 NSArray *descriptions;
260 descriptions = [[SyphonServerDirectory sharedDirectory] serversMatchingName:s->serverName appName:s->appName];
262 descriptions = [[SyphonServerDirectory sharedDirectory] servers];
264 if ([descriptions
count] == 0) {
275 s->
client = [[SyphonClient alloc] initWithServerDescription:[descriptions lastObject] context:CGLGetCurrentContext() options:nil newFrameHandler:^(SyphonClient *client) {
276 if ([
client hasNewFrame] == NO)
279 SyphonImage *img = [
client newFrameImage];
280 unsigned int width = [img textureSize].width;
281 unsigned int height = [img textureSize].height;
283 gl_context_make_current(&s->gl_context);
285 struct video_desc d = {width, height, s->use_rgb ? RGB : UYVY, s->override_fps ? s->override_fps : FPS, PROGRESSIVE, 1};
286 if (!video_desc_eq(s->saved_desc, d)) {
291 struct video_frame *f = vf_alloc_desc_data(d);
293 glBindTexture(GL_TEXTURE_RECTANGLE_ARB, [img textureName]);
295 glTexCoord2i(0, height); glVertex2i(0, 0);
296 glTexCoord2i(0, 0); glVertex2i(0, height);
297 glTexCoord2i(width, 0); glVertex2i(width, height);
298 glTexCoord2i(width, height); glVertex2i(width, 0);
301 glReadPixels(0, 0, width / (s->use_rgb ? 1 : 2), height, s->use_rgb ? GL_RGB : GL_RGBA, GL_UNSIGNED_BYTE, f->tiles[0].data);
306 pthread_mutex_lock(&s->lock);
308 if (simple_linked_list_append_if_less(s->q, f, s->max_queue_size)) {
311 log_msg(LOG_LEVEL_WARNING, MOD_NAME "Skipping frame.\n");
314 pthread_mutex_unlock(&s->lock);
317 pthread_cond_signal(&s->frame_ready_cv);
318 debug_msg(MOD_NAME "Frame acquired.\n");
331 pthread_mutex_lock(&s->
lock);
336 pthread_mutex_unlock(&s->
lock);
342 pthread_mutex_lock(&s->
lock);
345 pthread_mutex_unlock(&s->
lock);
346 [[NSApplication sharedApplication] run];
348 pthread_mutex_unlock(&s->
lock);
361 {
"name=<name>",
"Syphon server name" },
362 {
"app=<appname>",
"Syphon server application name" },
363 {
"override_fps",
"overrides FPS in metadata (but not the actual rate captured)" },
364 {
"RGB",
"use RGB as an output codec instead of default UYVY" },
367 struct key_val options_full[] = {
368 {
"queue_size=<qlen>",
"size of internal frame queue" },
374 printf(
"Available servers:\n");
376 NSArray *descriptions = [[SyphonServerDirectory sharedDirectory] servers];
377 for (
id item in descriptions) {
378 color_printf(
"\t%d) " TBOLD(
"app:")
" %s " TBOLD(
"name:")
" %s\n", i++, [[
item objectForKey:
@"SyphonServerDescriptionAppNameKey"] UTF8String], [[
item objectForKey:
@"SyphonServerDescriptionNameKey"] UTF8String]);
381 if ([descriptions
count] == 0) {
392 pthread_mutex_init(&s->
lock, NULL);
395 glEnable(GL_TEXTURE_2D);
396 glEnable(GL_TEXTURE_RECTANGLE_ARB);
397 CFRunLoopTimerContext timerCtxt = { .info = s };
401 char *
item, *save_ptr;
402 item = opts == NULL ? NULL : strtok_r(opts,
":", &save_ptr);
404 if (strcmp(
item,
"help") == 0 || strcmp(
item,
"fullhelp") == 0) {
409 }
else if (strstr(
item,
"app=") ==
item) {
410 s->
appName = [NSString stringWithCString: item + strlen("app=") encoding:NSASCIIStringEncoding];
411 }
else if (strstr(
item,
"name=") ==
item) {
412 s->
serverName = [NSString stringWithCString: item + strlen("name=") encoding:NSASCIIStringEncoding];
413 }
else if (strstr(
item,
"override_fps=") ==
item) {
415 }
else if (strstr(
item,
"queue_size=") ==
item) {
417 }
else if (strcasecmp(
item,
"RGB") == 0) {
424 item = strtok_r(NULL,
":", &save_ptr);
427 glGenFramebuffersEXT(1, &s->
fbo_id);
428 glGenTextures(1, &s->
tex_id);
433 MSG(ERROR,
"Cannot compile shader for YUV 4:2:2 conversion!\n");
477 CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), s->
timer, kCFRunLoopCommonModes);
481 glDeleteTextures(1, &s->
tex_id);
485 glDeleteFramebuffersEXT(1, &s->
fbo_id);
494 [s->appName release];
495 [s->serverName release];
505 pthread_mutex_destroy(&s->
lock);
517 pthread_mutex_lock(&s->
lock);
524 pthread_mutex_unlock(&s->
lock);
535 NSArray *descriptions = [[SyphonServerDirectory sharedDirectory] servers];
536 for (
id item in descriptions) {
541 ":app=%s", [[
item objectForKey:
@"SyphonServerDescriptionAppNameKey"] UTF8String]);
543 "Syphon %s", [[
item objectForKey:
@"SyphonServerDescriptionAppNameKey"] UTF8String]);
551 *available_devices = NULL;
struct audio_frame ** audio
int void(** deleter)(void *))
void vf_free(struct video_frame *buf)
Frees video_frame structure.
int color_printf(const char *format,...)
void log_msg(int level, const char *format,...)
#define LOG_LEVEL_NOTICE
information that may be interesting
#define LOG_LEVEL_ERROR
general errors
#define LOG_LEVEL_WARNING
less severe errors
bool init_gl_context(struct gl_context *context, int which)
initializes specified OpenGL context
void destroy_gl_context(struct gl_context *context)
void gl_context_make_current(struct gl_context *context)
GLuint glsl_compile_link(const char *vprogram, const char *fprogram)
#define GL_CONTEXT_LEGACY
#define VIDCAP_INIT_NOERR
state not initialized, other action performed (typically help)
#define VIDCAP_INIT_OK
initialization successful
#define VIDEO_CAPTURE_ABI_VERSION
#define VIDCAP_INIT_FAIL
error ocured
#define VIDCAP_INIT_AUDIO_NOT_SUPPORTED
card does not support audio
void register_should_exit_callback(struct module *mod, void(*callback)(void *), void *udata)
void unregister_should_exit_callback(struct module *mod, void(*callback)(void *), void *udata)
bool register_mainloop(mainloop_t m, void *u)
@ LIBRARY_CLASS_VIDEO_CAPTURE
#define REGISTER_MODULE(name, info, lclass, abi)
Registers module to global modules' registry.
int simple_linked_list_size(struct simple_linked_list *l)
struct simple_linked_list * simple_linked_list_init(void)
void simple_linked_list_destroy(struct simple_linked_list *l)
void * simple_linked_list_pop(struct simple_linked_list *l)
void print_module_usage(const char *module_name, const struct key_val *options, const struct key_val *options_full, bool print_full_help)
struct used to comunicate with audio capture/playback device
char name[1024]
human readable name of the device
struct gl_context gl_context
int show_help
only show help and exit - 1 - standard; 2 - full
bool probe_devices
devices probed
struct simple_linked_list * q
bool should_exit_triggered
should_exit callback called just before starting mainloop
pthread_cond_t frame_ready_cv
struct device_info * probed_devices
used only if state_vidcap_syphon::probe_devices is true
int probed_devices_count
used only if state_vidcap_syphon::probe_devices is true
struct video_desc saved_desc
bool mainloop_started
our mainloop is started (if display is GL/SDL, it won't be started)
unsigned int width
width of every tile (!)
unsigned int height
height of every tile (!)
void(* dispose)(struct video_frame *)
Struct video_frame represents a video frame and contains video description.
struct video_frame_callbacks callbacks
static void reconfigure(struct state_vidcap_syphon *s, struct video_desc desc)
static struct video_frame * vidcap_syphon_grab(void *state, struct audio_frame **audio)
static void should_exit_syphon(void *state)
static int vidcap_syphon_init(struct vidcap_params *params, void **state)
static void vidcap_syphon_probe(struct device_info **available_devices, int *count, void(**deleter)(void *))
static const struct video_capture_info vidcap_syphon_info
static void probe_devices_callback(struct state_vidcap_syphon *s)
static void oneshot_init(CFRunLoopTimerRef timer, void *context)
static const char fp_display_rgba_to_yuv422_legacy[]
static int vidcap_syphon_init_common(char *opts, struct state_vidcap_syphon **out)
static void stop_application(void)
static void vidcap_syphon_done(void *state)
#define DEFAULT_MAX_QUEUE_SIZE
static void schedule_next_event(struct state_vidcap_syphon *s)
static const char * get_syphon_description(SyphonClient *client)
static void syphon_mainloop(void *state)
void ts_add_nsec(struct timespec *ts, long long offset)
int timespec_get(struct timespec *ts, int base)
This is an umbrella header for video functions.
struct module * vidcap_params_get_parent(const struct vidcap_params *params)
const char * vidcap_params_get_fmt(const struct vidcap_params *params)
unsigned int vidcap_params_get_flags(const struct vidcap_params *params)
#define VIDCAP_FLAG_AUDIO_ANY