1 /*****************************************************************************
2 * glspectrum.c: spectrum visualization module based on OpenGL
3 *****************************************************************************
4 * Copyright © 2009-2013 VLC authors and VideoLAN
6 * Authors: Adrien Maglo <magsoft@videolan.org>
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.
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.
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 *****************************************************************************/
29 #include <vlc_common.h>
30 #include <vlc_plugin.h>
33 #include <vlc_vout_wrapper.h>
34 #include <vlc_opengl.h>
35 #include <vlc_filter.h>
42 #include "visual/fft.h"
45 /*****************************************************************************
47 *****************************************************************************/
48 static int Open(vlc_object_t *);
49 static void Close(vlc_object_t *);
51 #define WIDTH_TEXT N_("Video width")
52 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
54 #define HEIGHT_TEXT N_("Video height")
55 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
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)
64 add_integer("glspectrum-width", 400, WIDTH_TEXT, WIDTH_LONGTEXT, false)
65 add_integer("glspectrum-height", 300, HEIGHT_TEXT, HEIGHT_LONGTEXT, false)
67 add_shortcut("glspectrum")
68 set_callbacks(Open, Close)
72 /*****************************************************************************
74 *****************************************************************************/
84 unsigned i_prev_nb_samples;
85 int16_t *p_prev_s16_buff;
88 vout_thread_t *p_vout;
91 float f_rotationAngle;
92 float f_rotationIncrement;
100 static block_t *DoWork(filter_t *, block_t *);
101 static void *Thread(void *);
103 #define SPECTRUM_WIDTH 4.0
105 #define ROTATION_INCREMENT 0.1
106 #define BAR_DECREMENT 0.075
107 #define ROTATION_MAX 20
109 const GLfloat lightZeroColor[] = {1.0f, 1.0f, 1.0f, 1.0f};
110 const GLfloat lightZeroPosition[] = {0.0f, 3.0f, 10.0f, 0.0f};
114 * @param p_this: the filter object
115 * @return VLC_SUCCESS or vlc error codes
117 static int Open(vlc_object_t * p_this)
119 filter_t *p_filter = (filter_t *)p_this;
122 p_sys = p_filter->p_sys = (filter_sys_t*)malloc(sizeof(*p_sys));
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->p_prev_s16_buff = NULL;
134 p_sys->f_rotationAngle = 0;
135 p_sys->f_rotationIncrement = ROTATION_INCREMENT;
137 /* Create the FIFO for the audio data. */
138 p_sys->fifo = block_FifoNew();
139 if (p_sys->fifo == NULL)
142 /* Create the thread */
143 if (vlc_clone(&p_sys->thread, Thread, p_filter,
144 VLC_THREAD_PRIORITY_VIDEO))
147 /* Wait for the displaying thread to be ready. */
148 vlc_sem_wait(&p_sys->ready);
151 vlc_join(p_sys->thread, NULL);
155 p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
156 p_filter->fmt_out.audio = p_filter->fmt_in.audio;
157 p_filter->pf_audio_filter = DoWork;
162 vlc_sem_destroy(&p_sys->ready);
170 * @param p_this: the filter object
172 static void Close(vlc_object_t *p_this)
174 filter_t *p_filter = (filter_t *)p_this;
175 filter_sys_t *p_sys = p_filter->p_sys;
177 /* Terminate the thread. */
178 vlc_cancel(p_sys->thread);
179 vlc_join(p_sys->thread, NULL);
181 /* Free the ressources */
182 vout_DeleteDisplay(p_sys->p_vd, NULL);
183 vlc_object_release(p_sys->p_vout);
185 block_FifoRelease(p_sys->fifo);
186 free(p_sys->p_prev_s16_buff);
188 vlc_sem_destroy(&p_sys->ready);
194 * Do the actual work with the new sample.
195 * @param p_filter: filter object
196 * @param p_in_buf: input buffer
198 static block_t *DoWork(filter_t *p_filter, block_t *p_in_buf)
200 block_t *block = block_Duplicate(p_in_buf);
201 if (likely(block != NULL))
202 block_FifoPut(p_filter->p_sys->fifo, block);
208 * Init the OpenGL scene.
210 static void initOpenGLScene()
212 glEnable(GL_CULL_FACE);
213 glEnable(GL_DEPTH_TEST);
214 glDepthMask(GL_TRUE);
216 glMatrixMode(GL_PROJECTION);
217 glFrustum(-1.0f, 1.0f, -1.0f, 1.0f, 0.5f, 10.0f);
219 glMatrixMode(GL_MODELVIEW);
220 glTranslatef(0.0, -2.0, -2.0);
223 glEnable(GL_LIGHTING);
225 glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
226 glEnable(GL_COLOR_MATERIAL);
229 glLightfv(GL_LIGHT0, GL_DIFFUSE, lightZeroColor);
230 glLightfv(GL_LIGHT0, GL_POSITION, lightZeroPosition);
232 glShadeModel(GL_SMOOTH);
235 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
240 * Draw one bar of the Spectrum.
242 static void drawBar()
244 const float w = SPECTRUM_WIDTH / NB_BANDS - 0.05;
246 const GLfloat vertexCoords[] = {
247 0.0, 0.0, 0.0, w, 0.0, 0.0, 0.0, 1.0, 0.0,
248 0.0, 1.0, 0.0, w, 0.0, 0.0, w , 1.0, 0.0,
250 0.0, 0.0, -w, 0.0, 0.0, 0.0, 0.0, 1.0, -w,
251 0.0, 1.0, -w, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
253 w, 0.0, 0.0, w, 0.0, -w, w, 1.0, 0.0,
254 w, 1.0, 0.0, w, 0.0, -w, w, 1.0, -w,
256 w, 0.0, -w, 0.0, 0.0, -w, 0.0, 1.0, -w,
257 0.0, 1.0, -w, w, 1.0, -w, w, 0.0, -w,
259 0.0, 1.0, 0.0, w, 1.0, 0.0, w, 1.0, -w,
260 0.0, 1.0, 0.0, w, 1.0, -w, 0.0, 1.0, -w,
263 const GLfloat normals[] = {
264 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
265 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
267 -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
268 -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
270 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
271 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
273 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
274 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
276 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
277 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
280 glVertexPointer(3, GL_FLOAT, 0, vertexCoords);
281 glNormalPointer(GL_FLOAT, 0, normals);
282 glDrawArrays(GL_TRIANGLES, 0, 6 * 5);
287 * Set the color of one bar of the spectrum.
288 * @param f_height the height of the bar.
290 static void setBarColor(float f_height)
294 #define BAR_MAX_HEIGHT 4.2
295 r = -1.0 + 2 / BAR_MAX_HEIGHT * f_height;
296 b = 2.0 - 2 / BAR_MAX_HEIGHT * f_height;
297 #undef BAR_MAX_HEIGHT
299 /* Test the ranges. */
300 r = r > 1.0 ? 1.0 : r;
301 b = b > 1.0 ? 1.0 : b;
303 r = r < 0.0 ? 0.0 : r;
304 b = b < 0.0 ? 0.0 : b;
306 /* Set the bar color. */
307 glColor4f(r, 0.0, b, 1.0);
312 * Draw all the bars of the spectrum.
313 * @param heights the heights of all the bars.
315 static void drawBars(float heights[])
318 glTranslatef(-2.0, 0.0, 0.0);
320 glEnableClientState(GL_VERTEX_ARRAY);
321 glEnableClientState(GL_NORMAL_ARRAY);
323 float w = SPECTRUM_WIDTH / NB_BANDS;
324 for (unsigned i = 0; i < NB_BANDS; ++i)
327 glScalef(1.0, heights[i], 1.0);
328 setBarColor(heights[i]);
332 glTranslatef(w, 0.0, 0.0);
335 glDisableClientState(GL_VERTEX_ARRAY);
336 glDisableClientState(GL_NORMAL_ARRAY);
343 * Update thread which do the rendering
344 * @param p_this: the p_thread object
346 static void *Thread( void *p_data )
348 filter_t *p_filter = (filter_t*)p_data;
349 filter_sys_t *p_sys = p_filter->p_sys;
353 unsigned int i_last_width = 0;
354 unsigned int i_last_height = 0;
356 /* Create the openGL provider */
358 (vout_thread_t *)vlc_object_create(p_filter, sizeof(vout_thread_t));
362 /* Configure the video format for the opengl provider. */
363 video_format_Init(&fmt, 0);
364 video_format_Setup(&fmt, VLC_CODEC_RGB32,
365 p_sys->i_width, p_sys->i_height, 0, 1 );
369 /* Init vout state. */
370 vout_display_state_t state;
371 memset(&state, 0, sizeof(state));
372 state.cfg.display.sar.num = 1;
373 state.cfg.display.sar.den = 1;
374 state.cfg.is_display_filled = true;
375 state.cfg.zoom.num = 1;
376 state.cfg.zoom.den = 1;
380 p_sys->p_vd = vout_NewDisplay(p_sys->p_vout, &fmt, &state,
381 "opengl", 1000000, 1000000);
384 vlc_object_release(p_sys->p_vout);
388 gl = vout_GetDisplayOpengl(p_sys->p_vd);
391 vout_DeleteDisplay(p_sys->p_vd, NULL);
392 vlc_object_release(p_sys->p_vout);
396 vlc_sem_post(&p_sys->ready);
400 float height[NB_BANDS] = {0};
404 block_t *block = block_FifoGet(p_sys->fifo);
406 int canc = vlc_savecancel();
408 /* Manage the events */
409 vout_ManageDisplay(p_sys->p_vd, true);
410 if (p_sys->p_vd->cfg->display.width != i_last_width ||
411 p_sys->p_vd->cfg->display.height != i_last_height)
413 /* FIXME it is not perfect as we will have black bands */
414 vout_display_place_t place;
415 vout_display_PlacePicture(&place, &p_sys->p_vd->source,
416 p_sys->p_vd->cfg, false);
418 i_last_width = p_sys->p_vd->cfg->display.width;
419 i_last_height = p_sys->p_vd->cfg->display.height;
422 /* Horizontal scale for 20-band equalizer */
423 const unsigned xscale[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
424 36,47,62,82,107,141,184,255};
426 fft_state *p_state; /* internal FFT data */
429 float p_output[FFT_BUFFER_SIZE]; /* Raw FFT Result */
430 int16_t p_buffer1[FFT_BUFFER_SIZE]; /* Buffer on which we perform
431 the FFT (first channel) */
432 int16_t p_dest[FFT_BUFFER_SIZE]; /* Adapted FFT result */
433 float *p_buffl = (float*)block->p_buffer; /* Original buffer */
435 int16_t *p_buffs; /* int16_t converted buffer */
436 int16_t *p_s16_buff; /* int16_t converted buffer */
438 /* Allocate the buffer only if the number of samples change */
439 if (block->i_nb_samples != p_sys->i_prev_nb_samples)
441 free(p_sys->p_prev_s16_buff);
442 p_sys->p_prev_s16_buff = malloc(block->i_nb_samples *
445 if (!p_sys->p_prev_s16_buff)
447 p_sys->i_prev_nb_samples = block->i_nb_samples;
449 p_buffs = p_s16_buff = p_sys->p_prev_s16_buff;
451 /* Convert the buffer to int16_t
452 Pasted from float32tos16.c */
453 for (i = block->i_nb_samples * p_sys->i_channels; i--;)
455 union {float f; int32_t i;} u;
457 u.f = *p_buffl + 384.0;
458 if (u.i > 0x43c07fff)
460 else if (u.i < 0x43bf8000)
463 *p_buffs = u.i - 0x43c00000;
465 p_buffl++; p_buffs++;
467 p_state = visual_fft_init();
470 msg_Err(p_filter,"unable to initialize FFT transform");
473 p_buffs = p_s16_buff;
474 for (i = 0 ; i < FFT_BUFFER_SIZE; i++)
477 p_buffer1[i] = *p_buffs;
479 p_buffs += p_sys->i_channels;
480 if (p_buffs >= &p_s16_buff[block->i_nb_samples * p_sys->i_channels])
481 p_buffs = p_s16_buff;
483 fft_perform (p_buffer1, p_output, p_state);
485 for (i = 0; i< FFT_BUFFER_SIZE; ++i)
486 p_dest[i] = p_output[i] * (2 ^ 16)
487 / ((FFT_BUFFER_SIZE / 2 * 32768) ^ 2);
489 for (i = 0 ; i < NB_BANDS; i++)
491 /* Decrease the previous size of the bar. */
492 height[i] -= BAR_DECREMENT;
497 /* We search the maximum on one scale
498 to determine the current size of the bar. */
499 for (j = xscale[i]; j < xscale[i + 1]; j++)
504 /* Calculate the height of the bar */
505 float new_height = y != 0 ? log(y) * 0.4 : 0;
506 height[i] = new_height > height[i]
507 ? new_height : height[i];
510 /* Determine the camera rotation angle. */
511 p_sys->f_rotationAngle += p_sys->f_rotationIncrement;
512 if (p_sys->f_rotationAngle <= -ROTATION_MAX)
513 p_sys->f_rotationIncrement = ROTATION_INCREMENT;
514 else if (p_sys->f_rotationAngle >= ROTATION_MAX)
515 p_sys->f_rotationIncrement = -ROTATION_INCREMENT;
517 /* Render the frame. */
518 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
521 glRotatef(p_sys->f_rotationAngle, 0, 1, 0);
525 /* Wait to swapp the frame on time. */
526 mwait(block->i_pts + (block->i_length / 2));
527 if (!vlc_gl_Lock(gl))
534 block_Release(block);
535 vlc_restorecancel(canc);
541 p_sys->b_error = true;
542 vlc_sem_post(&p_sys->ready);