]> git.sesse.net Git - vlc/blob - modules/video_output/ios.m
opensles: set period size
[vlc] / modules / video_output / ios.m
1 /*****************************************************************************
2  * ios.m: iOS X OpenGLES provider
3  *****************************************************************************
4  * Copyright (C) 2001-2012 VLC Authors and VideoLAN
5  * $Id$
6  *
7  * Authors: Romain Goyet <romain.goyet at likid dot org>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #import <UIKit/UIKit.h>
29 #import <OpenGLES/ES1/gl.h>
30 #import <OpenGLES/ES1/glext.h>
31 #import <QuartzCore/QuartzCore.h>
32 #import <dlfcn.h>
33
34 #ifdef HAVE_CONFIG_H
35 # include "config.h"
36 #endif
37
38 #include <vlc_common.h>
39 #include <vlc_plugin.h>
40 #include <vlc_vout_display.h>
41 #include <vlc_opengl.h>
42
43 #include "opengl.h"
44
45 /**
46  * Forward declarations
47  */
48 static int Open(vlc_object_t *);
49 static void Close(vlc_object_t *);
50
51 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count);
52 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
53 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture);
54 static int Control (vout_display_t *vd, int query, va_list ap);
55
56 static void *OurGetProcAddress(vlc_gl_t *, const char *);
57
58 static int OpenglClean(vlc_gl_t *gl);
59 static void OpenglSwap(vlc_gl_t *gl);
60
61 /**
62  * Module declaration
63  */
64 vlc_module_begin ()
65     /* Will be loaded even without interface module. see voutgl.m */
66     set_shortname("iOS")
67     set_description( N_("iOS OpenGL ES video output (requires UIView)"))
68     set_category(CAT_VIDEO)
69     set_subcategory(SUBCAT_VIDEO_VOUT )
70     set_capability("vout display", 300)
71     set_callbacks(Open, Close)
72
73     add_shortcut("ios", "vout_ios")
74 vlc_module_end ()
75
76 @interface VLCOpenGLESVideoView : UIView
77 {
78     vout_display_t * _vd;
79     EAGLContext * _context;
80     GLuint _defaultFramebuffer;
81     GLuint _colorRenderbuffer;
82     BOOL _framebufferDirty;
83 }
84 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd;
85 @property (readonly) EAGLContext * context;
86 @property (readonly) GLuint colorRenderbuffer;
87 - (void)setVoutDisplay:(vout_display_t *)vd;
88 - (void)cleanFramebuffer;
89 @end
90
91
92 struct vout_display_sys_t
93 {
94     VLCOpenGLESVideoView *glView;
95     UIView * container;
96
97     vlc_gl_t gl;
98     vout_display_opengl_t *vgl;
99
100     picture_pool_t *pool;
101     picture_t *current;
102     bool has_first_frame;
103 };
104
105 static void *OurGetProcAddress(vlc_gl_t *gl, const char *name)
106 {
107     VLC_UNUSED(gl);
108
109     return dlsym(RTLD_DEFAULT, name);
110 }
111
112 // Called from vout thread
113 static int Open(vlc_object_t *this)
114 {
115     vout_display_t *vd = (vout_display_t *)this;
116     vout_display_sys_t *sys = calloc(1, sizeof(*sys));
117     NSAutoreleasePool *nsPool = nil;
118
119     if (!sys)
120         return VLC_ENOMEM;
121
122     vd->sys = sys;
123     sys->pool = NULL;
124     sys->gl.sys = NULL;
125
126     /* Get the drawable object */
127     UIView * container = (UIView *)var_CreateGetAddress(vd, "drawable-nsobject");
128
129     if (![container isKindOfClass:[UIView class]]) {
130         msg_Dbg(vd, "Container isn't an UIView, passing over.");
131         goto error;
132     }
133     vout_display_DeleteWindow(vd, NULL);
134
135     /* This will be released in Close(), on
136      * main thread, after we are done using it. */
137     sys->container = [container retain];
138
139     /* Get our main view*/
140     nsPool = [[NSAutoreleasePool alloc] init];
141
142     msg_Dbg(vd, "Creating VLCOpenGLESVideoView");
143     sys->glView = [[VLCOpenGLESVideoView alloc] initWithFrame:[container bounds] andVOutDisplay:vd];
144     if (!sys->glView)
145         goto error;
146
147     /* We don't wait, that means that we'll have to be careful about releasing
148      * container.
149      * That's why we'll release on main thread in Close(). */
150     [container performSelectorOnMainThread:@selector(addSubview:) withObject:sys->glView waitUntilDone:NO];
151
152     [nsPool drain];
153     nsPool = nil;
154
155     /* Initialize common OpenGL video display */
156     sys->gl.lock = OpenglClean; // We don't do locking, but sometimes we need to cleanup the framebuffer
157     sys->gl.unlock = NULL;
158     sys->gl.swap = OpenglSwap;
159         sys->gl.getProcAddress = OurGetProcAddress;
160     sys->gl.sys = sys;
161
162         sys->vgl = vout_display_opengl_New(&vd->fmt, NULL, &sys->gl);
163         if (!sys->vgl)
164     {
165         sys->gl.sys = NULL;
166         goto error;
167     }
168
169     /* */
170     vout_display_info_t info = vd->info;
171     info.has_pictures_invalid = false;
172
173     /* Setup vout_display_t once everything is fine */
174     vd->info = info;
175
176     vd->pool = Pool;
177     vd->prepare = PictureRender;
178     vd->display = PictureDisplay;
179     vd->control = Control;
180
181     /* */
182     CGRect bounds = sys->glView.layer.bounds;
183     CGFloat scaleFactor = sys->glView.contentScaleFactor;
184     /* we need to multiply the bounds dimensions by the scaleFactor to be save for Retina Displays */
185     vout_display_SendEventFullscreen (vd, false);
186     vout_display_SendEventDisplaySize (vd, bounds.size.width * scaleFactor, bounds.size.height * scaleFactor, false);
187
188     return VLC_SUCCESS;
189
190 error:
191     [nsPool release];
192     Close(this);
193     return VLC_EGENERIC;
194 }
195
196 // Called from vout thread as well
197 void Close(vlc_object_t *this)
198 {
199     vout_display_t *vd = (vout_display_t *)this;
200     vout_display_sys_t *sys = vd->sys;
201
202     [sys->glView setVoutDisplay:nil];
203
204     var_Destroy(vd, "drawable-nsobject");
205     /* release on main thread as explained in Open() */
206     [(id)sys->container performSelectorOnMainThread:@selector(release) withObject:nil waitUntilDone:NO];
207     [sys->glView performSelectorOnMainThread:@selector(removeFromSuperview) withObject:nil waitUntilDone:NO];
208
209     [sys->glView release];
210
211     if (sys->gl.sys != NULL)
212         vout_display_opengl_Delete(sys->vgl);
213
214     free (sys);
215 }
216
217 /*****************************************************************************
218  * vout display callbacks
219  *****************************************************************************/
220
221 static picture_pool_t *Pool(vout_display_t *vd, unsigned requested_count)
222 {
223     vout_display_sys_t *sys = vd->sys;
224
225     if (!sys->pool)
226         sys->pool = vout_display_opengl_GetPool (sys->vgl, requested_count);
227     assert(sys->pool);
228     return sys->pool;
229 }
230
231 static void PictureRender(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
232 {
233     vout_display_sys_t *sys = vd->sys;
234
235     vout_display_opengl_Prepare( sys->vgl, pic, subpicture );
236 }
237
238 static void PictureDisplay(vout_display_t *vd, picture_t *pic, subpicture_t *subpicture)
239 {
240     vout_display_sys_t *sys = vd->sys;
241     vout_display_opengl_Display(sys->vgl, &vd->fmt );
242     picture_Release (pic);
243     sys->has_first_frame = true;
244         (void)subpicture;
245 }
246
247 static int Control (vout_display_t *vd, int query, va_list ap)
248 {
249     vout_display_sys_t *sys = vd->sys;
250
251     switch (query)
252     {
253         case VOUT_DISPLAY_CHANGE_FULLSCREEN:
254         case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
255         case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
256         case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
257         case VOUT_DISPLAY_CHANGE_ZOOM:
258         case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
259         case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
260         {
261             return VLC_SUCCESS;
262         }
263         case VOUT_DISPLAY_HIDE_MOUSE:
264             return VLC_SUCCESS;
265
266         case VOUT_DISPLAY_GET_OPENGL:
267         {
268             vlc_gl_t **gl = va_arg (ap, vlc_gl_t **);
269             *gl = &sys->gl;
270             return VLC_SUCCESS;
271         }
272
273         case VOUT_DISPLAY_RESET_PICTURES:
274             assert (0);
275         default:
276             msg_Err (vd, "Unknown request in iOS vout display");
277             return VLC_EGENERIC;
278     }
279 }
280
281 /*****************************************************************************
282  * vout opengl callbacks
283  *****************************************************************************/
284
285 static int OpenglClean(vlc_gl_t *gl) {
286     vout_display_sys_t *sys = gl->sys;
287     [sys->glView cleanFramebuffer];
288     return 0;
289 }
290
291 static void OpenglSwap(vlc_gl_t *gl)
292 {
293     vout_display_sys_t *sys = gl->sys;
294     EAGLContext *context = [sys->glView context];
295     [context presentRenderbuffer:GL_RENDERBUFFER_OES];
296 }
297
298 /*****************************************************************************
299  * Our UIView object
300  * *ALL* OpenGL calls should happen in the render thread
301  *****************************************************************************/
302
303 @interface VLCOpenGLESVideoView (Private)
304 - (void)_createFramebuffer;
305 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight;
306 - (void)_destroyFramebuffer;
307 @end
308
309 @implementation VLCOpenGLESVideoView
310 @synthesize context=_context, colorRenderbuffer=_colorRenderbuffer;
311 #define VLCAssertMainThread() assert([[NSThread currentThread] isMainThread])
312
313 + (Class)layerClass {
314     return [CAEAGLLayer class];
315 }
316
317 /**
318  * Gets called by the Open() method.
319  */
320
321 - (id)initWithFrame:(CGRect)frame andVOutDisplay:(vout_display_t *)vd {
322     if (self = [super initWithFrame:frame]) {
323         _vd = vd;
324         CAEAGLLayer * eaglLayer = (CAEAGLLayer *)self.layer;
325
326         eaglLayer.opaque = TRUE;
327         eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
328 //                                        [NSNumber numberWithBool:FALSE], kEAGLDrawablePropertyRetainedBacking,
329                                         kEAGLColorFormatRGB565, kEAGLDrawablePropertyColorFormat,
330                                         nil];
331
332         _context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
333         NSAssert(_context && [EAGLContext setCurrentContext:_context], @"Creating context");
334
335         // This shouldn't need to be done on the main thread.
336         // Indeed, it works just fine from the render thread on iOS 3.2 to 4.1
337         // However, if you don't call it from the main thread, it doesn't work on iOS 4.2 beta 1
338         [self performSelectorOnMainThread:@selector(_createFramebuffer) withObject:nil waitUntilDone:YES];
339
340         _framebufferDirty = NO;
341
342         [self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
343     }
344     return self;
345 }
346
347 - (void) dealloc
348 {
349     [_context release];
350     [super dealloc];
351 }
352
353 /**
354  * Gets called by the Close and Open methods.
355  * (Non main thread).
356  */
357 - (void)setVoutDisplay:(vout_display_t *)aVd
358 {
359     @synchronized(self) {
360         _vd = aVd;
361     }
362 }
363
364
365 /**
366  * Method called by UIKit when we have been resized
367  */
368 - (void)layoutSubviews {
369     // CAUTION : This is called from the main thread
370     _framebufferDirty = YES;
371 }
372
373 - (void)cleanFramebuffer {
374     if (_framebufferDirty) {
375         [self _destroyFramebuffer];
376         [self _createFramebuffer];
377         _framebufferDirty = NO;
378     }
379 }
380
381 /* we don't get the correct scale factor if we don't overwrite this method */
382 - (void) drawRect: (CGRect) rect
383 {
384 }
385
386 @end
387
388 @implementation VLCOpenGLESVideoView (Private)
389 - (void)_createFramebuffer {
390     msg_Dbg(_vd, "Creating framebuffer for layer %p with bounds (%.1f,%.1f,%.1f,%.1f)", self.layer, self.layer.bounds.origin.x, self.layer.bounds.origin.y, self.layer.bounds.size.width, self.layer.bounds.size.height);
391     [EAGLContext setCurrentContext:_context];
392     // Create default framebuffer object. The backing will be allocated for the current layer in -resizeFromLayer
393     glGenFramebuffersOES(1, &_defaultFramebuffer); // Generate one framebuffer, store it in _defaultFrameBuffer
394     glGenRenderbuffersOES(1, &_colorRenderbuffer);
395     glBindFramebufferOES(GL_FRAMEBUFFER_OES, _defaultFramebuffer);
396     glBindRenderbufferOES(GL_RENDERBUFFER_OES, _colorRenderbuffer);
397
398     // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
399     // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
400     [_context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
401     glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, _colorRenderbuffer);
402
403     GLint backingWidth, backingHeight;
404     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
405     glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
406
407     if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
408         msg_Err(_vd, "Failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
409     }
410     [self _updateViewportWithBackingWitdh:backingWidth andBackingHeight:backingHeight];
411 }
412
413 - (void)_updateViewportWithBackingWitdh:(GLuint)backingWidth andBackingHeight:(GLuint)backingHeight {
414     msg_Dbg(_vd, "Reshaping to %dx%d", backingWidth, backingHeight);
415
416     CGFloat width = (CGFloat)backingWidth;
417     CGFloat height = (CGFloat)backingHeight;
418
419     GLint x = width, y = height;
420
421     if (_vd) {
422         CGFloat videoHeight = _vd->source.i_visible_height;
423         CGFloat videoWidth = _vd->source.i_visible_width;
424
425         GLint sarNum = _vd->source.i_sar_num;
426         GLint sarDen = _vd->source.i_sar_den;
427
428         if (height * videoWidth * sarNum < width * videoHeight * sarDen)
429         {
430             x = (height * videoWidth * sarNum) / (videoHeight * sarDen);
431             y = height;
432         }
433         else
434         {
435             x = width;
436             y = (width * videoHeight * sarDen) / (videoWidth * sarNum);
437         }
438
439         @synchronized (self)
440         {
441             vout_display_cfg_t cfg_tmp = *(_vd->cfg);
442             cfg_tmp.display.width  = width;
443             cfg_tmp.display.height = height;
444
445             vout_display_SendEventDisplaySize (_vd, width, height, false);
446         }
447     }
448
449     [EAGLContext setCurrentContext:_context];
450     glViewport((width - x) / 2, (height - y) / 2, x, y);
451 }
452
453 - (void)_destroyFramebuffer {
454     [EAGLContext setCurrentContext:_context];
455     glDeleteFramebuffersOES(1, &_defaultFramebuffer);
456     _defaultFramebuffer = 0;
457     glDeleteRenderbuffersOES(1, &_colorRenderbuffer);
458     _colorRenderbuffer = 0;
459 }
460 @end
461