1558 lines
49 KiB
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(¶m->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 */
|