a5b3a7e5dc447a0e0900f6bafd6e0c81d6801349
[vlc.git] / modules / visualization / projectm.cpp
1 /*****************************************************************************
2  * projectm: visualization module based on libprojectM
3  *****************************************************************************
4  * Copyright (C) 2009 the VideoLAN team
5  * $Id$
6  *
7  * Authors: RĂ©mi Duraffort <ivoire@videolan.org>
8  *
9  * This program is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU General Public License as published by the Free
11  * Software Foundation; either version 2 of the License, or (at your option)
12  * any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
17  * more details.
18  *
19  * You should have received a copy of the GNU General Public License along with
20  * this program; if not, write to the Free Software Foundation, Inc., 51
21  * Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22  *****************************************************************************/
23
24 #ifdef HAVE_CONFIG_H
25 # include "config.h"
26 #endif
27
28 #include <vlc_common.h>
29 #include <vlc_plugin.h>
30 #include <vlc_aout.h>
31 #include <vlc_vout.h>
32 #include <vlc_filter.h>
33
34 #include <libprojectM/projectM.hpp>
35
36
37 /*****************************************************************************
38  * Module descriptor
39  *****************************************************************************/
40 static int  Open         ( vlc_object_t * );
41 static void Close        ( vlc_object_t * );
42
43 #define CONFIG_TEXT N_("projectM configuration file")
44 #define CONFIG_LONGTEXT N_("File that will be used to configure the projectM " \
45                            "module.")
46
47 #define WIDTH_TEXT N_("Video width")
48 #define WIDTH_LONGTEXT N_("The width of the video window, in pixels.")
49
50 #define HEIGHT_TEXT N_("Video height")
51 #define HEIGHT_LONGTEXT N_("The height of the video window, in pixels.")
52
53 vlc_module_begin ()
54     set_shortname( N_("projectM"))
55     set_description( N_("libprojectM effect") )
56     set_capability( "visualization2", 0 )
57     set_category( CAT_AUDIO )
58     set_subcategory( SUBCAT_AUDIO_VISUAL )
59     add_file( "projectm-config", "/usr/share/projectM/config.inp", NULL,
60                 CONFIG_TEXT, CONFIG_LONGTEXT, true )
61     add_integer( "projectm-width", 800, NULL, WIDTH_TEXT, WIDTH_LONGTEXT,
62                  false )
63     add_integer( "projectm-height", 640, NULL, HEIGHT_TEXT, HEIGHT_LONGTEXT,
64                  false )
65     add_shortcut( "projectm" )
66     set_callbacks( Open, Close )
67 vlc_module_end ()
68
69
70 /*****************************************************************************
71  * Local prototypes
72  *****************************************************************************/
73 typedef struct
74 {
75     VLC_COMMON_MEMBERS
76
77     /* */
78     vlc_sem_t ready;
79
80     /* video output module and opengl provider */
81     vout_thread_t *p_opengl;
82     module_t      *p_module;
83
84     /* libprojectM objects */
85     projectM      *p_projectm;
86     char          *psz_config;
87
88     /* Window size */
89     int i_width;
90     int i_height;
91
92     /* audio info */
93     int i_channels;
94     float *p_buffer;
95     int   i_buffer_size;
96     int   i_nb_samples;
97
98     vlc_mutex_t lock;
99 } projectm_thread_t;
100
101
102 struct filter_sys_t
103 {
104     projectm_thread_t *p_thread;
105 };
106
107
108 static block_t *DoWork( filter_t *, block_t * );
109 static void* Thread( vlc_object_t * );
110
111
112 /**
113  * Init the openGL context
114  * p_thread: projectm thread object
115  * @return VLC_SUCCESS or vlc error codes
116  */
117 static int initOpenGL( projectm_thread_t *p_thread )
118 {
119     p_thread->p_opengl = (vout_thread_t *)vlc_object_create( p_thread,
120                                                     sizeof( vout_thread_t ) );
121     if( !p_thread->p_opengl )
122         return VLC_ENOMEM;
123
124     vlc_object_attach( p_thread->p_opengl, p_thread );
125
126     /* Initialize the opengl object */
127     video_format_Setup( &p_thread->p_opengl->fmt_in, VLC_CODEC_RGB32,
128                         p_thread->i_width, p_thread->i_height, 1 );
129     p_thread->p_opengl->i_window_width = p_thread->i_width;
130     p_thread->p_opengl->i_window_height = p_thread->i_height;
131     p_thread->p_opengl->render.i_width = p_thread->i_width;
132     p_thread->p_opengl->render.i_height = p_thread->i_height;
133     p_thread->p_opengl->render.i_aspect = VOUT_ASPECT_FACTOR;
134     p_thread->p_opengl->b_fullscreen = false;
135     p_thread->p_opengl->b_autoscale = true;
136     p_thread->p_opengl->i_alignment = 0;
137     p_thread->p_opengl->fmt_in.i_sar_num = 1;
138     p_thread->p_opengl->fmt_in.i_sar_den = 1;
139     p_thread->p_opengl->fmt_render = p_thread->p_opengl->fmt_in;
140
141     /* Ask for the opengl provider */
142     p_thread->p_module = module_need( p_thread->p_opengl, "opengl provider",
143                                       NULL, false );
144     if( !p_thread->p_module )
145     {
146         msg_Err( p_thread, "unable to initialize OpenGL" );
147         vlc_object_detach( p_thread->p_opengl );
148         vlc_object_release( p_thread->p_opengl );
149         return VLC_EGENERIC;
150     }
151
152     return VLC_SUCCESS;
153 }
154
155
156 /**
157  * Open the module
158  * @param p_this: the filter object
159  * @return VLC_SUCCESS or vlc error codes
160  */
161 static int Open( vlc_object_t * p_this )
162 {
163     filter_t            *p_filter = (filter_t *)p_this;
164     filter_sys_t        *p_sys;
165     projectm_thread_t   *p_thread;
166
167     /* Test the audio format */
168     if( p_filter->fmt_in.audio.i_format != VLC_CODEC_FL32 ||
169         p_filter->fmt_out.audio.i_format != VLC_CODEC_FL32 )
170     {
171         msg_Warn( p_filter, "bad input or output format" );
172         return VLC_EGENERIC;
173     }
174     if( !AOUT_FMTS_SIMILAR( &p_filter->fmt_in.audio, &p_filter->fmt_out.audio ) )
175     {
176         msg_Warn( p_filter, "input and outut are not similar" );
177         return VLC_EGENERIC;
178     }
179
180     p_filter->pf_audio_filter = DoWork;
181
182     p_sys = p_filter->p_sys = (filter_sys_t*)malloc( sizeof( *p_sys ) );
183     if( !p_sys )
184         return VLC_ENOMEM;
185
186     /* Create the object for the thread */
187     p_sys->p_thread = p_thread = (projectm_thread_t *)
188                     vlc_object_create( p_filter, sizeof( projectm_thread_t ) );
189     vlc_object_attach( p_sys->p_thread, p_filter );
190     vlc_sem_init( &p_thread->ready, 0 );
191     p_thread->b_error = false;
192     p_thread->i_width  = var_CreateGetInteger( p_filter, "projectm-width" );
193     p_thread->i_height = var_CreateGetInteger( p_filter, "projectm-height" );
194
195     p_thread->i_channels = aout_FormatNbChannels( &p_filter->fmt_in.audio );
196     p_thread->psz_config = var_CreateGetString( p_filter, "projectm-config" );
197     vlc_mutex_init( &p_thread->lock );
198     p_thread->p_buffer = NULL;
199     p_thread->i_buffer_size = 0;
200     p_thread->i_nb_samples = 0;
201
202     /* Create the thread */
203     if( vlc_thread_create( p_thread, "projectm update thread", Thread,
204                            VLC_THREAD_PRIORITY_LOW ) )
205         goto error;
206
207     vlc_sem_wait( &p_thread->ready );
208     if( p_thread->b_error )
209         goto error;
210
211     return VLC_SUCCESS;
212
213 error:
214     vlc_thread_join( p_thread );
215     vlc_sem_destroy( &p_thread->ready );
216     vlc_object_detach( p_thread );
217     vlc_object_release( p_thread );
218     free (p_sys );
219     return VLC_EGENERIC;
220 }
221
222
223 /**
224  * Close the module
225  * @param p_this: the filter object
226  */
227 static void Close( vlc_object_t *p_this )
228 {
229     filter_t     *p_filter = (filter_t *)p_this;
230     filter_sys_t *p_sys = p_filter->p_sys;
231     projectm_thread_t *p_thread = p_sys->p_thread;
232
233     /* Stop the thread */
234     vlc_object_kill( p_thread );
235     vlc_thread_join( p_thread );
236
237     /* Free the ressources */
238     vlc_sem_destroy( &p_thread->ready );
239     vlc_mutex_destroy( &p_thread->lock );
240     free( p_thread->p_buffer );
241     free( p_thread->psz_config );
242
243     vlc_object_detach( p_thread );
244     vlc_object_release( p_thread );
245
246     free( p_sys );
247 }
248
249
250 /**
251  * Do the actual work with the new sample
252  * @param p_aout: audio output object
253  * @param p_filter: filter object
254  * @param p_in_buf: input buffer
255  * @param p_out_buf: output buffer
256  */
257 static block_t *DoWork( filter_t *p_filter, block_t *p_in_buf )
258 {
259     projectm_thread_t *p_thread = p_filter->p_sys->p_thread;
260
261     vlc_mutex_lock( &p_thread->lock );
262     if( p_thread->i_buffer_size > 0 )
263     {
264         p_thread->p_buffer[0] = 0;
265         p_thread->i_nb_samples = __MIN( p_thread->i_buffer_size,
266                                         p_in_buf->i_nb_samples );
267         for( int i = 0; i < p_thread->i_nb_samples; i++ )
268             p_thread->p_buffer[i] = p_in_buf->p_buffer[i];
269     }
270
271     vlc_mutex_unlock( &p_thread->lock );
272
273     return p_in_buf;
274 }
275
276
277 /**
278  * ProjectM update thread which do the rendering
279  * @param p_this: the p_thread object
280  */
281 static void* Thread( vlc_object_t *p_this )
282 {
283     /* we don't want to be interupted in this thread */
284     int cancel = vlc_savecancel();
285     projectm_thread_t *p_thread = (projectm_thread_t *)p_this;
286
287     /* Create the openGL provider */
288     if( initOpenGL( p_thread ) )
289     {
290         p_thread->b_error = true;
291         vlc_sem_post( &p_thread->ready );
292         return NULL;
293     }
294
295     /* Initialize the opengl provider for this thread */
296     p_thread->p_opengl->pf_init( p_thread->p_opengl );
297
298     /* Create the projectM object */
299     p_thread->p_projectm = new projectM( p_thread->psz_config );
300     p_thread->i_buffer_size = p_thread->p_projectm->pcm()->maxsamples;
301     p_thread->p_buffer = (float*)malloc( p_thread->i_buffer_size *
302                                          sizeof( float ) );
303
304     vlc_sem_post( &p_thread->ready );
305
306     /* TODO: Give to projectm the name of the input
307     p_thread->p_projectm->projectM_setTitle( "" ); */
308
309     /* Reset the dislay to get the right size */
310     p_thread->p_projectm->projectM_resetGL( p_thread->i_width,
311                                             p_thread->i_height );
312
313     while( vlc_object_alive( p_thread ) )
314     {
315         /* Manage the events */
316         p_thread->p_opengl->pf_manage( p_thread->p_opengl );
317         /* Render the image and swap the buffers */
318         vlc_mutex_lock( &p_thread->lock );
319         if( p_thread->i_nb_samples > 0 )
320             p_thread->p_projectm->pcm()->addPCMfloat( p_thread->p_buffer,
321                                                       p_thread->i_nb_samples );
322
323         p_thread->p_projectm->renderFrame();
324         p_thread->p_opengl->pf_swap( p_thread->p_opengl );
325         vlc_mutex_unlock( &p_thread->lock );
326
327         /* TODO: use a fps limiter */
328         msleep( 10000 );
329     }
330
331
332     /* Cleanup */
333     delete p_thread->p_projectm;
334
335     /* Free the openGL provider */
336     module_unneed( p_thread->p_opengl, p_thread->p_module );
337     vlc_object_detach( p_thread->p_opengl );
338     vlc_object_release( p_thread->p_opengl );
339
340
341     vlc_restorecancel( cancel );
342     return NULL;
343 }