blob: 1067bd879976722930927ecfc0e428d605acaf3d [file] [log] [blame]
/* $Id$ */
/*
* Copyright (C) 2008-2010 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/converter.h>
#include <pjmedia-videodev/videodev_imp.h>
#include <pj/assert.h>
#include <pj/log.h>
#include <pj/os.h>
#if PJMEDIA_VIDEO_DEV_HAS_SDL
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
# include <Foundation/Foundation.h>
#endif
#include <SDL.h>
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
# include "SDL_opengl.h"
# define OPENGL_DEV_IDX 1
#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, 0, 0xFF000000, 0xFF0000, 0xFF00, 0xFF} ,
{PJMEDIA_FORMAT_RGB24, 0, 0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_BGRA, 0, 0xFF00, 0xFF0000, 0xFF000000, 0xFF} ,
#else
{PJMEDIA_FORMAT_RGBA, 0, 0xFF, 0xFF00, 0xFF0000, 0xFF000000} ,
{PJMEDIA_FORMAT_RGB24, 0, 0xFF, 0xFF00, 0xFF0000, 0} ,
{PJMEDIA_FORMAT_BGRA, 0, 0xFF0000, 0xFF00, 0xFF, 0xFF000000} ,
#endif
{PJMEDIA_FORMAT_DIB , 0, 0xFF0000, 0xFF00, 0xFF, 0} ,
{PJMEDIA_FORMAT_YUY2, SDL_YUY2_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_UYVY, SDL_UYVY_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YVYU, SDL_YVYU_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420, SDL_IYUV_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_YV12, SDL_YV12_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I420JPEG, SDL_IYUV_OVERLAY, 0, 0, 0, 0} ,
{PJMEDIA_FORMAT_I422JPEG, SDL_YV12_OVERLAY, 0, 0, 0, 0} ,
};
/* sdl_ device info */
struct sdl_dev_info
{
pjmedia_vid_dev_info info;
};
/* 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;
};
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
@interface SDLDelegate: NSObject
{
@public
struct sdl_stream *strm;
}
- (int)sdl_thread;
- (int)handle_event;
- (pj_status_t)put_frame;
@end
#endif
/* Video stream. */
struct sdl_stream
{
pjmedia_vid_dev_stream base; /**< Base stream */
pjmedia_vid_param param; /**< Settings */
pj_pool_t *pool; /**< Memory pool. */
pjmedia_vid_cb vid_cb; /**< Stream callback. */
void *user_data; /**< Application data. */
pj_thread_t *sdl_thread; /**< SDL thread. */
pj_bool_t is_quitting;
pj_bool_t is_running;
pj_bool_t render_exited;
pj_status_t status;
SDL_Rect rect; /**< Display rectangle. */
SDL_Surface *screen; /**< Display screen. */
SDL_Surface *surf; /**< RGB surface. */
SDL_Overlay *overlay; /**< YUV overlay. */
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
GLuint texture;
#endif
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
NSAutoreleasePool *apool;
SDLDelegate *delegate;
const pjmedia_frame *frame;
#endif
/* For frame conversion */
pjmedia_converter *conv;
pjmedia_conversion_param conv_param;
pjmedia_frame conv_buf;
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 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_param *param);
static pj_status_t sdl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_param *param,
const pjmedia_vid_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_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);
/* 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
};
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
};
/****************************************************************************
* 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", 1000, 1000, NULL);
f = PJ_POOL_ZALLOC_T(pool, struct sdl_factory);
f->pf = pf;
f->pool = pool;
f->base.op = &factory_op;
return &f->base;
}
/* 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;
sf->dev_count = 1;
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
sf->dev_count++;
#endif
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';
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.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
ddi->info.fmt = (pjmedia_format*)
pj_pool_calloc(sf->pool, ddi->info.fmt_cnt,
sizeof(pjmedia_format));
for (i = 0; i < ddi->info.fmt_cnt; i++) {
pjmedia_format *fmt = &ddi->info.fmt[i];
pjmedia_format_init_video(fmt, sdl_fmts[i].fmt_id,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
}
#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';
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;
ddi->info.fmt_cnt = PJ_ARRAY_SIZE(sdl_fmts);
ddi->info.fmt_cnt = 1;
ddi->info.fmt = (pjmedia_format*)
pj_pool_calloc(sf->pool, ddi->info.fmt_cnt,
sizeof(pjmedia_format));
for (i = 0; i < ddi->info.fmt_cnt; i++) {
pjmedia_format *fmt = &ddi->info.fmt[i];
pjmedia_format_init_video(fmt, sdl_fmts[i].fmt_id,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
}
#endif
PJ_LOG(4, (THIS_FILE, "SDL initialized"));
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;
sf->pool = NULL;
pj_pool_release(pool);
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_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));
if (di->info.dir == PJMEDIA_DIR_CAPTURE_RENDER) {
param->dir = PJMEDIA_DIR_CAPTURE_RENDER;
param->cap_id = index;
param->rend_id = index;
} else if (di->info.dir & PJMEDIA_DIR_CAPTURE) {
param->dir = PJMEDIA_DIR_CAPTURE;
param->cap_id = index;
param->rend_id = PJMEDIA_VID_INVALID_DEV;
} else if (di->info.dir & PJMEDIA_DIR_RENDER) {
param->dir = PJMEDIA_DIR_RENDER;
param->rend_id = index;
param->cap_id = PJMEDIA_VID_INVALID_DEV;
} else {
return PJMEDIA_EVID_INVDEV;
}
/* Set the device capabilities here */
param->flags = PJMEDIA_VID_DEV_CAP_FORMAT;
param->fmt.type = PJMEDIA_TYPE_VIDEO;
param->clock_rate = DEFAULT_CLOCK_RATE;
pjmedia_format_init_video(&param->fmt, sdl_fmts[0].fmt_id,
DEFAULT_WIDTH, DEFAULT_HEIGHT,
DEFAULT_FPS, 1);
return PJ_SUCCESS;
}
static sdl_fmt_info* get_sdl_format_info(pjmedia_format_id id)
{
unsigned i;
for (i = 0; i < sizeof(sdl_fmts)/sizeof(sdl_fmts[0]); i++) {
if (sdl_fmts[i].fmt_id == id)
return &sdl_fmts[i];
}
return NULL;
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
@implementation SDLDelegate
- (int)sdl_thread
{
#else
static int sdlthread(void * data)
{
struct sdl_stream *strm = (struct sdl_stream*)data;
#endif
sdl_fmt_info *sdl_info = get_sdl_format_info(strm->param.fmt.id);
const pjmedia_video_format_info *vfi;
pjmedia_video_format_detail *vfd;
vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
strm->param.fmt.id);
if (!vfi || !sdl_info) {
strm->status = PJMEDIA_EVID_BADFORMAT;
goto on_return;
}
strm->vafp.size = strm->param.fmt.det.vid.size;
strm->vafp.buffer = NULL;
if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS) {
strm->status = PJMEDIA_EVID_BADFORMAT;
goto on_return;
}
/* Initialize the SDL library */
if (SDL_Init(SDL_INIT_VIDEO)) {
PJ_LOG(4, (THIS_FILE, "Cannot initialize SDL"));
strm->status = PJMEDIA_EVID_INIT;
goto on_return;
}
vfd = pjmedia_format_get_video_format_detail(&strm->param.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 PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->param.rend_id == OPENGL_DEV_IDX) {
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
}
#endif
/* Initialize the display, requesting a software surface */
strm->screen = SDL_SetVideoMode(strm->rect.w, strm->rect.h, 0, (
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
strm->param.rend_id == OPENGL_DEV_IDX?
SDL_OPENGL:
#endif
SDL_RESIZABLE | SDL_SWSURFACE));
if (strm->screen == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
goto on_return;
}
SDL_WM_SetCaption("pjmedia-SDL video", NULL);
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->param.rend_id == OPENGL_DEV_IDX) {
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
glEnable(GL_TEXTURE_2D);
glViewport(0, 0, strm->rect.w, strm->rect.h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0.0, (GLdouble)strm->rect.w, (GLdouble)strm->rect.h,
0.0, 0.0, 1.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
glGenTextures(1, &strm->texture);
} else
#endif
if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) {
strm->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
strm->rect.w, strm->rect.h,
vfi->bpp,
sdl_info->Rmask,
sdl_info->Gmask,
sdl_info->Bmask,
sdl_info->Amask);
if (strm->surf == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
goto on_return;
}
} else if (vfi->color_model == PJMEDIA_COLOR_MODEL_YUV) {
strm->overlay = SDL_CreateYUVOverlay(strm->rect.w, strm->rect.h,
sdl_info->sdl_format,
strm->screen);
if (strm->overlay == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
goto on_return;
}
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
on_return:
if (strm->status != PJ_SUCCESS) {
if (strm->surf) {
SDL_FreeSurface(strm->surf);
strm->surf = NULL;
}
if (strm->overlay) {
SDL_FreeYUVOverlay(strm->overlay);
strm->overlay = NULL;
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->texture) {
glDeleteTextures(1, &strm->texture);
strm->texture = 0;
}
#endif
SDL_Quit();
strm->screen = NULL;
}
return strm->status;
}
- (int)handle_event
{
sdl_fmt_info *sdl_info = get_sdl_format_info(strm->param.fmt.id);
const pjmedia_video_format_info *vfi;
pjmedia_video_format_detail *vfd;
vfi = pjmedia_get_video_format_info(pjmedia_video_format_mgr_instance(),
strm->param.fmt.id);
vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE);
#else
while(!strm->is_quitting)
#endif
{
SDL_Event sevent;
pjmedia_vid_event pevent;
/**
* The event polling must be placed in the same thread that
* call SDL_SetVideoMode(). Please consult the official doc of
* SDL_PumpEvents().
*/
while (SDL_PollEvent(&sevent)) {
pj_bzero(&pevent, sizeof(pevent));
switch(sevent.type) {
case SDL_USEREVENT:
{
pjmedia_format *fmt;
if (sevent.user.code == PJMEDIA_EVENT_NONE) {
strm->is_quitting = PJ_TRUE;
goto on_return;
}
pj_assert(sevent.user.code == PJMEDIA_VID_DEV_CAP_FORMAT);
PJ_TODO(FIX_THIS_FOR_OPENGL);
fmt = (pjmedia_format *)sevent.user.data1;
vfi = pjmedia_get_video_format_info(
pjmedia_video_format_mgr_instance(),
fmt->id);
if (!vfi || !sdl_info) {
strm->status = PJMEDIA_EVID_BADFORMAT;
break;
}
vfd = pjmedia_format_get_video_format_detail(fmt, PJ_TRUE);
strm->vafp.size = vfd->size;
strm->vafp.buffer = NULL;
if (vfi->apply_fmt(vfi, &strm->vafp) != PJ_SUCCESS) {
strm->status = PJMEDIA_EVID_BADFORMAT;
break;
}
strm->rect.w = (Uint16)vfd->size.w;
strm->rect.h = (Uint16)vfd->size.h;
/* Stop the stream */
sdl_stream_stop((pjmedia_vid_dev_stream *)strm);
/* Initialize the display, requesting a software surface */
strm->screen = SDL_SetVideoMode(strm->rect.w,
strm->rect.h, 0,
SDL_RESIZABLE |
SDL_SWSURFACE);
if (strm->screen == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
break;
}
if (strm->surf)
SDL_FreeSurface(strm->surf);
if (strm->overlay)
SDL_FreeYUVOverlay(strm->overlay);
/* Update SDL info for the new format */
sdl_info = get_sdl_format_info(fmt->id);
if (vfi->color_model == PJMEDIA_COLOR_MODEL_RGB) {
strm->surf = SDL_CreateRGBSurface(SDL_SWSURFACE,
strm->rect.w,
strm->rect.h,
vfi->bpp,
sdl_info->Rmask,
sdl_info->Gmask,
sdl_info->Bmask,
sdl_info->Amask);
if (strm->surf == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
break;
}
} else if (vfi->color_model == PJMEDIA_COLOR_MODEL_YUV) {
strm->overlay = SDL_CreateYUVOverlay(
strm->rect.w,
strm->rect.h,
sdl_info->sdl_format,
strm->screen);
if (strm->overlay == NULL) {
strm->status = PJMEDIA_EVID_SYSERR;
break;
}
}
/* Restart the stream */
sdl_stream_start((pjmedia_vid_dev_stream *)strm);
strm->status = PJ_SUCCESS;
break;
}
case SDL_MOUSEBUTTONDOWN:
pevent.event_type = PJMEDIA_EVENT_MOUSEBUTTONDOWN;
if (strm->vid_cb.on_event_cb)
if ((*strm->vid_cb.on_event_cb)(&strm->base,
strm->user_data,
&pevent) != PJ_SUCCESS)
{
/* Application wants us to ignore this event */
break;
}
if (strm->is_running)
pjmedia_vid_dev_stream_stop(&strm->base);
else
pjmedia_vid_dev_stream_start(&strm->base);
break;
case SDL_VIDEORESIZE:
pevent.event_type = PJMEDIA_EVENT_WINDOW_RESIZE;
pevent.event_desc.resize.new_size.w = sevent.resize.w;
pevent.event_desc.resize.new_size.h = sevent.resize.h;
if (strm->vid_cb.on_event_cb) {
/**
* To process PJMEDIA_EVENT_WINDOW_RESIZE event,
* application should do this in the on_event_cb
* callback:
* 1. change the input frame size given to SDL
* to the new size.
* 2. call pjmedia_vid_dev_stream_set_cap()
* using PJMEDIA_VID_DEV_CAP_FORMAT capability
* and the new format size
*/
(*strm->vid_cb.on_event_cb)(&strm->base,
strm->user_data,
&pevent);
}
break;
case SDL_QUIT:
pevent.event_type = PJMEDIA_EVENT_WINDOW_CLOSE;
/**
* To process PJMEDIA_EVENT_WINDOW_CLOSE event,
* application should do this in the on_event_cb callback:
* 1. stop further calls to
* #pjmedia_vid_dev_stream_put_frame()
* 2. return PJ_SUCCESS
* Upon returning from the callback, SDL will destroy its
* own stream.
*
* Returning non-PJ_SUCCESS will cause SDL to ignore
* the event
*/
if (strm->vid_cb.on_event_cb) {
strm->is_quitting = PJ_TRUE;
if ((*strm->vid_cb.on_event_cb)(&strm->base,
strm->user_data,
&pevent) != PJ_SUCCESS)
{
/* Application wants us to ignore this event */
strm->is_quitting = PJ_FALSE;
break;
}
/* Destroy the stream */
sdl_stream_destroy(&strm->base);
goto on_return;
}
/**
* Default event-handler when there is no user-specified
* callback: close the renderer window. We cannot destroy
* the stream here since there is no callback to notify
* the application.
*/
sdl_stream_stop(&strm->base);
goto on_return;
default:
break;
}
}
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
return 0;
#endif
on_return:
if (strm->surf) {
SDL_FreeSurface(strm->surf);
strm->surf = NULL;
}
if (strm->overlay) {
SDL_FreeYUVOverlay(strm->overlay);
strm->overlay = NULL;
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
if (strm->texture) {
glDeleteTextures(1, &strm->texture);
strm->texture = 0;
}
#endif
SDL_Quit();
strm->screen = NULL;
return strm->status;
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
- (pj_status_t)put_frame
{
const pjmedia_frame *frame = strm->frame;
#else
/* API: Put frame from stream */
static pj_status_t sdl_stream_put_frame(pjmedia_vid_dev_stream *strm,
const pjmedia_frame *frame)
{
#endif
struct sdl_stream *stream = (struct sdl_stream*)strm;
pj_status_t status = PJ_SUCCESS;
if (!stream->is_running) {
stream->render_exited = PJ_TRUE;
goto on_return;
}
if (frame->size==0 || frame->buf==NULL ||
frame->size < stream->vafp.framebytes)
goto on_return;
if (stream->surf) {
if (SDL_MUSTLOCK(stream->surf)) {
if (SDL_LockSurface(stream->surf) < 0) {
PJ_LOG(3, (THIS_FILE, "Unable to lock SDL surface"));
status = PJMEDIA_EVID_NOTREADY;
goto on_return;
}
}
pj_memcpy(stream->surf->pixels, frame->buf,
stream->vafp.framebytes);
if (SDL_MUSTLOCK(stream->surf)) {
SDL_UnlockSurface(stream->surf);
}
SDL_BlitSurface(stream->surf, NULL, stream->screen, NULL);
SDL_UpdateRect(stream->screen, 0, 0, 0, 0);
} else if (stream->overlay) {
int i, sz, offset;
if (SDL_LockYUVOverlay(stream->overlay) < 0) {
PJ_LOG(3, (THIS_FILE, "Unable to lock SDL overlay"));
status = PJMEDIA_EVID_NOTREADY;
goto on_return;
}
for (i = 0, offset = 0; i < stream->overlay->planes; i++) {
sz = stream->vafp.plane_bytes[i];
pj_memcpy(stream->overlay->pixels[i],
(char *)frame->buf + offset, sz);
offset += sz;
}
SDL_UnlockYUVOverlay(stream->overlay);
SDL_DisplayYUVOverlay(stream->overlay, &stream->rect);
}
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
else if (stream->param.rend_id == OPENGL_DEV_IDX) {
if (frame && 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->rect.w, 0);
glTexCoord2f(0, 1); glVertex2i(0, stream->rect.h);
glTexCoord2f(1, 1);
glVertex2i(stream->rect.w, stream->rect.h);
glEnd();
SDL_GL_SwapBuffers();
}
}
#endif
on_return:
return status;
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
@end
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;
stream->frame = frame;
[stream->delegate performSelectorOnMainThread:@selector(put_frame)
withObject:nil waitUntilDone:YES];
return PJ_SUCCESS;
}
static int sdlthread(void * data)
{
struct sdl_stream *strm = (struct sdl_stream*)data;
while(!strm->is_quitting) {
[strm->delegate performSelectorOnMainThread:@selector(handle_event)
withObject:nil waitUntilDone:YES];
}
return 0;
}
#endif
/* API: create stream */
static pj_status_t sdl_factory_create_stream(
pjmedia_vid_dev_factory *f,
pjmedia_vid_param *param,
const pjmedia_vid_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;
/* 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;
pj_memcpy(&strm->vid_cb, cb, sizeof(*cb));
strm->user_data = user_data;
/* Create capture stream here */
if (param->dir & PJMEDIA_DIR_CAPTURE) {
}
/* Create render stream here */
if (param->dir & PJMEDIA_DIR_RENDER) {
strm->status = PJ_SUCCESS;
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
pj_assert(![NSThread isMainThread]);
strm->apool = [[NSAutoreleasePool alloc] init];
strm->delegate = [[SDLDelegate alloc]init];
strm->delegate->strm = strm;
[strm->delegate performSelectorOnMainThread:@selector(sdl_thread)
withObject:nil waitUntilDone:YES];
if ((status = strm->status) != PJ_SUCCESS) {
goto on_error;
}
#endif
status = pj_thread_create(pool, "sdl_thread", sdlthread,
strm, 0, 0, &strm->sdl_thread);
if (status != PJ_SUCCESS) {
goto on_error;
}
while(strm->status == PJ_SUCCESS && !strm->surf && !strm->overlay
#if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
&& !strm->texture
#endif
)
{
pj_thread_sleep(10);
}
if ((status = strm->status) != PJ_SUCCESS) {
goto on_error;
}
pjmedia_format_copy(&strm->conv_param.src, &param->fmt);
pjmedia_format_copy(&strm->conv_param.dst, &param->fmt);
/*
status = pjmedia_converter_create(NULL, pool, &strm->conv_param,
&strm->conv);
*/
}
/* Apply the remaining settings */
if (param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
sdl_stream_set_cap(&strm->base,
PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW,
&param->window);
}
/* 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_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->fmt.info_size) == PJ_SUCCESS)
{
pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW;
}
*/
return PJ_SUCCESS;
}
/* 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;
PJ_UNUSED_ARG(strm);
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW)
{
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
return PJ_SUCCESS;
} else {
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;
PJ_UNUSED_ARG(strm);
PJ_ASSERT_RETURN(s && pval, PJ_EINVAL);
if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) {
return PJ_SUCCESS;
} else if (cap == PJMEDIA_VID_DEV_CAP_FORMAT) {
SDL_Event sevent;
strm->status = PJ_TRUE;
sevent.type = SDL_USEREVENT;
sevent.user.code = PJMEDIA_VID_DEV_CAP_FORMAT;
sevent.user.data1 = (void *)pval;
SDL_PushEvent(&sevent);
while (strm->status == PJ_TRUE)
pj_thread_sleep(10);
if (strm->status != PJ_SUCCESS) {
pj_status_t status = strm->status;
/**
* Failed to change the output format. Try to revert
* to its original format.
*/
strm->status = PJ_TRUE;
sevent.user.data1 = &strm->param.fmt;
SDL_PushEvent(&sevent);
while (strm->status == PJ_TRUE)
pj_thread_sleep(10);
if (strm->status != PJ_SUCCESS) {
/**
* This means that we failed to revert to our
* original state!
*/
status = PJMEDIA_EVID_ERR;
}
strm->status = status;
} else if (strm->status == PJ_SUCCESS) {
pj_memcpy(&strm->param.fmt, pval, sizeof(strm->param.fmt));
}
return strm->status;
}
return PJMEDIA_EVID_INVCAP;
}
/* 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;
stream->render_exited = PJ_FALSE;
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;
unsigned i;
PJ_LOG(4, (THIS_FILE, "Stopping sdl video stream"));
/* Wait for renderer put_frame() to finish */
stream->is_running = PJ_FALSE;
for (i=0; !stream->render_exited && i<100; ++i)
pj_thread_sleep(10);
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;
SDL_Event sevent;
PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL);
sdl_stream_stop(strm);
if (!stream->is_quitting) {
sevent.type = SDL_USEREVENT;
sevent.user.code = PJMEDIA_EVENT_NONE;
SDL_PushEvent(&sevent);
if (stream->sdl_thread)
pj_thread_join(stream->sdl_thread);
}
#if defined(PJ_DARWINOS) && PJ_DARWINOS!=0
if (stream->delegate)
[stream->delegate release];
if (stream->apool)
[stream->apool release];
#endif
pj_pool_release(stream->pool);
return PJ_SUCCESS;
}
#ifdef _MSC_VER
# pragma comment( lib, "sdl.lib")
# if PJMEDIA_VIDEO_DEV_SDL_HAS_OPENGL
# pragma comment(lib, "OpenGL32.lib")
# endif
#endif
#endif /* PJMEDIA_VIDEO_DEV_HAS_SDL */