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"
43 #include "visual/window.h"
46 /*****************************************************************************
48 *****************************************************************************/
49 static int Open(vlc_object_t
*);
50 static void Close(vlc_object_t
*);
52 #define WIDTH_TEXT N_("Video width")
53 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
55 #define HEIGHT_TEXT N_("Video height")
56 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
59 set_shortname(N_("glSpectrum"))
60 set_description(N_("3D OpenGL spectrum visualization"))
61 set_capability("visualization", 0)
62 set_category(CAT_AUDIO
)
63 set_subcategory(SUBCAT_AUDIO_VISUAL
)
65 add_integer("glspectrum-width", 400, WIDTH_TEXT
, WIDTH_LONGTEXT
, false)
66 add_integer("glspectrum-height", 300, HEIGHT_TEXT
, HEIGHT_LONGTEXT
, false)
68 add_shortcut("glspectrum")
69 set_callbacks(Open
, Close
)
73 /*****************************************************************************
75 *****************************************************************************/
85 unsigned i_prev_nb_samples
;
86 int16_t *p_prev_s16_buff
;
89 vout_thread_t
*p_vout
;
92 float f_rotationAngle
;
93 float f_rotationIncrement
;
99 /* FFT window parameters */
100 window_param wind_param
;
104 static block_t
*DoWork(filter_t
*, block_t
*);
105 static void *Thread(void *);
107 #define SPECTRUM_WIDTH 4.0
109 #define ROTATION_INCREMENT 0.1
110 #define BAR_DECREMENT 0.075
111 #define ROTATION_MAX 20
113 const GLfloat lightZeroColor
[] = {1.0f
, 1.0f
, 1.0f
, 1.0f
};
114 const GLfloat lightZeroPosition
[] = {0.0f
, 3.0f
, 10.0f
, 0.0f
};
118 * @param p_this: the filter object
119 * @return VLC_SUCCESS or vlc error codes
121 static int Open(vlc_object_t
* p_this
)
123 filter_t
*p_filter
= (filter_t
*)p_this
;
126 p_sys
= p_filter
->p_sys
= (filter_sys_t
*)malloc(sizeof(*p_sys
));
130 /* Create the object for the thread */
131 vlc_sem_init(&p_sys
->ready
, 0);
132 p_sys
->b_error
= false;
133 p_sys
->i_width
= var_InheritInteger(p_filter
, "glspectrum-width");
134 p_sys
->i_height
= var_InheritInteger(p_filter
, "glspectrum-height");
135 p_sys
->i_channels
= aout_FormatNbChannels(&p_filter
->fmt_in
.audio
);
136 p_sys
->i_prev_nb_samples
= 0;
137 p_sys
->p_prev_s16_buff
= NULL
;
139 p_sys
->f_rotationAngle
= 0;
140 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
142 /* Fetch the FFT window parameters */
143 window_get_param( VLC_OBJECT( p_filter
), &p_sys
->wind_param
);
145 /* Create the FIFO for the audio data. */
146 p_sys
->fifo
= block_FifoNew();
147 if (p_sys
->fifo
== NULL
)
150 /* Create the thread */
151 if (vlc_clone(&p_sys
->thread
, Thread
, p_filter
,
152 VLC_THREAD_PRIORITY_VIDEO
))
155 /* Wait for the displaying thread to be ready. */
156 vlc_sem_wait(&p_sys
->ready
);
159 vlc_join(p_sys
->thread
, NULL
);
163 p_filter
->fmt_in
.audio
.i_format
= VLC_CODEC_FL32
;
164 p_filter
->fmt_out
.audio
= p_filter
->fmt_in
.audio
;
165 p_filter
->pf_audio_filter
= DoWork
;
170 vlc_sem_destroy(&p_sys
->ready
);
178 * @param p_this: the filter object
180 static void Close(vlc_object_t
*p_this
)
182 filter_t
*p_filter
= (filter_t
*)p_this
;
183 filter_sys_t
*p_sys
= p_filter
->p_sys
;
185 /* Terminate the thread. */
186 vlc_cancel(p_sys
->thread
);
187 vlc_join(p_sys
->thread
, NULL
);
189 /* Free the ressources */
190 vout_DeleteDisplay(p_sys
->p_vd
, NULL
);
191 vlc_object_release(p_sys
->p_vout
);
193 block_FifoRelease(p_sys
->fifo
);
194 free(p_sys
->p_prev_s16_buff
);
196 vlc_sem_destroy(&p_sys
->ready
);
202 * Do the actual work with the new sample.
203 * @param p_filter: filter object
204 * @param p_in_buf: input buffer
206 static block_t
*DoWork(filter_t
*p_filter
, block_t
*p_in_buf
)
208 block_t
*block
= block_Duplicate(p_in_buf
);
209 if (likely(block
!= NULL
))
210 block_FifoPut(p_filter
->p_sys
->fifo
, block
);
216 * Init the OpenGL scene.
218 static void initOpenGLScene(void)
220 glEnable(GL_CULL_FACE
);
221 glEnable(GL_DEPTH_TEST
);
222 glDepthMask(GL_TRUE
);
224 glMatrixMode(GL_PROJECTION
);
225 glFrustum(-1.0f
, 1.0f
, -1.0f
, 1.0f
, 0.5f
, 10.0f
);
227 glMatrixMode(GL_MODELVIEW
);
228 glTranslatef(0.0, -2.0, -2.0);
231 glEnable(GL_LIGHTING
);
233 glColorMaterial(GL_FRONT
, GL_AMBIENT_AND_DIFFUSE
);
234 glEnable(GL_COLOR_MATERIAL
);
237 glLightfv(GL_LIGHT0
, GL_DIFFUSE
, lightZeroColor
);
238 glLightfv(GL_LIGHT0
, GL_POSITION
, lightZeroPosition
);
240 glShadeModel(GL_SMOOTH
);
243 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
248 * Draw one bar of the Spectrum.
250 static void drawBar(void)
252 const float w
= SPECTRUM_WIDTH
/ NB_BANDS
- 0.05;
254 const GLfloat vertexCoords
[] = {
255 0.0, 0.0, 0.0, w
, 0.0, 0.0, 0.0, 1.0, 0.0,
256 0.0, 1.0, 0.0, w
, 0.0, 0.0, w
, 1.0, 0.0,
258 0.0, 0.0, -w
, 0.0, 0.0, 0.0, 0.0, 1.0, -w
,
259 0.0, 1.0, -w
, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0,
261 w
, 0.0, 0.0, w
, 0.0, -w
, w
, 1.0, 0.0,
262 w
, 1.0, 0.0, w
, 0.0, -w
, w
, 1.0, -w
,
264 w
, 0.0, -w
, 0.0, 0.0, -w
, 0.0, 1.0, -w
,
265 0.0, 1.0, -w
, w
, 1.0, -w
, w
, 0.0, -w
,
267 0.0, 1.0, 0.0, w
, 1.0, 0.0, w
, 1.0, -w
,
268 0.0, 1.0, 0.0, w
, 1.0, -w
, 0.0, 1.0, -w
,
271 const GLfloat normals
[] = {
272 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
273 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0,
275 -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
276 -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0,
278 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
279 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
281 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
282 0.0, 0.0, -1.0, 0.0, 0.0, -1.0, 0.0, 0.0, -1.0,
284 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
285 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
288 glVertexPointer(3, GL_FLOAT
, 0, vertexCoords
);
289 glNormalPointer(GL_FLOAT
, 0, normals
);
290 glDrawArrays(GL_TRIANGLES
, 0, 6 * 5);
295 * Set the color of one bar of the spectrum.
296 * @param f_height the height of the bar.
298 static void setBarColor(float f_height
)
302 #define BAR_MAX_HEIGHT 4.2
303 r
= -1.0 + 2 / BAR_MAX_HEIGHT
* f_height
;
304 b
= 2.0 - 2 / BAR_MAX_HEIGHT
* f_height
;
305 #undef BAR_MAX_HEIGHT
307 /* Test the ranges. */
308 r
= r
> 1.0 ? 1.0 : r
;
309 b
= b
> 1.0 ? 1.0 : b
;
311 r
= r
< 0.0 ? 0.0 : r
;
312 b
= b
< 0.0 ? 0.0 : b
;
314 /* Set the bar color. */
315 glColor4f(r
, 0.0, b
, 1.0);
320 * Draw all the bars of the spectrum.
321 * @param heights the heights of all the bars.
323 static void drawBars(float heights
[])
326 glTranslatef(-2.0, 0.0, 0.0);
328 glEnableClientState(GL_VERTEX_ARRAY
);
329 glEnableClientState(GL_NORMAL_ARRAY
);
331 float w
= SPECTRUM_WIDTH
/ NB_BANDS
;
332 for (unsigned i
= 0; i
< NB_BANDS
; ++i
)
335 glScalef(1.0, heights
[i
], 1.0);
336 setBarColor(heights
[i
]);
340 glTranslatef(w
, 0.0, 0.0);
343 glDisableClientState(GL_VERTEX_ARRAY
);
344 glDisableClientState(GL_NORMAL_ARRAY
);
351 * Update thread which do the rendering
352 * @param p_this: the p_thread object
354 static void *Thread( void *p_data
)
356 filter_t
*p_filter
= (filter_t
*)p_data
;
357 filter_sys_t
*p_sys
= p_filter
->p_sys
;
361 unsigned int i_last_width
= 0;
362 unsigned int i_last_height
= 0;
364 /* Create the openGL provider */
366 (vout_thread_t
*)vlc_object_create(p_filter
, sizeof(vout_thread_t
));
370 /* Configure the video format for the opengl provider. */
371 video_format_Init(&fmt
, 0);
372 video_format_Setup(&fmt
, VLC_CODEC_RGB32
, p_sys
->i_width
, p_sys
->i_height
,
373 p_sys
->i_width
, p_sys
->i_height
, 0, 1 );
377 /* Init vout state. */
378 vout_display_state_t state
;
379 memset(&state
, 0, sizeof(state
));
380 state
.cfg
.display
.sar
.num
= 1;
381 state
.cfg
.display
.sar
.den
= 1;
382 state
.cfg
.is_display_filled
= true;
383 state
.cfg
.zoom
.num
= 1;
384 state
.cfg
.zoom
.den
= 1;
388 p_sys
->p_vd
= vout_NewDisplay(p_sys
->p_vout
, &fmt
, &state
,
389 "opengl", 1000000, 1000000);
392 vlc_object_release(p_sys
->p_vout
);
396 gl
= vout_GetDisplayOpengl(p_sys
->p_vd
);
399 vout_DeleteDisplay(p_sys
->p_vd
, NULL
);
400 vlc_object_release(p_sys
->p_vout
);
404 vlc_sem_post(&p_sys
->ready
);
406 vlc_gl_MakeCurrent(gl
);
408 vlc_gl_ReleaseCurrent(gl
);
410 float height
[NB_BANDS
] = {0};
414 block_t
*block
= block_FifoGet(p_sys
->fifo
);
416 int canc
= vlc_savecancel();
418 vlc_gl_MakeCurrent(gl
);
419 /* Manage the events */
420 vout_ManageDisplay(p_sys
->p_vd
, true);
421 if (p_sys
->p_vd
->cfg
->display
.width
!= i_last_width
||
422 p_sys
->p_vd
->cfg
->display
.height
!= i_last_height
)
424 /* FIXME it is not perfect as we will have black bands */
425 vout_display_place_t place
;
426 vout_display_PlacePicture(&place
, &p_sys
->p_vd
->source
,
427 p_sys
->p_vd
->cfg
, false);
429 i_last_width
= p_sys
->p_vd
->cfg
->display
.width
;
430 i_last_height
= p_sys
->p_vd
->cfg
->display
.height
;
433 /* Horizontal scale for 20-band equalizer */
434 const unsigned xscale
[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
435 36,47,62,82,107,141,184,255};
437 fft_state
*p_state
= NULL
; /* internal FFT data */
438 DEFINE_WIND_CONTEXT(wind_ctx
); /* internal window data */
441 float p_output
[FFT_BUFFER_SIZE
]; /* Raw FFT Result */
442 int16_t p_buffer1
[FFT_BUFFER_SIZE
]; /* Buffer on which we perform
443 the FFT (first channel) */
444 int16_t p_dest
[FFT_BUFFER_SIZE
]; /* Adapted FFT result */
445 float *p_buffl
= (float*)block
->p_buffer
; /* Original buffer */
447 int16_t *p_buffs
; /* int16_t converted buffer */
448 int16_t *p_s16_buff
; /* int16_t converted buffer */
450 if (!block
->i_nb_samples
) {
451 msg_Err(p_filter
, "no samples yet");
455 /* Allocate the buffer only if the number of samples change */
456 if (block
->i_nb_samples
!= p_sys
->i_prev_nb_samples
)
458 free(p_sys
->p_prev_s16_buff
);
459 p_sys
->p_prev_s16_buff
= malloc(block
->i_nb_samples
*
462 if (!p_sys
->p_prev_s16_buff
)
464 p_sys
->i_prev_nb_samples
= block
->i_nb_samples
;
466 p_buffs
= p_s16_buff
= p_sys
->p_prev_s16_buff
;
468 /* Convert the buffer to int16_t
469 Pasted from float32tos16.c */
470 for (i
= block
->i_nb_samples
* p_sys
->i_channels
; i
--;)
472 union {float f
; int32_t i
;} u
;
474 u
.f
= *p_buffl
+ 384.0;
475 if (u
.i
> 0x43c07fff)
477 else if (u
.i
< 0x43bf8000)
480 *p_buffs
= u
.i
- 0x43c00000;
482 p_buffl
++; p_buffs
++;
484 p_state
= visual_fft_init();
487 msg_Err(p_filter
,"unable to initialize FFT transform");
490 if (!window_init(FFT_BUFFER_SIZE
, &p_sys
->wind_param
, &wind_ctx
))
492 msg_Err(p_filter
,"unable to initialize FFT window");
495 p_buffs
= p_s16_buff
;
496 for (i
= 0 ; i
< FFT_BUFFER_SIZE
; i
++)
499 p_buffer1
[i
] = *p_buffs
;
501 p_buffs
+= p_sys
->i_channels
;
502 if (p_buffs
>= &p_s16_buff
[block
->i_nb_samples
* p_sys
->i_channels
])
503 p_buffs
= p_s16_buff
;
505 window_scale_in_place (p_buffer1
, &wind_ctx
);
506 fft_perform (p_buffer1
, p_output
, p_state
);
508 for (i
= 0; i
< FFT_BUFFER_SIZE
; ++i
)
509 p_dest
[i
] = p_output
[i
] * (2 ^ 16)
510 / ((FFT_BUFFER_SIZE
/ 2 * 32768) ^ 2);
512 for (i
= 0 ; i
< NB_BANDS
; i
++)
514 /* Decrease the previous size of the bar. */
515 height
[i
] -= BAR_DECREMENT
;
520 /* We search the maximum on one scale
521 to determine the current size of the bar. */
522 for (j
= xscale
[i
]; j
< xscale
[i
+ 1]; j
++)
527 /* Calculate the height of the bar */
528 float new_height
= y
!= 0 ? log(y
) * 0.4 : 0;
529 height
[i
] = new_height
> height
[i
]
530 ? new_height
: height
[i
];
533 /* Determine the camera rotation angle. */
534 p_sys
->f_rotationAngle
+= p_sys
->f_rotationIncrement
;
535 if (p_sys
->f_rotationAngle
<= -ROTATION_MAX
)
536 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
537 else if (p_sys
->f_rotationAngle
>= ROTATION_MAX
)
538 p_sys
->f_rotationIncrement
= -ROTATION_INCREMENT
;
540 /* Render the frame. */
541 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
544 glRotatef(p_sys
->f_rotationAngle
, 0, 1, 0);
548 /* Wait to swapp the frame on time. */
549 mwait(block
->i_pts
+ (block
->i_length
/ 2));
550 if (!vlc_gl_Lock(gl
))
557 window_close(&wind_ctx
);
559 vlc_gl_ReleaseCurrent(gl
);
560 block_Release(block
);
561 vlc_restorecancel(canc
);
567 p_sys
->b_error
= true;
568 vlc_sem_post(&p_sys
->ready
);