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