1
/* $Id: sdl_dev.c 4157 2012-06-06 09:37:25Z nanang $ */
3
* Copyright (C) 2008-2011 Teluu Inc. (http://www.teluu.com)
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
#include <pjmedia-videodev/videodev_imp.h>
20
#include <pj/assert.h>
24
#if defined(PJMEDIA_VIDEO_DEV_HAS_SDL) && PJMEDIA_VIDEO_DEV_HAS_SDL != 0
27
#include <SDL_syswm.h>
28
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
29
# include "SDL_opengl.h"
30
# define OPENGL_DEV_IDX 1
31
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
33
#if !(SDL_VERSION_ATLEAST(1,3,0))
34
# error "SDL 1.3 or later is required"
37
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
38
# include "TargetConditionals.h"
39
# include <Foundation/Foundation.h>
42
#define THIS_FILE "sdl_dev.c"
43
#define DEFAULT_CLOCK_RATE 90000
44
#define DEFAULT_WIDTH 640
45
#define DEFAULT_HEIGHT 480
46
#define DEFAULT_FPS 25
48
typedef struct sdl_fmt_info
50
pjmedia_format_id fmt_id;
58
static sdl_fmt_info sdl_fmts[] =
61
{PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_RGBA8888,
62
0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
63
{PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_RGB24,
64
0xFF0000, 0xFF00, 0xFF, 0} ,
65
{PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_BGRA8888,
66
0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
67
#else /* PJ_IS_BIG_ENDIAN */
68
{PJMEDIA_FORMAT_RGBA, (Uint32)SDL_PIXELFORMAT_ABGR8888,
69
0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
70
{PJMEDIA_FORMAT_RGB24, (Uint32)SDL_PIXELFORMAT_BGR24,
71
0xFF, 0xFF00, 0xFF0000, 0} ,
72
{PJMEDIA_FORMAT_BGRA, (Uint32)SDL_PIXELFORMAT_ARGB8888,
73
0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
74
#endif /* PJ_IS_BIG_ENDIAN */
76
{PJMEDIA_FORMAT_DIB , (Uint32)SDL_PIXELFORMAT_RGB24,
77
0xFF0000, 0xFF00, 0xFF, 0} ,
79
{PJMEDIA_FORMAT_YUY2, SDL_PIXELFORMAT_YUY2, 0, 0, 0, 0} ,
80
{PJMEDIA_FORMAT_UYVY, SDL_PIXELFORMAT_UYVY, 0, 0, 0, 0} ,
81
{PJMEDIA_FORMAT_YVYU, SDL_PIXELFORMAT_YVYU, 0, 0, 0, 0} ,
82
{PJMEDIA_FORMAT_I420, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
83
{PJMEDIA_FORMAT_YV12, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0} ,
84
{PJMEDIA_FORMAT_I420JPEG, SDL_PIXELFORMAT_IYUV, 0, 0, 0, 0} ,
85
{PJMEDIA_FORMAT_I422JPEG, SDL_PIXELFORMAT_YV12, 0, 0, 0, 0}
88
/* sdl_ device info */
91
pjmedia_vid_dev_info info;
94
/* Linked list of streams */
97
PJ_DECL_LIST_MEMBER(struct stream_list);
98
struct sdl_stream *stream;
101
#define INITIAL_MAX_JOBS 64
102
#define JOB_QUEUE_INC_FACTOR 2
104
typedef pj_status_t (*job_func_ptr)(void *data);
113
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
114
@interface JQDelegate: NSObject
123
@implementation JQDelegate
126
pjob->retval = (*pjob->func)(pjob->data);
129
#endif /* PJ_DARWINOS */
131
typedef struct job_queue {
143
pj_bool_t is_quitting;
149
pjmedia_vid_dev_factory base;
154
struct sdl_dev_info *dev_info;
157
pj_thread_t *sdl_thread; /**< SDL thread. */
160
struct stream_list streams;
161
pj_bool_t is_quitting;
162
pj_thread_desc thread_desc;
163
pj_thread_t *ev_thread;
169
pjmedia_vid_dev_stream base; /**< Base stream */
170
pjmedia_vid_dev_param param; /**< Settings */
171
pj_pool_t *pool; /**< Memory pool. */
173
pjmedia_vid_dev_cb vid_cb; /**< Stream callback. */
174
void *user_data; /**< Application data. */
176
struct sdl_factory *sf;
177
const pjmedia_frame *frame;
178
pj_bool_t is_running;
179
pj_timestamp last_ts;
180
struct stream_list list_entry;
182
SDL_Window *window; /**< Display window. */
183
SDL_Renderer *renderer; /**< Display renderer. */
184
SDL_Texture *scr_tex; /**< Screen texture. */
185
int pitch; /**< Pitch value. */
186
SDL_Rect rect; /**< Frame rectangle. */
187
SDL_Rect dstrect; /**< Display rectangle. */
188
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
189
SDL_GLContext *gl_context;
191
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
193
pjmedia_video_apply_fmt_param vafp;
197
static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f);
198
static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f);
199
static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f);
200
static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f);
201
static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
203
pjmedia_vid_dev_info *info);
204
static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
205
pjmedia_vid_dev_factory *f,
207
pjmedia_vid_dev_param *param);
208
static pj_status_t sdl_factory_create_stream(
209
pjmedia_vid_dev_factory *f,
210
pjmedia_vid_dev_param *param,
211
const pjmedia_vid_dev_cb *cb,
213
pjmedia_vid_dev_stream **p_vid_strm);
215
static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *strm,
216
pjmedia_vid_dev_param *param);
217
static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *strm,
218
pjmedia_vid_dev_cap cap,
220
static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *strm,
221
pjmedia_vid_dev_cap cap,
223
static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
224
const pjmedia_frame *frame);
225
static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm);
226
static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm);
227
static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm);
229
static pj_status_t resize_disp(struct sdl_stream *strm,
230
pjmedia_rect_size *new_disp_size);
231
static pj_status_t sdl_destroy_all(void *data);
233
/* Job queue prototypes */
234
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq);
235
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
236
void *data, unsigned flags,
237
pj_status_t *retval);
238
static pj_status_t job_queue_destroy(job_queue *jq);
241
static pjmedia_vid_dev_factory_op factory_op =
244
&sdl_factory_destroy,
245
&sdl_factory_get_dev_count,
246
&sdl_factory_get_dev_info,
247
&sdl_factory_default_param,
248
&sdl_factory_create_stream,
252
static pjmedia_vid_dev_stream_op stream_op =
254
&sdl_stream_get_param,
259
&sdl_stream_put_frame,
265
/****************************************************************************
269
* Init sdl_ video driver.
271
pjmedia_vid_dev_factory* pjmedia_sdl_factory(pj_pool_factory *pf)
273
struct sdl_factory *f;
276
pool = pj_pool_create(pf, "sdl video", 1000, 1000, NULL);
277
f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory);
280
f->base.op = &factory_op;
285
static pj_status_t sdl_init(void * data)
289
if (SDL_Init(SDL_INIT_VIDEO)) {
290
PJ_LOG(3, (THIS_FILE, "Failed initializing SDL"));
291
return PJMEDIA_EVID_INIT;
297
static struct sdl_stream* find_stream(struct sdl_factory *sf,
299
pjmedia_event *pevent)
301
struct stream_list *it, *itBegin;
302
struct sdl_stream *strm = NULL;
304
itBegin = &sf->streams;
305
for (it = itBegin->next; it != itBegin; it = it->next) {
306
if (SDL_GetWindowID(it->stream->window) == windowID)
314
pjmedia_event_init(pevent, PJMEDIA_EVENT_NONE, &strm->last_ts,
320
static pj_status_t handle_event(void *data)
322
struct sdl_factory *sf = (struct sdl_factory*)data;
325
if (!pj_thread_is_registered())
326
pj_thread_register("sdl_ev", sf->thread_desc, &sf->ev_thread);
328
while (SDL_PollEvent(&sevent)) {
329
struct sdl_stream *strm = NULL;
330
pjmedia_event pevent;
332
pj_mutex_lock(sf->mutex);
333
pevent.type = PJMEDIA_EVENT_NONE;
334
switch(sevent.type) {
335
case SDL_MOUSEBUTTONDOWN:
336
strm = find_stream(sf, sevent.button.windowID, &pevent);
337
pevent.type = PJMEDIA_EVENT_MOUSE_BTN_DOWN;
339
case SDL_WINDOWEVENT:
340
strm = find_stream(sf, sevent.window.windowID, &pevent);
341
switch (sevent.window.event) {
342
case SDL_WINDOWEVENT_RESIZED:
343
pevent.type = PJMEDIA_EVENT_WND_RESIZED;
344
pevent.data.wnd_resized.new_size.w =
346
pevent.data.wnd_resized.new_size.h =
349
case SDL_WINDOWEVENT_CLOSE:
350
pevent.type = PJMEDIA_EVENT_WND_CLOSING;
358
if (strm && pevent.type != PJMEDIA_EVENT_NONE) {
361
pjmedia_event_publish(NULL, strm, &pevent, 0);
363
switch (pevent.type) {
364
case PJMEDIA_EVENT_WND_RESIZED:
365
status = resize_disp(strm, &pevent.data.wnd_resized.new_size);
366
if (status != PJ_SUCCESS)
367
PJ_LOG(3, (THIS_FILE, "Failed resizing the display."));
369
case PJMEDIA_EVENT_WND_CLOSING:
370
if (pevent.data.wnd_closing.cancel) {
371
/* Cancel the closing operation */
375
/* Proceed to cleanup SDL. App must still call
376
* pjmedia_dev_stream_destroy() when getting WND_CLOSED
379
sdl_stream_stop(&strm->base);
380
sdl_destroy_all(strm);
381
pjmedia_event_init(&pevent, PJMEDIA_EVENT_WND_CLOSED,
382
&strm->last_ts, strm);
383
pjmedia_event_publish(NULL, strm, &pevent, 0);
386
* Note: don't access the stream after this point, it
387
* might have been destroyed
391
/* Just to prevent gcc warning about unused enums */
396
pj_mutex_unlock(sf->mutex);
402
static int sdl_ev_thread(void *data)
404
struct sdl_factory *sf = (struct sdl_factory*)data;
409
pj_mutex_lock(sf->mutex);
410
if (pj_list_empty(&sf->streams)) {
411
pj_mutex_unlock(sf->mutex);
412
/* Wait until there is any stream. */
413
pj_sem_wait(sf->sem);
415
pj_mutex_unlock(sf->mutex);
420
job_queue_post_job(sf->jq, handle_event, sf, 0, &status);
428
static pj_status_t sdl_quit(void *data)
435
/* API: init factory */
436
static pj_status_t sdl_factory_init(pjmedia_vid_dev_factory *f)
438
struct sdl_factory *sf = (struct sdl_factory*)f;
439
struct sdl_dev_info *ddi;
444
status = job_queue_create(sf->pool, &sf->jq);
445
if (status != PJ_SUCCESS)
446
return PJMEDIA_EVID_INIT;
448
job_queue_post_job(sf->jq, sdl_init, NULL, 0, &status);
449
if (status != PJ_SUCCESS)
452
pj_list_init(&sf->streams);
453
status = pj_mutex_create_recursive(sf->pool, "sdl_factory",
455
if (status != PJ_SUCCESS)
458
status = pj_sem_create(sf->pool, NULL, 0, 1, &sf->sem);
459
if (status != PJ_SUCCESS)
462
/* Create event handler thread. */
463
status = pj_thread_create(sf->pool, "sdl_thread", sdl_ev_thread,
464
sf, 0, 0, &sf->sdl_thread);
465
if (status != PJ_SUCCESS)
469
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
471
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
472
sf->dev_info = (struct sdl_dev_info*)
473
pj_pool_calloc(sf->pool, sf->dev_count,
474
sizeof(struct sdl_dev_info));
476
ddi = &sf->dev_info[0];
477
pj_bzero(ddi, sizeof(*ddi));
478
strncpy(ddi->info.name, "SDL renderer", sizeof(ddi->info.name));
479
ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
480
ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
482
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
483
ddi = &sf->dev_info[OPENGL_DEV_IDX];
484
pj_bzero(ddi, sizeof(*ddi));
485
strncpy(ddi->info.name, "SDL openGL renderer", sizeof(ddi->info.name));
486
ddi->info.name[sizeof(ddi->info.name)-1] = '\0';
487
ddi->info.fmt_cnt = 1;
488
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
490
for (i = 0; i < sf->dev_count; i++) {
491
ddi = &sf->dev_info[i];
492
strncpy(ddi->info.driver, "SDL", sizeof(ddi->info.driver));
493
ddi->info.driver[sizeof(ddi->info.driver)-1] = '\0';
494
ddi->info.dir = PJMEDIA_DIR_RENDER;
495
ddi->info.has_callback = PJ_FALSE;
496
ddi->info.caps = PJMEDIA_VID_DEV_CAP_FORMAT |
497
PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
498
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
499
ddi->info.caps |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
501
for (j = 0; j < ddi->info.fmt_cnt; j++) {
502
pjmedia_format *fmt = &ddi->info.fmt[j];
503
pjmedia_format_init_video(fmt, sdl_fmts[j].fmt_id,
504
DEFAULT_WIDTH, DEFAULT_HEIGHT,
509
SDL_VERSION(&version);
510
PJ_LOG(4, (THIS_FILE, "SDL %d.%d initialized",
511
version.major, version.minor));
516
/* API: destroy factory */
517
static pj_status_t sdl_factory_destroy(pjmedia_vid_dev_factory *f)
519
struct sdl_factory *sf = (struct sdl_factory*)f;
520
pj_pool_t *pool = sf->pool;
523
pj_assert(pj_list_empty(&sf->streams));
525
sf->is_quitting = PJ_TRUE;
526
if (sf->sdl_thread) {
527
pj_sem_post(sf->sem);
528
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
529
/* To prevent pj_thread_join() of getting stuck if we are in
530
* the main thread and we haven't finished processing the job
531
* posted by sdl_thread.
533
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
535
pj_thread_join(sf->sdl_thread);
536
pj_thread_destroy(sf->sdl_thread);
540
pj_mutex_destroy(sf->mutex);
545
pj_sem_destroy(sf->sem);
549
job_queue_post_job(sf->jq, sdl_quit, NULL, 0, &status);
550
job_queue_destroy(sf->jq);
553
pj_pool_release(pool);
558
/* API: refresh the list of devices */
559
static pj_status_t sdl_factory_refresh(pjmedia_vid_dev_factory *f)
565
/* API: get number of devices */
566
static unsigned sdl_factory_get_dev_count(pjmedia_vid_dev_factory *f)
568
struct sdl_factory *sf = (struct sdl_factory*)f;
569
return sf->dev_count;
572
/* API: get device info */
573
static pj_status_t sdl_factory_get_dev_info(pjmedia_vid_dev_factory *f,
575
pjmedia_vid_dev_info *info)
577
struct sdl_factory *sf = (struct sdl_factory*)f;
579
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
581
pj_memcpy(info, &sf->dev_info[index].info, sizeof(*info));
586
/* API: create default device parameter */
587
static pj_status_t sdl_factory_default_param(pj_pool_t *pool,
588
pjmedia_vid_dev_factory *f,
590
pjmedia_vid_dev_param *param)
592
struct sdl_factory *sf = (struct sdl_factory*)f;
593
struct sdl_dev_info *di = &sf->dev_info[index];
595
PJ_ASSERT_RETURN(index < sf->dev_count, PJMEDIA_EVID_INVDEV);
599
pj_bzero(param, sizeof(*param));
600
param->dir = PJMEDIA_DIR_RENDER;
601
param->rend_id = index;
602
param->cap_id = PJMEDIA_VID_INVALID_DEV;
604
/* Set the device capabilities here */
605
param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
606
param->fmt.type = PJMEDIA_TYPE_VIDEO;
607
param->clock_rate = DEFAULT_CLOCK_RATE;
608
pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt));
613
static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
617
for (i = 0; i < sizeof(sdl_fmts)/sizeof(sdl_fmts[0]); i++) {
618
if (sdl_fmts[i].fmt_id == id)
625
static pj_status_t sdl_destroy(void *data)
627
struct sdl_stream *strm = (struct sdl_stream *)data;
629
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
631
glDeleteTextures(1, &strm->texture);
634
if (strm->gl_context) {
635
SDL_GL_DeleteContext(strm->gl_context);
636
strm->gl_context = NULL;
638
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
640
SDL_DestroyTexture(strm->scr_tex);
641
strm->scr_tex = NULL;
643
if (strm->renderer) {
644
SDL_DestroyRenderer(strm->renderer);
645
strm->renderer = NULL;
651
static pj_status_t sdl_destroy_all(void *data)
653
struct sdl_stream *strm = (struct sdl_stream *)data;
656
#if !defined(TARGET_OS_IPHONE) || TARGET_OS_IPHONE == 0
658
!(strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW))
660
SDL_DestroyWindow(strm->window);
663
#endif /* TARGET_OS_IPHONE */
668
static pj_status_t sdl_create_rend(struct sdl_stream * strm,
671
sdl_fmt_info *sdl_info;
672
const pjmedia_video_format_info *vfi;
673
pjmedia_video_format_detail *vfd;
675
sdl_info = get_sdl_format_info(fmt->id);
676
vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
678
if (!vfi || !sdl_info)
679
return PJMEDIA_EVID_BADFORMAT;
681
strm->vafp.size = fmt->det.vid.size;
682
strm->vafp.buffer = NULL;
683
if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS)
684
return PJMEDIA_EVID_BADFORMAT;
686
vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
687
strm->rect.x = strm->rect.y = 0;
688
strm->rect.w = (Uint16)vfd->size.w;
689
strm->rect.h = (Uint16)vfd->size.h;
690
if (strm->param.disp_size.w == 0)
691
strm->param.disp_size.w = strm->rect.w;
692
if (strm->param.disp_size.h == 0)
693
strm->param.disp_size.h = strm->rect.h;
694
strm->dstrect.x = strm->dstrect.y = 0;
695
strm->dstrect.w = (Uint16)strm->param.disp_size.w;
696
strm->dstrect.h = (Uint16)strm->param.disp_size.h;
700
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
701
if (strm->param.rend_id == OPENGL_DEV_IDX) {
702
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
704
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
709
if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
710
if (!(strm->param.window_flags & PJMEDIA_VID_DEV_WND_BORDER))
711
flags |= SDL_WINDOW_BORDERLESS;
712
if (strm->param.window_flags & PJMEDIA_VID_DEV_WND_RESIZABLE)
713
flags |= SDL_WINDOW_RESIZABLE;
715
flags |= SDL_WINDOW_BORDERLESS;
718
if (!((strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) &&
719
strm->param.window_hide))
721
flags |= SDL_WINDOW_SHOWN;
723
flags &= ~SDL_WINDOW_SHOWN;
724
flags |= SDL_WINDOW_HIDDEN;
727
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
728
if (strm->param.rend_id == OPENGL_DEV_IDX)
729
flags |= SDL_WINDOW_OPENGL;
730
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
732
if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
733
/* Use the window supplied by the application. */
734
strm->window = SDL_CreateWindowFrom(
735
strm->param.window.info.window);
739
x = y = SDL_WINDOWPOS_CENTERED;
740
if (strm->param.flags & PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
741
x = strm->param.window_pos.x;
742
y = strm->param.window_pos.y;
745
/* Create the window where we will draw. */
746
strm->window = SDL_CreateWindow("pjmedia-SDL video",
748
strm->param.disp_size.w,
749
strm->param.disp_size.h,
753
return PJMEDIA_EVID_SYSERR;
757
* We must call SDL_CreateRenderer in order for draw calls to
758
* affect this window.
760
strm->renderer = SDL_CreateRenderer(strm->window, -1, 0);
762
return PJMEDIA_EVID_SYSERR;
764
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
765
if (strm->param.rend_id == OPENGL_DEV_IDX) {
766
strm->gl_context = SDL_GL_CreateContext(strm->window);
767
if (!strm->gl_context)
768
return PJMEDIA_EVID_SYSERR;
769
SDL_GL_MakeCurrent(strm->window, strm->gl_context);
771
/* Init some OpenGL settings */
772
glDisable(GL_DEPTH_TEST);
773
glDisable(GL_CULL_FACE);
774
glEnable(GL_TEXTURE_2D);
776
/* Init the viewport */
777
glViewport(0, 0, strm->param.disp_size.w, strm->param.disp_size.h);
778
glMatrixMode(GL_PROJECTION);
781
glOrtho(0.0, (GLdouble)strm->param.disp_size.w,
782
(GLdouble)strm->param.disp_size.h, 0.0, 0.0, 1.0);
784
glMatrixMode(GL_MODELVIEW);
787
/* Create a texture */
788
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
789
glGenTextures(1, &strm->texture);
792
return PJMEDIA_EVID_SYSERR;
794
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
796
strm->scr_tex = SDL_CreateTexture(strm->renderer, sdl_info->sdl_format,
797
SDL_TEXTUREACCESS_STREAMING,
798
strm->rect.w, strm->rect.h);
799
if (strm->scr_tex == NULL)
800
return PJMEDIA_EVID_SYSERR;
802
strm->pitch = strm->rect.w * SDL_BYTESPERPIXEL(sdl_info->sdl_format);
808
static pj_status_t sdl_create(void *data)
810
struct sdl_stream *strm = (struct sdl_stream *)data;
811
return sdl_create_rend(strm, &strm->param.fmt);
814
static pj_status_t resize_disp(struct sdl_stream *strm,
815
pjmedia_rect_size *new_disp_size)
817
pj_memcpy(&strm->param.disp_size, new_disp_size,
818
sizeof(strm->param.disp_size));
821
strm->dstrect.x = strm->dstrect.y = 0;
822
strm->dstrect.w = (Uint16)strm->param.disp_size.w;
823
strm->dstrect.h = (Uint16)strm->param.disp_size.h;
824
SDL_RenderSetViewport(strm->renderer, &strm->dstrect);
826
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
827
else if (strm->param.rend_id == OPENGL_DEV_IDX) {
828
sdl_create_rend(strm, &strm->param.fmt);
830
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
835
static pj_status_t change_format(struct sdl_stream *strm,
836
pjmedia_format *new_fmt)
840
/* Recreate SDL renderer */
841
status = sdl_create_rend(strm, (new_fmt? new_fmt :
843
if (status == PJ_SUCCESS && new_fmt)
844
pjmedia_format_copy(&strm->param.fmt, new_fmt);
849
static pj_status_t put_frame(void *data)
851
struct sdl_stream *stream = (struct sdl_stream *)data;
852
const pjmedia_frame *frame = stream->frame;
854
if (stream->scr_tex) {
855
SDL_UpdateTexture(stream->scr_tex, NULL, frame->buf, stream->pitch);
856
SDL_RenderClear(stream->renderer);
857
SDL_RenderCopy(stream->renderer, stream->scr_tex,
858
&stream->rect, &stream->dstrect);
859
SDL_RenderPresent(stream->renderer);
861
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
862
else if (stream->param.rend_id == OPENGL_DEV_IDX && stream->texture) {
863
glBindTexture(GL_TEXTURE_2D, stream->texture);
864
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
865
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
866
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA,
867
stream->rect.w, stream->rect.h, 0,
868
GL_RGBA, GL_UNSIGNED_BYTE, frame->buf);
869
glBegin(GL_TRIANGLE_STRIP);
870
glTexCoord2f(0, 0); glVertex2i(0, 0);
871
glTexCoord2f(1, 0); glVertex2i(stream->param.disp_size.w, 0);
872
glTexCoord2f(0, 1); glVertex2i(0, stream->param.disp_size.h);
874
glVertex2i(stream->param.disp_size.w, stream->param.disp_size.h);
876
SDL_GL_SwapWindow(stream->window);
878
#endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
883
/* API: Put frame from stream */
884
static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
885
const pjmedia_frame *frame)
887
struct sdl_stream *stream = (struct sdl_stream*)strm;
890
stream->last_ts.u64 = frame->timestamp.u64;
892
if (!stream->is_running)
893
return PJ_EINVALIDOP;
895
if (frame->size==0 || frame->buf==NULL ||
896
frame->size < stream->vafp.framebytes)
899
stream->frame = frame;
900
job_queue_post_job(stream->sf->jq, put_frame, strm, 0, &status);
905
/* API: create stream */
906
static pj_status_t sdl_factory_create_stream(
907
pjmedia_vid_dev_factory *f,
908
pjmedia_vid_dev_param *param,
909
const pjmedia_vid_dev_cb *cb,
911
pjmedia_vid_dev_stream **p_vid_strm)
913
struct sdl_factory *sf = (struct sdl_factory*)f;
915
struct sdl_stream *strm;
918
PJ_ASSERT_RETURN(param->dir == PJMEDIA_DIR_RENDER, PJ_EINVAL);
920
/* Create and Initialize stream descriptor */
921
pool = pj_pool_create(sf->pf, "sdl-dev", 1000, 1000, NULL);
922
PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM);
924
strm = PJ_POOL_ZALLOC_T(pool, struct sdl_stream);
925
pj_memcpy(&strm->param, param, sizeof(*param));
928
pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
929
pj_list_init(&strm->list_entry);
930
strm->list_entry.stream = strm;
931
strm->user_data = user_data;
933
/* Create render stream here */
934
job_queue_post_job(sf->jq, sdl_create, strm, 0, &status);
935
if (status != PJ_SUCCESS) {
938
pj_mutex_lock(strm->sf->mutex);
939
if (pj_list_empty(&strm->sf->streams))
940
pj_sem_post(strm->sf->sem);
941
pj_list_insert_after(&strm->sf->streams, &strm->list_entry);
942
pj_mutex_unlock(strm->sf->mutex);
945
strm->base.op = &stream_op;
946
*p_vid_strm = &strm->base;
951
sdl_stream_destroy(&strm->base);
955
/* API: Get stream info. */
956
static pj_status_t sdl_stream_get_param(pjmedia_vid_dev_stream *s,
957
pjmedia_vid_dev_param *pi)
959
struct sdl_stream *strm = (struct sdl_stream*)s;
961
PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL);
963
pj_memcpy(pi, &strm->param, sizeof(*pi));
965
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
966
&pi->window) == PJ_SUCCESS)
968
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
970
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION,
971
&pi->window_pos) == PJ_SUCCESS)
973
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION;
975
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE,
976
&pi->disp_size) == PJ_SUCCESS)
978
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE;
980
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE,
981
&pi->window_hide) == PJ_SUCCESS)
983
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE;
985
if (sdl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS,
986
&pi->window_flags) == PJ_SUCCESS)
988
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS;
995
struct sdl_stream *strm;
996
pjmedia_vid_dev_cap cap;
1003
static pj_status_t get_cap(void *data)
1005
struct strm_cap *scap = (struct strm_cap *)data;
1006
struct sdl_stream *strm = scap->strm;
1007
pjmedia_vid_dev_cap cap = scap->cap;
1008
void *pval = scap->pval.pval;
1010
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
1013
SDL_VERSION(&info.version);
1015
if (SDL_GetWindowWMInfo(strm->window, &info)) {
1016
pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval;
1018
#if defined(SDL_VIDEO_DRIVER_WINDOWS)
1019
else if (info.subsystem == SDL_SYSWM_WINDOWS) {
1020
wnd->type = PJMEDIA_VID_DEV_HWND_TYPE_WINDOWS;
1021
wnd->info.win.hwnd = (void *)info.info.win.window;
1024
#if defined(SDL_VIDEO_DRIVER_X11)
1025
else if (info.subsystem == SDL_SYSWM_X11) {
1026
wnd->info.x11.window = (void *)info.info.x11.window;
1027
wnd->info.x11.display = (void *)info.info.x11.display;
1030
#if defined(SDL_VIDEO_DRIVER_COCOA)
1031
else if (info.subsystem == SDL_SYSWM_COCOA) {
1032
wnd->info.cocoa.window = (void *)info.info.cocoa.window;
1035
#if defined(SDL_VIDEO_DRIVER_UIKIT)
1036
else if (info.subsystem == SDL_SYSWM_UIKIT) {
1037
wnd->info.ios.window = (void *)info.info.uikit.window;
1041
return PJMEDIA_EVID_INVCAP;
1045
return PJMEDIA_EVID_INVCAP;
1046
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
1047
SDL_GetWindowPosition(strm->window, &((pjmedia_coord *)pval)->x,
1048
&((pjmedia_coord *)pval)->y);
1050
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
1051
SDL_GetWindowSize(strm->window, (int *)&((pjmedia_rect_size *)pval)->w,
1052
(int *)&((pjmedia_rect_size *)pval)->h);
1054
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
1055
Uint32 flag = SDL_GetWindowFlags(strm->window);
1056
*((pj_bool_t *)pval) = (flag & SDL_WINDOW_HIDDEN)? PJ_TRUE: PJ_FALSE;
1058
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW_FLAGS) {
1059
Uint32 flag = SDL_GetWindowFlags(strm->window);
1060
unsigned *wnd_flags = (unsigned *)pval;
1061
if (!(flag & SDL_WINDOW_BORDERLESS))
1062
*wnd_flags |= PJMEDIA_VID_DEV_WND_BORDER;
1063
if (flag & SDL_WINDOW_RESIZABLE)
1064
*wnd_flags |= PJMEDIA_VID_DEV_WND_RESIZABLE;
1068
return PJMEDIA_EVID_INVCAP;
1071
/* API: get capability */
1072
static pj_status_t sdl_stream_get_cap(pjmedia_vid_dev_stream *s,
1073
pjmedia_vid_dev_cap cap,
1076
struct sdl_stream *strm = (struct sdl_stream*)s;
1077
struct strm_cap scap;
1080
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1084
scap.pval.pval = pval;
1086
job_queue_post_job(strm->sf->jq, get_cap, &scap, 0, &status);
1091
static pj_status_t set_cap(void *data)
1093
struct strm_cap *scap = (struct strm_cap *)data;
1094
struct sdl_stream *strm = scap->strm;
1095
pjmedia_vid_dev_cap cap = scap->cap;
1096
const void *pval = scap->pval.cpval;
1098
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_POSITION) {
1100
* Setting window's position when the window is hidden also sets
1101
* the window's flag to shown (while the window is, actually,
1102
* still hidden). This causes problems later when setting/querying
1103
* the window's visibility.
1104
* See ticket #1429 (http://trac.pjsip.org/repos/ticket/1429)
1106
Uint32 flag = SDL_GetWindowFlags(strm->window);
1107
if (flag & SDL_WINDOW_HIDDEN)
1108
SDL_ShowWindow(strm->window);
1109
SDL_SetWindowPosition(strm->window, ((pjmedia_coord *)pval)->x,
1110
((pjmedia_coord *)pval)->y);
1111
if (flag & SDL_WINDOW_HIDDEN)
1112
SDL_HideWindow(strm->window);
1114
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_HIDE) {
1115
if (*(pj_bool_t *)pval)
1116
SDL_HideWindow(strm->window);
1118
SDL_ShowWindow(strm->window);
1120
} else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
1123
status = change_format(strm, (pjmedia_format *)pval);
1124
if (status != PJ_SUCCESS) {
1125
pj_status_t status_;
1128
* Failed to change the output format. Try to revert
1129
* to its original format.
1131
status_ = change_format(strm, &strm->param.fmt);
1132
if (status_ != PJ_SUCCESS) {
1134
* This means that we failed to revert to our
1137
status = PJMEDIA_EVID_ERR;
1142
} else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_RESIZE) {
1143
pjmedia_rect_size *new_size = (pjmedia_rect_size *)pval;
1145
SDL_SetWindowSize(strm->window, new_size->w, new_size->h);
1146
return resize_disp(strm, new_size);
1149
return PJMEDIA_EVID_INVCAP;
1152
/* API: set capability */
1153
static pj_status_t sdl_stream_set_cap(pjmedia_vid_dev_stream *s,
1154
pjmedia_vid_dev_cap cap,
1157
struct sdl_stream *strm = (struct sdl_stream*)s;
1158
struct strm_cap scap;
1161
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
1165
scap.pval.cpval = pval;
1167
job_queue_post_job(strm->sf->jq, set_cap, &scap, 0, &status);
1172
/* API: Start stream. */
1173
static pj_status_t sdl_stream_start(pjmedia_vid_dev_stream *strm)
1175
struct sdl_stream *stream = (struct sdl_stream*)strm;
1177
PJ_LOG(4, (THIS_FILE, "Starting sdl video stream"));
1179
stream->is_running = PJ_TRUE;
1185
/* API: Stop stream. */
1186
static pj_status_t sdl_stream_stop(pjmedia_vid_dev_stream *strm)
1188
struct sdl_stream *stream = (struct sdl_stream*)strm;
1190
PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
1192
stream->is_running = PJ_FALSE;
1198
/* API: Destroy stream. */
1199
static pj_status_t sdl_stream_destroy(pjmedia_vid_dev_stream *strm)
1201
struct sdl_stream *stream = (struct sdl_stream*)strm;
1204
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
1206
sdl_stream_stop(strm);
1208
job_queue_post_job(stream->sf->jq, sdl_destroy_all, strm, 0, &status);
1209
if (status != PJ_SUCCESS)
1212
pj_mutex_lock(stream->sf->mutex);
1213
if (!pj_list_empty(&stream->list_entry))
1214
pj_list_erase(&stream->list_entry);
1215
pj_mutex_unlock(stream->sf->mutex);
1217
pj_pool_release(stream->pool);
1222
/****************************************************************************
1223
* Job queue implementation
1226
static int job_thread(void * data)
1228
job_queue *jq = (job_queue *)data;
1233
/* Wait until there is a job. */
1234
pj_sem_wait(jq->sem);
1236
/* Make sure there is no pending jobs before we quit. */
1237
if (jq->is_quitting && jq->head == jq->tail && !jq->is_full)
1240
jb = jq->jobs[jq->head];
1241
jb->retval = (*jb->func)(jb->data);
1242
/* If job queue is full and we already finish all the pending
1243
* jobs, increase the size.
1245
if (jq->is_full && ((jq->head + 1) % jq->size == jq->tail)) {
1250
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
1251
pj_sem_destroy(jq->old_sem[i]);
1254
jq->old_sem = jq->job_sem;
1256
/* Double the job queue size. */
1257
jq->size *= JOB_QUEUE_INC_FACTOR;
1258
pj_sem_destroy(jq->sem);
1259
status = pj_sem_create(jq->pool, "thread_sem", 0, jq->size + 1,
1261
if (status != PJ_SUCCESS) {
1262
PJ_LOG(3, (THIS_FILE, "Failed growing SDL job queue size."));
1265
jq->jobs = (job **)pj_pool_calloc(jq->pool, jq->size,
1267
jq->job_sem = (pj_sem_t **) pj_pool_calloc(jq->pool, jq->size,
1268
sizeof(pj_sem_t *));
1269
for (i = 0; i < jq->size; i++) {
1270
status = pj_sem_create(jq->pool, "job_sem", 0, 1,
1272
if (status != PJ_SUCCESS) {
1273
PJ_LOG(3, (THIS_FILE, "Failed growing SDL job "
1278
jq->is_full = PJ_FALSE;
1280
jq->head = jq->tail = 0;
1281
pj_sem_post(jq->old_sem[head]);
1283
pj_sem_post(jq->job_sem[jq->head]);
1284
jq->head = (jq->head + 1) % jq->size;
1292
static pj_status_t job_queue_create(pj_pool_t *pool, job_queue **pjq)
1297
job_queue *jq = PJ_POOL_ZALLOC_T(pool, job_queue);
1299
jq->size = INITIAL_MAX_JOBS;
1300
status = pj_sem_create(pool, "thread_sem", 0, jq->size + 1, &jq->sem);
1301
if (status != PJ_SUCCESS)
1303
jq->jobs = (job **)pj_pool_calloc(pool, jq->size, sizeof(job *));
1304
jq->job_sem = (pj_sem_t **) pj_pool_calloc(pool, jq->size,
1305
sizeof(pj_sem_t *));
1306
for (i = 0; i < jq->size; i++) {
1307
status = pj_sem_create(pool, "job_sem", 0, 1, &jq->job_sem[i]);
1308
if (status != PJ_SUCCESS)
1312
status = pj_mutex_create_recursive(pool, "job_mutex", &jq->mutex);
1313
if (status != PJ_SUCCESS)
1316
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
1317
PJ_UNUSED_ARG(status);
1319
status = pj_thread_create(pool, "job_th", job_thread, jq, 0, 0,
1321
if (status != PJ_SUCCESS)
1323
#endif /* PJ_DARWINOS */
1329
job_queue_destroy(jq);
1333
static pj_status_t job_queue_post_job(job_queue *jq, job_func_ptr func,
1334
void *data, unsigned flags,
1335
pj_status_t *retval)
1340
if (jq->is_quitting)
1347
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
1348
PJ_UNUSED_ARG(tail);
1349
NSAutoreleasePool *apool = [[NSAutoreleasePool alloc]init];
1350
JQDelegate *jqd = [[JQDelegate alloc]init];
1352
[jqd performSelectorOnMainThread:@selector(run_job)
1353
withObject:nil waitUntilDone:YES];
1356
#else /* PJ_DARWINOS */
1357
pj_mutex_lock(jq->mutex);
1358
jq->jobs[jq->tail] = &jb;
1360
jq->tail = (jq->tail + 1) % jq->size;
1361
if (jq->tail == jq->head) {
1362
jq->is_full = PJ_TRUE;
1363
PJ_LOG(4, (THIS_FILE, "SDL job queue is full, increasing "
1364
"the queue size."));
1365
pj_sem_post(jq->sem);
1366
/* Wait until our posted job is completed. */
1367
pj_sem_wait(jq->job_sem[tail]);
1368
pj_mutex_unlock(jq->mutex);
1370
pj_mutex_unlock(jq->mutex);
1371
pj_sem_post(jq->sem);
1372
/* Wait until our posted job is completed. */
1373
pj_sem_wait(jq->job_sem[tail]);
1375
#endif /* PJ_DARWINOS */
1377
*retval = jb.retval;
1382
static pj_status_t job_queue_destroy(job_queue *jq)
1386
jq->is_quitting = PJ_TRUE;
1389
pj_sem_post(jq->sem);
1390
pj_thread_join(jq->thread);
1391
pj_thread_destroy(jq->thread);
1395
pj_sem_destroy(jq->sem);
1398
for (i = 0; i < jq->size; i++) {
1399
if (jq->job_sem[i]) {
1400
pj_sem_destroy(jq->job_sem[i]);
1401
jq->job_sem[i] = NULL;
1405
for (i = 0; i < jq->size / JOB_QUEUE_INC_FACTOR; i++) {
1406
if (jq->old_sem[i]) {
1407
pj_sem_destroy(jq->old_sem[i]);
1408
jq->old_sem[i] = NULL;
1413
pj_mutex_destroy(jq->mutex);
1421
# if SDL_VERSION_ATLEAST(2,0,0)
1422
# pragma comment( lib, "sdl2.lib")
1423
# elif SDL_VERSION_ATLEAST(1,3,0)
1424
# pragma comment( lib, "sdl.lib")
1426
# if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
1427
# pragma comment(lib, "OpenGL32.lib")
1428
# endif /* PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL */
1429
#endif /* _MSC_VER */
1432
#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */