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>
32 #include <vlc_vout_window.h>
33 #include <vlc_opengl.h>
34 #include <vlc_filter.h>
38 # include <OpenGL/gl.h>
45 #include "visual/fft.h"
46 #include "visual/window.h"
49 /*****************************************************************************
51 *****************************************************************************/
52 static int Open(vlc_object_t
*);
53 static void Close(vlc_object_t
*);
55 #define WIDTH_TEXT N_("Video width")
56 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
58 #define HEIGHT_TEXT N_("Video height")
59 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
62 set_shortname(N_("glSpectrum"))
63 set_description(N_("3D OpenGL spectrum visualization"))
64 set_capability("visualization", 0)
65 set_category(CAT_AUDIO
)
66 set_subcategory(SUBCAT_AUDIO_VISUAL
)
68 add_integer("glspectrum-width", 400, WIDTH_TEXT
, WIDTH_LONGTEXT
, false)
69 add_integer("glspectrum-height", 300, HEIGHT_TEXT
, HEIGHT_LONGTEXT
, false)
71 add_shortcut("glspectrum")
72 set_callbacks(Open
, Close
)
76 /*****************************************************************************
78 *****************************************************************************/
86 unsigned i_prev_nb_samples
;
87 int16_t *p_prev_s16_buff
;
92 float f_rotationAngle
;
93 float f_rotationIncrement
;
95 /* FFT window parameters */
96 window_param wind_param
;
100 static block_t
*DoWork(filter_t
*, block_t
*);
101 static void *Thread(void *);
103 #define SPECTRUM_WIDTH 4.f
105 #define ROTATION_INCREMENT .1f
106 #define BAR_DECREMENT .075f
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 p_sys
->i_channels
= aout_FormatNbChannels(&p_filter
->fmt_in
.audio
);
128 p_sys
->i_prev_nb_samples
= 0;
129 p_sys
->p_prev_s16_buff
= NULL
;
131 p_sys
->f_rotationAngle
= 0;
132 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
134 /* Fetch the FFT window parameters */
135 window_get_param( VLC_OBJECT( p_filter
), &p_sys
->wind_param
);
137 /* Create the FIFO for the audio data. */
138 p_sys
->fifo
= block_FifoNew();
139 if (p_sys
->fifo
== NULL
)
142 /* Create the openGL provider */
143 vout_window_cfg_t cfg
= {
144 .width
= var_InheritInteger(p_filter
, "glspectrum-width"),
145 .height
= var_InheritInteger(p_filter
, "glspectrum-height"),
148 p_sys
->gl
= vlc_gl_surface_Create(p_this
, &cfg
, NULL
);
149 if (p_sys
->gl
== NULL
)
151 block_FifoRelease(p_sys
->fifo
);
155 /* Create the thread */
156 if (vlc_clone(&p_sys
->thread
, Thread
, p_filter
,
157 VLC_THREAD_PRIORITY_VIDEO
))
160 p_filter
->fmt_in
.audio
.i_format
= VLC_CODEC_FL32
;
161 p_filter
->fmt_out
.audio
= p_filter
->fmt_in
.audio
;
162 p_filter
->pf_audio_filter
= DoWork
;
174 * @param p_this: the filter object
176 static void Close(vlc_object_t
*p_this
)
178 filter_t
*p_filter
= (filter_t
*)p_this
;
179 filter_sys_t
*p_sys
= p_filter
->p_sys
;
181 /* Terminate the thread. */
182 vlc_cancel(p_sys
->thread
);
183 vlc_join(p_sys
->thread
, NULL
);
185 /* Free the ressources */
186 vlc_gl_surface_Destroy(p_sys
->gl
);
187 block_FifoRelease(p_sys
->fifo
);
188 free(p_sys
->p_prev_s16_buff
);
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(void)
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(void)
244 const float w
= SPECTRUM_WIDTH
/ NB_BANDS
- 0.05f
;
246 const GLfloat vertexCoords
[] = {
247 0.f
, 0.f
, 0.f
, w
, 0.f
, 0.f
, 0.f
, 1.f
, 0.f
,
248 0.f
, 1.f
, 0.f
, w
, 0.f
, 0.f
, w
, 1.f
, 0.f
,
250 0.f
, 0.f
, -w
, 0.f
, 0.f
, 0.f
, 0.f
, 1.f
, -w
,
251 0.f
, 1.f
, -w
, 0.f
, 0.f
, 0.f
, 0.f
, 1.f
, 0.f
,
253 w
, 0.f
, 0.f
, w
, 0.f
, -w
, w
, 1.f
, 0.f
,
254 w
, 1.f
, 0.f
, w
, 0.f
, -w
, w
, 1.f
, -w
,
256 w
, 0.f
, -w
, 0.f
, 0.f
, -w
, 0.f
, 1.f
, -w
,
257 0.f
, 1.f
, -w
, w
, 1.f
, -w
, w
, 0.f
, -w
,
259 0.f
, 1.f
, 0.f
, w
, 1.f
, 0.f
, w
, 1.f
, -w
,
260 0.f
, 1.f
, 0.f
, w
, 1.f
, -w
, 0.f
, 1.f
, -w
,
263 const GLfloat normals
[] = {
264 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
,
265 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
,
267 -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
,
268 -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
,
270 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
,
271 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
,
273 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
,
274 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
,
276 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
,
277 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
,
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.2f
295 r
= -1.f
+ 2 / BAR_MAX_HEIGHT
* f_height
;
296 b
= 2.f
- 2 / BAR_MAX_HEIGHT
* f_height
;
297 #undef BAR_MAX_HEIGHT
299 /* Test the ranges. */
300 r
= r
> 1.f
? 1.f
: r
;
301 b
= b
> 1.f
? 1.f
: b
;
303 r
= r
< 0.f
? 0.f
: r
;
304 b
= b
< 0.f
? 0.f
: b
;
306 /* Set the bar color. */
307 glColor4f(r
, 0.f
, b
, 1.f
);
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.f
, 0.f
, 0.f
);
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.f
, heights
[i
], 1.f
);
328 setBarColor(heights
[i
]);
332 glTranslatef(w
, 0.f
, 0.f
);
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
;
350 vlc_gl_t
*gl
= p_sys
->gl
;
352 if (vlc_gl_MakeCurrent(gl
) != VLC_SUCCESS
)
354 msg_Err(p_filter
, "Can't attach gl context");
358 vlc_gl_ReleaseCurrent(gl
);
360 float height
[NB_BANDS
] = {0};
364 block_t
*block
= block_FifoGet(p_sys
->fifo
);
366 int canc
= vlc_savecancel();
367 unsigned win_width
, win_height
;
369 vlc_gl_MakeCurrent(gl
);
370 if (vlc_gl_surface_CheckSize(gl
, &win_width
, &win_height
))
371 glViewport(0, 0, win_width
, win_height
);
373 /* Horizontal scale for 20-band equalizer */
374 const unsigned xscale
[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
375 36,47,62,82,107,141,184,255};
377 fft_state
*p_state
= NULL
; /* internal FFT data */
378 DEFINE_WIND_CONTEXT(wind_ctx
); /* internal window data */
381 float p_output
[FFT_BUFFER_SIZE
]; /* Raw FFT Result */
382 int16_t p_buffer1
[FFT_BUFFER_SIZE
]; /* Buffer on which we perform
383 the FFT (first channel) */
384 int16_t p_dest
[FFT_BUFFER_SIZE
]; /* Adapted FFT result */
385 float *p_buffl
= (float*)block
->p_buffer
; /* Original buffer */
387 int16_t *p_buffs
; /* int16_t converted buffer */
388 int16_t *p_s16_buff
; /* int16_t converted buffer */
390 if (!block
->i_nb_samples
) {
391 msg_Err(p_filter
, "no samples yet");
395 /* Allocate the buffer only if the number of samples change */
396 if (block
->i_nb_samples
!= p_sys
->i_prev_nb_samples
)
398 free(p_sys
->p_prev_s16_buff
);
399 p_sys
->p_prev_s16_buff
= malloc(block
->i_nb_samples
*
402 if (!p_sys
->p_prev_s16_buff
)
404 p_sys
->i_prev_nb_samples
= block
->i_nb_samples
;
406 p_buffs
= p_s16_buff
= p_sys
->p_prev_s16_buff
;
408 /* Convert the buffer to int16_t
409 Pasted from float32tos16.c */
410 for (i
= block
->i_nb_samples
* p_sys
->i_channels
; i
--;)
412 union {float f
; int32_t i
;} u
;
414 u
.f
= *p_buffl
+ 384.f
;
415 if (u
.i
> 0x43c07fff)
417 else if (u
.i
< 0x43bf8000)
420 *p_buffs
= u
.i
- 0x43c00000;
422 p_buffl
++; p_buffs
++;
424 p_state
= visual_fft_init();
427 msg_Err(p_filter
,"unable to initialize FFT transform");
430 if (!window_init(FFT_BUFFER_SIZE
, &p_sys
->wind_param
, &wind_ctx
))
432 msg_Err(p_filter
,"unable to initialize FFT window");
435 p_buffs
= p_s16_buff
;
436 for (i
= 0 ; i
< FFT_BUFFER_SIZE
; i
++)
439 p_buffer1
[i
] = *p_buffs
;
441 p_buffs
+= p_sys
->i_channels
;
442 if (p_buffs
>= &p_s16_buff
[block
->i_nb_samples
* p_sys
->i_channels
])
443 p_buffs
= p_s16_buff
;
445 window_scale_in_place (p_buffer1
, &wind_ctx
);
446 fft_perform (p_buffer1
, p_output
, p_state
);
448 for (i
= 0; i
< FFT_BUFFER_SIZE
; ++i
)
449 p_dest
[i
] = p_output
[i
] * (2 ^ 16)
450 / ((FFT_BUFFER_SIZE
/ 2 * 32768) ^ 2);
452 for (i
= 0 ; i
< NB_BANDS
; i
++)
454 /* Decrease the previous size of the bar. */
455 height
[i
] -= BAR_DECREMENT
;
460 /* We search the maximum on one scale
461 to determine the current size of the bar. */
462 for (j
= xscale
[i
]; j
< xscale
[i
+ 1]; j
++)
467 /* Calculate the height of the bar */
468 float new_height
= y
!= 0 ? logf(y
) * 0.4f
: 0;
469 height
[i
] = new_height
> height
[i
]
470 ? new_height
: height
[i
];
473 /* Determine the camera rotation angle. */
474 p_sys
->f_rotationAngle
+= p_sys
->f_rotationIncrement
;
475 if (p_sys
->f_rotationAngle
<= -ROTATION_MAX
)
476 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
477 else if (p_sys
->f_rotationAngle
>= ROTATION_MAX
)
478 p_sys
->f_rotationIncrement
= -ROTATION_INCREMENT
;
480 /* Render the frame. */
481 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
484 glRotatef(p_sys
->f_rotationAngle
, 0, 1, 0);
488 /* Wait to swapp the frame on time. */
489 mwait(block
->i_pts
+ (block
->i_length
/ 2));
493 window_close(&wind_ctx
);
495 vlc_gl_ReleaseCurrent(gl
);
496 block_Release(block
);
497 vlc_restorecancel(canc
);
500 vlc_assert_unreachable();