2020-01-10 07:14:08 +00:00
|
|
|
#include <obs/obs-module.h>
|
2020-04-24 11:30:28 +00:00
|
|
|
#include <obs/util/threading.h>
|
2020-01-10 07:14:08 +00:00
|
|
|
|
|
|
|
#include <common/ivshmem.h>
|
|
|
|
#include <common/KVMFR.h>
|
|
|
|
#include <common/framebuffer.h>
|
|
|
|
#include <lgmp/client.h>
|
|
|
|
|
|
|
|
#include <stdio.h>
|
2020-04-24 11:30:28 +00:00
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
typedef enum
|
|
|
|
{
|
|
|
|
STATE_STOPPED,
|
|
|
|
STATE_OPEN,
|
|
|
|
STATE_STARTING,
|
|
|
|
STATE_RUNNING,
|
|
|
|
STATE_STOPPING
|
|
|
|
}
|
|
|
|
LGState;
|
2020-01-10 07:14:08 +00:00
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
obs_source_t * context;
|
2020-04-24 11:30:28 +00:00
|
|
|
LGState state;
|
2020-01-10 07:14:08 +00:00
|
|
|
char * shmFile;
|
|
|
|
uint32_t width, height;
|
|
|
|
FrameType type;
|
|
|
|
struct IVSHMEM shmDev;
|
|
|
|
PLGMPClient lgmp;
|
|
|
|
PLGMPClientQueue frameQueue;
|
|
|
|
gs_texture_t * texture;
|
2020-05-20 21:31:12 +00:00
|
|
|
uint8_t * texData;
|
|
|
|
uint32_t linesize;
|
2020-04-24 11:30:28 +00:00
|
|
|
|
|
|
|
pthread_t frameThread;
|
|
|
|
os_sem_t * frameSem;
|
2020-01-10 07:14:08 +00:00
|
|
|
}
|
|
|
|
LGPlugin;
|
|
|
|
|
|
|
|
static void lgUpdate(void * data, obs_data_t * settings);
|
|
|
|
|
|
|
|
static const char * lgGetName(void * unused)
|
|
|
|
{
|
|
|
|
return obs_module_text("Looking Glass Client");
|
|
|
|
}
|
|
|
|
|
|
|
|
static void * lgCreate(obs_data_t * settings, obs_source_t * context)
|
|
|
|
{
|
|
|
|
LGPlugin * this = bzalloc(sizeof(LGPlugin));
|
|
|
|
this->context = context;
|
2020-04-24 11:30:28 +00:00
|
|
|
os_sem_init(&this->frameSem, 0);
|
|
|
|
|
2020-01-10 07:14:08 +00:00
|
|
|
lgUpdate(this, settings);
|
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void deinit(LGPlugin * this)
|
|
|
|
{
|
2020-04-24 11:30:28 +00:00
|
|
|
switch(this->state)
|
|
|
|
{
|
|
|
|
case STATE_STARTING:
|
|
|
|
/* wait for startup to finish */
|
|
|
|
while(this->state == STATE_STARTING)
|
|
|
|
usleep(1);
|
|
|
|
/* fallthrough */
|
|
|
|
|
|
|
|
case STATE_RUNNING:
|
|
|
|
case STATE_STOPPING:
|
|
|
|
this->state = STATE_STOPPING;
|
|
|
|
pthread_join(this->frameThread, NULL);
|
|
|
|
this->state = STATE_STOPPED;
|
|
|
|
/* fallthrough */
|
|
|
|
|
|
|
|
case STATE_OPEN:
|
|
|
|
lgmpClientFree(&this->lgmp);
|
|
|
|
ivshmemClose(&this->shmDev);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case STATE_STOPPED:
|
|
|
|
break;
|
|
|
|
}
|
2020-01-10 07:14:08 +00:00
|
|
|
|
|
|
|
if (this->shmFile)
|
|
|
|
{
|
|
|
|
bfree(this->shmFile);
|
|
|
|
this->shmFile = NULL;
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
if (this->texture)
|
|
|
|
{
|
|
|
|
obs_enter_graphics();
|
|
|
|
gs_texture_destroy(this->texture);
|
2020-05-20 21:31:12 +00:00
|
|
|
gs_texture_unmap(this->texture);
|
2020-04-24 11:30:28 +00:00
|
|
|
obs_leave_graphics();
|
2020-05-20 21:31:12 +00:00
|
|
|
this->texture = NULL;
|
2020-04-24 11:30:28 +00:00
|
|
|
}
|
2020-01-10 07:14:08 +00:00
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
this->state = STATE_STOPPED;
|
2020-01-10 07:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void lgDestroy(void * data)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
deinit(this);
|
2020-04-24 11:30:28 +00:00
|
|
|
os_sem_destroy(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
bfree(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lgGetDefaults(obs_data_t * defaults)
|
|
|
|
{
|
|
|
|
obs_data_set_default_string(defaults, "shmFile", "/dev/shm/looking-glass");
|
|
|
|
}
|
|
|
|
|
|
|
|
static obs_properties_t * lgGetProperties(void * data)
|
|
|
|
{
|
|
|
|
obs_properties_t * props = obs_properties_create();
|
|
|
|
|
|
|
|
obs_properties_add_text(props, "shmFile", obs_module_text("SHM File"), OBS_TEXT_DEFAULT);
|
|
|
|
|
|
|
|
return props;
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
static void * frameThread(void * data)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
|
|
|
|
if (lgmpClientSubscribe(this->lgmp, LGMP_Q_FRAME, &this->frameQueue) != LGMP_OK)
|
|
|
|
{
|
|
|
|
this->state = STATE_STOPPING;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->state = STATE_RUNNING;
|
|
|
|
os_sem_post(this->frameSem);
|
|
|
|
|
|
|
|
while(this->state == STATE_RUNNING)
|
|
|
|
{
|
|
|
|
LGMP_STATUS status;
|
|
|
|
|
|
|
|
os_sem_wait(this->frameSem);
|
|
|
|
if ((status = lgmpClientAdvanceToLast(this->frameQueue)) != LGMP_OK)
|
|
|
|
{
|
|
|
|
if (status != LGMP_ERR_QUEUE_EMPTY)
|
|
|
|
{
|
|
|
|
os_sem_post(this->frameSem);
|
2020-05-20 21:31:12 +00:00
|
|
|
printf("lgmpClientAdvanceToLast: %s\n", lgmpStatusString(status));
|
2020-04-24 11:30:28 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
os_sem_post(this->frameSem);
|
2020-05-20 21:31:12 +00:00
|
|
|
usleep(1000);
|
2020-04-24 11:30:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
lgmpClientUnsubscribe(&this->frameQueue);
|
|
|
|
this->state = STATE_STOPPING;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2020-01-10 07:14:08 +00:00
|
|
|
static void lgUpdate(void * data, obs_data_t * settings)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
|
|
|
|
deinit(this);
|
|
|
|
this->shmFile = bstrdup(obs_data_get_string(settings, "shmFile"));
|
|
|
|
if (!ivshmemOpenDev(&this->shmDev, this->shmFile))
|
|
|
|
return;
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
this->state = STATE_OPEN;
|
2020-01-10 07:14:08 +00:00
|
|
|
|
2020-05-17 02:04:41 +00:00
|
|
|
uint32_t udataSize;
|
|
|
|
KVMFR * udata;
|
|
|
|
|
2020-05-20 23:28:41 +00:00
|
|
|
if (lgmpClientInit(this->shmDev.mem, this->shmDev.size, &this->lgmp)
|
|
|
|
!= LGMP_OK)
|
|
|
|
return;
|
|
|
|
|
2020-05-20 23:37:20 +00:00
|
|
|
usleep(200000);
|
|
|
|
|
|
|
|
if (lgmpClientSessionInit(this->lgmp, &udataSize, (uint8_t **)&udata)
|
|
|
|
!= LGMP_OK)
|
2020-01-10 07:14:08 +00:00
|
|
|
return;
|
|
|
|
|
2020-05-17 02:04:41 +00:00
|
|
|
if (udataSize != sizeof(KVMFR) ||
|
|
|
|
memcmp(udata->magic, KVMFR_MAGIC, sizeof(udata->magic)) != 0 ||
|
|
|
|
udata->version != KVMFR_VERSION)
|
|
|
|
{
|
|
|
|
printf("The host application is not compatible with this client\n");
|
|
|
|
printf("Expected KVMFR version %d\n", KVMFR_VERSION);
|
|
|
|
printf("This is not a Looking Glass error, do not report this\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
this->state = STATE_STARTING;
|
|
|
|
pthread_create(&this->frameThread, NULL, frameThread, this);
|
2020-01-10 07:14:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void lgVideoTick(void * data, float seconds)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
if (this->state != STATE_RUNNING)
|
2020-01-10 07:14:08 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
LGMP_STATUS status;
|
|
|
|
LGMPMessage msg;
|
|
|
|
|
2020-04-24 11:30:28 +00:00
|
|
|
os_sem_wait(this->frameSem);
|
|
|
|
if (this->state != STATE_RUNNING)
|
2020-01-10 07:14:08 +00:00
|
|
|
{
|
2020-04-24 11:30:28 +00:00
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((status = lgmpClientProcess(this->frameQueue, &msg)) != LGMP_OK)
|
|
|
|
{
|
|
|
|
if (status == LGMP_ERR_QUEUE_EMPTY)
|
2020-04-24 11:30:28 +00:00
|
|
|
{
|
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
return;
|
2020-04-24 11:30:28 +00:00
|
|
|
}
|
2020-01-10 07:14:08 +00:00
|
|
|
|
|
|
|
printf("lgmpClientProcess: %s\n", lgmpStatusString(status));
|
2020-04-24 11:30:28 +00:00
|
|
|
this->state = STATE_STOPPING;
|
2020-04-24 16:26:34 +00:00
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-05-20 21:31:12 +00:00
|
|
|
bool updateTexture = false;
|
2020-01-10 07:14:08 +00:00
|
|
|
KVMFRFrame * frame = (KVMFRFrame *)msg.mem;
|
|
|
|
if (this->width != frame->width ||
|
|
|
|
this->height != frame->height ||
|
|
|
|
this->type != frame->type)
|
|
|
|
{
|
2020-05-20 21:31:12 +00:00
|
|
|
updateTexture = true;
|
2020-01-10 07:14:08 +00:00
|
|
|
this->width = frame->width;
|
|
|
|
this->height = frame->height;
|
|
|
|
this->type = frame->type;
|
|
|
|
}
|
|
|
|
|
2020-05-20 21:31:12 +00:00
|
|
|
if (!this->texture || updateTexture)
|
2020-01-10 07:14:08 +00:00
|
|
|
{
|
2020-05-20 21:31:12 +00:00
|
|
|
obs_enter_graphics();
|
|
|
|
if (this->texture)
|
|
|
|
{
|
|
|
|
gs_texture_unmap(this->texture);
|
|
|
|
gs_texture_destroy(this->texture);
|
|
|
|
}
|
|
|
|
this->texture = NULL;
|
|
|
|
|
2020-01-10 07:14:08 +00:00
|
|
|
enum gs_color_format format;
|
|
|
|
switch(this->type)
|
|
|
|
{
|
|
|
|
case FRAME_TYPE_BGRA : format = GS_BGRA ; break;
|
|
|
|
case FRAME_TYPE_RGBA : format = GS_RGBA ; break;
|
|
|
|
case FRAME_TYPE_RGBA10: format = GS_R10G10B10A2; break;
|
|
|
|
default:
|
|
|
|
printf("invalid type %d\n", this->type);
|
2020-04-24 13:03:40 +00:00
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
obs_leave_graphics();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->texture = gs_texture_create(
|
|
|
|
this->width, this->height, format, 1, NULL, GS_DYNAMIC);
|
|
|
|
|
|
|
|
if (!this->texture)
|
|
|
|
{
|
|
|
|
printf("create texture failed\n");
|
2020-04-24 13:03:40 +00:00
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
obs_leave_graphics();
|
|
|
|
return;
|
|
|
|
}
|
2020-05-20 21:31:12 +00:00
|
|
|
|
|
|
|
gs_texture_map(this->texture, &this->texData, &this->linesize);
|
|
|
|
obs_leave_graphics();
|
2020-01-10 07:14:08 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 08:30:49 +00:00
|
|
|
FrameBuffer * fb = (FrameBuffer *)(((uint8_t*)frame) + frame->offset);
|
2020-04-14 03:27:07 +00:00
|
|
|
framebuffer_read(
|
|
|
|
fb,
|
2020-05-20 21:31:12 +00:00
|
|
|
this->texData, // dst
|
|
|
|
this->linesize, // dstpitch
|
2020-04-14 03:27:07 +00:00
|
|
|
frame->height, // height
|
|
|
|
frame->width, // width
|
|
|
|
4, // bpp
|
|
|
|
frame->pitch // linepitch
|
|
|
|
);
|
2020-01-10 07:14:08 +00:00
|
|
|
|
|
|
|
lgmpClientMessageDone(this->frameQueue);
|
2020-04-24 11:30:28 +00:00
|
|
|
os_sem_post(this->frameSem);
|
2020-01-10 07:14:08 +00:00
|
|
|
|
2020-05-20 21:31:12 +00:00
|
|
|
obs_enter_graphics();
|
|
|
|
gs_texture_unmap(this->texture);
|
|
|
|
gs_texture_map(this->texture, &this->texData, &this->linesize);
|
2020-01-10 07:14:08 +00:00
|
|
|
obs_leave_graphics();
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lgVideoRender(void * data, gs_effect_t * effect)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
|
|
|
|
if (!this->texture)
|
|
|
|
return;
|
|
|
|
|
|
|
|
effect = obs_get_base_effect(OBS_EFFECT_OPAQUE);
|
|
|
|
gs_eparam_t *image = gs_effect_get_param_by_name(effect, "image");
|
|
|
|
gs_effect_set_texture(image, this->texture);
|
|
|
|
while (gs_effect_loop(effect, "Draw")) {
|
|
|
|
gs_draw_sprite(this->texture, 0, 0, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t lgGetWidth(void * data)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
return this->width;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t lgGetHeight(void * data)
|
|
|
|
{
|
|
|
|
LGPlugin * this = (LGPlugin *)data;
|
|
|
|
return this->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct obs_source_info lg_source =
|
|
|
|
{
|
|
|
|
.id = "looking-glass-obs",
|
|
|
|
.type = OBS_SOURCE_TYPE_INPUT,
|
|
|
|
.output_flags = OBS_SOURCE_VIDEO | OBS_SOURCE_CUSTOM_DRAW |
|
|
|
|
OBS_SOURCE_DO_NOT_DUPLICATE,
|
|
|
|
.get_name = lgGetName,
|
|
|
|
.create = lgCreate,
|
|
|
|
.destroy = lgDestroy,
|
|
|
|
.update = lgUpdate,
|
|
|
|
.get_defaults = lgGetDefaults,
|
|
|
|
.get_properties = lgGetProperties,
|
|
|
|
.video_tick = lgVideoTick,
|
|
|
|
.video_render = lgVideoRender,
|
|
|
|
.get_width = lgGetWidth,
|
|
|
|
.get_height = lgGetHeight,
|
|
|
|
// .icon_type = OBS_ICON_TYPE_DESKTOP_CAPTURE
|
2020-04-14 03:27:07 +00:00
|
|
|
};
|