From d51bdd6b6a732b6eb0e1946079bcd28df9470565 Mon Sep 17 00:00:00 2001 From: xufuji456 <839789740@qq.com> Date: Tue, 17 Aug 2021 00:26:42 +0800 Subject: [PATCH] Add: calculate spectrum by running FFT --- app/CMakeLists.txt | 4 + app/src/main/cpp/visualizer/execute_fft.c | 232 +++++++++++++++++++ app/src/main/cpp/visualizer/execute_fft.h | 77 +++++++ app/src/main/cpp/visualizer/fft.c | 206 +++++++++++++++++ app/src/main/cpp/visualizer/fft.h | 55 +++++ app/src/main/cpp/visualizer/queue.c | 155 +++++++++++++ app/src/main/cpp/visualizer/vlc_queue.h | 260 ++++++++++++++++++++++ app/src/main/cpp/visualizer/window.c | 227 +++++++++++++++++++ app/src/main/cpp/visualizer/window.h | 69 ++++++ 9 files changed, 1285 insertions(+) create mode 100644 app/src/main/cpp/visualizer/execute_fft.c create mode 100644 app/src/main/cpp/visualizer/execute_fft.h create mode 100644 app/src/main/cpp/visualizer/fft.c create mode 100644 app/src/main/cpp/visualizer/fft.h create mode 100644 app/src/main/cpp/visualizer/queue.c create mode 100644 app/src/main/cpp/visualizer/vlc_queue.h create mode 100644 app/src/main/cpp/visualizer/window.c create mode 100644 app/src/main/cpp/visualizer/window.h diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3802ec9..c7f5747 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -34,6 +34,10 @@ add_library( # Sets the name of the library. src/main/cpp/video_filter.c src/main/cpp/fast_start.c src/main/cpp/ffprobe_cmd.cpp + src/main/cpp/visualizer/fft.c + src/main/cpp/visualizer/queue.c + src/main/cpp/visualizer/execute_fft.c + src/main/cpp/visualizer/window.c ) add_library( ffmpeg diff --git a/app/src/main/cpp/visualizer/execute_fft.c b/app/src/main/cpp/visualizer/execute_fft.c new file mode 100644 index 0000000..bf7f1ab --- /dev/null +++ b/app/src/main/cpp/visualizer/execute_fft.c @@ -0,0 +1,232 @@ +// +// Created by frank on 2021/8/16. +// + +#include "execute_fft.h" + +#include +#define LOG_TAG "execute_fft" +#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, \ + __VA_ARGS__)) + +#define SPECTRUM_WIDTH 4.f +#define NB_BANDS 20 +#define ROTATION_INCREMENT .1f +#define BAR_DECREMENT .075f +#define ROTATION_MAX 20 + +static inline void block_CopyProperties(block_t *dst, const block_t *src) +{ + dst->i_dts = src->i_dts; + dst->i_pts = src->i_pts; + dst->i_flags = src->i_flags; + dst->i_length = src->i_length; + dst->i_nb_samples = src->i_nb_samples; +} + +static inline block_t *block_Duplicate(const block_t *p_block) +{ +// block_t *p_dup = block_Alloc(p_block->i_buffer);//TODO + block_t *p_dup = (block_t*) malloc(sizeof(block_t)); + if(p_dup == NULL) return NULL; + p_dup->i_buffer = p_block->i_buffer; + p_dup->p_buffer = (uint8_t*) malloc(p_block->i_buffer);//TODO + + block_CopyProperties(p_dup, p_block); + memcpy(p_dup->p_buffer, p_block->p_buffer, p_block->i_buffer); + + return p_dup; +} + +/*static*/ int Open(filter_sys_t *p_sys) +{ + if (p_sys == NULL) + return VLC_ENOMEM; + + /* Create the object for the thread */ + p_sys->i_channels = 2;//TODO + p_sys->i_prev_nb_samples = 0; + p_sys->p_prev_s16_buff = NULL; + + p_sys->f_rotationAngle = 0; + p_sys->f_rotationIncrement = ROTATION_INCREMENT; + + window_param *w_param = (window_param*) malloc(sizeof(window_param)); + p_sys->wind_param = *w_param;//TODO + + /* Fetch the FFT window parameters */ + window_get_param(/*VLC_OBJECT( p_filter )*/NULL, &p_sys->wind_param);//TODO + + /* Create the FIFO for the audio data. */ + vlc_queue_Init(&p_sys->queue, offsetof (block_t, p_next)); + p_sys->dead = false; + + /* Create the thread */ +// if (vlc_clone(&p_sys->thread, Thread, p_filter, +// VLC_THREAD_PRIORITY_VIDEO)) { +// return VLC_ENOMEM; +// } + + pthread_create (&p_sys->thread, NULL, Thread, p_sys); + + return VLC_SUCCESS; +} + +/*static*/ block_t *filter_audio(filter_sys_t *p_sys, block_t *p_in_buf) +{ + vlc_queue_Enqueue(&p_sys->queue, block_Duplicate(p_in_buf)); + return p_in_buf; +} + +/*static*/ void Close(filter_sys_t *p_filter) +{ + filter_sys_t *p_sys = p_filter; + + /* Terminate the thread. */ + vlc_queue_Kill(&p_sys->queue, &p_sys->dead); +// vlc_join(p_sys->thread, NULL); + pthread_join(p_sys->thread, NULL); + + free(p_sys->p_prev_s16_buff); +} + +static void *Thread(void *p_data) +{ + filter_sys_t *p_sys = (filter_sys_t*)p_data; + block_t *block; + + float height[NB_BANDS] = {0}; + LOGE("start FFT thread..."); + while ((block = vlc_queue_DequeueKillable(&p_sys->queue, &p_sys->dead))) + { + LOGE("running FFT transform..."); + unsigned win_width, win_height; + + /* Horizontal scale for 20-band equalizer */ + const unsigned xscale[] = {0,1,2,3,4,5,6,7,8,11,15,20,27, + 36,47,62,82,107,141,184,255}; + + fft_state *p_state = NULL; /* internal FFT data */ + DEFINE_WIND_CONTEXT(wind_ctx); /* internal window data */ + + unsigned i, j; + float p_output[FFT_BUFFER_SIZE]; /* Raw FFT Result */ + int16_t p_buffer1[FFT_BUFFER_SIZE]; /* Buffer on which we perform + the FFT (first channel) */ + int16_t p_dest[FFT_BUFFER_SIZE]; /* Adapted FFT result */ + float *p_buffl = (float*)block->p_buffer; /* Original buffer */ + + int16_t *p_buffs; /* int16_t converted buffer */ + int16_t *p_s16_buff; /* int16_t converted buffer */ + + if (!block->i_nb_samples) { + LOGE("no samples yet..."); + goto release; + } + + /* Allocate the buffer only if the number of samples change */ + if (block->i_nb_samples != p_sys->i_prev_nb_samples) + { + free(p_sys->p_prev_s16_buff); + p_sys->p_prev_s16_buff = malloc(block->i_nb_samples * + p_sys->i_channels * + sizeof(int16_t)); + if (!p_sys->p_prev_s16_buff) + goto release; + p_sys->i_prev_nb_samples = block->i_nb_samples; + } + p_buffs = p_s16_buff = p_sys->p_prev_s16_buff; + + /* Convert the buffer to int16_t */ + for (i = block->i_nb_samples * p_sys->i_channels; i--;) + { + union {float f; int32_t i;} u; + + u.f = *p_buffl + 384.f; + if (u.i > 0x43c07fff) + *p_buffs = 32767; + else if (u.i < 0x43bf8000) + *p_buffs = -32768; + else + *p_buffs = u.i - 0x43c00000; + + p_buffl++; p_buffs++; + } + p_state = visual_fft_init(); + if (!p_state) + { + LOGE("unable to initialize FFT transform..."); + goto release; + } + if (!window_init(FFT_BUFFER_SIZE, &p_sys->wind_param, &wind_ctx)) + { + LOGE("unable to initialize FFT window..."); + goto release; + } + p_buffs = p_s16_buff; + for (i = 0 ; i < FFT_BUFFER_SIZE; i++) + { + p_output[i] = 0; + p_buffer1[i] = *p_buffs; + + p_buffs += p_sys->i_channels; + if (p_buffs >= &p_s16_buff[block->i_nb_samples * p_sys->i_channels]) + p_buffs = p_s16_buff; + } + window_scale_in_place (p_buffer1, &wind_ctx); + fft_perform (p_buffer1, p_output, p_state); + + for (i = 0; i< FFT_BUFFER_SIZE; ++i) + p_dest[i] = p_output[i] * (2 ^ 16) + / ((FFT_BUFFER_SIZE / 2 * 32768) ^ 2); + + for (i = 0 ; i < NB_BANDS; i++) + { + /* Decrease the previous size of the bar. */ + height[i] -= BAR_DECREMENT; + if (height[i] < 0) + height[i] = 0; + + int y = 0; + /* We search the maximum on one scale + to determine the current size of the bar. */ + for (j = xscale[i]; j < xscale[i + 1]; j++) + { + if (p_dest[j] > y) + y = p_dest[j]; + } + /* Calculate the height of the bar */ + float new_height = y != 0 ? logf(y) * 0.4f : 0; + height[i] = new_height > height[i] + ? new_height : height[i]; + } + + /* Determine the camera rotation angle. */ + p_sys->f_rotationAngle += p_sys->f_rotationIncrement; + if (p_sys->f_rotationAngle <= -ROTATION_MAX) + p_sys->f_rotationIncrement = ROTATION_INCREMENT; + else if (p_sys->f_rotationAngle >= ROTATION_MAX) + p_sys->f_rotationIncrement = -ROTATION_INCREMENT; + + /* Render the frame. */ +// glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +// glPushMatrix(); +// glRotatef(p_sys->f_rotationAngle, 0, 1, 0); +// drawBars(height); +// glPopMatrix(); + + /* Wait to swapp the frame on time. */ +// vlc_tick_wait(block->i_pts + (block->i_length / 2)); +// vlc_gl_Swap(gl); + usleep(block->i_pts + (block->i_length / 2)); + +release: + window_close(&wind_ctx); + fft_close(p_state); +// block_Release(block);//TODO + if (block->p_buffer) free(block->p_buffer); + free(block); + } + + return NULL; +} diff --git a/app/src/main/cpp/visualizer/execute_fft.h b/app/src/main/cpp/visualizer/execute_fft.h new file mode 100644 index 0000000..bb1aae2 --- /dev/null +++ b/app/src/main/cpp/visualizer/execute_fft.h @@ -0,0 +1,77 @@ +// +// Created by frank on 2021/8/16. +// + +#ifndef PLAYER_CORE_RUN_FFT_H +#define PLAYER_CORE_RUN_FFT_H + +#include "vlc_queue.h" +#include "fft.h" +#include "window.h" + +#include +#include + +/** No error */ +#define VLC_SUCCESS (-0) +/** Unspecified error */ +#define VLC_EGENERIC (-1) +/** Not enough memory */ +#define VLC_ENOMEM (-2) + +typedef int64_t vlc_tick_t; + +typedef struct block_t block_t; + +struct vlc_block_callbacks +{ + void (*free)(block_t *); +}; + +struct block_t +{ + block_t *p_next; + + uint8_t *p_buffer; /**< Payload start */ + size_t i_buffer; /**< Payload length */ + uint8_t *p_start; /**< Buffer start */ + size_t i_size; /**< Buffer total size */ + + uint32_t i_flags; + unsigned i_nb_samples; /* Used for audio */ + + vlc_tick_t i_pts; + vlc_tick_t i_dts; + vlc_tick_t i_length; + + const struct vlc_block_callbacks *cbs; +}; + +typedef struct +{ +// vlc_thread_t thread; + pthread_t thread; + + /* Audio data */ + vlc_queue_t queue; + bool dead; + unsigned i_channels; + unsigned i_prev_nb_samples; + int16_t *p_prev_s16_buff; + + float f_rotationAngle; + float f_rotationIncrement; + + /* FFT window parameters */ + window_param wind_param; +} filter_sys_t; + +static void *Thread(void *); + +/*static*/ int Open(filter_sys_t *p_sys); + +/*static*/ block_t *filter_audio(filter_sys_t *p_sys, block_t *p_in_buf); + +/*static*/ void Close(filter_sys_t *p_filter); + +#endif //PLAYER_CORE_RUN_FFT_H diff --git a/app/src/main/cpp/visualizer/fft.c b/app/src/main/cpp/visualizer/fft.c new file mode 100644 index 0000000..e40b9ca --- /dev/null +++ b/app/src/main/cpp/visualizer/fft.c @@ -0,0 +1,206 @@ +/***************************************************************************** + * fft.c: Iterative implementation of a FFT + ***************************************************************************** + * + * Mainly taken from XMMS's code + * + * Authors: Richard Boulton + * Ralph Loader + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#include +#include "fft.h" + +#include +#ifndef PI + #ifdef M_PI + #define PI M_PI + #else + #define PI 3.14159265358979323846 /* pi */ + #endif +#endif + +/****************************************************************************** + * Local prototypes + *****************************************************************************/ +static void fft_prepare(const sound_sample *input, float * re, float * im, + const unsigned int *bitReverse); +static void fft_calculate(float * re, float * im, + const float *costable, const float *sintable ); +static void fft_output(const float *re, const float *im, float *output); +static int reverseBits(unsigned int initial); + +/***************************************************************************** + * These functions are the ones called externally + *****************************************************************************/ + +/* + * Initialisation routine - sets up tables and space to work in. + * Returns a pointer to internal state, to be used when performing calls. + * On error, returns NULL. + * The pointer should be freed when it is finished with, by fft_close(). + */ +fft_state *visual_fft_init(void) +{ + fft_state *p_state; + unsigned int i; + + p_state = malloc( sizeof(*p_state) ); + if(! p_state ) + return NULL; + + for(i = 0; i < FFT_BUFFER_SIZE; i++) + { + p_state->bitReverse[i] = reverseBits(i); + } + for(i = 0; i < FFT_BUFFER_SIZE / 2; i++) + { + float j = 2 * PI * i / FFT_BUFFER_SIZE; + p_state->costable[i] = cos(j); + p_state->sintable[i] = sin(j); + } + + return p_state; +} + +/* + * Do all the steps of the FFT, taking as input sound data (as described in + * sound.h) and returning the intensities of each frequency as floats in the + * range 0 to ((FFT_BUFFER_SIZE / 2) * 32768) ^ 2 + * + * The input array is assumed to have FFT_BUFFER_SIZE elements, + * and the output array is assumed to have (FFT_BUFFER_SIZE / 2 + 1) elements. + * state is a (non-NULL) pointer returned by visual_fft_init. + */ +void fft_perform(const sound_sample *input, float *output, fft_state *state) { + /* Convert data from sound format to be ready for FFT */ + fft_prepare(input, state->real, state->imag, state->bitReverse ); + + /* Do the actual FFT */ + fft_calculate(state->real, state->imag, state->costable, state->sintable); + + /* Convert the FFT output into intensities */ + fft_output(state->real, state->imag, output); +} + +/* + * Free the state. + */ +void fft_close(fft_state *state) { + free( state ); +} + +/***************************************************************************** + * These functions are called from the other ones + *****************************************************************************/ + +/* + * Prepare data to perform an FFT on + */ +static void fft_prepare( const sound_sample *input, float * re, float * im, + const unsigned int *bitReverse ) { + unsigned int i; + float *p_real = re; + float *p_imag = im; + + /* Get input, in reverse bit order */ + for(i = 0; i < FFT_BUFFER_SIZE; i++) + { + *p_real++ = input[bitReverse[i]]; + *p_imag++ = 0; + } +} + +/* + * Take result of an FFT and calculate the intensities of each frequency + * Note: only produces half as many data points as the input had. + */ +static void fft_output(const float * re, const float * im, float *output) +{ + float *p_output = output; + const float *p_real = re; + const float *p_imag = im; + float *p_end = output + FFT_BUFFER_SIZE / 2; + + while(p_output <= p_end) + { + *p_output = (*p_real * *p_real) + (*p_imag * *p_imag); + p_output++; p_real++; p_imag++; + } + /* Do divisions to keep the constant and highest frequency terms in scale + * with the other terms. */ + *output /= 4; + *p_end /= 4; +} + + +/* + * Actually perform the FFT + */ +static void fft_calculate(float * re, float * im, const float *costable, const float *sintable ) +{ + unsigned int i, j, k; + unsigned int exchanges; + float fact_real, fact_imag; + float tmp_real, tmp_imag; + unsigned int factfact; + + /* Set up some variables to reduce calculation in the loops */ + exchanges = 1; + factfact = FFT_BUFFER_SIZE / 2; + + /* Loop through the divide and conquer steps */ + for(i = FFT_BUFFER_SIZE_LOG; i != 0; i--) { + /* In this step, we have 2 ^ (i - 1) exchange groups, each with + * 2 ^ (FFT_BUFFER_SIZE_LOG - i) exchanges + */ + /* Loop through the exchanges in a group */ + for(j = 0; j != exchanges; j++) { + /* Work out factor for this exchange + * factor ^ (exchanges) = -1 + * So, real = cos(j * PI / exchanges), + * imag = sin(j * PI / exchanges) + */ + fact_real = costable[j * factfact]; + fact_imag = sintable[j * factfact]; + + /* Loop through all the exchange groups */ + for(k = j; k < FFT_BUFFER_SIZE; k += exchanges << 1) { + int k1 = k + exchanges; + tmp_real = fact_real * re[k1] - fact_imag * im[k1]; + tmp_imag = fact_real * im[k1] + fact_imag * re[k1]; + re[k1] = re[k] - tmp_real; + im[k1] = im[k] - tmp_imag; + re[k] += tmp_real; + im[k] += tmp_imag; + } + } + exchanges <<= 1; + factfact >>= 1; + } +} + +static int reverseBits(unsigned int initial) +{ + unsigned int reversed = 0, loop; + for(loop = 0; loop < FFT_BUFFER_SIZE_LOG; loop++) { + reversed <<= 1; + reversed += (initial & 1); + initial >>= 1; + } + return reversed; +} diff --git a/app/src/main/cpp/visualizer/fft.h b/app/src/main/cpp/visualizer/fft.h new file mode 100644 index 0000000..ac9605f --- /dev/null +++ b/app/src/main/cpp/visualizer/fft.h @@ -0,0 +1,55 @@ +/***************************************************************************** + * fft.h: Headers for iterative implementation of a FFT + ***************************************************************************** + * + * Mainly taken from XMMS's code + * + * Authors: Richard Boulton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef VLC_VISUAL_FFT_H_ +#define VLC_VISUAL_FFT_H_ + +#define FFT_BUFFER_SIZE_LOG 9 + +#define FFT_BUFFER_SIZE (1 << FFT_BUFFER_SIZE_LOG) + +/* sound sample - should be an signed 16 bit value */ +typedef short int sound_sample; + +struct _struct_fft_state { + /* Temporary data stores to perform FFT in. */ + float real[FFT_BUFFER_SIZE]; + float imag[FFT_BUFFER_SIZE]; + + /* */ + unsigned int bitReverse[FFT_BUFFER_SIZE]; + + /* The next two tables could be made to use less space in memory, since they + * overlap hugely, but hey. */ + float sintable[FFT_BUFFER_SIZE / 2]; + float costable[FFT_BUFFER_SIZE / 2]; +}; + +/* FFT prototypes */ +typedef struct _struct_fft_state fft_state; +fft_state *visual_fft_init (void); +void fft_perform (const sound_sample *input, float *output, fft_state *state); +void fft_close (fft_state *state); + + +#endif /* include-guard */ diff --git a/app/src/main/cpp/visualizer/queue.c b/app/src/main/cpp/visualizer/queue.c new file mode 100644 index 0000000..1a85935 --- /dev/null +++ b/app/src/main/cpp/visualizer/queue.c @@ -0,0 +1,155 @@ +/***************************************************************************** + * queue.c: generic queue (FIFO) + ***************************************************************************** + * Copyright (C) 2020 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#include +#include +#include + +#include "vlc_queue.h" + +/* Opaque struct type. + * + * ISO C uses the same representation for all pointer-to-struct types. + * Still different pointer types are not compatible, i.e. cannot alias. + * So use memcpy() to read/write pointer values. + */ +struct vlc_queue_entry; + +static void entry_set(struct vlc_queue_entry **pp, struct vlc_queue_entry *e) +{ + memcpy(pp, &e, sizeof (e)); +} + +static struct vlc_queue_entry *entry_get(struct vlc_queue_entry *const *pp) +{ + struct vlc_queue_entry *e; + + memcpy(&e, pp, sizeof (e)); + return e; +} + +static struct vlc_queue_entry **next_p(const struct vlc_queue_entry *e, + ptrdiff_t offset) +{ + return (struct vlc_queue_entry **)(((unsigned char *)e) + offset); +} + +static void next_set(struct vlc_queue_entry *e, struct vlc_queue_entry *next, + ptrdiff_t offset) +{ + entry_set(next_p(e, offset), next); +} + +static struct vlc_queue_entry *next_get(const struct vlc_queue_entry *e, + ptrdiff_t offset) +{ + return entry_get(next_p(e, offset)); +} + +void vlc_queue_Init(vlc_queue_t *q, ptrdiff_t next_offset) +{ + q->first = NULL; + q->lastp = &q->first; + q->next_offset = next_offset; + pthread_mutex_init(&q->lock, NULL); + pthread_cond_init(&q->wait, NULL); +} + +void vlc_queue_EnqueueUnlocked(vlc_queue_t *q, void *entry) +{ + struct vlc_queue_entry **lastp; + const ptrdiff_t offset = q->next_offset; + +// vlc_mutex_assert(&q->lock); + assert(entry_get(q->lastp) == NULL); + entry_set(q->lastp, entry); + + for (lastp = q->lastp; entry != NULL; entry = next_get(entry, offset)) + lastp = next_p(entry, offset); + + q->lastp = lastp; + vlc_queue_Signal(q); +} + +void *vlc_queue_DequeueUnlocked(vlc_queue_t *q) +{ +// vlc_mutex_assert(&q->lock); + + void *entry = q->first; + const ptrdiff_t offset = q->next_offset; + + if (entry != NULL) { + struct vlc_queue_entry *next = next_get(entry, offset); + + next_set(entry, NULL, offset); + q->first = next; + + if (next == NULL) + q->lastp = &q->first; + } + + return entry; +} + +void *vlc_queue_DequeueAllUnlocked(vlc_queue_t *q) +{ +// vlc_mutex_assert(&q->lock); + + void *entry = q->first; + + q->first = NULL; + q->lastp = &q->first; + + return entry; +} + +void vlc_queue_Enqueue(vlc_queue_t *q, void *entry) +{ + vlc_queue_Lock(q); + vlc_queue_EnqueueUnlocked(q, entry); + vlc_queue_Unlock(q); +} + +void *vlc_queue_Dequeue(vlc_queue_t *q) +{ + void *entry; + + vlc_queue_Lock(q); +// vlc_testcancel(); + + while (vlc_queue_IsEmpty(q)) + vlc_queue_Wait(q); + + entry = vlc_queue_DequeueUnlocked(q); + vlc_queue_Unlock(q); + + return entry; +} + +void *vlc_queue_DequeueAll(vlc_queue_t *q) +{ + void *entry; + + vlc_queue_Lock(q); + entry = vlc_queue_DequeueAllUnlocked(q); + vlc_queue_Unlock(q); + + return entry; +} diff --git a/app/src/main/cpp/visualizer/vlc_queue.h b/app/src/main/cpp/visualizer/vlc_queue.h new file mode 100644 index 0000000..7453ae1 --- /dev/null +++ b/app/src/main/cpp/visualizer/vlc_queue.h @@ -0,0 +1,260 @@ +/***************************************************************************** + * vlc_queue.h: generic queue functions + ***************************************************************************** + * Copyright (C) 2020 Rémi Denis-Courmont + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef VLC_QUEUE_H +#define VLC_QUEUE_H + +/** + * @defgroup queue Thread-safe queues (FIFO) + * @ingroup cext + * @{ + * @file vlc_queue.h + */ + +#include +#include +#include +#include + +/** + * Opaque type for queue entry. + */ +struct vlc_queue_entry; + +/** + * Thread-safe queue (a.k.a. FIFO). + */ +typedef struct vlc_queue +{ + struct vlc_queue_entry *first; + struct vlc_queue_entry **lastp; + ptrdiff_t next_offset; +// vlc_mutex_t lock; + pthread_mutex_t lock; +// vlc_cond_t wait; + pthread_cond_t wait; +} vlc_queue_t; + +/** + * Initializes a queue. + * + * @param queue storage space for the queue + * @param next_offset offset of the pointer to the next element + * within a queue entry (as per @c offsetof()) + */ +void vlc_queue_Init(vlc_queue_t *queue, ptrdiff_t next_offset); + +/** + * @defgroup queue_ll Queue internals + * + * Low-level queue functions. + * + * In some cases, the high-level queue functions do not exactly fit the + * use case requirements, and it is necessary to access the queue internals. + * This typically occurs when threads wait for elements to be added to the + * queue at the same time as some other type of events. + * @{ + */ +/** + * Locks a queue. + * + * No more than one thread can lock a queue at any given time, and no other + * thread can modify the queue while it is locked. + * Accordingly, if the queue is already locked by another thread, this function + * waits. + * + * Use vlc_queue_Unlock() to release the lock. + * + * @warning Recursively locking a single queue is undefined. + * Also locking more than one queue at a time may lead to lock inversion: + * mind the locking order! + */ +static inline void vlc_queue_Lock(vlc_queue_t *q) +{ + pthread_mutex_lock(&q->lock); +} + +/** + * Unlocks a queue. + * + * This releases the lock on a queue, allowing other threads to manipulate the + * queue. The behaviour is undefined if the calling thread is not holding the + * queue lock. + */ +static inline void vlc_queue_Unlock(vlc_queue_t *q) +{ + pthread_mutex_unlock(&q->lock); +} + +/** + * Wakes one thread waiting for a queue entry up. + */ +static inline void vlc_queue_Signal(vlc_queue_t *q) +{ + pthread_cond_signal(&q->wait); +} + +/** + * Waits for a queue entry. + * + * @note This function is a cancellation point. + * In case of cancellation, the queue will be locked, + * as is consistent for condition variable semantics. + * + * @bug This function should probably not be aware of cancellation. + */ +static inline void vlc_queue_Wait(vlc_queue_t *q) +{ + pthread_cond_wait(&q->wait, &q->lock); +} + +/** + * Queues an entry (without locking). + * + * This function enqueues an entry, or rather a linked-list of entries, in a + * thread-safe queue, without taking the queue lock. + * + * @warning It is assumed that the caller already holds the queue lock; + * otherwise the behaviour is undefined. + * + * @param entry NULL-terminated list of entries to queue + * (if NULL, this function has no effects) + */ +void vlc_queue_EnqueueUnlocked(vlc_queue_t *, void *entry); + +/** + * Dequeues the oldest entry (without locking). + * + * This function dequeues an entry from a thread-safe queue. It is assumed + * that the caller already holds the queue lock; otherwise the behaviour is + * undefined. + * + * @warning It is assumed that the caller already holds the queue lock; + * otherwise the behaviour is undefined. + * + * @return the first entry in the queue, or NULL if the queue is empty + */ +void *vlc_queue_DequeueUnlocked(vlc_queue_t *); + +/** + * Dequeues all entries (without locking). + * + * This is equivalent to calling vlc_queue_DequeueUnlocked() repeatedly until + * the queue is emptied. However this function is much faster than that, as it + * does not need to update the linked-list pointers. + * + * @warning It is assumed that the caller already holds the queue lock; + * otherwise the behaviour is undefined. + * + * @return a linked-list of all entries (possibly NULL if none) + */ +void *vlc_queue_DequeueAllUnlocked(vlc_queue_t *); + +/** + * Checks if a queue is empty (without locking). + * + * @warning It is assumed that the caller already holds the queue lock; + * otherwise the behaviour is undefined. + * + * @retval false the queue contains one or more entries + * @retval true the queue is empty + */ +static inline bool vlc_queue_IsEmpty(const vlc_queue_t *q) +{ + return q->first == NULL; +} + +/** @} */ + +/** + * Queues an entry. + * + * This function enqueues an entry, or rather a linked-list of entries, in a + * thread-safe queue. + * + * @param entry list of entries (if NULL, this function has no effects) + */ +void vlc_queue_Enqueue(vlc_queue_t *, void *entry); + +/** + * Dequeues the oldest entry. + * + * This function dequeues an entry from a thread-safe queue. If the queue is + * empty, it will wait until at least one entry is available. + * + * @param offset offset of the next pointer within a queue entry + * + * @return the first entry in the queue, or NULL if the queue is empty + */ +void *vlc_queue_Dequeue(vlc_queue_t *); + +/** + * Dequeues all entries. + * + * This is equivalent to calling vlc_queue_Dequeue() repeatedly until the queue + * is emptied. However this function is much faster than that, as it + * does not need to update the linked-list pointers. + * + * @return a linked-list of all entries (possibly NULL if none) + */ +void *vlc_queue_DequeueAll(vlc_queue_t *); + +/** + * @defgroup queue_killable Killable queues + * + * Thread-safe queues with an end flag. + * + * @{ + */ + +/** + * Marks a queue ended. + */ +static inline void vlc_queue_Kill(vlc_queue_t *q, + bool */*restrict*/ tombstone) +{ + vlc_queue_Lock(q); + *tombstone = true; + vlc_queue_Signal(q); + vlc_queue_Unlock(q); + pthread_cond_destroy(&q->wait); + pthread_mutex_destroy(&q->lock); +} + +/** + * Dequeues one entry from a killable queue. + * + * @return an entry, or NULL if the queue is empty and has been ended. + */ +static inline void *vlc_queue_DequeueKillable(vlc_queue_t *q, + bool */*restrict*/ tombstone) +{ + void *entry; + + vlc_queue_Lock(q); + while (vlc_queue_IsEmpty(q) && !*tombstone) + vlc_queue_Wait(q); + + entry = vlc_queue_DequeueUnlocked(q); + vlc_queue_Unlock(q); + return entry; +} + +#endif diff --git a/app/src/main/cpp/visualizer/window.c b/app/src/main/cpp/visualizer/window.c new file mode 100644 index 0000000..bc0bee7 --- /dev/null +++ b/app/src/main/cpp/visualizer/window.c @@ -0,0 +1,227 @@ +/***************************************************************************** + * window.c : Implementation of FFT window routines + ***************************************************************************** + * Copyright (C) 2014 Ronald Wright + * + * Author: Ronald Wright + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#include +#include "window.h" + +#include + +/* Flat top window coefficients */ +#define FT_A0 1.000f +#define FT_A1 1.930f +#define FT_A2 1.290f +#define FT_A3 0.388f +#define FT_A4 0.028f + +/* Blackman-Harris window coefficients */ +#define BH_A0 0.35875f +#define BH_A1 0.48829f +#define BH_A2 0.14128f +#define BH_A3 0.01168f + +/* Window functions supported by VLC. These are the typical window types used + * in spectrum analyzers. */ +#define NB_WINDOWS 5 +static const char * const window_list[NB_WINDOWS] = { + "none", "hann", "flattop", "blackmanharris", "kaiser", +}; +static const char * const window_list_text[NB_WINDOWS] = { +// N_("None"), N_("Hann"), N_("Flat Top"), N_("Blackman-Harris"), N_("Kaiser"), + "None", "Hann", "Flat Top", "Blackman-Harris", "Kaiser", +}; + +/* + * The modified Bessel function I0(x). See Chapter 6 of the "Numerical Recipes + * in C: The Art of Scientific Computing" book at + * http://www.aip.de/groups/soe/local/numres/bookcpdf/c6-6.pdf + */ +static float bessi0(float x) +{ + float ax, ans; + double y; /* Accumulate polynomials in double precision. */ + if( ( ax = fabsf( x ) ) < 3.75f ) /* Polynomial fit. */ + { + y = x / 3.75; + y *= y; + ans = 1.0 + y * ( 3.5156229 + y * ( 3.0899424 + y * ( 1.2067492 + + y * ( 0.2659732 + y * ( 0.360768e-1 + + y * 0.45813e-2 ) ) ) ) ); + } + else + { + y = 3.75 / ax; + ans = ( exp( ax ) / sqrt( ax ) ) * ( 0.39894228 + y * ( 0.1328592e-1 + + y * ( 0.225319e-2 + y * ( -0.157565e-2 + y * ( 0.916281e-2 + + y * ( -0.2057706e-1 + y * ( 0.2635537e-1 + y * ( -0.1647633e-1 + + y * 0.392377e-2 ) ) ) ) ) ) ) ); + } + return ans; +} + +/* + * Obtain the window type from the window type variable. + */ +void window_get_param(void * p_aout, window_param * p_param) +{ + /* Fetch Kaiser parameter */ + p_param->f_kaiser_alpha = 1; //TODO var_InheritFloat( p_aout, "effect-kaiser-param" ); + + /* Fetch window type */ + char * psz_preset = "hann"; //TODO var_InheritString( p_aout, "effect-fft-window" ); + if( !psz_preset ) + { + goto no_preset; + } + + for( int i = 0; i < NB_WINDOWS; i++ ) + { + if( !strcasecmp( psz_preset, window_list[i] ) ) + { +// free( psz_preset );//TODO + p_param->wind_type = i; + return; + } + } +// free( psz_preset );//TODO + +no_preset: +// msg_Warn( p_aout, "No matching window preset found; using rectangular " +// "window (i.e. no window)" ); + p_param->wind_type = NONE; +} + +/* + * Initialization routine - sets up a lookup table for scaling a sample of data + * by window data. If the lookup table is successfully allocated, its memory + * location and its specified size are stored at the specified memory location + * of the internal context. + * Returns true if initialization succeeded and returns false otherwise. + * The internal context should be freed when it is finished with, by + * window_close(). + */ +bool window_init(int i_buffer_size, window_param * p_param, window_context * p_ctx) +{ + float * pf_table = NULL; + window_type wind_type = p_param->wind_type; + + if( wind_type != HANN && wind_type != FLATTOP + && wind_type != BLACKMANHARRIS + && wind_type != KAISER ) + { + /* Assume a rectangular window (i.e. no window) */ + i_buffer_size = 0; + goto exit; + } + +// pf_table = vlc_alloc( i_buffer_size, sizeof( *pf_table ) ); + pf_table = (float * )malloc( i_buffer_size * sizeof(*pf_table)); + if( !pf_table ) + { + /* Memory allocation failed */ + return false; + } + + int i_buffer_size_minus_1 = i_buffer_size - 1; + switch( wind_type ) + { + case HANN: + /* Hann window */ + for( int i = 0; i < i_buffer_size; i++ ) + { + float f_val = (float) i / (float) i_buffer_size_minus_1; + pf_table[i] = 0.5f - 0.5f * cosf( 2.0f * (float) M_PI * f_val ); + } + break; + case FLATTOP: + /* Flat top window */ + for( int i = 0; i < i_buffer_size; i++ ) + { + float f_val = (float) i / (float) i_buffer_size_minus_1; + pf_table[i] = FT_A0 + - FT_A1 * cosf( 2.0f * (float) M_PI * f_val ) + + FT_A2 * cosf( 4.0f * (float) M_PI * f_val ) + - FT_A3 * cosf( 6.0f * (float) M_PI * f_val ) + + FT_A4 * cosf( 8.0f * (float) M_PI * f_val ); + } + break; + case BLACKMANHARRIS: + /* Blackman-Harris window */ + for( int i = 0; i < i_buffer_size; i++ ) + { + float f_val = (float) i / (float) i_buffer_size_minus_1; + pf_table[i] = BH_A0 + - BH_A1 * cosf( 2.0f * (float) M_PI * f_val ) + + BH_A2 * cosf( 4.0f * (float) M_PI * f_val ) + - BH_A3 * cosf( 6.0f * (float) M_PI * f_val ); + } + break; + case KAISER: + { + /* Kaiser window */ + float f_pialph = (float) M_PI * p_param->f_kaiser_alpha; + float f_bessi0_pialph = bessi0( f_pialph ); + for( int i = 0; i < i_buffer_size; i++ ) + { + float f_val = (float) i / (float) i_buffer_size_minus_1; + float f_term_to_square = 2.0f * f_val - 1.0f; + float f_sqd_term = f_term_to_square * f_term_to_square; + float f_sqr_term = sqrtf( 1.0f - f_sqd_term ); + pf_table[i] = bessi0( f_pialph * f_sqr_term ) / f_bessi0_pialph; + } + break; + } + default: + /* We should not reach here */ +// vlc_assert_unreachable(); + break; + } + +exit: + p_ctx->pf_window_table = pf_table; + p_ctx->i_buffer_size = i_buffer_size; + return true; +} + +/* + * Perform an in-place scaling of the input buffer by the window data + * referenced from the specified context. + */ +void window_scale_in_place(int16_t * p_buffer, window_context * p_ctx) +{ + for(int i = 0; i < p_ctx->i_buffer_size; i++) + { + p_buffer[i] *= p_ctx->pf_window_table[i]; + } +} + +/* + * Free the context. + */ +void window_close(window_context * p_ctx) +{ + if(p_ctx->pf_window_table) + { + free(p_ctx->pf_window_table); + p_ctx->pf_window_table = NULL; + p_ctx->i_buffer_size = 0; + } +} diff --git a/app/src/main/cpp/visualizer/window.h b/app/src/main/cpp/visualizer/window.h new file mode 100644 index 0000000..085d0a5 --- /dev/null +++ b/app/src/main/cpp/visualizer/window.h @@ -0,0 +1,69 @@ +/***************************************************************************** + * window.h : Header for FFT window routines + ***************************************************************************** + * Copyright (C) 2014 Ronald Wright + * + * Author: Ronald Wright + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + *****************************************************************************/ + +#ifndef VLC_VISUAL_WINDOW_H_ +#define VLC_VISUAL_WINDOW_H_ + +#include +#include + +#ifndef __cplusplus +# include +#endif + +/* Window type enum */ +enum _enum_window_type { NONE, HANN, FLATTOP, BLACKMANHARRIS, KAISER }; + +/* Window context structure */ +struct _struct_window_context { + + /* Storage for window function values */ + float * pf_window_table; + + /* Buffer size for the window */ + int i_buffer_size; +}; + +typedef enum _enum_window_type window_type; + +/* Window parameter structure */ +struct _struct_window_param { + + /* Window type */ + window_type wind_type; + + /* Kaiser window parameter */ + float f_kaiser_alpha; +}; + +/* Prototypes for the window function */ +typedef struct _struct_window_context window_context; +typedef struct _struct_window_param window_param; +void window_get_param(void * p_aout, window_param * p_param); +bool window_init(int i_buffer_size, window_param * p_param, window_context * p_ctx); +void window_scale_in_place(int16_t * p_buffer, window_context * p_ctx); +void window_close(window_context * p_ctx); + +/* Macro for defining a new window context */ +#define DEFINE_WIND_CONTEXT(name) window_context name = {NULL, 0} + +#endif /* include-guard */