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>
35 #include <vlc_queue.h>
39 # include <OpenGL/gl.h>
46 #include "visual/fft.h"
47 #include "visual/window.h"
50 /*****************************************************************************
52 *****************************************************************************/
53 static int Open(vlc_object_t
*);
54 static void Close(filter_t
*);
56 #define WIDTH_TEXT N_("Video width")
57 #define WIDTH_LONGTEXT N_("The width of the visualization window, in pixels.")
59 #define HEIGHT_TEXT N_("Video height")
60 #define HEIGHT_LONGTEXT N_("The height of the visualization window, in pixels.")
63 set_shortname(N_("glSpectrum"))
64 set_description(N_("3D OpenGL spectrum visualization"))
65 set_capability("visualization", 0)
66 set_category(CAT_AUDIO
)
67 set_subcategory(SUBCAT_AUDIO_VISUAL
)
69 add_integer("glspectrum-width", 400, WIDTH_TEXT
, WIDTH_LONGTEXT
, false)
70 add_integer("glspectrum-height", 300, HEIGHT_TEXT
, HEIGHT_LONGTEXT
, false)
72 add_shortcut("glspectrum")
77 /*****************************************************************************
79 *****************************************************************************/
88 unsigned i_prev_nb_samples
;
89 int16_t *p_prev_s16_buff
;
94 float f_rotationAngle
;
95 float f_rotationIncrement
;
97 /* FFT window parameters */
98 window_param wind_param
;
102 static block_t
*DoWork(filter_t
*, block_t
*);
103 static void *Thread(void *);
105 #define SPECTRUM_WIDTH 4.f
107 #define ROTATION_INCREMENT .1f
108 #define BAR_DECREMENT .075f
109 #define ROTATION_MAX 20
111 const GLfloat lightZeroColor
[] = {1.0f
, 1.0f
, 1.0f
, 1.0f
};
112 const GLfloat lightZeroPosition
[] = {0.0f
, 3.0f
, 10.0f
, 0.0f
};
114 static const struct vlc_filter_operations filter_ops
= {
115 .filter_audio
= DoWork
, .close
= Close
,
120 * @param p_this: the filter object
121 * @return VLC_SUCCESS or vlc error codes
123 static int Open(vlc_object_t
* p_this
)
125 filter_t
*p_filter
= (filter_t
*)p_this
;
126 filter_sys_t
*p_sys
= vlc_obj_malloc(p_this
, sizeof (*p_sys
));
131 p_filter
->p_sys
= p_sys
;
133 /* Create the object for the thread */
134 p_sys
->i_channels
= aout_FormatNbChannels(&p_filter
->fmt_in
.audio
);
135 p_sys
->i_prev_nb_samples
= 0;
136 p_sys
->p_prev_s16_buff
= NULL
;
138 p_sys
->f_rotationAngle
= 0;
139 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
141 /* Fetch the FFT window parameters */
142 window_get_param( VLC_OBJECT( p_filter
), &p_sys
->wind_param
);
144 /* Create the FIFO for the audio data. */
145 vlc_queue_Init(&p_sys
->queue
, offsetof (block_t
, p_next
));
148 /* Create the openGL provider */
149 vout_window_cfg_t cfg
= {
150 .width
= var_InheritInteger(p_filter
, "glspectrum-width"),
151 .height
= var_InheritInteger(p_filter
, "glspectrum-height"),
154 p_sys
->gl
= vlc_gl_surface_Create(p_this
, &cfg
, NULL
);
155 if (p_sys
->gl
== NULL
)
158 /* Create the thread */
159 if (vlc_clone(&p_sys
->thread
, Thread
, p_filter
,
160 VLC_THREAD_PRIORITY_VIDEO
)) {
161 vlc_gl_surface_Destroy(p_sys
->gl
);
165 p_filter
->fmt_in
.audio
.i_format
= VLC_CODEC_FL32
;
166 p_filter
->fmt_out
.audio
= p_filter
->fmt_in
.audio
;
167 p_filter
->ops
= &filter_ops
;
175 * @param p_this: the filter object
177 static void Close(filter_t
*p_filter
)
179 filter_sys_t
*p_sys
= p_filter
->p_sys
;
181 /* Terminate the thread. */
182 vlc_queue_Kill(&p_sys
->queue
, &p_sys
->dead
);
183 vlc_join(p_sys
->thread
, NULL
);
185 /* Free the ressources */
186 vlc_gl_surface_Destroy(p_sys
->gl
);
187 free(p_sys
->p_prev_s16_buff
);
192 * Do the actual work with the new sample.
193 * @param p_filter: filter object
194 * @param p_in_buf: input buffer
196 static block_t
*DoWork(filter_t
*p_filter
, block_t
*p_in_buf
)
198 filter_sys_t
*p_sys
= p_filter
->p_sys
;
200 vlc_queue_Enqueue(&p_sys
->queue
, block_Duplicate(p_in_buf
));
206 * Init the OpenGL scene.
208 static void initOpenGLScene(void)
210 glEnable(GL_CULL_FACE
);
211 glEnable(GL_DEPTH_TEST
);
212 glDepthMask(GL_TRUE
);
214 glMatrixMode(GL_PROJECTION
);
215 glFrustum(-1.0f
, 1.0f
, -1.0f
, 1.0f
, 0.5f
, 10.0f
);
217 glMatrixMode(GL_MODELVIEW
);
218 glTranslatef(0.0, -2.0, -2.0);
221 glEnable(GL_LIGHTING
);
223 glColorMaterial(GL_FRONT
, GL_AMBIENT_AND_DIFFUSE
);
224 glEnable(GL_COLOR_MATERIAL
);
227 glLightfv(GL_LIGHT0
, GL_DIFFUSE
, lightZeroColor
);
228 glLightfv(GL_LIGHT0
, GL_POSITION
, lightZeroPosition
);
230 glShadeModel(GL_SMOOTH
);
233 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
238 * Draw one bar of the Spectrum.
240 static void drawBar(void)
242 const float w
= SPECTRUM_WIDTH
/ NB_BANDS
- 0.05f
;
244 const GLfloat vertexCoords
[] = {
245 0.f
, 0.f
, 0.f
, w
, 0.f
, 0.f
, 0.f
, 1.f
, 0.f
,
246 0.f
, 1.f
, 0.f
, w
, 0.f
, 0.f
, w
, 1.f
, 0.f
,
248 0.f
, 0.f
, -w
, 0.f
, 0.f
, 0.f
, 0.f
, 1.f
, -w
,
249 0.f
, 1.f
, -w
, 0.f
, 0.f
, 0.f
, 0.f
, 1.f
, 0.f
,
251 w
, 0.f
, 0.f
, w
, 0.f
, -w
, w
, 1.f
, 0.f
,
252 w
, 1.f
, 0.f
, w
, 0.f
, -w
, w
, 1.f
, -w
,
254 w
, 0.f
, -w
, 0.f
, 0.f
, -w
, 0.f
, 1.f
, -w
,
255 0.f
, 1.f
, -w
, w
, 1.f
, -w
, w
, 0.f
, -w
,
257 0.f
, 1.f
, 0.f
, w
, 1.f
, 0.f
, w
, 1.f
, -w
,
258 0.f
, 1.f
, 0.f
, w
, 1.f
, -w
, 0.f
, 1.f
, -w
,
261 const GLfloat normals
[] = {
262 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
,
263 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
,
265 -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
,
266 -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
,
269 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
,
271 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
,
272 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
, 0.f
, 0.f
, -1.f
,
274 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
,
275 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
, 0.f
, 1.f
, 0.f
,
278 glVertexPointer(3, GL_FLOAT
, 0, vertexCoords
);
279 glNormalPointer(GL_FLOAT
, 0, normals
);
280 glDrawArrays(GL_TRIANGLES
, 0, 6 * 5);
285 * Set the color of one bar of the spectrum.
286 * @param f_height the height of the bar.
288 static void setBarColor(float f_height
)
292 #define BAR_MAX_HEIGHT 4.2f
293 r
= -1.f
+ 2 / BAR_MAX_HEIGHT
* f_height
;
294 b
= 2.f
- 2 / BAR_MAX_HEIGHT
* f_height
;
295 #undef BAR_MAX_HEIGHT
297 /* Test the ranges. */
298 r
= r
> 1.f
? 1.f
: r
;
299 b
= b
> 1.f
? 1.f
: b
;
301 r
= r
< 0.f
? 0.f
: r
;
302 b
= b
< 0.f
? 0.f
: b
;
304 /* Set the bar color. */
305 glColor4f(r
, 0.f
, b
, 1.f
);
310 * Draw all the bars of the spectrum.
311 * @param heights the heights of all the bars.
313 static void drawBars(float heights
[])
316 glTranslatef(-2.f
, 0.f
, 0.f
);
318 glEnableClientState(GL_VERTEX_ARRAY
);
319 glEnableClientState(GL_NORMAL_ARRAY
);
321 float w
= SPECTRUM_WIDTH
/ NB_BANDS
;
322 for (unsigned i
= 0; i
< NB_BANDS
; ++i
)
325 glScalef(1.f
, heights
[i
], 1.f
);
326 setBarColor(heights
[i
]);
330 glTranslatef(w
, 0.f
, 0.f
);
333 glDisableClientState(GL_VERTEX_ARRAY
);
334 glDisableClientState(GL_NORMAL_ARRAY
);
341 * Update thread which do the rendering
342 * @param p_this: the p_thread object
344 static void *Thread( void *p_data
)
346 filter_t
*p_filter
= (filter_t
*)p_data
;
347 filter_sys_t
*p_sys
= p_filter
->p_sys
;
348 vlc_gl_t
*gl
= p_sys
->gl
;
351 if (vlc_gl_MakeCurrent(gl
) != VLC_SUCCESS
)
353 msg_Err(p_filter
, "Can't attach gl context");
357 vlc_gl_ReleaseCurrent(gl
);
359 float height
[NB_BANDS
] = {0};
361 while ((block
= vlc_queue_DequeueKillable(&p_sys
->queue
, &p_sys
->dead
)))
363 unsigned win_width
, win_height
;
365 vlc_gl_MakeCurrent(gl
);
366 if (vlc_gl_surface_CheckSize(gl
, &win_width
, &win_height
))
367 glViewport(0, 0, win_width
, win_height
);
369 /* Horizontal scale for 20-band equalizer */
370 const unsigned xscale
[] = {0,1,2,3,4,5,6,7,8,11,15,20,27,
371 36,47,62,82,107,141,184,255};
373 fft_state
*p_state
= NULL
; /* internal FFT data */
374 DEFINE_WIND_CONTEXT(wind_ctx
); /* internal window data */
377 float p_output
[FFT_BUFFER_SIZE
]; /* Raw FFT Result */
378 int16_t p_buffer1
[FFT_BUFFER_SIZE
]; /* Buffer on which we perform
379 the FFT (first channel) */
380 int16_t p_dest
[FFT_BUFFER_SIZE
]; /* Adapted FFT result */
381 float *p_buffl
= (float*)block
->p_buffer
; /* Original buffer */
383 int16_t *p_buffs
; /* int16_t converted buffer */
384 int16_t *p_s16_buff
; /* int16_t converted buffer */
386 if (!block
->i_nb_samples
) {
387 msg_Err(p_filter
, "no samples yet");
391 /* Allocate the buffer only if the number of samples change */
392 if (block
->i_nb_samples
!= p_sys
->i_prev_nb_samples
)
394 free(p_sys
->p_prev_s16_buff
);
395 p_sys
->p_prev_s16_buff
= malloc(block
->i_nb_samples
*
398 if (!p_sys
->p_prev_s16_buff
)
400 p_sys
->i_prev_nb_samples
= block
->i_nb_samples
;
402 p_buffs
= p_s16_buff
= p_sys
->p_prev_s16_buff
;
404 /* Convert the buffer to int16_t
405 Pasted from float32tos16.c */
406 for (i
= block
->i_nb_samples
* p_sys
->i_channels
; i
--;)
408 union {float f
; int32_t i
;} u
;
410 u
.f
= *p_buffl
+ 384.f
;
411 if (u
.i
> 0x43c07fff)
413 else if (u
.i
< 0x43bf8000)
416 *p_buffs
= u
.i
- 0x43c00000;
418 p_buffl
++; p_buffs
++;
420 p_state
= visual_fft_init();
423 msg_Err(p_filter
,"unable to initialize FFT transform");
426 if (!window_init(FFT_BUFFER_SIZE
, &p_sys
->wind_param
, &wind_ctx
))
428 msg_Err(p_filter
,"unable to initialize FFT window");
431 p_buffs
= p_s16_buff
;
432 for (i
= 0 ; i
< FFT_BUFFER_SIZE
; i
++)
435 p_buffer1
[i
] = *p_buffs
;
437 p_buffs
+= p_sys
->i_channels
;
438 if (p_buffs
>= &p_s16_buff
[block
->i_nb_samples
* p_sys
->i_channels
])
439 p_buffs
= p_s16_buff
;
441 window_scale_in_place (p_buffer1
, &wind_ctx
);
442 fft_perform (p_buffer1
, p_output
, p_state
);
444 for (i
= 0; i
< FFT_BUFFER_SIZE
; ++i
)
445 p_dest
[i
] = p_output
[i
] * (2 ^ 16)
446 / ((FFT_BUFFER_SIZE
/ 2 * 32768) ^ 2);
448 for (i
= 0 ; i
< NB_BANDS
; i
++)
450 /* Decrease the previous size of the bar. */
451 height
[i
] -= BAR_DECREMENT
;
456 /* We search the maximum on one scale
457 to determine the current size of the bar. */
458 for (j
= xscale
[i
]; j
< xscale
[i
+ 1]; j
++)
463 /* Calculate the height of the bar */
464 float new_height
= y
!= 0 ? logf(y
) * 0.4f
: 0;
465 height
[i
] = new_height
> height
[i
]
466 ? new_height
: height
[i
];
469 /* Determine the camera rotation angle. */
470 p_sys
->f_rotationAngle
+= p_sys
->f_rotationIncrement
;
471 if (p_sys
->f_rotationAngle
<= -ROTATION_MAX
)
472 p_sys
->f_rotationIncrement
= ROTATION_INCREMENT
;
473 else if (p_sys
->f_rotationAngle
>= ROTATION_MAX
)
474 p_sys
->f_rotationIncrement
= -ROTATION_INCREMENT
;
476 /* Render the frame. */
477 glClear(GL_COLOR_BUFFER_BIT
| GL_DEPTH_BUFFER_BIT
);
480 glRotatef(p_sys
->f_rotationAngle
, 0, 1, 0);
484 /* Wait to swapp the frame on time. */
485 vlc_tick_wait(block
->i_pts
+ (block
->i_length
/ 2));
489 window_close(&wind_ctx
);
491 vlc_gl_ReleaseCurrent(gl
);
492 block_Release(block
);