diff options
author | Liong Sauw Ming <ming@teluu.com> | 2014-04-08 09:03:35 +0000 |
---|---|---|
committer | Liong Sauw Ming <ming@teluu.com> | 2014-04-08 09:03:35 +0000 |
commit | f8f81f703f4715440cbfb28c2232345df5564b80 (patch) | |
tree | 973fddaaf0cc475e41ec893a4c7a8b02c8f3e137 | |
parent | 8917c6a09a7f3a4332157b73c014df525a17d46f (diff) |
Re #1757: Initial implementation of iOS OpenGL ES renderer. To use it, application needs to add:
#define PJMEDIA_VIDEO_DEV_HAS_OPENGL 1
#define PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES 1
#define PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL 1
git-svn-id: http://svn.pjsip.org/repos/pjproject/trunk@4812 74dad513-b988-da41-8d7b-12977e46ad98
-rw-r--r-- | pjmedia/build/Makefile | 2 | ||||
-rw-r--r-- | pjmedia/build/os-auto.mak.in | 2 | ||||
-rw-r--r-- | pjmedia/include/pjmedia-videodev/opengl_dev.h | 49 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/ios_opengl_dev.m | 429 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/opengl_dev.c | 475 | ||||
-rw-r--r-- | pjmedia/src/pjmedia-videodev/videodev.c | 7 |
6 files changed, 962 insertions, 2 deletions
diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile index 8012cb75..23dc715e 100644 --- a/pjmedia/build/Makefile +++ b/pjmedia/build/Makefile @@ -106,7 +106,7 @@ export PJMEDIA_AUDIODEV_LDFLAGS += $(PJLIB_LDLIB) \ # export PJMEDIA_VIDEODEV_SRCDIR = ../src/pjmedia-videodev export PJMEDIA_VIDEODEV_OBJS += errno.o videodev.o avi_dev.o ffmpeg_dev.o \ - colorbar_dev.o v4l2_dev.o + colorbar_dev.o v4l2_dev.o opengl_dev.o export PJMEDIA_VIDEODEV_CFLAGS += $(_CFLAGS) export PJMEDIA_VIDEODEV_CXXFLAGS += $(_CXXFLAGS) export PJMEDIA_VIDEODEV_LDFLAGS += $(PJLIB_LDLIB) \ diff --git a/pjmedia/build/os-auto.mak.in b/pjmedia/build/os-auto.mak.in index c0642cc8..ea057824 100644 --- a/pjmedia/build/os-auto.mak.in +++ b/pjmedia/build/os-auto.mak.in @@ -214,7 +214,7 @@ endif # iOS video device # ifeq ($(AC_PJMEDIA_VIDEO),iphone_os) -export PJMEDIA_VIDEODEV_OBJS += ios_dev.o +export PJMEDIA_VIDEODEV_OBJS += ios_dev.o ios_opengl_dev.o endif # diff --git a/pjmedia/include/pjmedia-videodev/opengl_dev.h b/pjmedia/include/pjmedia-videodev/opengl_dev.h new file mode 100644 index 00000000..f5eb67b0 --- /dev/null +++ b/pjmedia/include/pjmedia-videodev/opengl_dev.h @@ -0,0 +1,49 @@ +/* $Id$ */ +/* + * Copyright (C) 2013-2014 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 + */ +#ifndef PJMEDIA_VIDEODEV_OPENGL_DEV_H__ +#define PJMEDIA_VIDEODEV_OPENGL_DEV_H__ + +#include <pjmedia-videodev/videodev_imp.h> + +/* OpenGL implementation on each platform needs to implement this and + * stream operations. + */ +pj_status_t +pjmedia_vid_dev_opengl_imp_create_stream(pj_pool_t *pool, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm); + +/* OpenGL buffers opaque structure. */ +typedef struct gl_buffers gl_buffers; + +/* Create OpenGL buffers. */ +void pjmedia_vid_dev_opengl_create_buffers(pj_pool_t *pool, + gl_buffers **glb); +/* Initialize OpenGL buffers. */ +pj_status_t pjmedia_vid_dev_opengl_init_buffers(gl_buffers *glb); +/* Render a texture. */ +pj_status_t pjmedia_vid_dev_opengl_draw(gl_buffers *glb, + unsigned int texture, + unsigned int name); +/* Destroy OpenGL buffers. */ +void pjmedia_vid_dev_opengl_destroy_buffers(gl_buffers *glb); + +#endif /* PJMEDIA_VIDEODEV_OPENGL_DEV_H__ */ diff --git a/pjmedia/src/pjmedia-videodev/ios_opengl_dev.m b/pjmedia/src/pjmedia-videodev/ios_opengl_dev.m new file mode 100644 index 00000000..bf608e3e --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/ios_opengl_dev.m @@ -0,0 +1,429 @@ +/* $Id$ */ +/* + * Copyright (C) 2013-2014 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 <pj/assert.h> +#include <pj/log.h> + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL) && \ + PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL != 0 + +#include <pjmedia-videodev/opengl_dev.h> +#import <UIKit/UIKit.h> + +#define THIS_FILE "ios_opengl_dev.c" + +typedef struct iosgl_fmt_info +{ + pjmedia_format_id pjmedia_format; + UInt32 iosgl_format; +} iosgl_fmt_info; + +/* Supported formats */ +static iosgl_fmt_info iosgl_fmts[] = +{ + {PJMEDIA_FORMAT_BGRA, kCVPixelFormatType_32BGRA} , +}; + +@interface GLView : UIView +{ +@public + struct iosgl_stream *stream; +} + +@end + +/* Video stream. */ +struct iosgl_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 */ + + pj_status_t status; + pj_timestamp frame_ts; + unsigned ts_inc; + + gl_buffers *gl_buf; + GLView *gl_view; + EAGLContext *ogl_context; + CVOpenGLESTextureCacheRef vid_texture; + CVImageBufferRef pb; + CVOpenGLESTextureRef texture; +}; + + +/* Prototypes */ +static pj_status_t iosgl_stream_get_param(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_param *param); +static pj_status_t iosgl_stream_get_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + void *value); +static pj_status_t iosgl_stream_set_cap(pjmedia_vid_dev_stream *strm, + pjmedia_vid_dev_cap cap, + const void *value); +static pj_status_t iosgl_stream_start(pjmedia_vid_dev_stream *strm); +static pj_status_t iosgl_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame); +static pj_status_t iosgl_stream_stop(pjmedia_vid_dev_stream *strm); +static pj_status_t iosgl_stream_destroy(pjmedia_vid_dev_stream *strm); + +/* Operations */ +static pjmedia_vid_dev_stream_op stream_op = +{ + &iosgl_stream_get_param, + &iosgl_stream_get_cap, + &iosgl_stream_set_cap, + &iosgl_stream_start, + NULL, + &iosgl_stream_put_frame, + &iosgl_stream_stop, + &iosgl_stream_destroy +}; + +static iosgl_fmt_info* get_iosgl_format_info(pjmedia_format_id id) +{ + unsigned i; + + for (i = 0; i < PJ_ARRAY_SIZE(iosgl_fmts); i++) { + if (iosgl_fmts[i].pjmedia_format == id) + return &iosgl_fmts[i]; + } + + return NULL; +} + +@implementation GLView + ++ (Class) layerClass +{ + return [CAEAGLLayer class]; +} + +- (void) init_buffers +{ + /* Initialize OpenGL ES 2 */ + CAEAGLLayer *eagl_layer = (CAEAGLLayer *)[stream->gl_view layer]; + eagl_layer.opaque = YES; + eagl_layer.drawableProperties = + [NSDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, + kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, + nil]; + + stream->ogl_context = [[EAGLContext alloc] initWithAPI: + kEAGLRenderingAPIOpenGLES2]; + if (!stream->ogl_context || + ![EAGLContext setCurrentContext:stream->ogl_context]) + { + stream->status = PJMEDIA_EVID_SYSERR; + return; + } + + /* Create GL buffers */ + pjmedia_vid_dev_opengl_create_buffers(stream->pool, &stream->gl_buf); + + [stream->ogl_context renderbufferStorage:GL_RENDERBUFFER + fromDrawable:(CAEAGLLayer *)[stream->gl_view layer]]; + + /* Init GL buffers */ + stream->status = pjmedia_vid_dev_opengl_init_buffers(stream->gl_buf); +} + +- (void)render +{ + if (![EAGLContext setCurrentContext:stream->ogl_context]) { + /* Failed to set context */ + return; + } + + pjmedia_vid_dev_opengl_draw(stream->gl_buf, + (unsigned int)CVOpenGLESTextureGetTarget(stream->texture), + (unsigned int)CVOpenGLESTextureGetName(stream->texture)); +} + +@end + +/* API: create stream */ +pj_status_t +pjmedia_vid_dev_opengl_imp_create_stream(pj_pool_t *pool, + pjmedia_vid_dev_param *param, + const pjmedia_vid_dev_cb *cb, + void *user_data, + pjmedia_vid_dev_stream **p_vid_strm) +{ + struct iosgl_stream *strm; + const pjmedia_video_format_detail *vfd; + pj_status_t status = PJ_SUCCESS; + iosgl_fmt_info *ifi; + + if (!(ifi = get_iosgl_format_info(param->fmt.id))) + return PJMEDIA_EVID_BADFORMAT; + + strm = PJ_POOL_ZALLOC_T(pool, struct iosgl_stream); + pj_memcpy(&strm->param, param, sizeof(*param)); + strm->pool = pool; + pj_memcpy(&strm->vid_cb, cb, sizeof(*cb)); + strm->user_data = user_data; + + vfd = pjmedia_format_get_video_format_detail(&strm->param.fmt, PJ_TRUE); + strm->ts_inc = PJMEDIA_SPF2(param->clock_rate, &vfd->fps, 1); + + if (param->dir & PJMEDIA_DIR_RENDER) { + CVReturn err; + UIWindow *window; + CGRect rect; + + if ((param->flags & PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) && + param->window.info.ios.window) + { + /* Get output window handle provided by the application */ + window = (UIWindow *)param->window.info.ios.window; + rect = window.bounds; + } else { + rect = CGRectMake(0, 0, strm->param.disp_size.w, + strm->param.disp_size.h); + } + + strm->gl_view = [[GLView alloc] initWithFrame:rect]; + if (!strm->gl_view) + return PJ_ENOMEM; + strm->gl_view->stream = strm; + + /* Perform OpenGL buffer initializations in the main thread. */ + strm->status = PJ_SUCCESS; + [strm->gl_view performSelectorOnMainThread:@selector(init_buffers) + withObject:nil waitUntilDone:YES]; + if ((status = strm->status) != PJ_SUCCESS) { + PJ_LOG(3, (THIS_FILE, "Unable to create and init OpenGL buffers")); + goto on_error; + } + + /* Create a new CVOpenGLESTexture cache */ + err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, NULL, + strm->ogl_context, NULL, + &strm->vid_texture); + if (err) { + PJ_LOG(3, (THIS_FILE, "Unable to create OpenGL texture cache %d", + err)); + status = PJMEDIA_EVID_SYSERR; + goto on_error; + } + + PJ_LOG(4, (THIS_FILE, "iOS OpenGL ES renderer successfully created")); + } + + /* Apply the remaining settings */ + /* + if (param->flags & PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + iosgl_stream_set_cap(&strm->base, + PJMEDIA_VID_DEV_CAP_INPUT_SCALE, + ¶m->fmt); + } + */ + /* Done */ + strm->base.op = &stream_op; + *p_vid_strm = &strm->base; + + return PJ_SUCCESS; + +on_error: + iosgl_stream_destroy((pjmedia_vid_dev_stream *)strm); + + return status; +} + +/* API: Get stream info. */ +static pj_status_t iosgl_stream_get_param(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_param *pi) +{ + struct iosgl_stream *strm = (struct iosgl_stream*)s; + + PJ_ASSERT_RETURN(strm && pi, PJ_EINVAL); + + pj_memcpy(pi, &strm->param, sizeof(*pi)); + + if (iosgl_stream_get_cap(s, PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW, + &pi->window) == PJ_SUCCESS) + { + pi->flags |= PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + } + + return PJ_SUCCESS; +} + +/* API: get capability */ +static pj_status_t iosgl_stream_get_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + void *pval) +{ + struct iosgl_stream *strm = (struct iosgl_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + return PJMEDIA_EVID_INVCAP; + } else if (cap == PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW) { + pjmedia_vid_dev_hwnd *wnd = (pjmedia_vid_dev_hwnd *)pval; + wnd->info.ios.window = strm->gl_view; + return PJ_SUCCESS; + } else { + return PJMEDIA_EVID_INVCAP; + } +} + +/* API: set capability */ +static pj_status_t iosgl_stream_set_cap(pjmedia_vid_dev_stream *s, + pjmedia_vid_dev_cap cap, + const void *pval) +{ + struct iosgl_stream *strm = (struct iosgl_stream*)s; + + PJ_UNUSED_ARG(strm); + + PJ_ASSERT_RETURN(s && pval, PJ_EINVAL); + + if (cap==PJMEDIA_VID_DEV_CAP_INPUT_SCALE) { + return PJ_SUCCESS; + } + + return PJMEDIA_EVID_INVCAP; +} + +/* API: Start stream. */ +static pj_status_t iosgl_stream_start(pjmedia_vid_dev_stream *strm) +{ + struct iosgl_stream *stream = (struct iosgl_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Starting ios opengl stream")); + + return PJ_SUCCESS; +} + +/* API: Put frame from stream */ +static pj_status_t iosgl_stream_put_frame(pjmedia_vid_dev_stream *strm, + const pjmedia_frame *frame) +{ + struct iosgl_stream *stream = (struct iosgl_stream*)strm; + CVReturn err; + + err = CVPixelBufferCreateWithBytes(kCFAllocatorDefault, + stream->param.disp_size.w, + stream->param.disp_size.h, + kCVPixelFormatType_32BGRA, + frame->buf, + stream->param.disp_size.w * 4, + NULL, NULL, NULL, &stream->pb); + if (err) { + PJ_LOG(3, (THIS_FILE, "Unable to create pixel buffer %d", err)); + return PJMEDIA_EVID_SYSERR; + } + + /* Create a CVOpenGLESTexture from the CVImageBuffer */ + err=CVOpenGLESTextureCacheCreateTextureFromImage(kCFAllocatorDefault, + stream->vid_texture, + stream->pb, NULL, + GL_TEXTURE_2D, GL_RGBA, + stream->param.disp_size.w, + stream->param.disp_size.h, + GL_BGRA, + GL_UNSIGNED_BYTE, + 0, &stream->texture); + if (!stream->texture || err) { + PJ_LOG(3, (THIS_FILE, "Unable to create OpenGL texture %d", err)); + CVPixelBufferRelease(stream->pb); + return PJMEDIA_EVID_SYSERR; + } + + /* Perform OpenGL drawing in the main thread. */ + [stream->gl_view performSelectorOnMainThread:@selector(render) + withObject:nil waitUntilDone:YES]; + // dispatch_async(dispatch_get_main_queue(), + // ^{[stream->gl_view render];}); + + [stream->ogl_context presentRenderbuffer:GL_RENDERBUFFER]; + + /* Flush the CVOpenGLESTexture cache and release the texture */ + CVOpenGLESTextureCacheFlush(stream->vid_texture, 0); + CFRelease(stream->texture); + CVPixelBufferRelease(stream->pb); + + return PJ_SUCCESS; +} + +/* API: Stop stream. */ +static pj_status_t iosgl_stream_stop(pjmedia_vid_dev_stream *strm) +{ + struct iosgl_stream *stream = (struct iosgl_stream*)strm; + + PJ_UNUSED_ARG(stream); + + PJ_LOG(4, (THIS_FILE, "Stopping ios opengl stream")); + + return PJ_SUCCESS; +} + + +/* API: Destroy stream. */ +static pj_status_t iosgl_stream_destroy(pjmedia_vid_dev_stream *strm) +{ + struct iosgl_stream *stream = (struct iosgl_stream*)strm; + + PJ_ASSERT_RETURN(stream != NULL, PJ_EINVAL); + + iosgl_stream_stop(strm); + + if (stream->vid_texture) { + CFRelease(stream->vid_texture); + stream->vid_texture = NULL; + } + + if ([EAGLContext currentContext] == stream->ogl_context) + [EAGLContext setCurrentContext:nil]; + + if (stream->ogl_context) { + [stream->ogl_context release]; + stream->ogl_context = NULL; + } + + if (stream->gl_view) { + UIView *view = stream->gl_view; + dispatch_async(dispatch_get_main_queue(), + ^{ + [view removeFromSuperview]; + [view release]; + }); + stream->gl_view = NULL; + } + + pjmedia_vid_dev_opengl_destroy_buffers(stream->gl_buf); + + pj_pool_release(stream->pool); + + return PJ_SUCCESS; +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_IOS_OPENGL */ diff --git a/pjmedia/src/pjmedia-videodev/opengl_dev.c b/pjmedia/src/pjmedia-videodev/opengl_dev.c new file mode 100644 index 00000000..a25090ef --- /dev/null +++ b/pjmedia/src/pjmedia-videodev/opengl_dev.c @@ -0,0 +1,475 @@ +/* $Id$ */ +/* + * Copyright (C) 2013-2014 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 <pj/assert.h> +#include <pj/log.h> + +#if defined(PJMEDIA_HAS_VIDEO) && PJMEDIA_HAS_VIDEO != 0 && \ + defined(PJMEDIA_VIDEO_DEV_HAS_OPENGL) && \ + PJMEDIA_VIDEO_DEV_HAS_OPENGL != 0 + +#include <pjmedia-videodev/opengl_dev.h> +#ifdef PJMEDIA_VIDEO_DEV_HAS_OPENGL_ES +# include <OpenGLES/ES2/gl.h> +# include <OpenGLES/ES2/glext.h> +#else +# include <GL/gl.h> +# include <GL/glext.h> +#endif + +#define THIS_FILE "opengl_dev.c" +#define DEFAULT_CLOCK_RATE 90000 +#define DEFAULT_WIDTH 480 +#define DEFAULT_HEIGHT 360 +#define DEFAULT_FPS 15 + +#define LOG(a) + +enum { + ATTRIB_VERTEX, + ATTRIB_TEXTUREPOSITON, + NUM_ATTRIBUTES +}; + +/* Vertex and fragment shaders */ +static const GLchar *vertSrc = " \ +attribute vec4 position; \ +attribute vec4 inTexCoord; \ +varying vec2 texCoord; \ +void main() \ +{ \ + gl_Position = position; \ + texCoord = inTexCoord.xy; \ +} \ +"; +static const GLchar *fragSrc = " \ +varying highp vec2 texCoord; \ +uniform sampler2D videoFrame; \ +void main() \ +{ \ + gl_FragColor = texture2D(videoFrame, texCoord); \ +} \ +"; + +/* OpenGL buffers structure. */ +struct gl_buffers { + GLuint frameBuf; + GLuint rendBuf; + GLuint directProg; + + int rendBufW; + int rendBufH; +}; + +/* Supported formats */ +static pjmedia_format_id opengl_fmts[] = {PJMEDIA_FORMAT_BGRA}; + +/* opengl device info */ +struct opengl_dev_info +{ + pjmedia_vid_dev_info info; +}; + +/* opengl factory */ +struct opengl_factory +{ + pjmedia_vid_dev_factory base; + pj_pool_t *pool; + pj_pool_factory *pf; + + unsigned dev_count; + struct opengl_dev_info *dev_info; +}; + +/* Prototypes */ +static pj_status_t opengl_factory_init(pjmedia_vid_dev_factory *f); +static pj_status_t opengl_factory_destroy(pjmedia_vid_dev_factory *f); +static pj_status_t opengl_factory_refresh(pjmedia_vid_dev_factory *f); +static unsigned opengl_factory_get_dev_count(pjmedia_vid_dev_factory *f); +static pj_status_t opengl_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info); +static pj_status_t opengl_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param); +static pj_status_t +opengl_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); + +/* Operations */ +static pjmedia_vid_dev_factory_op factory_op = +{ + &opengl_factory_init, + &opengl_factory_destroy, + &opengl_factory_get_dev_count, + &opengl_factory_get_dev_info, + &opengl_factory_default_param, + &opengl_factory_create_stream, + &opengl_factory_refresh +}; + +/**************************************************************************** + * OpenGL utility functions + */ +/* Compile a shader from the provided source(s) */ +GLint compile_shader(GLenum target, GLsizei count, const GLchar **sources, + GLuint *shader) +{ + GLint status; + + *shader = glCreateShader(target); + glShaderSource(*shader, count, sources, NULL); + glCompileShader(*shader); + + glGetShaderiv(*shader, GL_COMPILE_STATUS, &status); + + return status; +} + +/* Create program, compile shader, link program, bind attributes */ +GLint create_program(const GLchar *vertSource, const GLchar *fragSource, + GLsizei attribNameCnt, const GLchar **attribNames, + const GLint *attribLocations, GLuint *program) +{ + GLuint vertShader = 0, fragShader = 0, prog = 0, i; + GLint status; + + /* Create shader program */ + prog = glCreateProgram(); + *program = prog; + + /* Create and compile vertex shader */ + status = compile_shader(GL_VERTEX_SHADER, 1, &vertSource, &vertShader); + if (status == 0) { + LOG("Unable to compile vertex shader"); + return status; + } + + /* Create and compile fragment shader */ + status = compile_shader(GL_FRAGMENT_SHADER, 1, &fragSource, &fragShader); + if (status == 0) { + LOG("Unable to compile fragment shader"); + return status; + } + + /* Attach vertex shader to program */ + glAttachShader(prog, vertShader); + + /* Attach fragment shader to program */ + glAttachShader(prog, fragShader); + + /* Bind attribute locations prior to linking */ + for (i = 0; i < attribNameCnt; i++) { + glBindAttribLocation(prog, attribLocations[i], attribNames[i]); + } + + /* Link program */ + glLinkProgram(prog); + glGetProgramiv(prog, GL_LINK_STATUS, &status); + if (status == 0) { + LOG("Unable to link program"); + return status; + } + + /* Release vertex and fragment shaders */ + if (vertShader) + glDeleteShader(vertShader); + if (fragShader) + glDeleteShader(fragShader); + + return status; +} + +void pjmedia_vid_dev_opengl_create_buffers(pj_pool_t *pool, gl_buffers **glb) +{ + gl_buffers *glbuf = PJ_POOL_ZALLOC_T(pool, gl_buffers); + + *glb = glbuf; + glDisable(GL_DEPTH_TEST); + + glGenFramebuffers(1, &glbuf->frameBuf); + glBindFramebuffer(GL_FRAMEBUFFER, glbuf->frameBuf); + + glGenRenderbuffers(1, &glbuf->rendBuf); + glBindRenderbuffer(GL_RENDERBUFFER, glbuf->rendBuf); +} + +pj_status_t pjmedia_vid_dev_opengl_init_buffers(gl_buffers *glb) +{ + /* Attributes */ + GLint attribLocation[NUM_ATTRIBUTES] = { ATTRIB_VERTEX, + ATTRIB_TEXTUREPOSITON }; + GLchar *attribName[NUM_ATTRIBUTES] = { "position", "texCoord" }; + + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, + &glb->rendBufW); + glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, + &glb->rendBufH); + + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, + GL_RENDERBUFFER, glb->rendBuf); + if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LOG("Unable to create frame buffer"); + return -1; + } + + create_program(vertSrc, fragSrc, NUM_ATTRIBUTES, + (const GLchar **)&attribName[0], attribLocation, + &glb->directProg); + + if (!glb->directProg) { + LOG("Unable to create program"); + return -2; + } + + return PJ_SUCCESS; +} + +pj_status_t pjmedia_vid_dev_opengl_draw(gl_buffers *glb, unsigned int texture, + unsigned int name) +{ + static const GLfloat squareVertices[] = { + -1.0f, -1.0f, + 1.0f, -1.0f, + -1.0f, 1.0f, + 1.0f, 1.0f, + }; + GLfloat textureVertices[] = { + 0, 1, 1, 1, 0, 0, 1, 0 + }; + GLenum tex = (GLenum) texture; + GLuint nam = (GLuint) name; + + glBindTexture(tex, nam); + + /* Set texture parameters */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindFramebuffer(GL_FRAMEBUFFER, glb->frameBuf); + + /* Set the view port to the entire view */ + glViewport(0, 0, glb->rendBufW, glb->rendBufH); + + /* Draw the texture on the screen with OpenGL ES 2 */ + /* Use program */ + glUseProgram(glb->directProg); + + /* Update attribute values */ + glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, squareVertices); + glEnableVertexAttribArray(ATTRIB_VERTEX); + glVertexAttribPointer(ATTRIB_TEXTUREPOSITON, 2, GL_FLOAT, 0, 0, + textureVertices); + glEnableVertexAttribArray(ATTRIB_TEXTUREPOSITON); + + /* Update uniform values if there are any */ + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + /* Present */ + glBindRenderbuffer(GL_RENDERBUFFER, glb->rendBuf); + + return PJ_SUCCESS; +} + +void pjmedia_vid_dev_opengl_destroy_buffers(gl_buffers *glb) +{ + if (glb->frameBuf) { + glDeleteFramebuffers(1, &glb->frameBuf); + glb->frameBuf = 0; + } + + if (glb->rendBuf) { + glDeleteRenderbuffers(1, &glb->rendBuf); + glb->rendBuf = 0; + } + + if (glb->directProg) { + glDeleteProgram(glb->directProg); + glb->directProg = 0; + } +} + +/**************************************************************************** + * Factory operations + */ +/* + * Init opengl video driver. + */ +pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf) +{ + struct opengl_factory *f; + pj_pool_t *pool; + + pool = pj_pool_create(pf, "opengl rend", 512, 512, NULL); + f = PJ_POOL_ZALLOC_T(pool, struct opengl_factory); + f->pf = pf; + f->pool = pool; + f->base.op = &factory_op; + + return &f->base; +} + +/* API: init factory */ +static pj_status_t opengl_factory_init(pjmedia_vid_dev_factory *f) +{ + struct opengl_factory *qf = (struct opengl_factory*)f; + struct opengl_dev_info *qdi; + unsigned i, l; + + /* Initialize input and output devices here */ + qf->dev_info = (struct opengl_dev_info*) + pj_pool_calloc(qf->pool, 1, sizeof(struct opengl_dev_info)); + + qf->dev_count = 0; + qdi = &qf->dev_info[qf->dev_count++]; + pj_bzero(qdi, sizeof(*qdi)); + strcpy(qdi->info.name, "OpenGL renderer"); + strcpy(qdi->info.driver, "OpenGL"); + qdi->info.dir = PJMEDIA_DIR_RENDER; + qdi->info.has_callback = PJ_FALSE; + qdi->info.caps = PJMEDIA_VID_DEV_CAP_OUTPUT_WINDOW; + + for (i = 0; i < qf->dev_count; i++) { + qdi = &qf->dev_info[i]; + qdi->info.fmt_cnt = PJ_ARRAY_SIZE(opengl_fmts); + qdi->info.caps |= PJMEDIA_VID_DEV_CAP_FORMAT; + + for (l = 0; l < PJ_ARRAY_SIZE(opengl_fmts); l++) { + pjmedia_format *fmt = &qdi->info.fmt[l]; + pjmedia_format_init_video(fmt, + opengl_fmts[l], + DEFAULT_WIDTH, + DEFAULT_HEIGHT, + DEFAULT_FPS, 1); + } + } + + PJ_LOG(4, (THIS_FILE, "OpenGL initialized with %d devices", + qf->dev_count)); + + return PJ_SUCCESS; +} + +/* API: destroy factory */ +static pj_status_t opengl_factory_destroy(pjmedia_vid_dev_factory *f) +{ + struct opengl_factory *qf = (struct opengl_factory*)f; + pj_pool_t *pool = qf->pool; + + qf->pool = NULL; + pj_pool_release(pool); + + return PJ_SUCCESS; +} + +/* API: refresh the list of devices */ +static pj_status_t opengl_factory_refresh(pjmedia_vid_dev_factory *f) +{ + PJ_UNUSED_ARG(f); + return PJ_SUCCESS; +} + +/* API: get number of devices */ +static unsigned opengl_factory_get_dev_count(pjmedia_vid_dev_factory *f) +{ + struct opengl_factory *qf = (struct opengl_factory*)f; + return qf->dev_count; +} + +/* API: get device info */ +static pj_status_t opengl_factory_get_dev_info(pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_info *info) +{ + struct opengl_factory *qf = (struct opengl_factory*)f; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + pj_memcpy(info, &qf->dev_info[index].info, sizeof(*info)); + + return PJ_SUCCESS; +} + +/* API: create default device parameter */ +static pj_status_t opengl_factory_default_param(pj_pool_t *pool, + pjmedia_vid_dev_factory *f, + unsigned index, + pjmedia_vid_dev_param *param) +{ + struct opengl_factory *qf = (struct opengl_factory*)f; + struct opengl_dev_info *di = &qf->dev_info[index]; + + PJ_ASSERT_RETURN(index < qf->dev_count, PJMEDIA_EVID_INVDEV); + + PJ_UNUSED_ARG(pool); + + pj_bzero(param, sizeof(*param)); + 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; + } + + param->flags = PJMEDIA_VID_DEV_CAP_FORMAT; + param->clock_rate = DEFAULT_CLOCK_RATE; + pj_memcpy(¶m->fmt, &di->info.fmt[0], sizeof(param->fmt)); + + return PJ_SUCCESS; +} + +/* API: create stream */ +static pj_status_t +opengl_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 opengl_factory *qf = (struct opengl_factory*)f; + pj_pool_t *pool; + const pjmedia_video_format_info *vfi; + + PJ_ASSERT_RETURN(f && param && p_vid_strm, PJ_EINVAL); + PJ_ASSERT_RETURN(param->fmt.type == PJMEDIA_TYPE_VIDEO && + param->fmt.detail_type == PJMEDIA_FORMAT_DETAIL_VIDEO && + (param->dir == PJMEDIA_DIR_CAPTURE || + param->dir == PJMEDIA_DIR_RENDER), + PJ_EINVAL); + + vfi = pjmedia_get_video_format_info(NULL, param->fmt.id); + if (!vfi) + return PJMEDIA_EVID_BADFORMAT; + + /* Create and Initialize stream descriptor */ + pool = pj_pool_create(qf->pf, "opengl-dev", 4000, 4000, NULL); + PJ_ASSERT_RETURN(pool != NULL, PJ_ENOMEM); + + return pjmedia_vid_dev_opengl_imp_create_stream(pool, param, cb, + user_data, p_vid_strm); +} + +#endif /* PJMEDIA_VIDEO_DEV_HAS_OPENGL */ diff --git a/pjmedia/src/pjmedia-videodev/videodev.c b/pjmedia/src/pjmedia-videodev/videodev.c index 658d091a..9eacc51f 100644 --- a/pjmedia/src/pjmedia-videodev/videodev.c +++ b/pjmedia/src/pjmedia-videodev/videodev.c @@ -97,6 +97,10 @@ pjmedia_vid_dev_factory* pjmedia_qt_factory(pj_pool_factory *pf); pjmedia_vid_dev_factory* pjmedia_ios_factory(pj_pool_factory *pf); #endif +#if PJMEDIA_VIDEO_DEV_HAS_OPENGL +pjmedia_vid_dev_factory* pjmedia_opengl_factory(pj_pool_factory *pf); +#endif + #define MAX_DRIVERS 16 #define MAX_DEVS 64 @@ -380,6 +384,9 @@ PJ_DEF(pj_status_t) pjmedia_vid_dev_subsys_init(pj_pool_factory *pf) #if PJMEDIA_VIDEO_DEV_HAS_IOS vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_ios_factory; #endif +#if PJMEDIA_VIDEO_DEV_HAS_OPENGL + vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_opengl_factory; +#endif #if PJMEDIA_VIDEO_DEV_HAS_DSHOW vid_subsys.drv[vid_subsys.drv_cnt++].create = &pjmedia_dshow_factory; #endif |