omxil: vout: Handle dynamic aspect changes.
[vlc.git] / modules / codec / omxil / vout.c
1 /*****************************************************************************
2  * vout.c: OpenMAX IL video output
3  *****************************************************************************
4  * Copyright © 2013 VideoLAN
5  *
6  * Authors: Martin Storsjo <martin@martin.st>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU Lesser General Public License as published by
10  * the Free Software Foundation; either version 2.1 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public License
19  * along with this program; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #include <vlc_common.h>
28 #include <vlc_plugin.h>
29 #include <vlc_vout_display.h>
30 #include <vlc_picture_pool.h>
31
32 #include "omxil.h"
33 #include "omxil_core.h"
34 #include "OMX_Broadcom.h"
35
36 // Defined in the broadcom version of OMX_Index.h
37 #define OMX_IndexConfigDisplayRegion 0x7f000010
38
39 /*****************************************************************************
40  * Module descriptor
41  *****************************************************************************/
42 static int  Open (vlc_object_t *);
43 static void Close(vlc_object_t *);
44
45 vlc_module_begin()
46     set_category(CAT_VIDEO)
47     set_subcategory(SUBCAT_VIDEO_VOUT)
48     set_shortname("omxil_vout")
49     set_description(N_("OpenMAX IL video output"))
50     set_capability("vout display", 0)
51     set_callbacks(Open, Close)
52 vlc_module_end()
53
54 /*****************************************************************************
55  * Local prototypes
56  *****************************************************************************/
57
58 static picture_pool_t   *Pool  (vout_display_t *, unsigned);
59 static void             Display(vout_display_t *, picture_t *, subpicture_t *);
60 static int              Control(vout_display_t *, int, va_list);
61
62 /* */
63 struct vout_display_sys_t {
64     picture_pool_t *pool;
65
66     OMX_HANDLETYPE omx_handle;
67
68     char psz_component[OMX_MAX_STRINGNAME_SIZE];
69
70     OmxPort port;
71
72     OmxEventQueue event_queue;
73 };
74
75 struct picture_sys_t {
76     OMX_BUFFERHEADERTYPE *buf;
77     vout_display_sys_t *sys;
78 };
79
80 static int LockSurface(picture_t *);
81 static void UnlockSurface(picture_t *);
82
83 static OMX_ERRORTYPE OmxEventHandler(OMX_HANDLETYPE, OMX_PTR, OMX_EVENTTYPE,
84                                      OMX_U32, OMX_U32, OMX_PTR);
85 static OMX_ERRORTYPE OmxEmptyBufferDone(OMX_HANDLETYPE, OMX_PTR,
86                                         OMX_BUFFERHEADERTYPE *);
87 static OMX_ERRORTYPE OmxFillBufferDone(OMX_HANDLETYPE, OMX_PTR,
88                                        OMX_BUFFERHEADERTYPE *);
89
90 static OMX_ERRORTYPE OmxEventHandler(OMX_HANDLETYPE omx_handle,
91     OMX_PTR app_data, OMX_EVENTTYPE event, OMX_U32 data_1,
92     OMX_U32 data_2, OMX_PTR event_data)
93 {
94     vout_display_t *vd = (vout_display_t *)app_data;
95     vout_display_sys_t *p_sys = vd->sys;
96     (void)omx_handle;
97
98     PrintOmxEvent((vlc_object_t *) vd, event, data_1, data_2, event_data);
99     PostOmxEvent(&p_sys->event_queue, event, data_1, data_2, event_data);
100     return OMX_ErrorNone;
101 }
102
103 static OMX_ERRORTYPE OmxEmptyBufferDone(OMX_HANDLETYPE omx_handle,
104     OMX_PTR app_data, OMX_BUFFERHEADERTYPE *omx_header)
105 {
106     vout_display_t *vd = (vout_display_t *)app_data;
107     vout_display_sys_t *p_sys = vd->sys;
108     (void)omx_handle;
109
110 #ifndef NDEBUG
111     msg_Dbg(vd, "OmxEmptyBufferDone %p, %p", omx_header, omx_header->pBuffer);
112 #endif
113
114     OMX_FIFO_PUT(&p_sys->port.fifo, omx_header);
115
116     return OMX_ErrorNone;
117 }
118
119 static OMX_ERRORTYPE OmxFillBufferDone(OMX_HANDLETYPE omx_handle,
120     OMX_PTR app_data, OMX_BUFFERHEADERTYPE *omx_header)
121 {
122     (void)omx_handle;
123     (void)app_data;
124     (void)omx_header;
125
126     return OMX_ErrorNone;
127 }
128
129 static void UpdateDisplaySize(vout_display_t *vd, vout_display_cfg_t *cfg)
130 {
131     OMX_CONFIG_DISPLAYREGIONTYPE config_display;
132     OMX_INIT_STRUCTURE(config_display);
133
134     /* OMX_DISPLAY_SET_PIXEL is broadcom specific */
135     if (strcmp(vd->sys->psz_component, "OMX.broadcom.video_render"))
136         return;
137
138     config_display.nPortIndex = vd->sys->port.i_port_index;
139     config_display.set = OMX_DISPLAY_SET_PIXEL;
140     config_display.pixel_x = cfg->display.width  * vd->fmt.i_height;
141     config_display.pixel_y = cfg->display.height * vd->fmt.i_width;
142     OMX_SetConfig(vd->sys->omx_handle, OMX_IndexConfigDisplayRegion, &config_display);
143 }
144
145 static int Open(vlc_object_t *p_this)
146 {
147     vout_display_t *vd = (vout_display_t *)p_this;
148     vout_display_t *p_dec = vd;
149     char ppsz_components[MAX_COMPONENTS_LIST_SIZE][OMX_MAX_STRINGNAME_SIZE];
150     picture_t** pictures = NULL;
151     OMX_PARAM_PORTDEFINITIONTYPE *def;
152
153     static OMX_CALLBACKTYPE callbacks =
154         { OmxEventHandler, OmxEmptyBufferDone, OmxFillBufferDone };
155
156     if (InitOmxCore(p_this) != VLC_SUCCESS)
157         return VLC_EGENERIC;
158
159     int components = CreateComponentsList(p_this, "iv_renderer", ppsz_components);
160     if (components <= 0) {
161         DeinitOmxCore();
162         return VLC_EGENERIC;
163     }
164
165     /* Allocate structure */
166     vout_display_sys_t *p_sys = (struct vout_display_sys_t*) calloc(1, sizeof(*p_sys));
167     if (!p_sys) {
168         DeinitOmxCore();
169         return VLC_ENOMEM;
170     }
171
172     vd->sys     = p_sys;
173     strcpy(p_sys->psz_component, ppsz_components[0]);
174
175     /* Load component */
176     OMX_ERRORTYPE omx_error = pf_get_handle(&p_sys->omx_handle,
177                                             p_sys->psz_component, vd, &callbacks);
178     CHECK_ERROR(omx_error, "OMX_GetHandle(%s) failed (%x: %s)",
179                 p_sys->psz_component, omx_error, ErrorToString(omx_error));
180
181     InitOmxEventQueue(&p_sys->event_queue);
182     vlc_mutex_init (&p_sys->port.fifo.lock);
183     vlc_cond_init (&p_sys->port.fifo.wait);
184     p_sys->port.fifo.offset = offsetof(OMX_BUFFERHEADERTYPE, pOutputPortPrivate) / sizeof(void *);
185     p_sys->port.fifo.pp_last = &p_sys->port.fifo.p_first;
186     p_sys->port.b_direct = false;
187     p_sys->port.b_flushed = true;
188
189     OMX_PORT_PARAM_TYPE param;
190     OMX_INIT_STRUCTURE(param);
191     omx_error = OMX_GetParameter(p_sys->omx_handle, OMX_IndexParamVideoInit, &param);
192     CHECK_ERROR(omx_error, "OMX_GetParameter(OMX_IndexParamVideoInit) failed (%x: %s)",
193                 omx_error, ErrorToString(omx_error));
194
195     p_sys->port.i_port_index = param.nStartPortNumber;
196     p_sys->port.b_valid = true;
197     p_sys->port.omx_handle = p_sys->omx_handle;
198
199     def = &p_sys->port.definition;
200     OMX_INIT_STRUCTURE(*def);
201     def->nPortIndex = p_sys->port.i_port_index;
202     omx_error = OMX_GetParameter(p_sys->omx_handle, OMX_IndexParamPortDefinition, def);
203     CHECK_ERROR(omx_error, "OMX_GetParameter(OMX_IndexParamPortDefinition) failed (%x: %s)",
204                 omx_error, ErrorToString(omx_error));
205
206 #define ALIGN(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
207
208     def->format.video.nFrameWidth = vd->fmt.i_width;
209     def->format.video.nFrameHeight = vd->fmt.i_height;
210     def->format.video.nStride = 0;
211     def->format.video.nSliceHeight = 0;
212     p_sys->port.definition.format.video.eColorFormat = OMX_COLOR_FormatYUV420PackedPlanar;
213
214     if (!strcmp(p_sys->psz_component, "OMX.broadcom.video_render")) {
215         def->format.video.nSliceHeight = ALIGN(def->format.video.nFrameHeight, 16);
216     }
217
218     omx_error = OMX_SetParameter(p_sys->omx_handle, OMX_IndexParamPortDefinition, &p_sys->port.definition);
219     CHECK_ERROR(omx_error, "OMX_SetParameter(OMX_IndexParamPortDefinition) failed (%x: %s)",
220                 omx_error, ErrorToString(omx_error));
221     OMX_GetParameter(p_sys->omx_handle, OMX_IndexParamPortDefinition, &p_sys->port.definition);
222
223     if (def->format.video.nStride < (int) def->format.video.nFrameWidth)
224         def->format.video.nStride = def->format.video.nFrameWidth;
225     if (def->format.video.nSliceHeight < def->format.video.nFrameHeight)
226         def->format.video.nSliceHeight = def->format.video.nFrameHeight;
227
228     p_sys->port.pp_buffers =
229             malloc(p_sys->port.definition.nBufferCountActual *
230                    sizeof(OMX_BUFFERHEADERTYPE*));
231     p_sys->port.i_buffers = p_sys->port.definition.nBufferCountActual;
232
233     omx_error = OMX_SendCommand(p_sys->omx_handle, OMX_CommandStateSet, OMX_StateIdle, 0);
234     CHECK_ERROR(omx_error, "OMX_CommandStateSet Idle failed (%x: %s)",
235                 omx_error, ErrorToString(omx_error));
236
237     unsigned int i;
238     for (i = 0; i < p_sys->port.i_buffers; i++) {
239         omx_error = OMX_AllocateBuffer(p_sys->omx_handle, &p_sys->port.pp_buffers[i],
240                                        p_sys->port.i_port_index, 0,
241                                        p_sys->port.definition.nBufferSize);
242         if (omx_error != OMX_ErrorNone)
243             break;
244         OMX_FIFO_PUT(&p_sys->port.fifo, p_sys->port.pp_buffers[i]);
245     }
246     if (omx_error != OMX_ErrorNone) {
247         p_sys->port.i_buffers = i;
248         for (i = 0; i < p_sys->port.i_buffers; i++)
249             OMX_FreeBuffer(p_sys->omx_handle, p_sys->port.i_port_index, p_sys->port.pp_buffers[i]);
250         msg_Err(vd, "OMX_AllocateBuffer failed (%x: %s)",
251                 omx_error, ErrorToString(omx_error));
252         goto error;
253     }
254
255     omx_error = WaitForSpecificOmxEvent(&p_sys->event_queue, OMX_EventCmdComplete, 0, 0, 0);
256     CHECK_ERROR(omx_error, "Wait for Idle failed (%x: %s)",
257                 omx_error, ErrorToString(omx_error));
258
259     omx_error = OMX_SendCommand(p_sys->omx_handle, OMX_CommandStateSet,
260                                 OMX_StateExecuting, 0);
261     CHECK_ERROR(omx_error, "OMX_CommandStateSet Executing failed (%x: %s)",
262                 omx_error, ErrorToString(omx_error));
263     omx_error = WaitForSpecificOmxEvent(&p_sys->event_queue, OMX_EventCmdComplete, 0, 0, 0);
264     CHECK_ERROR(omx_error, "Wait for Executing failed (%x: %s)",
265                 omx_error, ErrorToString(omx_error));
266
267     if (!strcmp(p_sys->psz_component, "OMX.broadcom.video_render")) {
268         OMX_CONFIG_DISPLAYREGIONTYPE config_display;
269         OMX_INIT_STRUCTURE(config_display);
270         config_display.nPortIndex = p_sys->port.i_port_index;
271
272         config_display.set = OMX_DISPLAY_SET_SRC_RECT;
273         config_display.src_rect.width = vd->cfg->display.width;
274         config_display.src_rect.height = vd->cfg->display.height;
275         OMX_SetConfig(p_sys->omx_handle, OMX_IndexConfigDisplayRegion, &config_display);
276         config_display.set = OMX_DISPLAY_SET_FULLSCREEN;
277         config_display.fullscreen = OMX_TRUE;
278         OMX_SetConfig(p_sys->omx_handle, OMX_IndexConfigDisplayRegion, &config_display);
279
280         UpdateDisplaySize(vd, vd->cfg);
281     }
282
283
284     /* Setup chroma */
285     video_format_t fmt = vd->fmt;
286
287     fmt.i_chroma = VLC_CODEC_I420;
288     video_format_FixRgb(&fmt);
289
290     /* Setup vout_display */
291     vd->fmt     = fmt;
292     vd->pool    = Pool;
293     vd->display = Display;
294     vd->control = Control;
295     vd->prepare = NULL;
296     vd->manage  = NULL;
297
298     /* Create the associated picture */
299     pictures = calloc(sizeof(*pictures), p_sys->port.i_buffers);
300     if (!pictures)
301         goto error;
302     for (unsigned int i = 0; i < p_sys->port.i_buffers; i++) {
303         picture_sys_t *picsys = malloc(sizeof(*picsys));
304         if (unlikely(picsys == NULL))
305             goto error;
306         picsys->sys = p_sys;
307
308         picture_resource_t resource = { .p_sys = picsys };
309
310         picture_t *picture = picture_NewFromResource(&fmt, &resource);
311         if (unlikely(picture == NULL))
312         {
313             free(picsys);
314             goto error;
315         }
316         pictures[i] = picture;
317     }
318
319     /* Wrap it into a picture pool */
320     picture_pool_configuration_t pool_cfg;
321     memset(&pool_cfg, 0, sizeof(pool_cfg));
322     pool_cfg.picture_count = p_sys->port.i_buffers;
323     pool_cfg.picture       = pictures;
324     pool_cfg.lock          = LockSurface;
325     pool_cfg.unlock        = UnlockSurface;
326
327     p_sys->pool = picture_pool_NewExtended(&pool_cfg);
328     if (!p_sys->pool) {
329         for (unsigned int i = 0; i < p_sys->port.i_buffers; i++)
330             picture_Release(pictures[i]);
331         goto error;
332     }
333
334     /* Fix initial state */
335     vout_display_SendEventFullscreen(vd, true);
336
337     free(pictures);
338     return VLC_SUCCESS;
339
340 error:
341     free(pictures);
342     Close(p_this);
343     return VLC_EGENERIC;
344 }
345
346 static void Close(vlc_object_t *p_this)
347 {
348     vout_display_t *vd = (vout_display_t *)p_this;
349     vout_display_sys_t *p_sys = vd->sys;
350
351     if (p_sys->omx_handle) {
352         OMX_STATETYPE state;
353         OMX_GetState(p_sys->omx_handle, &state);
354         if (state == OMX_StateExecuting) {
355             OMX_SendCommand(p_sys->omx_handle, OMX_CommandStateSet, OMX_StateIdle, 0);
356             while (1) {
357                 OMX_U32 cmd, state;
358                 WaitForSpecificOmxEvent(&p_sys->event_queue, OMX_EventCmdComplete, &cmd, &state, 0);
359                 if (cmd == OMX_CommandStateSet && state == OMX_StateIdle)
360                     break;
361             }
362         }
363         OMX_GetState(p_sys->omx_handle, &state);
364         if (state == OMX_StateIdle) {
365             OMX_SendCommand(p_sys->omx_handle, OMX_CommandStateSet, OMX_StateLoaded, 0);
366             for (unsigned int i = 0; i < p_sys->port.i_buffers; i++) {
367                 OMX_BUFFERHEADERTYPE *p_buffer;
368                 OMX_FIFO_GET(&p_sys->port.fifo, p_buffer);
369                 OMX_FreeBuffer(p_sys->omx_handle, p_sys->port.i_port_index, p_buffer);
370             }
371             WaitForSpecificOmxEvent(&p_sys->event_queue, OMX_EventCmdComplete, 0, 0, 0);
372         }
373         free(p_sys->port.pp_buffers);
374         pf_free_handle(p_sys->omx_handle);
375         DeinitOmxEventQueue(&p_sys->event_queue);
376         vlc_mutex_destroy(&p_sys->port.fifo.lock);
377         vlc_cond_destroy(&p_sys->port.fifo.wait);
378     }
379
380     if (p_sys->pool)
381         picture_pool_Delete(p_sys->pool);
382     free(p_sys);
383     DeinitOmxCore();
384 }
385
386 static picture_pool_t *Pool(vout_display_t *vd, unsigned count)
387 {
388     VLC_UNUSED(count);
389
390     return vd->sys->pool;
391 }
392
393 static int LockSurface(picture_t *picture)
394 {
395     picture_sys_t *picsys = picture->p_sys;
396     vout_display_sys_t *p_sys = picsys->sys;
397     OMX_BUFFERHEADERTYPE *p_buffer;
398
399     OMX_FIFO_GET(&p_sys->port.fifo, p_buffer);
400     for (int i = 0; i < 3; i++) {
401         picture->p[i].p_pixels = p_buffer->pBuffer;
402         picture->p[i].i_pitch = p_sys->port.definition.format.video.nStride;
403         picture->p[i].i_lines = p_sys->port.definition.format.video.nSliceHeight;
404         if (i > 0) {
405             picture->p[i].p_pixels = picture->p[i-1].p_pixels + picture->p[i-1].i_pitch*picture->p[i-1].i_lines;
406             picture->p[i].i_pitch /= 2;
407             picture->p[i].i_lines /= 2;
408         }
409     }
410     p_buffer->nOffset = 0;
411     p_buffer->nFlags = 0;
412     p_buffer->nTimeStamp = ToOmxTicks(0);
413     p_buffer->nFilledLen = 0;
414     picsys->buf = p_buffer;
415
416     return VLC_SUCCESS;
417 }
418
419 static void UnlockSurface(picture_t *picture)
420 {
421     picture_sys_t *picsys = picture->p_sys;
422     vout_display_sys_t *p_sys = picsys->sys;
423     OMX_BUFFERHEADERTYPE *p_buffer = picsys->buf;
424
425     if (!p_buffer->nFilledLen)
426         OMX_FIFO_PUT(&p_sys->port.fifo, p_buffer);
427     else
428         OMX_EmptyThisBuffer(p_sys->omx_handle, p_buffer);
429 }
430
431 static void Display(vout_display_t *vd, picture_t *picture, subpicture_t *subpicture)
432 {
433     VLC_UNUSED(vd);
434     VLC_UNUSED(subpicture);
435     picture_sys_t *picsys = picture->p_sys;
436     vout_display_sys_t *p_sys = picsys->sys;
437     OMX_BUFFERHEADERTYPE *p_buffer = picsys->buf;
438
439     p_buffer->nFilledLen = 3*p_sys->port.definition.format.video.nStride*p_sys->port.definition.format.video.nSliceHeight/2;
440     p_buffer->nFlags = OMX_BUFFERFLAG_ENDOFFRAME;
441
442     /* refcount lowers to 0, and pool_cfg.unlock is called */
443
444     picture_Release(picture);
445 }
446
447 static int Control(vout_display_t *vd, int query, va_list args)
448 {
449     VLC_UNUSED(args);
450
451     switch (query) {
452     case VOUT_DISPLAY_HIDE_MOUSE:
453         return VLC_SUCCESS;
454
455     default:
456         msg_Err(vd, "Unknown request in omxil vout display");
457
458     case VOUT_DISPLAY_CHANGE_SOURCE_ASPECT:
459         return VLC_SUCCESS;
460     case VOUT_DISPLAY_CHANGE_DISPLAY_SIZE:
461     {
462         const vout_display_cfg_t *cfg = va_arg(args, const vout_display_cfg_t *);
463         UpdateDisplaySize(vd, cfg);
464         return VLC_SUCCESS;
465     }
466     case VOUT_DISPLAY_CHANGE_FULLSCREEN:
467     case VOUT_DISPLAY_CHANGE_WINDOW_STATE:
468     case VOUT_DISPLAY_CHANGE_DISPLAY_FILLED:
469     case VOUT_DISPLAY_CHANGE_ZOOM:
470     case VOUT_DISPLAY_CHANGE_SOURCE_CROP:
471     case VOUT_DISPLAY_GET_OPENGL:
472         return VLC_EGENERIC;
473     }
474 }