]> git.sesse.net Git - vlc/blob - modules/visualization/glspectrum.c
Direct3D: less verbose output
[vlc] / modules / visualization / glspectrum.c
1 /*****************************************************************************
2  * glspectrum.c: spectrum visualization module based on OpenGL
3  *****************************************************************************
4  * Copyright © 2009-2013 VLC authors and VideoLAN
5  *
6  * Authors: Adrien Maglo <magsoft@videolan.org>
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 <assert.h>
28
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
31 #include <vlc_aout.h>
32 #include <vlc_vout.h>
33 #include <vlc_vout_wrapper.h>
34 #include <vlc_opengl.h>
35 #include <vlc_filter.h>
36 #include <vlc_rand.h>
37
38 #include <GL/gl.h>
39
40 #include <math.h>
41
42 #include "visual/fft.h"
43
44
45 /*****************************************************************************
46  * Module descriptor
47  *****************************************************************************/
48 static int Open(vlc_object_t *);
49 static void Close(vlc_object_t *);
50
51 #define WIDTH_TEXT N_("Video width")
52 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
53
54 #define HEIGHT_TEXT N_("Video height")
55 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
56
57 vlc_module_begin()
58     set_shortname(N_("glSpectrum"))
59     set_description(N_("3D OpenGL spectrum visualization"))
60     set_capability("visualization", 0)
61     set_category(CAT_AUDIO)
62     set_subcategory(SUBCAT_AUDIO_VISUAL)
63
64     add_integer("glspectrum-width", 400, WIDTH_TEXT, WIDTH_LONGTEXT, false)
65     add_integer("glspectrum-height", 300, HEIGHT_TEXT, HEIGHT_LONGTEXT, false)
66
67     add_shortcut("glspectrum")
68     set_callbacks(Open, Close)
69 vlc_module_end()
70
71
72 /*****************************************************************************
73  * Local prototypes
74  *****************************************************************************/
75 struct filter_sys_t
76 {
77     vlc_thread_t thread;
78     vlc_sem_t    ready;
79     bool         b_error;
80
81     /* Audio data */
82     unsigned i_channels;
83     block_fifo_t    *fifo;
84     unsigned i_prev_nb_samples;
85     int16_t *p_prev_s16_buff;
86
87     /* Opengl */
88     vout_thread_t  *p_vout;
89     vout_display_t *p_vd;
90
91     float f_rotationAngle;
92     float f_rotationIncrement;
93
94     /* Window size */
95     int i_width;
96     int i_height;
97 };
98
99
100 static block_t *DoWork(filter_t *, block_t *);
101 static void *Thread(void *);
102
103 #define SPECTRUM_WIDTH 4.0
104 #define NB_BANDS 20
105 #define ROTATION_INCREMENT 0.1
106 #define BAR_DECREMENT 0.075
107 #define ROTATION_MAX 20
108
109 const GLfloat lightZeroColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
110 const GLfloat lightZeroPosition[] = {0.0f, 3.0f, 10.0f, 0.0f};
111
112 /**
113  * Open the module.
114  * @param p_this: the filter object
115  * @return VLC_SUCCESS or vlc error codes
116  */
117 static int Open(vlc_object_t * p_this)
118 {
119     filter_t *p_filter = (filter_t *)p_this;
120     filter_sys_t *p_sys;
121
122     p_sys = p_filter->p_sys = (filter_sys_t*)malloc(sizeof(*p_sys));
123     if (p_sys == NULL)
124         return VLC_ENOMEM;
125
126     /* Create the object for the thread */
127     vlc_sem_init(&p_sys->ready, 0);
128     p_sys->b_error = false;
129     p_sys->i_width = var_InheritInteger(p_filter, "glspectrum-width");
130     p_sys->i_height = var_InheritInteger(p_filter, "glspectrum-height");
131     p_sys->i_channels = aout_FormatNbChannels(&p_filter->fmt_in.audio);
132     p_sys->i_prev_nb_samples = 0;
133     p_sys->p_prev_s16_buff = NULL;
134
135     p_sys->f_rotationAngle = 0;
136     p_sys->f_rotationIncrement = ROTATION_INCREMENT;
137
138     /* Create the FIFO for the audio data. */
139     p_sys->fifo = block_FifoNew();
140     if (p_sys->fifo == NULL)
141         goto error;
142
143     /* Create the thread */
144     if (vlc_clone(&p_sys->thread, Thread, p_filter,
145                   VLC_THREAD_PRIORITY_VIDEO))
146         goto error;
147
148     /* Wait for the displaying thread to be ready. */
149     vlc_sem_wait(&p_sys->ready);
150     if (p_sys->b_error)
151     {
152         vlc_join(p_sys->thread, NULL);
153         goto error;
154     }
155
156     p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
157     p_filter->fmt_out.audio = p_filter->fmt_in.audio;
158     p_filter->pf_audio_filter = DoWork;
159
160     return VLC_SUCCESS;
161
162 error:
163     vlc_sem_destroy(&p_sys->ready);
164     free(p_sys);
165     return VLC_EGENERIC;
166 }
167
168
169 /**
170  * Close the module.
171  * @param p_this: the filter object
172  */
173 static void Close(vlc_object_t *p_this)
174 {
175     filter_t *p_filter = (filter_t *)p_this;
176     filter_sys_t *p_sys = p_filter->p_sys;
177
178     /* Terminate the thread. */
179     vlc_cancel(p_sys->thread);
180     vlc_join(p_sys->thread, NULL);
181
182     /* Free the ressources */
183     vout_DeleteDisplay(p_sys->p_vd, NULL);
184     vlc_object_release(p_sys->p_vout);
185
186     block_FifoRelease(p_sys->fifo);
187     free(p_sys->p_prev_s16_buff);
188
189     vlc_sem_destroy(&p_sys->ready);
190     free(p_sys);
191 }
192
193
194 /**
195  * Do the actual work with the new sample.
196  * @param p_filter: filter object
197  * @param p_in_buf: input buffer
198  */
199 static block_t *DoWork(filter_t *p_filter, block_t *p_in_buf)
200 {
201     block_t *block = block_Duplicate(p_in_buf);
202     if (likely(block != NULL))
203         block_FifoPut(p_filter->p_sys->fifo, block);
204     return p_in_buf;
205 }
206
207
208 /**
209   * Init the OpenGL scene.
210   **/
211 static void initOpenGLScene()
212 {
213     glEnable(GL_CULL_FACE);
214     glEnable(GL_DEPTH_TEST);
215     glDepthMask(GL_TRUE);
216
217     glMatrixMode(GL_PROJECTION);
218     glFrustum(-1.0f, 1.0f, -1.0f, 1.0f, 0.5f, 10.0f);
219
220     glMatrixMode(GL_MODELVIEW);
221     glTranslatef(0.0, -2.0, -2.0);
222
223     // Init the light.
224     glEnable(GL_LIGHTING);
225
226     glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
227     glEnable(GL_COLOR_MATERIAL);
228
229     glEnable(GL_LIGHT0);
230     glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
231     glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
232
233     glShadeModel(GL_SMOOTH);
234
235     glEnable(GL_BLEND);
236     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
237 }
238
239
240 /**
241  * Draw one bar of the Spectrum.
242  */
243 static void drawBar()
244 {
245     const float w = SPECTRUM_WIDTH / NB_BANDS - 0.05;
246
247     const GLfloat vertexCoords[] = {
248         0.0, 0.0, 0.0,   w, 0.0, 0.0,   0.0, 1.0, 0.0,
249         0.0, 1.0, 0.0,   w, 0.0, 0.0,   w  , 1.0, 0.0,
250
251         0.0, 0.0, -w,    0.0, 0.0, 0.0,   0.0, 1.0, -w,
252         0.0, 1.0, -w,    0.0, 0.0, 0.0,   0.0, 1.0, 0.0,
253
254         w, 0.0, 0.0,     w, 0.0, -w,   w, 1.0, 0.0,
255         w, 1.0, 0.0,     w, 0.0, -w,   w, 1.0, -w,
256
257         w, 0.0, -w,      0.0, 0.0, -w,  0.0, 1.0, -w,
258         0.0, 1.0, -w,    w, 1.0, -w,    w, 0.0, -w,
259
260         0.0, 1.0, 0.0,   w, 1.0, 0.0,   w, 1.0, -w,
261         0.0, 1.0, 0.0,   w, 1.0, -w,    0.0, 1.0, -w,
262     };
263
264     const GLfloat normals[] = {
265         0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,
266         0.0, 0.0, 1.0,   0.0, 0.0, 1.0,   0.0, 0.0, 1.0,
267
268         -1.0, 0.0, 0.0,   -1.0, 0.0, 0.0,   -1.0, 0.0, 0.0,
269         -1.0, 0.0, 0.0,   -1.0, 0.0, 0.0,   -1.0, 0.0, 0.0,
270
271         1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,
272         1.0, 0.0, 0.0,   1.0, 0.0, 0.0,   1.0, 0.0, 0.0,
273
274         0.0, 0.0, -1.0,   0.0, 0.0, -1.0,   0.0, 0.0, -1.0,
275         0.0, 0.0, -1.0,   0.0, 0.0, -1.0,   0.0, 0.0, -1.0,
276
277         0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,
278         0.0, 1.0, 0.0,   0.0, 1.0, 0.0,   0.0, 1.0, 0.0,
279     };
280
281     glVertexPointer(3, GL_FLOAT, 0, vertexCoords);
282     glNormalPointer(GL_FLOAT, 0, normals);
283     glDrawArrays(GL_TRIANGLES, 0, 6 * 5);
284 }
285
286
287 /**
288  * Set the color of one bar of the spectrum.
289  * @param f_height the height of the bar.
290  */
291 static void setBarColor(float f_height)
292 {
293     float r, b;
294
295 #define BAR_MAX_HEIGHT 4.2
296     r = -1.0 + 2 / BAR_MAX_HEIGHT * f_height;
297     b = 2.0 - 2 / BAR_MAX_HEIGHT * f_height;
298 #undef BAR_MAX_HEIGHT
299
300     /* Test the ranges. */
301     r = r > 1.0 ? 1.0 : r;
302     b = b > 1.0 ? 1.0 : b;
303
304     r = r < 0.0 ? 0.0 : r;
305     b = b < 0.0 ? 0.0 : b;
306
307     /* Set the bar color. */
308     glColor4f(r, 0.0, b, 1.0);
309 }
310
311
312 /**
313  * Draw all the bars of the spectrum.
314  * @param heights the heights of all the bars.
315  */
316 static void drawBars(float heights[])
317 {
318     glPushMatrix();
319     glTranslatef(-2.0, 0.0, 0.0);
320
321     glEnableClientState(GL_VERTEX_ARRAY);
322     glEnableClientState(GL_NORMAL_ARRAY);
323
324     float w = SPECTRUM_WIDTH / NB_BANDS;
325     for (unsigned i = 0; i < NB_BANDS; ++i)
326     {
327         glPushMatrix();
328         glScalef(1.0, heights[i], 1.0);
329         setBarColor(heights[i]);
330         drawBar();
331         glPopMatrix();
332
333         glTranslatef(w, 0.0, 0.0);
334     }
335
336     glDisableClientState(GL_VERTEX_ARRAY);
337     glDisableClientState(GL_NORMAL_ARRAY);
338
339     glPopMatrix();
340 }
341
342
343 /**
344  * Update thread which do the rendering
345  * @param p_this: the p_thread object
346  */
347 static void *Thread( void *p_data )
348 {
349     filter_t  *p_filter = (filter_t*)p_data;
350     filter_sys_t *p_sys = p_filter->p_sys;
351
352     video_format_t fmt;
353     vlc_gl_t *gl;
354     unsigned int i_last_width = 0;
355     unsigned int i_last_height = 0;
356
357     /* Create the openGL provider */
358     p_sys->p_vout =
359         (vout_thread_t *)vlc_object_create(p_filter, sizeof(vout_thread_t));
360     if (!p_sys->p_vout)
361         goto error;
362
363     /* Configure the video format for the opengl provider. */
364     video_format_Init(&fmt, 0);
365     video_format_Setup(&fmt, VLC_CODEC_RGB32,
366                        p_sys->i_width, p_sys->i_height, 0, 1 );
367     fmt.i_sar_num = 1;
368     fmt.i_sar_den = 1;
369
370     /* Init vout state. */
371     vout_display_state_t state;
372     memset(&state, 0, sizeof(state));
373     state.cfg.display.sar.num = 1;
374     state.cfg.display.sar.den = 1;
375     state.cfg.is_display_filled = true;
376     state.cfg.zoom.num = 1;
377     state.cfg.zoom.den = 1;
378     state.sar.num = 1;
379     state.sar.den = 1;
380
381     p_sys->p_vd = vout_NewDisplay(p_sys->p_vout, &fmt, &state,
382                                   "opengl", 1000000, 1000000);
383     if (!p_sys->p_vd)
384     {
385         vlc_object_release(p_sys->p_vout);
386         goto error;
387     }
388
389     gl = vout_GetDisplayOpengl(p_sys->p_vd);
390     if (!gl)
391     {
392         vout_DeleteDisplay(p_sys->p_vd, NULL);
393         vlc_object_release(p_sys->p_vout);
394         goto error;
395     }
396
397     vlc_sem_post(&p_sys->ready);
398
399     initOpenGLScene();
400
401     float height[NB_BANDS] = {0};
402
403     while (1)
404     {
405         block_t *block = block_FifoGet(p_sys->fifo);
406
407         int canc = vlc_savecancel();
408
409         /* Manage the events */
410         vout_ManageDisplay(p_sys->p_vd, true);
411         if (p_sys->p_vd->cfg->display.width != i_last_width ||
412             p_sys->p_vd->cfg->display.height != i_last_height)
413         {
414             /* FIXME it is not perfect as we will have black bands */
415             vout_display_place_t place;
416             vout_display_PlacePicture(&place, &p_sys->p_vd->source,
417                                       p_sys->p_vd->cfg, false);
418
419             i_last_width  = p_sys->p_vd->cfg->display.width;
420             i_last_height = p_sys->p_vd->cfg->display.height;
421         }
422
423         /* Horizontal scale for 20-band equalizer */
424         const unsigned xscale[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
425                                    36,47,62,82,107,141,184,255};
426
427         fft_state *p_state = NULL; /* internal FFT data */
428
429         unsigned i, j;
430         float p_output[FFT_BUFFER_SIZE];           /* Raw FFT Result  */
431         int16_t p_buffer1[FFT_BUFFER_SIZE];        /* Buffer on which we perform
432                                                       the FFT (first channel) */
433         int16_t p_dest[FFT_BUFFER_SIZE];           /* Adapted FFT result */
434         float *p_buffl = (float*)block->p_buffer;  /* Original buffer */
435
436         int16_t  *p_buffs;                         /* int16_t converted buffer */
437         int16_t  *p_s16_buff;                      /* int16_t converted buffer */
438
439         /* Allocate the buffer only if the number of samples change */
440         if (block->i_nb_samples != p_sys->i_prev_nb_samples)
441         {
442             free(p_sys->p_prev_s16_buff);
443             p_sys->p_prev_s16_buff = malloc(block->i_nb_samples *
444                                             p_sys->i_channels *
445                                             sizeof(int16_t));
446             if (!p_sys->p_prev_s16_buff)
447                 goto release;
448             p_sys->i_prev_nb_samples = block->i_nb_samples;
449         }
450         p_buffs = p_s16_buff = p_sys->p_prev_s16_buff;
451
452         /* Convert the buffer to int16_t
453            Pasted from float32tos16.c */
454         for (i = block->i_nb_samples * p_sys->i_channels; i--;)
455         {
456             union {float f; int32_t i;} u;
457
458             u.f = *p_buffl + 384.0;
459             if (u.i > 0x43c07fff)
460                 *p_buffs = 32767;
461             else if (u.i < 0x43bf8000)
462                 *p_buffs = -32768;
463             else
464                 *p_buffs = u.i - 0x43c00000;
465
466             p_buffl++; p_buffs++;
467         }
468         p_state = visual_fft_init();
469         if (!p_state)
470         {
471             msg_Err(p_filter,"unable to initialize FFT transform");
472             goto release;
473         }
474         p_buffs = p_s16_buff;
475         for (i = 0 ; i < FFT_BUFFER_SIZE; i++)
476         {
477             p_output[i] = 0;
478             p_buffer1[i] = *p_buffs;
479
480             p_buffs += p_sys->i_channels;
481             if (p_buffs >= &p_s16_buff[block->i_nb_samples * p_sys->i_channels])
482                 p_buffs = p_s16_buff;
483         }
484         fft_perform (p_buffer1, p_output, p_state);
485
486         for (i = 0; i< FFT_BUFFER_SIZE; ++i)
487             p_dest[i] = p_output[i] *  (2 ^ 16)
488                         / ((FFT_BUFFER_SIZE / 2 * 32768) ^ 2);
489
490         for (i = 0 ; i < NB_BANDS; i++)
491         {
492             /* Decrease the previous size of the bar. */
493             height[i] -= BAR_DECREMENT;
494             if (height[i] < 0)
495                 height[i] = 0;
496
497             int y = 0;
498             /* We search the maximum on one scale
499                to determine the current size of the bar. */
500             for (j = xscale[i]; j < xscale[i + 1]; j++)
501             {
502                 if (p_dest[j] > y)
503                      y = p_dest[j];
504             }
505             /* Calculate the height of the bar */
506             float new_height = y != 0 ? log(y) * 0.4 : 0;
507             height[i] = new_height > height[i]
508                         ? new_height : height[i];
509         }
510
511         /* Determine the camera rotation angle. */
512         p_sys->f_rotationAngle += p_sys->f_rotationIncrement;
513         if (p_sys->f_rotationAngle <= -ROTATION_MAX)
514             p_sys->f_rotationIncrement = ROTATION_INCREMENT;
515         else if (p_sys->f_rotationAngle >= ROTATION_MAX)
516             p_sys->f_rotationIncrement = -ROTATION_INCREMENT;
517
518         /* Render the frame. */
519         glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
520
521         glPushMatrix();
522             glRotatef(p_sys->f_rotationAngle, 0, 1, 0);
523             drawBars(height);
524         glPopMatrix();
525
526         /* Wait to swapp the frame on time. */
527         mwait(block->i_pts + (block->i_length / 2));
528         if (!vlc_gl_Lock(gl))
529         {
530             vlc_gl_Swap(gl);
531             vlc_gl_Unlock(gl);
532         }
533
534 release:
535         fft_close(p_state);
536         block_Release(block);
537         vlc_restorecancel(canc);
538     }
539
540     assert(0);
541
542 error:
543     p_sys->b_error = true;
544     vlc_sem_post(&p_sys->ready);
545     return NULL;
546 }
547