#include <vad.h>
Métodos públicos | |
vad () | |
Constructor. | |
vad (const vad_config &cfg, DOUBLE snr, FLOAT FrameLen, XFFT_WIN WinType) | |
Constructor. | |
vad (const vad_config &cfg, DOUBLE snr, FLOAT FrameLen) | |
Constructor. | |
~vad () | |
Destructor. | |
bool | Reset (const vad_config &cfg, DOUBLE snr, FLOAT FrameLen, XFFT_WIN WinType=XFFT_WIN_HAMM) |
Inicializa los valores de configuración y la memoria reservada. | |
void | NoiseInit (pDOUBLE frame) |
Utiliza una trama para la inicialización de ruido. | |
bool | doVAD (pDOUBLE frame) |
Realiza la decisión VAD sobre la trama. | |
get-ters y set-ters | |
Es seguro llamar a estas funciones incluso después de haber inicializado el objeto. Pocas veces tiene sentido hacerlo, pero es seguro. | |
LONG | getFrameLen () const |
Número de muestras que deben tener las tramas. | |
INT | getNfft () const |
Número de puntos FFT que se están usando. | |
INT | getLTWindowLen () const |
Número de tramas usadas para análisis LongTerm. | |
bool | IsBufferFull () const |
El estado del buffer. | |
DOUBLE | getNoisePow () const |
Potencia estimada del ruido en este momento. | |
void | setNoisePow (DOUBLE np) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getSignalPow () const |
Potencia estimada de la señal en este momento. | |
void | setSignalPow (DOUBLE sp) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getSNR () const |
SNR estimada en este momento. | |
void | setSNR (DOUBLE snr) |
DOUBLE | getSNR0 () const |
Límite inferior de SNR. | |
void | setSNR0 (DOUBLE snr0) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getSNR1 () const |
Límite superior de SNR. | |
void | setSNR1 (DOUBLE snr1) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getGamma0 () const |
Umbral de SNR0. | |
void | setGamma0 (DOUBLE gamma0) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getGamma1 () const |
Umbral de SNR1. | |
void | setGamma1 (DOUBLE gamma1) |
Número de muestras que deben tener las tramas. | |
INT | getActWindowLen () const |
Número de tramas usadas para actualización de ruido y señal. | |
void | setActWindowLen (INT ActWindowLen) |
Número de muestras que deben tener las tramas. | |
FLOAT | getNalfa () const |
Coeficiente de memoria para la actualización del ruido. | |
void | setNalfa (FLOAT Nalfa) |
Número de muestras que deben tener las tramas. | |
FLOAT | getSNRalfa () const |
Coeficiente de memoria para la actualización del la señal. | |
void | setSNRalfa (FLOAT SNRalfa) |
Número de muestras que deben tener las tramas. | |
DOUBLE | getOffset () const |
offsest | |
void | setOffset (DOUBLE offset) |
Número de muestras que deben tener las tramas. | |
INT | getHandover () const |
offsest | |
void | setHandover (INT Handover) |
Número de muestras que deben tener las tramas. | |
Métodos protegidos | |
void | Destruct () |
Destruye el objeto. | |
DOUBLE | Gamma () const |
Calcula el umbral de LTSD. | |
void | ActualizeSignal () |
Actualiza el modelo de señal. | |
void | ActualizeNoise () |
Actualiza el modelo de ruido. | |
void | AddToBuffer (pDOUBLE frame) |
Añade una trama nueva al buffer. | |
void | LTSE () |
Calcula la LTSE del contenido del buffer. | |
DOUBLE | LTSD () const |
Calcula la LTSD a partir de la LTSE y el espectro de ruido. | |
Atributos protegidos | |
Variables asociadas con el enventantado y el análisis FFT | |
LONG | m_FrameLen |
Longitud de la trama en muestras. | |
XFFT_WIN | m_WinType |
Tipo de ventana. | |
pRFFT | m_fftObject |
Objeto de análisis FFT. | |
INT | m_Nfft |
Número de puntos de las FFT. | |
buffer2D | m_memFFT |
Array circular de FFT necesario para el cálculo de LTSE. | |
INT | m_bufferedFrames |
Auxiliar para saber si ya se ha llenado la mitad del buffer;. | |
Variables asociadas con la señal y el ruido estimados | |
pDOUBLE | m_NoiseFFT |
Espectro medio del ruido. | |
INT | m_NoiseInitFrames |
Número de tramas usadas en la inicialización del ruido. | |
DOUBLE | m_NoisePow |
Potencia estimada del ruido. | |
DOUBLE | m_SignalPow |
Potencia estimada de la señal. | |
DOUBLE | m_SNR |
SNR estimada. | |
DOUBLE | m_SNR0 |
Mínima SNR considerada. | |
DOUBLE | m_SNR1 |
Máxima SNR considerada. | |
INT | m_ActWindowLen |
Número de tramas usadas para actualización de ruido y señal. | |
FLOAT | m_Nalfa |
Coeficiente de memoria para la actualización del ruido. | |
FLOAT | m_SNRalfa |
Coeficiente de memoria para la actualización de la SNR. | |
pDOUBLE | m_NoiseFFTAct |
Usado durante la actualización del espectro de ruido. | |
Variables asociadas con el algoritmo LTSD | |
INT | m_LTWindowLen |
Número de tramas usadas para análisis LongTerm. | |
DOUBLE | m_gamma0 |
Umbral asociado a SNR0. | |
DOUBLE | m_gamma1 |
Umbral asociado a SNR1. | |
INT | m_Handover |
Número de tramas usadas para Hand Over. | |
LONG | m_NumSil |
Número de tramas silenciosas hasta ahora (para actualizar espectro). | |
LONG | m_NumVoice |
Número de tramas sonoras hasta ahora (para actualizar espectro). | |
LONG | m_HoSil |
Contador para HandOver. | |
DOUBLE | m_offset |
Offset para la LTSD. | |
pDOUBLE | m_LTSE |
Usado durante el cálculo de la LTSE. | |
Estructuras de datos | |
struct | vad_config |
Estructura para inicialización de la clase vad. Más... |
El sistema realiza una estimación del espectro de las tramas silenciosas (es decir, el espectro del ruido), y lo compara con el espectro de la trama actual. Si la diferencia supera un umbral, se considera que la trama contiene voz, y si no, que se trata de un silencio. El umbral es adaptativo, y varía en función de la estimación de SNR. De esta forma, puede adaptarse a niveles de ruido cambiantes a lo largo de la señal. Para ello es necesario indicar el menor y mayor valor de SNR esperado, con SNR0
y SNR1
respectivamente.
Cada vez que una trama es clasificada como silencio, se actualiza la estimación del espectro y la potencia de ruido, con un factor de olvido Nalfa
. Igualmente, para cada trama clasificada como voz, se actualiza la estimación de SNR con un factor de olvido SNRalfa
. De esta forma, se van adaptando los umbrales a las nuevas condiciones de ruido.
Si RealTime
no está activado, la inicialización se realiza mediante un pre-procesado y una sencilla detección de silencios por umbral de potencia. todas las tramas clasificadas como silencio son usadas para realizar la estimación del espectro y la potencia de ruido. Igualmente, las tramas clasificadas como voz se utilizan para estimar la potencia de señal. Así se consigue unos valores generales con los que empezar a realizar una detección más precisa. El umbral de este pre-procesado se indica mediante PowThr
.
PowThr
se especifica en porcentaje. Durante el pre-procesado se estiman los valores máximo y mínimo de la potencia, y el umbral se fija como este porcentaje medido en su rango. Es decir, si indicamos 20% y los valores extremos de potencia son 50 dB y 100 dB, el umbral se fijará en (100-50)*0.2+50=60 dB.Ninit
. Esta variable indica el mínimo número de tramas que han de ser utilizadas para calcular el nivel de ruido de la señal. Si durante el pre-procesaro no se encuentran Ninit
tramas clasificadas como silenciosas, el algoritmo no podrá inicializarse, y dará como salida todo-voz (una única marca de voz durante toda la longitud del archivo), presentando un warning a la salida. Por tanto, hay que asegurarse que en los archivos a procesar haya al menos Ninit*FrameRateMs
milisegundos de silencio. Si es necesario, se puede reducir el valor de Ninit
, pero un valor de inicialización demasiado bajo puede dar lugar también a una mala estimación del espectro de ruido, y por tanto, una mala detección de silencios. Por eso, en este caso, se ha optado por suponer que todo es voz.
Si RealTime
está activado, esta inicialización se realiza en línea (aunque no realmente en tiempor real). Se utilizan las primeras Ninit
tramas para inicializar el espectro y potencia de ruido. Por lo tanto, es necesario que estas primeras tramas no tengan voz, o la detección de silencios fallará estrepitosamente.
En el caso de la inicialización en línea, no hay forma de conocer la potencia de la señal, por lo que la SNR se tiene que inicializar de otra forma. Si se conoce la SNR aproximada de la señal de antemano, se puede indicar mediante la opción -SNRstart
. El algoritmo toma este valor como SNR de partida, y luego ya se irá adaptando según vaya detectando segmentos de voz. Si no se conoce este valor, se toma la media entre SNR0
y SNR1
. No es que sea la mejor aproximación, pero es la única que tenemos.
SNRalfa
se rebaje un poco, de forma que la potencia de señal se adapte más rápidamente.Handover
controla el número de tramas que han de ser clasificadas como silencio antes de que se decida que realmente es un segmento silencioso. Es decir, las primeras Handover
tramas clasificadas como silencio después de un segmento sonoro quedarán incluidas en la marca del segmento sonoro. La cuestión es que es preferible extender las marcas de voz y marcar como voz un trozo de silencio que marcar como silencio un trozo de voz mal clasificado y perder esa información. En cualquier caso, una vez finalizado el algoritmo, se realiza un post-procesado para eliminar todas las marcas de silencio con una duración inferior a MinSilDur
y las marcas de voz de duración inferior a MinVocDur
.
Version dd/mm/aa Autor Proposito de la edicion ------- -------- -------- ----------------------- 1.0.0 04/03/10 ikerl Codificación inicial
Ramirez, Javier and Segura, Jose C. and Benitez, Carmen and de la Torre, Angel and Rubio, Antonio, "Efficient Voice Activity Detection Algorithms Using Long Term Speech Information", Speech Communication, vol. 42, pp. 271-287, 2004.
El algoritmo modificado que está implementado puede encontrarse en:
Luengo, Iker and Navas, Eva and Odriozola, Igor and Saratxaga, Ibon and Hernaez, Inmaculada and Sainz, Iñaki and Erro, Daniel "Modified LTSE VAD algorithm for applications requiring reduced silence frame misclassification", Proc. of LREC 2010
Entre los dos artículos se debería tener una idea bastante maja de cómo va el algoritmo, por lo que aquí sólo voy a tratar de aclarar cuestiones sobre su implementación y configuración. Sin embargo, para entender qué representa cada parámetro de configuración, es necesario dar una visión general de cómo funciona este algoritmo. Ojo, es una descripción muy poco rigurosa, no hay que tomarse todo al pié de la letra. Sólo se explica lo extrictamente necesario.
La LTSE de una trama es una aproximación de la envolvente espectral de la trama más menos una ventana de N tramas por delante y por detrás. esta ventana de 2N+1 tramas, cuya trama central es la actual, es lo que vamos a llamar 'ventana LTSE'. Calculada la LTSE de la trama actual, se compara esta LTSE con el espectro estimado del ruido. Si la diferencia es superior a un umbral , se considera que la trama contiene voz. Si no, se considera que sólo es ruido.
El umbral depende de la SNR estimada de la señal. Además, por cada trama que se clasifica como silencio, se actualiza el espectro estimado de ruido y la potencia estimada de ruido. Igualmente, por cada trama clasificada como voz se actualiza la potencia estimada de señal. Con esto, la SNR también se va actualizando.
Por tanto, los elementos clave de este algoritmo son:
Es necesario proporcionar la SNR inicial, pues el algoritmo no tiene forma alguna de estimarla. Esta SNR inicial puede calcularse mediante un pre-procesado de la señal, o en el peor de los casos, estimarse a huevo. Arréglatelas como quieras, pero el algoritmo la necesita.
Las tramas que se dan al sistema no tienen que estar enventanadas, él mismo ya aplica la ventan correcta. En caso de que las tramas ya estén enventanadas, será necesario indicar XFFT_WIN_NONE como ventana, para evitar que se enventane dos veces. Por defecto, se utiliza ventana de Hamming.
El constructor de vad_config ya define los valores por defecto que se han estimado óptimos hasta el momento, por lo que si se quiere dejar los valores por defecto, basta con no modificar ningún elemento. En caso contrario, reescribir el elemento que se quiere modificar y ya está.
Una forma de inicializar el objeto vad es a través del constructor:
vad_config cfg; //Si no modificamos nada usamos los valores por defecto vad vad_object(cfg, 80); //Tramas de 80 muestras
Otra forma es utilizando el constructor por defecto y mediante la función Reset():
vad vad_object; //Constructor por defecto //Hacemos cositas por el camino vad_config cfg; cfg.Nalfa = 0.95; //Modificamos algunos parámetros cfg.SNRalfa = 0.95; //Inicializamos el objeto vad_object.Reset(cfg, 80);
Este método de construir puede ser útil si queremos aplicar el VAD a varios archivos diferentes o con diferentes configuraciones. El VAD mantiene una serie de memorias internas que es necesario resetear si pasamos a procesar otro archivo o queremos procesar el mismo otra vez, lo cual es precisamente lo que hace Reset():
vad vad_object; //Constructor por defecto //Hacemos cositas por el camino vad_config cfg; cfg.Nalfa = 0.95; //Modificamos algunos parámetros cfg.SNRalfa = 0.95; for (int i = 0; i < NumFicheros; ++i) { //Hay que resetear el VAD para cada fichero vad_object.Reset((cfg, 80, XFFT_WIN_BLACK); //Y esta vez, con ventena personalizada //Abrir ficheros, tomar tramas, aplicar el VAD.... }
Para ello se ha de utilizar la función NoiseInit(). En lugar de darle al objeto todas las tramas de inicialización de golpe, se las damos una a una, llamando varias veces a esta función:
vad vad_object; //Inicialización y otras cosas ... //noise_frame_number es el número de tramas que usaremos para inicializar el ruido int noise_frame_number = GetNoiseFrameNumber() for (int i = 0; i = noise_frame_number; ++i) { //Tomamos la trama y se la damos al sistema double* noise_frame = GetNoiseFrame(i); vad_object.NoiseInit(noise_frame); } //Y ya está
Aunque no hay ninguna condición, se recomienda que la inicialización del ruido se haga con unas 10 a 20 tramas, para tener una estimación bastante robusta. Como regla general se considera que 15 están bien.
Estas tramas de inicialización pueden conseguirse mediante un pre-procesado (haciendo un VAD rápido y cutre por potencia, por ejemplo, y cortando a un cierto umbral) o suponer que las primeras tramas de la señal no contienen voz y usar esas.
Dicho de otra forma, si tomamos un archivo de audio y en un momento dado estamos leyendo la trama i-ésima (i=1,...,NSamples), y se la pasamos al VAD (mediante doVAD()), el resultado devuelto es la decisión para la trama i-N.
Si ahora suponemos que i < N, entonces estará devolviendo el resultado para una trama i-N <= 0, lo cual no existe. Por lo tanto, debemos llenar el buffer con N tramas antes de poder recuperar la primera decisión. Las primeras N veces que se llame a doVAD(), el sistema no hace nada más que poblar el buffer, y la función siempre devuelve SILENCIO. A partir de ahí empieza a devolver resultados.
El tamaño completo del buffer es 2N+1, y viene dado por vad_config::LTWindowLen. Este valor DEBE SER IMPAR!!!!! (para poder tener una ventana centrada en la trama actual)
vad_config cfg; cfg.LTWindowLen = 15 //Debe ser impar vad vad_object (cfg, 80); //Inicialización del ruido ... for (int i = 0; i < (cfg.LTWindowLen-1)/2; ++i) { //Tomamos la trama y se la damos al sistema double* frame = GetFrame(i); vad_object.doVAD(frame); //Descartamos el resultado, no sirve para nada } //Y ya podemos empezar for (; i < NFrames; ++i) { //Tomamos la trama y se la damos al sistema double* frame = GetFrame(i); //Recuerda que el VAD estimado no es el que se corresponde con la trama actual vad[i-(cfg.LTWindowLen-1)/2] = vad_object.doVAD(frame); }
Además, para permitir personalizar el punto de trabajo, se permite aplicar un offset a la LTSD (configurado mediante la opción vad_config::offset). Valores positivos del offset hacen que se favorezca la decisión VOZ. Valores negativos hacen que se tienda más a la decisión SILENCIO. Con esto es posible modificar el comportamiento del sistema según si es más importante que no haya tramas de silencio clasificadas como voz o tramas de voz clasificadas como silencio.
Cuando una trama se clasifica como VOZ, se toman cfg::ActWindowLen tramas anteriores a ella y se calcula la potencia media de señal en estas tramas. La potencia de señal se actualiza como:
//Promediamos la potencia de cfg::ActWindowLen tramas anteriores double SpowAct = GetMeanPow(cfg.ActWindowLen); SignaPow = cfg.SNRalfa * SignalPow + (1-cfg.SNRalfa) * SpowAct;
Igualmente, por cada trama que se clasifica como SILENCIO, se hace algo parecido con la potencia de ruido. En este caso además se actualiza el espectro del ruido:
//Promediamos la potencia y el espectro de cfg::ActWindowLen tramas anteriores double NpowAct = GetMeanPow(cfg.ActWindowLen); double* NspecAct = GetMeanSpectrum(cfg.ActWindowLen); NoisePow = cfg.Nalfa * NoisePow + (1-cfg.Nalfa) * NpowAct; for (int i = 0; i < Nfft; ++i) NoiseSpec[i] = cfg.Nalfa * NoiseSpec[i] + (1-cfg.Nalfa) * NspecAct[i];
El handover viene controlado por la opción cad_config::Handover.
Definición en la línea 397 del archivo vad.h.
vad::vad | ( | ) | [inline] |
vad::vad | ( | const vad_config & | cfg, | |
DOUBLE | snr, | |||
FLOAT | FrameLen, | |||
XFFT_WIN | WinType | |||
) | [inline] |
vad::vad | ( | const vad_config & | cfg, | |
DOUBLE | snr, | |||
FLOAT | FrameLen | |||
) | [inline] |
bool vad::Reset | ( | const vad_config & | cfg, | |
DOUBLE | snr, | |||
FLOAT | FrameLen, | |||
XFFT_WIN | WinType = XFFT_WIN_HAMM | |||
) |
Es la verdadera encargada de inicializar el objeto. Una llamada a esta función resetea completamente el objeto, es decir, se pierden los buffers internos, la memoria, el modelado del ruido... y nos deja un objeto como recién construido, en blanco, listo para usar.
Es muy útil en dos casos:
Además, una llamada a esta función es la única forma de cambiar el tamaño de la ventana de trama y el tamaño de la ventana LTSE. Esto es así porque estos dos valores están fuertemente relacionados con los buffers internos y la memoria FFT, y en caso de cambiarlos, el historial de FFT sería totalmente inutil, dejando el objeto en un estado incoherente.
El resto de los parámetros puede modificarse mediante la función get apropiada.
true si todo ha ido bien
void vad::NoiseInit | ( | pDOUBLE | frame | ) |
La trama que se le pasa a esta función se utiliza para la inicialización del ruido. Antes de utilizar el objeto, es necesario llamar a esta función varias veces con tramas de ruido, para que el sistema pueda estimar el espectro medio de ruido. Se recomienda llamar a esta función con unas 15 tramas diferentes antes de usar el objeto.
bool vad::doVAD | ( | pDOUBLE | frame | ) |
Las primeras (LTWindowLen-1)/2 veces que se llame a esta función, el buffer de la ventana LTSE todavía no estará lleno hasta la mitad, por lo que no se puede calcular el VAD (se estaría calculando el VAD de una trama de índice negativo). En estos casos devuelve silencio.
Es cosa del programador tener esto en cuenta (al igual que lo es tener en cuenta que el VAD que se devuelve es el de la trama que se pasa -(LTWindowLen-1)/2).
false si la trama es silencio
INT vad::getNfft | ( | ) | const [inline] |
será la siguiente potencia de dos de getFrameLen().
Bueno, en realidad, la mitad de la siguiente potencia de dos, ya que al ser simétrica, sólo devuelve medio espectro.
bool vad::IsBufferFull | ( | ) | const [inline] |
void vad::setSNR | ( | DOUBLE | snr | ) | [inline] |
void vad::setActWindowLen | ( | INT | ActWindowLen | ) | [inline] |
void vad::Destruct | ( | ) | [protected] |
LONG vad::m_FrameLen [protected] |
XFFT_WIN vad::m_WinType [protected] |
pRFFT vad::m_fftObject [protected] |
INT vad::m_Nfft [protected] |
buffer2D vad::m_memFFT [protected] |
INT vad::m_bufferedFrames [protected] |
pDOUBLE vad::m_NoiseFFT [protected] |
INT vad::m_NoiseInitFrames [protected] |
DOUBLE vad::m_NoisePow [protected] |
DOUBLE vad::m_SignalPow [protected] |
DOUBLE vad::m_SNR [protected] |
DOUBLE vad::m_SNR0 [protected] |
DOUBLE vad::m_SNR1 [protected] |
INT vad::m_ActWindowLen [protected] |
FLOAT vad::m_Nalfa [protected] |
FLOAT vad::m_SNRalfa [protected] |
pDOUBLE vad::m_NoiseFFTAct [protected] |
INT vad::m_LTWindowLen [protected] |
DOUBLE vad::m_gamma0 [protected] |
DOUBLE vad::m_gamma1 [protected] |
INT vad::m_Handover [protected] |
LONG vad::m_NumSil [protected] |
LONG vad::m_NumVoice [protected] |
LONG vad::m_HoSil [protected] |
DOUBLE vad::m_offset [protected] |
pDOUBLE vad::m_LTSE [protected] |