pjproject/pjmedia/src/pjmedia-videodev/sdl_dev.c

1558 lines
49 KiB
C

/*
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <pjmedia-videodev/videodev_imp.h>
#include <pjmedia/event.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \
defined(PJMEDIA_VIDEO_DEV_HAS_SDL) && PJMEDIA_VIDEO_DEV_HAS_SDL != 0
#include <SDL.h>
#include <SDL_syswm.h>
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
# include "SDL_opengl.h"
# define OPENGL_DEV_IDX 1
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
#if !(SDL_VERSION_ATLEAST(1,3,0))
# error "SDL 1.3 or later is required"
#endif
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
# include "TargetConditionals.h"
# include <Foundation/Foundation.h>
#endif
#define THIS_FILE "sdl_dev.c"
#define DEFAULT_CLOCK_RATE 90000
#define DEFAULT_WIDTH 640
#define DEFAULT_HEIGHT 480
#define DEFAULT_FPS 25
typedef struct sdl_fmt_info
{
pjmedia_format_id fmt_id;
Uint32 sdl_format;
Uint32 Rmask;
Uint32 Gmask;
Uint32 Bmask;
Uint32 Amask;
} sdl_fmt_info;
static sdl_fmt_info sdl_fmts[] =
{
#if PJ_IS_BIG_ENDIAN
{PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_RGBA8888,
0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
{PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_RGB24,
0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_BGRA8888,
0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
#else /* PJ_IS_BIG_ENDIAN */
{PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_ABGR8888,
0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
{PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_BGR24,
0xFF, 0xFF00, 0xFF0000, 0} ,
{PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_ARGB8888,
0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
#endif /* PJ_IS_BIG_ENDIAN */
{PJMEDIA_FORMAT_DIB , (Uint32)SDL_PIXELFORMAT_RGB24,
0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_YUY2, SDL_PIXELFORMAT_YUY2, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_UYVY, SDL_PIXELFORMAT_UYVY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YVYU, SDL_PIXELFORMAT_YVYU, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YV12, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420JPEG, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I422JPEG, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0}
};
/* sdl_ device info */
struct sdl_dev_info
{
pjmedia_vid_dev_info info;
};
/* Linked list of streams */
struct stream_list
{
PJ_DECL_LIST_MEMBER(struct stream_list);
struct sdl_stream *stream;
};
#define INITIAL_MAX_JOBS 64
#define JOB_QUEUE_INC_FACTOR 2
typedef pj_status_t (*job_func_ptr)(void *data);
typedef struct job {
job_func_ptr func;
void *data;
unsigned flags;
pj_status_t retval;
} job;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
@interface JQDelegate: NSObject
{
@public
job *pjob;
}
- (void)run_job;
@end
@implementation JQDelegate
- (void)run_job
{
pjob->retval = (*pjob->func)(pjob->data);
}
@end
#endif /* PJ_DARWINOS */
typedef struct job_queue {
pj_pool_t *pool;
job **jobs;
pj_sem_t **job_sem;
pj_sem_t **old_sem;
pj_mutex_t *mutex;
pj_thread_t *thread;
pj_sem_t *sem;
unsigned size;
unsigned head, tail;
pj_bool_t is_full;
pj_bool_t is_quitting;
} job_queue;
/* sdl_ factory */
struct sdl_factory
{
pjmedia_vid_dev_factory base;
pj_pool_t *pool;
pj_pool_factory *pf;
unsigned dev_count;
struct sdl_dev_info *dev_info;
job_queue *jq;
pj_thread_t *sdl_thread; /**< SDL thread. */
pj_sem_t *sem;
pj_mutex_t *mutex;
struct stream_list streams;
pj_bool_t is_quitting;
pj_thread_desc thread_desc;
pj_thread_t *ev_thread;
};
/* Video stream. */
struct sdl_stream
{
pjmedia_vid_dev_stream base; /**< Base stream */
pjmedia_vid_dev_param param; /**< Settings */
pj_pool_t *pool; /**< Memory pool. */
pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
void *user_data; /**< Application data. */
struct sdl_factory *sf;
const pjmedia_frame *frame;
pj_bool_t is_running;
pj_timestamp last_ts;
struct stream_list list_entry;
SDL_Window *window; /**< Display window. */
SDL_Renderer *renderer; /**< Display renderer. */
SDL_Texture *scr_tex; /**< Screen texture. */
int pitch; /**< Pitch value. */
SDL_Rect rect; /**< Frame rectangle. */
SDL_Rect dstrect; /**< Display rectangle. */
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
SDL_GLContext *gl_context;
GLuint texture;
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
pjmedia_video_apply_fmt_param vafp;
};
/* Prototypes */
static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f);
static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f);
static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f);
static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f);
static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_info *info);
static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_param *param);
static pj_status_t sdl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_dev_param *param,
const pjmedia_vid_dev_cb *cb,
void *user_data,
pjmedia_vid_dev_stream **p_vid_strm);
static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_param *param);
static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_cap cap,
void *value);
static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *strm,
pjmedia_vid_dev_cap cap,
const void *value);
static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
const pjmedia_frame *frame);
static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm);
static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm);
static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm);
static pj_status_t resize_disp(struct sdl_stream *strm,
pjmedia_rect_size *new_disp_size);
static pj_status_t sdl_destroy_all(void *data);
/* Job queue prototypes */
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
void *data, unsigned flags,
pj_status_t *retval);
static pj_status_t job_queue_destroy(job_queue *jq);
/* Operations */
static pjmedia_vid_dev_factory_op factory_op =
{
&sdl_factory_init,
&sdl_factory_destroy,
&sdl_factory_get_dev_count,
&sdl_factory_get_dev_info,
&sdl_factory_default_param,
&sdl_factory_create_stream,
&sdl_factory_refresh
};
static pjmedia_vid_dev_stream_op stream_op =
{
&sdl_stream_get_param,
&sdl_stream_get_cap,
&sdl_stream_set_cap,
&sdl_stream_start,
NULL,
&sdl_stream_put_frame,
&sdl_stream_stop,
&sdl_stream_destroy
};
/*
* Util
*/
static void sdl_log_err(const char *op)
{
PJ_LOG(1,(THIS_FILE, "%s error: %s", op, SDL_GetError()));
}
/****************************************************************************
* Factory operations
*/
/*
* Init sdl_ video driver.
*/
pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf)
{
struct sdl_factory *f;
pj_pool_t *pool;
pool = pj_pool_create(pf, "sdl video", 4000, 4000, NULL);
f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory);
f->pf = pf;
f->pool = pool;
f->base.op = &factory_op;
return &f->base;
}
static pj_status_t sdl_init(void * data)
{
PJ_UNUSED_ARG(data);
#if SDL_VERSION_ATLEAST(2,0,2)
/* Since SDL 2.0.2, screensaver is disabled by default, to maintain
* the existing behavior, let's enable screensaver.
*/
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
#endif
if (SDL_Init(SDL_INIT_VIDEO)) {
sdl_log_err("SDL_Init()");
return PJMEDIA_EVID_INIT;
}
return PJ_SUCCESS;
}
static struct sdl_stream* find_stream(struct sdl_factory *sf,
Uint32 windowID,
pjmedia_event *pevent)
{
struct stream_list *it, *itBegin;
struct sdl_stream *strm = NULL;
itBegin = &sf->streams;
for (it = itBegin->next; it != itBegin; it = it->next) {
if (SDL_GetWindowID(it->stream->window) == windowID)
{
strm = it->stream;
break;
}
}
if (strm)
pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
strm);
return strm;
}
static pj_status_t handle_event(void *data)
{
struct sdl_factory *sf = (struct sdl_factory*)data;
SDL_Event sevent;
if (!pj_thread_is_registered())
pj_thread_register("sdl_ev", sf->thread_desc, &sf->ev_thread);
while (SDL_PollEvent(&sevent)) {
struct sdl_stream *strm = NULL;
pjmedia_event pevent;
pj_mutex_lock(sf->mutex);
pevent.type = PJMEDIA_EVENT_NONE;
switch(sevent.type) {
case SDL_MOUSEBUTTONDOWN:
strm = find_stream(sf, sevent.button.windowID, &pevent);
pevent.type = PJMEDIA_EVENT_MOUSE_BTN_DOWN;
break;
case SDL_WINDOWEVENT:
strm = find_stream(sf, sevent.window.windowID, &pevent);
switch (sevent.window.event) {
case SDL_WINDOWEVENT_RESIZED:
pevent.type = PJMEDIA_EVENT_WND_RESIZED;
pevent.data.wnd_resized.new_size.w =
sevent.window.data1;
pevent.data.wnd_resized.new_size.h =
sevent.window.data2;
break;
case SDL_WINDOWEVENT_CLOSE:
pevent.type = PJMEDIA_EVENT_WND_CLOSING;
break;
}
break;
default:
break;
}
if (strm && pevent.type != PJMEDIA_EVENT_NONE) {
pj_status_t status;
pjmedia_event_publish(NULL, strm, &pevent, 0);
switch (pevent.type) {
case PJMEDIA_EVENT_WND_RESIZED:
status = resize_disp(strm, &pevent.data.wnd_resized.new_size);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed resizing the display."));
}
break;
case PJMEDIA_EVENT_WND_CLOSING:
if (pevent.data.wnd_closing.cancel) {
/* Cancel the closing operation */
break;
}
/* Proceed to cleanup SDL. App must still call
* pjmedia_dev_stream_destroy() when getting WND_CLOSED
* event
*/
sdl_stream_stop(&strm->base);
sdl_destroy_all(strm);
pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED,
&strm->last_ts, strm);
pjmedia_event_publish(NULL, strm, &pevent, 0);
/*
* Note: don't access the stream after this point, it
* might have been destroyed
*/
break;
default:
/* Just to prevent gcc warning about unused enums */
break;
}
}
pj_mutex_unlock(sf->mutex);
}
return PJ_SUCCESS;
}
static int sdl_ev_thread(void *data)
{
struct sdl_factory *sf = (struct sdl_factory*)data;
while(1) {
pj_status_t status;
pj_mutex_lock(sf->mutex);
if (pj_list_empty(&sf->streams)) {
pj_mutex_unlock(sf->mutex);
/* Wait until there is any stream. */
pj_sem_wait(sf->sem);
} else
pj_mutex_unlock(sf->mutex);
if (sf->is_quitting)
break;
job_queue_post_job(sf->jq, handle_event, sf, 0, &status);
pj_thread_sleep(50);
}
return 0;
}
static pj_status_t sdl_quit(void *data)
{
PJ_UNUSED_ARG(data);
SDL_Quit();
return PJ_SUCCESS;
}
/* API: init factory */
static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
struct sdl_dev_info *ddi;
unsigned i, j;
pj_status_t status;
SDL_version version;
pj_list_init(&sf->streams);
status = job_queue_create(sf->pool, &sf->jq);
if (status != PJ_SUCCESS)
return PJMEDIA_EVID_INIT;
job_queue_post_job(sf->jq, sdl_init, NULL, 0, &status);
if (status != PJ_SUCCESS)
return status;
status = pj_mutex_create_recursive(sf->pool, "sdl_factory",
&sf->mutex);
if (status != PJ_SUCCESS)
return status;
status = pj_sem_create(sf->pool, NULL, 0, 1, &sf->sem);
if (status != PJ_SUCCESS)
return status;
/* Create event handler thread. */
status = pj_thread_create(sf->pool, "sdl_thread", sdl_ev_thread,
sf, 0, 0, &sf->sdl_thread);
if (status != PJ_SUCCESS)
return status;
sf->dev_count = 1;
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
sf->dev_count++;
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
sf->dev_info = (struct sdl_dev_info*)
pj_pool_calloc(sf->pool, sf->dev_count,
sizeof(struct sdl_dev_info));
ddi = &sf->dev_info[0];
pj_bzero(ddi, sizeof(*ddi));
strncpy(ddi->info.name, "SDL renderer", sizeof(ddi->info.name));
ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
ddi = &sf->dev_info[OPENGL_DEV_IDX];
pj_bzero(ddi, sizeof(*ddi));
strncpy(ddi->info.name, "SDL openGL renderer", sizeof(ddi->info.name));
ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
ddi->info.fmt_cnt = 1;
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
for (i = 0; i < sf->dev_count; i++) {
ddi = &sf->dev_info[i];
strncpy(ddi->info.driver, "SDL", sizeof(ddi->info.driver));
ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
ddi->info.dir = PJMEDIA_DIR_RENDER;
ddi->info.has_callback = PJ_FALSE;
ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT |
PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
for (j = 0; j < ddi->info.fmt_cnt; j++) {
pjmedia_format *fmt = &ddi->info.fmt[j];
pjmedia_format_init_video(fmt, sdl_fmts[j].fmt_id,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
}
}
SDL_VERSION(&version);
PJ_LOG(4, (THIS_FILE, "SDL %d.%d initialized",
version.major, version.minor));
return PJ_SUCCESS;
}
/* API: destroy factory */
static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
pj_pool_t *pool = sf->pool;
pj_status_t status;
pj_assert(pj_list_empty(&sf->streams));
sf->is_quitting = PJ_TRUE;
if (sf->sdl_thread) {
pj_sem_post(sf->sem);
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
/* To prevent pj_thread_join() of getting stuck if we are in
* the main thread and we haven't finished processing the job
* posted by sdl_thread.
*/
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
#endif
pj_thread_join(sf->sdl_thread);
pj_thread_destroy(sf->sdl_thread);
}
if (sf->mutex) {
pj_mutex_destroy(sf->mutex);
sf->mutex = NULL;
}
if (sf->sem) {
pj_sem_destroy(sf->sem);
sf->sem = NULL;
}
job_queue_post_job(sf->jq, sdl_quit, NULL, 0, &status);
job_queue_destroy(sf->jq);
sf->pool = NULL;
pj_pool_release(pool);
return PJ_SUCCESS;
}
/* API: refresh the list of devices */
static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f)
{
PJ_UNUSED_ARG(f);
return PJ_SUCCESS;
}
/* API: get number of devices */
static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
return sf->dev_count;
}
/* API: get device info */
static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_info *info)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info));
return PJ_SUCCESS;
}
/* API: create default device parameter */
static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
pjmedia_vid_dev_factory *f,
unsigned index,
pjmedia_vid_dev_param *param)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
struct sdl_dev_info *di = &sf->dev_info[index];
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
PJ_UNUSED_ARG(pool);
pj_bzero(param, sizeof(*param));
param->dir = PJMEDIA_DIR_RENDER;
param->rend_id = index;
param->cap_id = PJMEDIA_VID_INVALID_DEV;
/* Set the device capabilities here */
param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
param->fmt.type = PJMEDIA_TYPE_VIDEO;
param->clock_rate = DEFAULT_CLOCK_RATE;
pj_memcpy(&param->fmt, &di->info.fmt[0], sizeof(param->fmt));
return PJ_SUCCESS;
}
static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
{
unsigned i;
for (i = 0; i < PJ_ARRAY_SIZE(sdl_fmts); i++) {
if (sdl_fmts[i].fmt_id == id)
return &sdl_fmts[i];
}
return NULL;
}
static pj_status_t sdl_destroy(void *data)
{
struct sdl_stream *strm = (struct sdl_stream *)data;
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->texture) {
glDeleteTextures(1, &strm->texture);
strm->texture = 0;
}
if (strm->gl_context) {
SDL_GL_DeleteContext(strm->gl_context);
strm->gl_context = NULL;
}
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
if (strm->scr_tex) {
SDL_DestroyTexture(strm->scr_tex);
strm->scr_tex = NULL;
}
if (strm->renderer) {
SDL_DestroyRenderer(strm->renderer);
strm->renderer = NULL;
}
return PJ_SUCCESS;
}
static pj_status_t sdl_destroy_all(void *data)
{
struct sdl_stream *strm = (struct sdl_stream *)data;
sdl_destroy(data);
#if !defined(TARGET_OS_IPHONE) || TARGET_OS_IPHONE == 0
if (strm->window &&
!(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW))
{
SDL_DestroyWindow(strm->window);
}
strm->window = NULL;
#endif /* TARGET_OS_IPHONE */
return PJ_SUCCESS;
}
static pj_status_t sdl_create_window(struct sdl_stream *strm,
pj_bool_t use_app_win,
Uint32 sdl_format,
pjmedia_vid_dev_hwnd *hwnd)
{
if (!strm->window) {
Uint32 flags = 0;
if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
if (!(strm->param.window_flags & PJMEDIA_VID_DEV_WND_BORDER))
flags |= SDL_WINDOW_BORDERLESS;
if (strm->param.window_flags & PJMEDIA_VID_DEV_WND_RESIZABLE)
flags |= SDL_WINDOW_RESIZABLE;
} else {
flags |= SDL_WINDOW_BORDERLESS;
}
if (!((strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) &&
strm->param.window_hide))
{
flags |= SDL_WINDOW_SHOWN;
} else {
flags &= ~SDL_WINDOW_SHOWN;
flags |= SDL_WINDOW_HIDDEN;
}
if ((strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN) &&
strm->param.window_fullscreen)
{
if (strm->param.window_fullscreen == PJMEDIA_VID_DEV_FULLSCREEN)
flags |= SDL_WINDOW_FULLSCREEN;
else
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->param.rend_id == OPENGL_DEV_IDX)
flags |= SDL_WINDOW_OPENGL;
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
if (use_app_win) {
/* Use the window supplied by the application. */
strm->window = SDL_CreateWindowFrom(hwnd->info.window);
if (!strm->window) {
sdl_log_err("SDL_CreateWindowFrom()");
return PJMEDIA_EVID_SYSERR;
}
} else {
int x, y;
x = y = SDL_WINDOWPOS_CENTERED;
if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
x = strm->param.window_pos.x;
y = strm->param.window_pos.y;
}
/* Create the window where we will draw. */
strm->window = SDL_CreateWindow("pjmedia-SDL video",
x, y,
strm->param.disp_size.w,
strm->param.disp_size.h,
flags);
if (!strm->window) {
sdl_log_err("SDL_CreateWindow()");
return PJMEDIA_EVID_SYSERR;
}
}
}
/**
* We must call SDL_CreateRenderer in order for draw calls to
* affect this window.
*/
strm->renderer = SDL_CreateRenderer(strm->window, -1, 0);
if (!strm->renderer) {
sdl_log_err("SDL_CreateRenderer()");
return PJMEDIA_EVID_SYSERR;
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->param.rend_id == OPENGL_DEV_IDX) {
strm->gl_context = SDL_GL_CreateContext(strm->window);
if (!strm->gl_context) {
sdl_log_err("SDL_GL_CreateContext()");
return PJMEDIA_EVID_SYSERR;
}
SDL_GL_MakeCurrent(strm->window, strm->gl_context);
/* Init some OpenGL settings */
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
/* Init the viewport */
glViewport(0, 0, strm->param.disp_size.w, strm->param.disp_size.h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)strm->param.disp_size.w,
(GLdouble)strm->param.disp_size.h, 0.0, 0.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
/* Create a texture */
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenTextures(1, &strm->texture);
if (!strm->texture)
return PJMEDIA_EVID_SYSERR;
} else
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
{
strm->scr_tex = SDL_CreateTexture(strm->renderer, sdl_format,
SDL_TEXTUREACCESS_STREAMING,
strm->rect.w, strm->rect.h);
if (strm->scr_tex == NULL) {
sdl_log_err("SDL_CreateTexture()");
return PJMEDIA_EVID_SYSERR;
}
strm->pitch = strm->rect.w * SDL_BYTESPERPIXEL(sdl_format);
}
return PJ_SUCCESS;
}
static pj_status_t sdl_create_rend(struct sdl_stream * strm,
pjmedia_format *fmt)
{
sdl_fmt_info *sdl_info;
const pjmedia_video_format_info *vfi;
pjmedia_video_format_detail *vfd;
sdl_info = get_sdl_format_info(fmt->id);
vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
fmt->id);
if (!vfi || !sdl_info)
return PJMEDIA_EVID_BADFORMAT;
strm->vafp.size = fmt->det.vid.size;
strm->vafp.buffer = NULL;
if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS)
return PJMEDIA_EVID_BADFORMAT;
vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
strm->rect.x = strm->rect.y = 0;
strm->rect.w = (Uint16)vfd->size.w;
strm->rect.h = (Uint16)vfd->size.h;
if (strm->param.disp_size.w == 0)
strm->param.disp_size.w = strm->rect.w;
if (strm->param.disp_size.h == 0)
strm->param.disp_size.h = strm->rect.h;
strm->dstrect.x = strm->dstrect.y = 0;
strm->dstrect.w = (Uint16)strm->param.disp_size.w;
strm->dstrect.h = (Uint16)strm->param.disp_size.h;
sdl_destroy(strm);
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->param.rend_id == OPENGL_DEV_IDX) {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
}
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
return sdl_create_window(strm,
(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW),
sdl_info->sdl_format,
&strm->param.window);
}
static pj_status_t sdl_create(void *data)
{
struct sdl_stream *strm = (struct sdl_stream *)data;
return sdl_create_rend(strm, &strm->param.fmt);
}
static pj_status_t resize_disp(struct sdl_stream *strm,
pjmedia_rect_size *new_disp_size)
{
pj_memcpy(&strm->param.disp_size, new_disp_size,
sizeof(strm->param.disp_size));
if (strm->scr_tex) {
strm->dstrect.x = strm->dstrect.y = 0;
strm->dstrect.w = (Uint16)strm->param.disp_size.w;
strm->dstrect.h = (Uint16)strm->param.disp_size.h;
SDL_RenderSetViewport(strm->renderer, &strm->dstrect);
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
else if (strm->param.rend_id == OPENGL_DEV_IDX) {
sdl_create_rend(strm, &strm->param.fmt);
}
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
return PJ_SUCCESS;
}
static pj_status_t change_format(struct sdl_stream *strm,
pjmedia_format *new_fmt)
{
pj_status_t status;
/* Recreate SDL renderer */
status = sdl_create_rend(strm, (new_fmt? new_fmt :
&strm->param.fmt));
if (status == PJ_SUCCESS && new_fmt)
pjmedia_format_copy(&strm->param.fmt, new_fmt);
return status;
}
static pj_status_t put_frame(void *data)
{
struct sdl_stream *stream = (struct sdl_stream *)data;
const pjmedia_frame *frame = stream->frame;
if (stream->scr_tex) {
SDL_UpdateTexture(stream->scr_tex, NULL, frame->buf, stream->pitch);
SDL_RenderClear(stream->renderer);
SDL_RenderCopy(stream->renderer, stream->scr_tex,
&stream->rect, &stream->dstrect);
SDL_RenderPresent(stream->renderer);
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
else if (stream->param.rend_id == OPENGL_DEV_IDX && stream->texture) {
glBindTexture(GL_TEXTURE_2D, stream->texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
stream->rect.w, stream->rect.h, 0,
GL_RGBA, GL_UNSIGNED_BYTE, frame->buf);
glBegin(GL_TRIANGLE_STRIP);
glTexCoord2f(0, 0); glVertex2i(0, 0);
glTexCoord2f(1, 0); glVertex2i(stream->param.disp_size.w, 0);
glTexCoord2f(0, 1); glVertex2i(0, stream->param.disp_size.h);
glTexCoord2f(1, 1);
glVertex2i(stream->param.disp_size.w, stream->param.disp_size.h);
glEnd();
SDL_GL_SwapWindow(stream->window);
}
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
return PJ_SUCCESS;
}
/* API: Put frame from stream */
static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
const pjmedia_frame *frame)
{
struct sdl_stream *stream = (struct sdl_stream*)strm;
pj_status_t status;
stream->last_ts.u64 = frame->timestamp.u64;
/* Video conference just trying to send heart beat for updating timestamp
* or keep-alive, this port doesn't need any, just ignore.
*/
if (frame->size==0 || frame->buf==NULL)
return PJ_SUCCESS;
if (frame->size < stream->vafp.framebytes)
return PJ_ETOOSMALL;
if (!stream->is_running)
return PJ_EINVALIDOP;
stream->frame = frame;
job_queue_post_job(stream->sf->jq, put_frame, strm, 0, &status);
return status;
}
/* API: create stream */
static pj_status_t sdl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_dev_param *param,
const pjmedia_vid_dev_cb *cb,
void *user_data,
pjmedia_vid_dev_stream **p_vid_strm)
{
struct sdl_factory *sf = (struct sdl_factory*)f;
pj_pool_t *pool;
struct sdl_stream *strm;
pj_status_t status;
PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
/* Create and Initialize stream descriptor */
pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL);
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
strm = PJ_POOL_ZALLOC_T(pool, struct sdl_stream);
pj_memcpy(&strm->param, param, sizeof(*param));
strm->pool = pool;
strm->sf = sf;
pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
pj_list_init(&strm->list_entry);
strm->list_entry.stream = strm;
strm->user_data = user_data;
/* Create render stream here */
job_queue_post_job(sf->jq, sdl_create, strm, 0, &status);
if (status != PJ_SUCCESS) {
goto on_error;
}
pj_mutex_lock(strm->sf->mutex);
if (pj_list_empty(&strm->sf->streams))
pj_sem_post(strm->sf->sem);
pj_list_insert_after(&strm->sf->streams, &strm->list_entry);
pj_mutex_unlock(strm->sf->mutex);
/* Done */
strm->base.op = &stream_op;
*p_vid_strm = &strm->base;
return PJ_SUCCESS;
on_error:
sdl_stream_destroy(&strm->base);
return status;
}
/* API: Get stream info. */
static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_param *pi)
{
struct sdl_stream *strm = (struct sdl_stream*)s;
PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
pj_memcpy(pi, &strm->param, sizeof(*pi));
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
&pi->window) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
}
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION,
&pi->window_pos) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION;
}
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE,
&pi->disp_size) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
}
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
&pi->window_hide) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
}
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
&pi->window_flags) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
}
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN,
&pi->window_fullscreen) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN;
}
return PJ_SUCCESS;
}
struct strm_cap {
struct sdl_stream *strm;
pjmedia_vid_dev_cap cap;
union {
void *pval;
const void *cpval;
} pval;
};
static pj_status_t get_cap(void *data)
{
struct strm_cap *scap = (struct strm_cap *)data;
struct sdl_stream *strm = scap->strm;
pjmedia_vid_dev_cap cap = scap->cap;
void *pval = scap->pval.pval;
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
if (SDL_GetWindowWMInfo(strm->window, &info)) {
pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
if (0) { }
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
else if (info.subsystem == SDL_SYSWM_WINDOWS) {
wnd->type = PJMEDIA_VID_DEV_HWND_TYPE_WINDOWS;
wnd->info.win.hwnd = (void *)info.info.win.window;
}
#endif
#if defined(SDL_VIDEO_DRIVER_X11)
else if (info.subsystem == SDL_SYSWM_X11) {
wnd->info.x11.window = (void *)info.info.x11.window;
wnd->info.x11.display = (void *)info.info.x11.display;
}
#endif
#if defined(SDL_VIDEO_DRIVER_COCOA)
else if (info.subsystem == SDL_SYSWM_COCOA) {
wnd->info.cocoa.window = (void *)info.info.cocoa.window;
}
#endif
#if defined(SDL_VIDEO_DRIVER_UIKIT)
else if (info.subsystem == SDL_SYSWM_UIKIT) {
wnd->info.ios.window = (void *)info.info.uikit.window;
}
#endif
else {
return PJMEDIA_EVID_INVCAP;
}
return PJ_SUCCESS;
} else
return PJMEDIA_EVID_INVCAP;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
SDL_GetWindowPosition(strm->window, &((pjmedia_coord *)pval)->x,
&((pjmedia_coord *)pval)->y);
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
SDL_GetWindowSize(strm->window, (int *)&((pjmedia_rect_size *)pval)->w,
(int *)&((pjmedia_rect_size *)pval)->h);
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
Uint32 flag = SDL_GetWindowFlags(strm->window);
*((pj_bool_t *)pval) = (flag & SDL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE;
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
Uint32 flag = SDL_GetWindowFlags(strm->window);
unsigned *wnd_flags = (unsigned *)pval;
if (!(flag & SDL_WINDOW_BORDERLESS))
*wnd_flags |= PJMEDIA_VID_DEV_WND_BORDER;
if (flag & SDL_WINDOW_RESIZABLE)
*wnd_flags |= PJMEDIA_VID_DEV_WND_RESIZABLE;
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN) {
Uint32 flag = SDL_GetWindowFlags(strm->window);
pjmedia_vid_dev_fullscreen_flag val = PJMEDIA_VID_DEV_WINDOWED;
if ((flag & SDL_WINDOW_FULLSCREEN_DESKTOP) ==
SDL_WINDOW_FULLSCREEN_DESKTOP)
{
val = PJMEDIA_VID_DEV_FULLSCREEN_DESKTOP;
} else if ((flag & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN) {
val = PJMEDIA_VID_DEV_FULLSCREEN;
}
*((pjmedia_vid_dev_fullscreen_flag*)pval) = val;
return PJ_SUCCESS;
}
return PJMEDIA_EVID_INVCAP;
}
/* API: get capability */
static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_cap cap,
void *pval)
{
struct sdl_stream *strm = (struct sdl_stream*)s;
struct strm_cap scap;
pj_status_t status;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
scap.strm = strm;
scap.cap = cap;
scap.pval.pval = pval;
job_queue_post_job(strm->sf->jq, get_cap, &scap, 0, &status);
return status;
}
static pj_status_t set_cap(void *data)
{
struct strm_cap *scap = (struct strm_cap *)data;
struct sdl_stream *strm = scap->strm;
pjmedia_vid_dev_cap cap = scap->cap;
const void *pval = scap->pval.cpval;
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
/**
* Setting window's position when the window is hidden also sets
* the window's flag to shown (while the window is, actually,
* still hidden). This causes problems later when setting/querying
* the window's visibility.
* See ticket #1429 (https://github.com/pjsip/pjproject/issues/1429)
*/
Uint32 flag = SDL_GetWindowFlags(strm->window);
if (flag & SDL_WINDOW_HIDDEN)
SDL_ShowWindow(strm->window);
SDL_SetWindowPosition(strm->window, ((pjmedia_coord *)pval)->x,
((pjmedia_coord *)pval)->y);
if (flag & SDL_WINDOW_HIDDEN)
SDL_HideWindow(strm->window);
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
if (*(pj_bool_t *)pval)
SDL_HideWindow(strm->window);
else
SDL_ShowWindow(strm->window);
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
pj_status_t status;
status = change_format(strm, (pjmedia_format *)pval);
if (status != PJ_SUCCESS) {
pj_status_t status_;
/**
* Failed to change the output format. Try to revert
* to its original format.
*/
status_ = change_format(strm, &strm->param.fmt);
if (status_ != PJ_SUCCESS) {
/**
* This means that we failed to revert to our
* original state!
*/
status = PJMEDIA_EVID_ERR;
}
}
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
pjmedia_rect_size *new_size = (pjmedia_rect_size *)pval;
Uint32 flag = SDL_GetWindowFlags(strm->window);
pj_status_t status;
/**
* Exit full-screen if engaged, since resizing while in full-screen is
* not supported.
*/
if (flag & SDL_WINDOW_FULLSCREEN_DESKTOP)
SDL_SetWindowFullscreen(strm->window, 0);
SDL_SetWindowSize(strm->window, new_size->w, new_size->h);
status = resize_disp(strm, new_size);
/* Restore full-screen if it was engaged. */
if (flag & SDL_WINDOW_FULLSCREEN_DESKTOP)
SDL_SetWindowFullscreen(strm->window, SDL_WINDOW_FULLSCREEN_DESKTOP);
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
pjmedia_vid_dev_hwnd *hwnd = (pjmedia_vid_dev_hwnd*)pval;
pj_status_t status = PJ_SUCCESS;
sdl_fmt_info *sdl_info = get_sdl_format_info(strm->param.fmt.id);
/* Re-init SDL */
status = sdl_destroy_all(strm);
if (status != PJ_SUCCESS)
return status;
status = sdl_create_window(strm, PJ_TRUE, sdl_info->sdl_format, hwnd);
PJ_PERROR(4, (THIS_FILE, status,
"Re-initializing SDL with native window %d",
hwnd->info.window));
return status;
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_FULLSCREEN) {
Uint32 flag;
pjmedia_vid_dev_fullscreen_flag val =
*(pjmedia_vid_dev_fullscreen_flag*)pval;
flag = SDL_GetWindowFlags(strm->window);
if (val == PJMEDIA_VID_DEV_FULLSCREEN_DESKTOP)
flag |= SDL_WINDOW_FULLSCREEN_DESKTOP;
else if (val == PJMEDIA_VID_DEV_FULLSCREEN)
flag |= SDL_WINDOW_FULLSCREEN;
else
flag &= (~SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_SetWindowFullscreen(strm->window, flag);
/* Trying to restore the border after returning from fullscreen,
* unfortunately not sure how to put back the resizable flag.
*/
if ((flag & SDL_WINDOW_FULLSCREEN)==0 &&
(flag & SDL_WINDOW_BORDERLESS)==0)
{
SDL_SetWindowBordered(strm->window, SDL_FALSE);
SDL_SetWindowBordered(strm->window, SDL_TRUE);
}
return PJ_SUCCESS;
}
return PJMEDIA_EVID_INVCAP;
}
/* API: set capability */
static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *s,
pjmedia_vid_dev_cap cap,
const void *pval)
{
struct sdl_stream *strm = (struct sdl_stream*)s;
struct strm_cap scap;
pj_status_t status;
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
scap.strm = strm;
scap.cap = cap;
scap.pval.cpval = pval;
job_queue_post_job(strm->sf->jq, set_cap, &scap, 0, &status);
return status;
}
/* API: Start stream. */
static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm)
{
struct sdl_stream *stream = (struct sdl_stream*)strm;
PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
stream->is_running = PJ_TRUE;
return PJ_SUCCESS;
}
/* API: Stop stream. */
static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm)
{
struct sdl_stream *stream = (struct sdl_stream*)strm;
PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
stream->is_running = PJ_FALSE;
return PJ_SUCCESS;
}
/* API: Destroy stream. */
static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm)
{
struct sdl_stream *stream = (struct sdl_stream*)strm;
pj_status_t status;
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
sdl_stream_stop(strm);
job_queue_post_job(stream->sf->jq, sdl_destroy_all, strm, 0, &status);
if (status != PJ_SUCCESS)
return status;
pj_mutex_lock(stream->sf->mutex);
if (!pj_list_empty(&stream->list_entry))
pj_list_erase(&stream->list_entry);
pj_mutex_unlock(stream->sf->mutex);
pj_pool_release(stream->pool);
return PJ_SUCCESS;
}
/****************************************************************************
* Job queue implementation
*/
#if PJ_DARWINOS==0
static int job_thread(void * data)
{
job_queue *jq = (job_queue *)data;
while (1) {
job *jb;
/* Wait until there is a job. */
pj_sem_wait(jq->sem);
/* Make sure there is no pending jobs before we quit. */
if (jq->is_quitting && jq->head == jq->tail && !jq->is_full)
break;
jb = jq->jobs[jq->head];
jb->retval = (*jb->func)(jb->data);
/* If job queue is full and we already finish all the pending
* jobs, increase the size.
*/
if (jq->is_full && ((jq->head + 1) % jq->size == jq->tail)) {
unsigned i, head;
pj_status_t status;
if (jq->old_sem) {
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
pj_sem_destroy(jq->old_sem[i]);
}
}
jq->old_sem = jq->job_sem;
/* Double the job queue size. */
jq->size *= JOB_QUEUE_INC_FACTOR;
pj_sem_destroy(jq->sem);
status = pj_sem_create(jq->pool, "thread_sem", 0, jq->size + 1,
&jq->sem);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed growing SDL job queue size."));
return 0;
}
jq->jobs = (job **)pj_pool_calloc(jq->pool, jq->size,
sizeof(job *));
jq->job_sem = (pj_sem_t **) pj_pool_calloc(jq->pool, jq->size,
sizeof(pj_sem_t *));
for (i = 0; i < jq->size; i++) {
status = pj_sem_create(jq->pool, "job_sem", 0, 1,
&jq->job_sem[i]);
if (status != PJ_SUCCESS) {
PJ_PERROR(3, (THIS_FILE, status,
"Failed growing SDL job queue size."));
return 0;
}
}
jq->is_full = PJ_FALSE;
head = jq->head;
jq->head = jq->tail = 0;
pj_sem_post(jq->old_sem[head]);
} else {
pj_sem_post(jq->job_sem[jq->head]);
jq->head = (jq->head + 1) % jq->size;
}
}
return 0;
}
#endif
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
{
unsigned i;
pj_status_t status;
job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
jq->pool = pool;
jq->size = INITIAL_MAX_JOBS;
status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
if (status != PJ_SUCCESS)
goto on_error;
jq->jobs = (job **)pj_pool_calloc(pool, jq->size, sizeof(job *));
jq->job_sem = (pj_sem_t **) pj_pool_calloc(pool, jq->size,
sizeof(pj_sem_t *));
for (i = 0; i < jq->size; i++) {
status = pj_sem_create(pool, "job_sem", 0, 1, &jq->job_sem[i]);
if (status != PJ_SUCCESS)
goto on_error;
}
status = pj_mutex_create_recursive(pool, "job_mutex", &jq->mutex);
if (status != PJ_SUCCESS)
goto on_error;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
PJ_UNUSED_ARG(status);
#else
status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
&jq->thread);
if (status != PJ_SUCCESS)
goto on_error;
#endif /* PJ_DARWINOS */
*pjq = jq;
return PJ_SUCCESS;
on_error:
job_queue_destroy(jq);
return status;
}
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
void *data, unsigned flags,
pj_status_t *retval)
{
job jb;
int tail;
if (jq->is_quitting)
return PJ_EBUSY;
jb.func = func;
jb.data = data;
jb.flags = flags;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
PJ_UNUSED_ARG(tail);
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
JQDelegate *jqd = [[JQDelegate alloc]init];
jqd->pjob = &jb;
[jqd performSelectorOnMainThread:@selector(run_job)
withObject:nil waitUntilDone:YES];
[jqd release];
[apool release];
#else /* PJ_DARWINOS */
pj_mutex_lock(jq->mutex);
jq->jobs[jq->tail] = &jb;
tail = jq->tail;
jq->tail = (jq->tail + 1) % jq->size;
if (jq->tail == jq->head) {
jq->is_full = PJ_TRUE;
PJ_LOG(4, (THIS_FILE, "SDL job queue is full, increasing "
"the queue size."));
pj_sem_post(jq->sem);
/* Wait until our posted job is completed. */
pj_sem_wait(jq->job_sem[tail]);
pj_mutex_unlock(jq->mutex);
} else {
pj_mutex_unlock(jq->mutex);
pj_sem_post(jq->sem);
/* Wait until our posted job is completed. */
pj_sem_wait(jq->job_sem[tail]);
}
#endif /* PJ_DARWINOS */
*retval = jb.retval;
return PJ_SUCCESS;
}
static pj_status_t job_queue_destroy(job_queue *jq)
{
unsigned i;
jq->is_quitting = PJ_TRUE;
if (jq->thread) {
pj_sem_post(jq->sem);
pj_thread_join(jq->thread);
pj_thread_destroy(jq->thread);
}
if (jq->sem) {
pj_sem_destroy(jq->sem);
jq->sem = NULL;
}
for (i = 0; i < jq->size; i++) {
if (jq->job_sem[i]) {
pj_sem_destroy(jq->job_sem[i]);
jq->job_sem[i] = NULL;
}
}
if (jq->old_sem) {
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
if (jq->old_sem[i]) {
pj_sem_destroy(jq->old_sem[i]);
jq->old_sem[i] = NULL;
}
}
}
if (jq->mutex) {
pj_mutex_destroy(jq->mutex);
jq->mutex = NULL;
}
return PJ_SUCCESS;
}
#ifdef _MSC_VER
# if defined(PJMEDIA_SDL_LIB)
# pragma comment( lib, PJMEDIA_SDL_LIB)
# elif SDL_VERSION_ATLEAST(2,0,0)
# pragma comment( lib, "sdl2.lib")
# elif SDL_VERSION_ATLEAST(1,3,0)
# pragma comment( lib, "sdl.lib")
# endif
# if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
# pragma comment(lib, "OpenGL32.lib")
# endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
#endif /* _MSC_VER */
#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */