mmal: Add deinterlace filter
[vlc.git] / modules / hw / mmal / deinterlace.c
1 /*****************************************************************************
2  * mmal.c: MMAL-based deinterlace plugin for Raspberry Pi
3  *****************************************************************************
4  * Copyright © 2014 jusst technologies GmbH
5  * $Id$
6  *
7  * Authors: Julian Scheel <julian@jusst.de>
8  *          Dennis Hamester <dennis.hamester@gmail.com>
9  *
10  * This program is free software; you can redistribute it and/or modify it
11  * under the terms of the GNU Lesser General Public License as published by
12  * the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public License
21  * along with this program; if not, write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include <vlc_picture_pool.h>
30 #include <vlc_common.h>
31 #include <vlc_plugin.h>
32 #include <vlc_filter.h>
33
34 #include "mmal_picture.h"
35
36 #include <bcm_host.h>
37 #include <interface/mmal/mmal.h>
38 #include <interface/mmal/util/mmal_util.h>
39 #include <interface/mmal/util/mmal_default_components.h>
40
41 #define NUM_EXTRA_BUFFERS 2
42 #define MIN_NUM_BUFFERS_IN_TRANSIT 2
43
44 #define MAX_ALLOWED_FPS 30
45
46 static int Open(filter_t *filter);
47 static void Close(filter_t *filter);
48
49 vlc_module_begin()
50     set_shortname(N_("MMAL deinterlace"))
51     set_description(N_("MMAL-based deinterlace filter plugin"))
52     set_capability("video filter2", 10)
53     set_category(CAT_VIDEO)
54     set_subcategory(SUBCAT_VIDEO_VFILTER)
55     set_callbacks(Open, Close)
56     add_shortcut("deinterlace")
57 vlc_module_end()
58
59 struct filter_sys_t {
60     MMAL_COMPONENT_T *component;
61     MMAL_PORT_T *input;
62     MMAL_POOL_T *input_pool;
63     MMAL_PORT_T *output;
64     MMAL_POOL_T *output_pool;
65
66     picture_pool_t *picture_pool;
67     picture_t **pictures;
68
69     MMAL_QUEUE_T *filtered_pictures;
70     vlc_mutex_t mutex;
71 };
72
73 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
74 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
75 static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer);
76 static picture_t *deinterlace(filter_t *filter, picture_t *picture);
77
78 #define MMAL_COMPONENT_DEFAULT_DEINTERLACE "vc.ril.image_fx"
79
80 static int create_picture_pool(filter_t *filter)
81 {
82     picture_pool_configuration_t picture_pool_cfg;
83     filter_sys_t *sys = filter->p_sys;
84     picture_resource_t picture_res;
85     int ret = 0;
86
87     memset(&picture_res, 0, sizeof(picture_resource_t));
88     sys->pictures = calloc(sys->output->buffer_num, sizeof(picture_t *));
89     if (!sys->pictures) {
90         ret = -ENOMEM;
91         goto out;
92     }
93
94     for (int i = 0; i < sys->output->buffer_num; i++) {
95         picture_res.p_sys = calloc(1, sizeof(picture_sys_t));
96         if (!picture_res.p_sys) {
97             ret = -ENOMEM;
98             goto out;
99         }
100
101         picture_res.p_sys->owner = (vlc_object_t *)filter;
102         picture_res.p_sys->queue = sys->output_pool->queue;
103
104         sys->pictures[i] = picture_NewFromResource(&filter->fmt_out.video,
105                 &picture_res);
106         if (!sys->pictures[i]) {
107             free(picture_res.p_sys);
108             ret = -ENOMEM;
109             goto out;
110         }
111     }
112
113     memset(&picture_pool_cfg, 0, sizeof(picture_pool_configuration_t));
114     picture_pool_cfg.picture_count = sys->output->buffer_num;
115     picture_pool_cfg.picture = sys->pictures;
116     picture_pool_cfg.lock = mmal_picture_lock;
117     picture_pool_cfg.unlock = mmal_picture_unlock;
118
119     sys->picture_pool = picture_pool_NewExtended(&picture_pool_cfg);
120     if (!sys->picture_pool) {
121         msg_Err(filter, "Failed to create picture pool");
122         ret = -ENOMEM;
123         goto out;
124     }
125
126 out:
127     return ret;
128 }
129
130 static void destroy_picture_pool(filter_t *filter)
131 {
132     filter_sys_t *sys = filter->p_sys;
133
134     if (sys->picture_pool)
135         picture_pool_Delete(sys->picture_pool);
136     free(sys->pictures);
137 }
138
139 static int Open(filter_t *filter)
140 {
141     int32_t frame_duration = filter->fmt_in.video.i_frame_rate != 0 ?
142             (int64_t)1000000 * filter->fmt_in.video.i_frame_rate_base /
143             filter->fmt_in.video.i_frame_rate : 0;
144
145     MMAL_PARAMETER_IMAGEFX_PARAMETERS_T imfx_param = {
146             { MMAL_PARAMETER_IMAGE_EFFECT_PARAMETERS, sizeof(imfx_param) },
147             MMAL_PARAM_IMAGEFX_DEINTERLACE_ADV,
148             2,
149             { 3, frame_duration }
150     };
151     MMAL_PARAMETER_UINT32_T extra_buffers;
152
153     int ret = VLC_SUCCESS;
154     MMAL_STATUS_T status;
155     filter_sys_t *sys;
156
157     msg_Dbg(filter, "Try to open mmal_deinterlace filter. frame_duration: %d!", frame_duration);
158
159     if (filter->fmt_in.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
160         return VLC_EGENERIC;
161
162     if (filter->fmt_out.video.i_chroma != VLC_CODEC_MMAL_OPAQUE)
163         return VLC_EGENERIC;
164
165     sys = calloc(1, sizeof(filter_sys_t));
166     if (!sys)
167         return VLC_ENOMEM;
168     filter->p_sys = sys;
169
170     bcm_host_init();
171
172     status = mmal_component_create(MMAL_COMPONENT_DEFAULT_DEINTERLACE, &sys->component);
173     if (status != MMAL_SUCCESS) {
174         msg_Err(filter, "Failed to create MMAL component %s (status=%"PRIx32" %s)",
175                 MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
176         ret = VLC_EGENERIC;
177         goto out;
178     }
179
180     status = mmal_port_parameter_set(sys->component->output[0], &imfx_param.hdr);
181     if (status != MMAL_SUCCESS) {
182         msg_Err(filter, "Failed to configure MMAL component %s (status=%"PRIx32" %s)",
183                 MMAL_COMPONENT_DEFAULT_DEINTERLACE, status, mmal_status_to_string(status));
184         ret = VLC_EGENERIC;
185         goto out;
186     }
187
188     sys->component->control->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
189     status = mmal_port_enable(sys->component->control, control_port_cb);
190     if (status != MMAL_SUCCESS) {
191         msg_Err(filter, "Failed to enable control port %s (status=%"PRIx32" %s)",
192                 sys->component->control->name, status, mmal_status_to_string(status));
193         ret = VLC_EGENERIC;
194         goto out;
195     }
196
197     sys->input = sys->component->input[0];
198     sys->input->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
199     if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE)
200         sys->input->format->encoding = MMAL_ENCODING_OPAQUE;
201     sys->input->format->es->video.width = filter->fmt_in.video.i_width;
202     sys->input->format->es->video.height = filter->fmt_in.video.i_height;
203     sys->input->format->es->video.crop.x = 0;
204     sys->input->format->es->video.crop.y = 0;
205     sys->input->format->es->video.crop.width = filter->fmt_in.video.i_width;
206     sys->input->format->es->video.crop.height = filter->fmt_in.video.i_height;
207     sys->input->format->es->video.par.num = filter->fmt_in.video.i_sar_num;
208     sys->input->format->es->video.par.den = filter->fmt_in.video.i_sar_den;
209
210     es_format_Copy(&filter->fmt_out, &filter->fmt_in);
211     filter->fmt_out.video.i_frame_rate *= 2;
212
213     status = mmal_port_format_commit(sys->input);
214     if (status != MMAL_SUCCESS) {
215         msg_Err(filter, "Failed to commit format for input port %s (status=%"PRIx32" %s)",
216                         sys->input->name, status, mmal_status_to_string(status));
217         ret = VLC_EGENERIC;
218         goto out;
219     }
220     sys->input->buffer_size = sys->input->buffer_size_recommended;
221
222     sys->input->buffer_num = sys->input->buffer_num_recommended;
223     status = mmal_port_enable(sys->input, input_port_cb);
224     if (status != MMAL_SUCCESS) {
225         msg_Err(filter, "Failed to enable input port %s (status=%"PRIx32" %s)",
226                 sys->input->name, status, mmal_status_to_string(status));
227         ret = VLC_EGENERIC;
228         goto out;
229     }
230
231     sys->output = sys->component->output[0];
232     sys->output->userdata = (struct MMAL_PORT_USERDATA_T *)filter;
233     mmal_format_full_copy(sys->output->format, sys->input->format);
234
235     if (filter->fmt_in.i_codec == VLC_CODEC_MMAL_OPAQUE) {
236         extra_buffers.hdr.id = MMAL_PARAMETER_EXTRA_BUFFERS;
237         extra_buffers.hdr.size = sizeof(MMAL_PARAMETER_UINT32_T);
238         extra_buffers.value = NUM_EXTRA_BUFFERS;
239         status = mmal_port_parameter_set(sys->output, &extra_buffers.hdr);
240         if (status != MMAL_SUCCESS) {
241             msg_Err(filter, "Failed to set MMAL_PARAMETER_EXTRA_BUFFERS on output port (status=%"PRIx32" %s)",
242                     status, mmal_status_to_string(status));
243             ret = VLC_EGENERIC;
244             goto out;
245         }
246     }
247
248     status = mmal_port_format_commit(sys->output);
249     if (status != MMAL_SUCCESS) {
250         msg_Err(filter, "Failed to commit format for output port %s (status=%"PRIx32" %s)",
251                         sys->input->name, status, mmal_status_to_string(status));
252         ret = VLC_EGENERIC;
253         goto out;
254     }
255
256     sys->output->buffer_num = sys->output->buffer_num_recommended;
257     status = mmal_port_enable(sys->output, output_port_cb);
258     if (status != MMAL_SUCCESS) {
259         msg_Err(filter, "Failed to enable output port %s (status=%"PRIx32" %s)",
260                 sys->output->name, status, mmal_status_to_string(status));
261         ret = VLC_EGENERIC;
262         goto out;
263     }
264
265     status = mmal_component_enable(sys->component);
266     if (status != MMAL_SUCCESS) {
267         msg_Err(filter, "Failed to enable component %s (status=%"PRIx32" %s)",
268                 sys->component->name, status, mmal_status_to_string(status));
269         ret = VLC_EGENERIC;
270         goto out;
271     }
272
273     sys->output_pool = mmal_pool_create(sys->output->buffer_num, 0);
274     sys->output_pool = mmal_pool_create_with_allocator(sys->output->buffer_num,
275             sys->output->buffer_size, sys->output,
276             (mmal_pool_allocator_alloc_t)mmal_port_payload_alloc,
277             (mmal_pool_allocator_free_t)mmal_port_payload_free);
278     if (!sys->output_pool) {
279         msg_Err(filter, "Failed to create MMAL pool for %u buffers of size %"PRIu32,
280                         sys->output->buffer_num, sys->output->buffer_size);
281         goto out;
282     }
283     sys->input_pool = mmal_pool_create(sys->input->buffer_num, 0);
284     sys->filtered_pictures = mmal_queue_create();
285
286     ret = create_picture_pool(filter);
287     if (ret != VLC_SUCCESS)
288         goto out;
289
290     filter->pf_video_filter = deinterlace;
291     vlc_mutex_init_recursive(&sys->mutex);
292
293 out:
294     if (ret != VLC_SUCCESS)
295         Close(filter);
296
297     return ret;
298 }
299
300 static void Close(filter_t *filter)
301 {
302     filter_sys_t *sys = filter->p_sys;
303     MMAL_BUFFER_HEADER_T *buffer;
304
305     if (!sys)
306         return;
307
308     if (sys->component && sys->component->control->is_enabled)
309         mmal_port_disable(sys->component->control);
310
311     if (sys->input && sys->input->is_enabled)
312         mmal_port_disable(sys->input);
313
314     if (sys->output && sys->output->is_enabled)
315         mmal_port_disable(sys->output);
316
317     if (sys->component && sys->component->is_enabled)
318         mmal_component_disable(sys->component);
319
320     if (sys->input_pool)
321         mmal_pool_destroy(sys->input_pool);
322
323     destroy_picture_pool(filter);
324
325     while ((buffer = mmal_queue_get(sys->filtered_pictures))) {
326         picture_t *pic = (picture_t *)buffer->user_data;
327         picture_Release(pic);
328
329         buffer->user_data = NULL;
330         buffer->alloc_size = 0;
331         buffer->data = NULL;
332         mmal_buffer_header_release(buffer);
333     }
334
335     if (sys->filtered_pictures)
336         mmal_queue_destroy(sys->filtered_pictures);
337
338     if (sys->output_pool)
339         mmal_pool_destroy(sys->output_pool);
340
341     if (sys->component)
342         mmal_component_release(sys->component);
343
344     free(sys);
345
346     bcm_host_deinit();
347 }
348
349 static int send_output_buffer(filter_t *filter)
350 {
351     filter_sys_t *sys = filter->p_sys;
352     MMAL_BUFFER_HEADER_T *buffer;
353     MMAL_STATUS_T status;
354     picture_t *picture;
355     int ret = 0;
356
357     picture = picture_pool_Get(sys->picture_pool);
358     if (!picture) {
359         msg_Warn(filter, "Failed to get new picture");
360         ret = -1;
361         goto out;
362     }
363     picture->format.i_frame_rate = filter->fmt_out.video.i_frame_rate;
364     picture->format.i_frame_rate_base = filter->fmt_out.video.i_frame_rate_base;
365
366     buffer = picture->p_sys->buffer;
367     buffer->cmd = 0;
368     buffer->alloc_size = sys->output->buffer_size;
369
370     status = mmal_port_send_buffer(sys->output, buffer);
371     if (status != MMAL_SUCCESS) {
372         msg_Err(filter, "Failed to send buffer to output port (status=%"PRIx32" %s)",
373                 status, mmal_status_to_string(status));
374         picture_Release(picture);
375         ret = -1;
376         goto out;
377     }
378
379 out:
380     return ret;
381 }
382
383 static void fill_output_port(filter_t *filter)
384 {
385     filter_sys_t *sys = filter->p_sys;
386     /* allow at least 2 buffers in transit */
387     unsigned max_buffers_in_transit = __MAX(sys->output->buffer_num,
388             MIN_NUM_BUFFERS_IN_TRANSIT);
389     unsigned buffers_available = mmal_queue_length(sys->output_pool->queue);
390     unsigned buffers_in_transit = sys->output_pool->headers_num - buffers_available -
391         mmal_queue_length(sys->filtered_pictures);
392     unsigned buffers_to_send = max_buffers_in_transit - buffers_in_transit;
393     unsigned i;
394
395     if (buffers_to_send > buffers_available)
396         buffers_to_send = buffers_available;
397
398 #ifndef NDEBUG
399     msg_Dbg(filter, "Send %d buffers to output port (available: %d, in_transit: %d, buffer_num: %d)",
400                     buffers_to_send, buffers_available, buffers_in_transit,
401                     sys->output->buffer_num);
402 #endif
403     for (i = 0; i < buffers_to_send; ++i) {
404         if (send_output_buffer(filter) < 0)
405             break;
406     }
407 }
408
409 static picture_t *deinterlace(filter_t *filter, picture_t *picture)
410 {
411     filter_sys_t *sys = filter->p_sys;
412     MMAL_BUFFER_HEADER_T *buffer;
413     picture_t *out_picture = NULL;
414     picture_t *ret = NULL;
415     MMAL_STATUS_T status;
416
417     /*
418      * Send output buffers
419      */
420     if (sys->output_pool) {
421         int i = 0;
422         vlc_mutex_lock(&sys->mutex);
423         while((buffer = mmal_queue_get(sys->filtered_pictures))) {
424             i++;
425             if (!out_picture) {
426                 out_picture = (picture_t *)buffer->user_data;
427                 ret = out_picture;
428             } else {
429                 out_picture->p_next = (picture_t *)buffer->user_data;
430                 out_picture = out_picture->p_next;
431             }
432             out_picture->date = buffer->pts;
433         }
434         if (out_picture)
435             out_picture->p_next = NULL;
436         fill_output_port(filter);
437         vlc_mutex_unlock(&sys->mutex);
438     }
439
440     /*
441      * Process input
442      */
443     if (!picture)
444         return ret;
445
446     vlc_mutex_lock(&sys->mutex);
447     buffer = mmal_queue_timedwait(sys->input_pool->queue, 2);
448     if (!buffer) {
449         msg_Err(filter, "Failed to retrieve buffer header for input picture");
450         goto out;
451     }
452
453     mmal_buffer_header_reset(buffer);
454     buffer->user_data = picture;
455     buffer->pts = picture->date;
456     buffer->cmd = 0;
457     buffer->alloc_size = sys->input->buffer_size;
458     buffer->length = sys->input->buffer_size;
459     buffer->data = picture->p[0].p_pixels;
460
461     status = mmal_port_send_buffer(sys->input, buffer);
462     if (status != MMAL_SUCCESS) {
463         msg_Err(filter, "Failed to send buffer to input port (status=%"PRIx32" %s)",
464                 status, mmal_status_to_string(status));
465     }
466
467 out:
468     vlc_mutex_unlock(&sys->mutex);
469     return ret;
470 }
471
472 static void control_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
473 {
474     filter_t *filter = (filter_t *)port->userdata;
475     MMAL_STATUS_T status;
476
477     if (buffer->cmd == MMAL_EVENT_ERROR) {
478         status = *(uint32_t *)buffer->data;
479         msg_Err(filter, "MMAL error %"PRIx32" \"%s\"", status,
480                 mmal_status_to_string(status));
481     }
482
483     mmal_buffer_header_release(buffer);
484 }
485
486 static void input_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
487 {
488     picture_t *picture = (picture_t *)buffer->user_data;
489     filter_t *filter = (filter_t *)port->userdata;
490     filter_sys_t *sys = filter->p_sys;
491
492     buffer->user_data = NULL;
493     mmal_buffer_header_release(buffer);
494     if (picture)
495         picture_Release(picture);
496 }
497
498 static void output_port_cb(MMAL_PORT_T *port, MMAL_BUFFER_HEADER_T *buffer)
499 {
500     filter_t *filter = (filter_t *)port->userdata;
501     filter_sys_t *sys = filter->p_sys;
502     picture_t *picture;
503
504     if (buffer->cmd == 0) {
505         if (buffer->length > 0) {
506             mmal_queue_put(sys->filtered_pictures, buffer);
507             fill_output_port(filter);
508         } else {
509             picture = (picture_t *)buffer->user_data;
510             picture_Release(picture);
511             buffer->user_data = NULL;
512         }
513     } else if (buffer->cmd == MMAL_EVENT_FORMAT_CHANGED) {
514         msg_Warn(filter, "MMAL_EVENT_FORMAT_CHANGED seen but not handled");
515         mmal_buffer_header_release(buffer);
516     } else {
517         mmal_buffer_header_release(buffer);
518     }
519 }