Guía de Implementación de Remuestreo de Audio

Principios Fundamentales y Arquitectura

1.1 ¿Qué es el remuestreo de audio?

El remuestreo de audio es el proceso de convertir una señal de audio desde una frecuencia de muestreo de origen (Fs_origen) a una frecuencia de muestreo de destino (Fs_destino). Por ejemplo: convertir voz capturada a 48kHz de un micrófono a 16kHz para su uso en algoritmos de reconocimiento de voz.

1.2 Esencia matemática del remuestreo

Señal original x[n] (Fs_origen)  →  Interpolación/Submuestreo  →  Nueva señal y[m] (Fs_destino)


1.3 Dos métodos de remuestreo

Tipo Fórmula Características Casos de uso
Remuestreo de múltiplo entero Fs_destino = L × Fs_origen Simple y eficiente Procesamiento en tiempo real en MCUs
Remuestreo de fracción Fs_destino = (L/M) × Fs_origen Flexible y general PC/Embebido de uso general

Implementación de Remuestreo de Múltiplo Entero en STM32 (Recomendado)

2.1 Arquitectura del sistema

Entrada de audio/micrófono
      ↓
Filtro anti-aliasing (FIR)
      ↓
Interpolación (relleno de ceros)
      ↓
Filtro pasa-bajo (FIR)
      ↓
Submuestreo
      ↓
Salida a DAC/algoritmo


2.2 Implementación completa del código fuente

(1) Archivo de encabezado (audio_remuestreo.h)

/**
  * @file audio_remuestreo.h
  * @brief Biblioteca de remuestreo de audio para STM32 (múltiplo entero)
  */
#ifndef __AUDIO_REMUESTREO_H
#define __AUDIO_REMUESTREO_H

#include <stdint.h>
#include <stdbool.h>

/* Configuración de remuestreo */
#define FACTOR_MAXIMO_REMUESTREO   8       // Factor máximo de interpolación
#define ORDEN_FIR                33       // Orden del filtro FIR (impar)
#define TAMANO_BUFFER_AUDIO      256       // Tamaño del buffer de audio

/* Estructura de estado de remuestreo */
typedef struct {
    uint8_t factor_interpolacion;            // Factor de interpolación L
    uint8_t factor_submuestreo;              // Factor de submuestreo M
    int16_t coeficientes_fir[ORDEN_FIR];     // Coeficientes del filtro FIR
    int32_t historico[ORDEN_FIR];            // Caché de muestras históricas
    uint8_t indice_historico;               // Índice de caché histórico
    uint32_t muestras_entrada;               // Contador de muestras de entrada
    uint32_t muestras_salida;                // Contador de muestras de salida
    bool inicializado;                       // Bandera de inicialización
} EstadoRemuestreo;

/* Declaración de funciones */
void IniciarRemuestreo(EstadoRemuestreo* er, uint8_t L, uint8_t M);
int16_t ProcesarMuestra(EstadoRemuestreo* er, int16_t muestra_entrada);
void ProcesarBloque(EstadoRemuestreo* er, 
                   int16_t* entrada, uint16_t longitud_entrada,
                   int16_t* salida, uint16_t* longitud_salida);
void ReiniciarRemuestreo(EstadoRemuestreo* er);

#endif /* __AUDIO_REMUESTREO_H */


(2) Generación de coeficientes del filtro FIR (audio_remuestreo.c)

/**
  * @file audio_remuestreo.c
  * @brief Implementación de remuestreo de audio (basado en filtro FIR)
  */
#include "audio_remuestreo.h"
#include <string.h>
#include <math.h>

/* Generación de coeficientes FIR pasa-bajo (ventana Hamming) */
static void GenerarCoeficientesFIR(int16_t* coef, uint8_t orden, float frecuencia_corte)
{
    float suma = 0.0f;
    
    for (int i = 0; i < orden; i++) {
        float n = (float)i - (orden - 1) / 2.0f;
        float sinc = (n == 0) ? 1.0f : sinf(2.0f * M_PI * frecuencia_corte * n) / (2.0f * M_PI * frecuencia_corte * n);
        float ventana = 0.54f - 0.46f * cosf(2.0f * M_PI * i / (orden - 1)); // Ventana Hamming
        coef[i] = (int16_t)(sinc * ventana * 32767.0f);
        suma += coef[i];
    }
    
    // Normalización
    for (int i = 0; i < orden; i++) {
        coef[i] = (int16_t)((float)coef[i] * 32767.0f / suma);
    }
}

/* Inicializador del remuestreador */
void IniciarRemuestreo(EstadoRemuestreo* er, uint8_t L, uint8_t M)
{
    if (er == NULL || L == 0 || M == 0) return;
    
    er->factor_interpolacion = L;
    er->factor_submuestreo = M;
    
    // Generación de coeficientes FIR
    float frecuencia_corte = 0.5f / (L > M ? L : M);  // Frecuencia de corte
    GenerarCoeficientesFIR(er->coeficientes_fir, ORDEN_FIR, frecuencia_corte);
    
    // Inicialización del caché histórico
    memset(er->historico, 0, sizeof(er->historico));
    er->indice_historico = 0;
    er->muestras_entrada = 0;
    er->muestras_salida = 0;
    er->inicializado = true;
}

/* Procesamiento de una sola muestra de remuestreo */
int16_t ProcesarMuestra(EstadoRemuestreo* er, int16_t muestra_entrada)
{
    if (!er->inicializado) return 0;
    
    int32_t acumulador = 0;
    int16_t muestra_salida = 0;
    
    // 1. Almacenar la muestra de entrada en el caché histórico
    er->historico[er->indice_historico] = muestra_entrada;
    er->indice_historico = (er->indice_historico + 1) % ORDEN_FIR;
    
    // 2. Interpolación (relleno de ceros)
    static uint8_t fase = 0;
    
    if (fase == 0) {
        // 3. Filtrado FIR
        uint8_t hist_idx = er->indice_historico;
        for (int i = 0; i < ORDEN_FIR; i++) {
            hist_idx = (hist_idx == 0) ? ORDEN_FIR - 1 : hist_idx - 1;
            acumulador += (int32_t)er->historico[hist_idx] * er->coeficientes_fir[i];
        }
        muestra_salida = (int16_t)(acumulador >> 15);  // Desplazar 15 bits para restaurar escala
    }
    
    // 4. Submuestreo
    fase++;
    if (fase >= er->factor_interpolacion) {
        fase = 0;
    }
    
    er->muestras_entrada++;
    if (fase == 0) {
        er->muestras_salida++;
        return muestra_salida;
    }
    
    return 0;  // Devuelve 0 en momentos no de salida
}

/* Procesamiento de bloque de remuestreo */
void ProcesarBloque(EstadoRemuestreo* er, 
                   int16_t* entrada, uint16_t longitud_entrada,
                   int16_t* salida, uint16_t* longitud_salida)
{
    uint16_t idx_salida = 0;
    
    for (uint16_t i = 0; i < longitud_entrada; i++) {
        int16_t muestra = ProcesarMuestra(er, entrada[i]);
        if (muestra != 0) {  // No cero indica salida válida
            if (idx_salida < *longitud_salida) {
                salida[idx_salida++] = muestra;
            }
        }
    }
    
    *longitud_salida = idx_salida;
}

/* Reinicio del estado del remuestreador */
void ReiniciarRemuestreo(EstadoRemuestreo* er)
{
    memset(er->historico, 0, sizeof(er->historico));
    er->indice_historico = 0;
    er->muestras_entrada = 0;
    er->muestras_salida = 0;
}


(3) Ejemplo de prograam principal (main.c)

/**
  * @file main.c
  * @brief Ejemplo de remuestreo de audio para STM32
  */
#include "stm32f10x.h"
#include "audio_remuestreo.h"
#include "i2s.h"
#include "dma.h"

/* Variables globales */
EstadoRemuestreo remuestreador;
int16_t audio_entrada[TAMANO_BUFFER_AUDIO];
int16_t audio_salida[TAMANO_BUFFER_AUDIO * 4];  // Buffer 4 veces más grande
uint16_t longitud_salida = 0;

int main(void)
{
    /* 1. Inicialización del sistema */
    InicializarSistema();
    InicializarI2S();
    InicializarDMA();
    
    /* 2. Inicializar el remuestreador (48kHz → 16kHz, L=1, M=3) */
    IniciarRemuestreo(&remuestreador, 1, 3);  // 48/3 = 16kHz
    
    /* 3. Bucle principal */
    while (1) {
        /* 3.1 Esperar a que la recepción I2S se complete */
        if (RecepcionI2SCompleta()) {
            /* 3.2 Obtener datos de audio */
            ObtenerDatosI2S(audio_entrada, TAMANO_BUFFER_AUDIO);
            
            /* 3.3 Procesamiento de remuestreo */
            uint16_t maxima_salida = TAMANO_BUFFER_AUDIO * 4;
            ProcesarBloque(&remuestreador, 
                           audio_entrada, TAMANO_BUFFER_AUDIO,
                           audio_salida, &maxima_salida);
            longitud_salida = maxima_salida;
            
            /* 3.4 Enviar datos procesados al algoritmo de reconocimiento de voz */
            ProcesarVoz(audio_salida, longitud_salida);
        }
    }
}


Remuestreo de Fracción en PC/Linux (General)

3.1 Implementación ligera basada en interpolación lineal

Adecuado para sistemas con recursos limitados, calidad de audio aceptable.

/**
  * @file interpolacion_lineal.c
  * @brief Remuestreo por interpolación lineal (fracción)
  */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <math.h>

/* Remuestreo por interpolación lineal */
uint32_t InterpolacionLineal(int16_t* entrada, uint32_t longitud_entrada,
                             int16_t* salida, uint32_t capacidad_salida,
                             uint32_t fs_entrada, uint32_t fs_salida)
{
    if (fs_entrada == fs_salida) {
        uint32_t longitud_copia = (longitud_entrada < capacidad_salida) ? longitud_entrada : capacidad_salida;
        memcpy(salida, entrada, longitud_copia * sizeof(int16_t));
        return longitud_copia;
    }
    
    double ratio = (double)fs_salida / fs_entrada;
    uint32_t longitud_salida = (uint32_t)(longitud_entrada * ratio);
    
    if (longitud_salida > capacidad_salida) {
        longitud_salida = capacidad_salida;
    }
    
    for (uint32_t i = 0; i < longitud_salida; i++) {
        double indice_origen = i / ratio;
        uint32_t indice_bajo = (uint32_t)indice_origen;
        uint32_t indice_alto = indice_bajo + 1;
        double fraccion = indice_origen - indice_bajo;
        
        if (indice_alto >= longitud_entrada) {
            salida[i] = entrada[indice_bajo];
        } else {
            // Interpolación lineal: y = y0 + (y1 - y0) * fraccion
            salida[i] = (int16_t)(entrada[indice_bajo] + 
                      (entrada[indice_alto] - entrada[indice_bajo]) * fraccion);
        }
    }
    
    return longitud_salida;
}

/* Ejemplo de prueba */
int main()
{
    int16_t entrada[10] = {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000};
    int16_t salida[20];
    
    uint32_t longitud_salida = InterpolacionLineal(entrada, 10, salida, 20, 48000, 16000);
    
    printf("Salida Remuestreada (%d muestras):\n", longitud_salida);
    for (uint32_t i = 0; i < longitud_salida; i++) {
        printf("%d ", salida[i]);
    }
    printf("\n");
    
    return 0;
}


3.2 Remuestreo de alta calidad basado en SpeexDSP

Esta es la biblioteca de remuestreo de alta calidad comúnmente utilizada en la industria.

/**
  * @file speex_remuestreo.c
  * @brief Ejemplo de remuestreo usando la biblioteca SpeexDSP
  */
#include <speex/speex_resampler.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define TAMANO_MUESTRA 160

int main()
{
    SpeexResamplerState *remuestreador;
    int err;
    spx_uint32_t cuadros_entrada = TAMANO_MUESTRA;
    spx_uint32_t cuadros_salida = TAMANO_MUESTRA;
    spx_uint32_t cuadros_entrada_usados, cuadros_salida_generados;
    
    short buffer_entrada[TAMANO_MUESTRA];
    short buffer_salida[TAMANO_MUESTRA * 2];  // Buffer ampliado
    
    // 1. Inicializar el remuestreador (mono, 48kHz → 16kHz)
    remuestreador = speex_resampler_init(1, 48000, 16000, 10, &err);
    if (err != 0) {
        printf("Fallo en la inicialización del remuestreador: %d\n", err);
        return -1;
    }
    
    // 2. Establecer la calidad de remuestreo (0-10, cuanto mayor mejor)
    speex_resampler_set_quality(remuestreador, 10);
    
    // 3. Procesar datos de audio
    while (1) {
        // Leer un cuadro de datos de audio
        if (leer_cuadro_audio(buffer_entrada, TAMANO_MUESTRA) <= 0) break;
        
        // Realizar el remuestreo
        speex_resampler_process_int(remuestreador, 0,
                                   buffer_entrada, &cuadros_entrada,
                                   buffer_salida, &cuadros_salida);
        
        // Salida de datos remuestreados
        escribir_cuadro_audio(buffer_salida, cuadros_salida);
    }
    
    // 4. Liberar recursos
    speex_resampler_destroy(remuestreador);
    
    return 0;
}


Sugerencias de Optimización de Rendimiento

4.1 Optimización algorítmica

/* Optimización de punto fijo (evitar punto flotante) */
typedef struct {
    int32_t coeficientes_fir_punto_fijo[ORDEN_FIR];  // Formato Q15
    int64_t acumulador;                               // Acumulador de 64 bits
} RemuestreadorRapido;

/* Optimización con instrucciones SIMD (ARM NEON) */
#ifdef __ARM_NEON
#include <arm_neon.h>
void FiltradoFIR_NEON(int16_t* entrada, int16_t* coef, int16_t* salida, int longitud)
{
    int16x8_t vec_entrada, vec_coef;
    int32x4_t acum_bajo, acum_alto;
    
    for (int i = 0; i < longitud; i += 8) {
        vec_entrada = vld1q_s16(entrada + i);
        vec_coef = vld1q_s16(coef);
        acum_bajo = vmull_s16(vget_low_s16(vec_entrada), vget_low_s16(vec_coef));
        acum_alto = vmull_s16(vget_high_s16(vec_entrada), vget_high_s16(vec_coef));
        // ... Procesamiento de acumulación
    }
}
#endif


4.2 Optimización de memoria

/* Buffer circular en lugar de buffer lineal */
typedef struct {
    int16_t buffer[TAMANO_BUFFER_AUDIO];
    uint16_t ptr_escritura;
    uint16_t ptr_lectura;
    uint16_t cuenta;
} BufferCircular;

/* Remuestreo in situ (evitar copia adicional) */
void RemuestreoInSitu(int16_t* buffer, uint32_t longitud, uint32_t factor)
{
    // Procesar de atrás hacia adelante para evitar sobrescribir datos no procesados
    for (int i = longitud - 1; i >= 0; i--) {
        buffer[i * factor] = buffer[i];
        for (int j = 1; j < factor; j++) {
            buffer[i * factor + j] = 0;  // Rellenar con ceros para interpolación
        }
    }
}


Pruebas y Verificación

5.1 Código de prueba

/* Prueba de calidad de remuestreo */
void PruebaCalidadRemuestreo(void)
{
    int16_t onda_seno[1000];
    int16_t remuestreado[3000];
    uint32_t longitud_salida;
    
    // Generar onda seno de 1kHz (frecuencia de muestreo 48kHz)
    for (int i = 0; i < 1000; i++) {
        onda_seno[i] = (int16_t)(32767 * sin(2 * M_PI * 1000 * i / 48000.0));
    }
    
    // Remuestrear a 16kHz
    EstadoRemuestreo er;
    IniciarRemuestreo(&er, 1, 3);  // 48kHz → 16kHz
    ProcesarBloque(&er, onda_seno, 1000, remuestreado, &longitud_salida);
    
    printf("Muestras de entrada: 1000, Muestras de salida: %d\n", longitud_salida);
    
    // Verificar si la frecuencia es correcta
    // Teóricamente, la salida debería ser una onda seno de 1kHz con 333 muestras
}


5.2 Rendimiento de referencia

Algoritmo Complejidad Latencia Calidad de audio Casos de uso
Vecino más cercano O(n) 0 Baja Prototipado rápido
Interpolación lineal O(n) Baja Media Embebido en tiempo real
Filtrado FIR O(n×orden) Media Buena Audio general
SpeexDSP O(n×log n) Alta Excelente Audio profesional

Solución de Problemas Comunes

6.1 Manejo de anti-aliasing

/* Se debe agregar un filtro anti-aliasing antes del remuestreo */
void FiltradoAntiAlias(int16_t* entrada, uint32_t longitud, uint32_t fs_salida)
{
    // Frecuencia de corte establecida en fs_salida/2
    float frecuencia_corte = fs_salida / 2.0f;
    // Usar filtro FIR o IIR
}


6.2 Distorsión de fase

/* Usar filtro FIR de fase lineal para evitar distorsión de fase */
#define FIR_FASE_LINEAL 1
#define FIR_FASE_MINIMA 0


6.3 Desbordamiento de buffer

/* Calcular tamaño seguro del buffer de salida */
uint32_t TamanoSalidaSeguro(uint32_t longitud_entrada, uint32_t fs_entrada, uint32_t fs_salida)
{
    return (uint32_t)ceil((double)longitud_entrada * fs_salida / fs_entrada) + 10;
}


Etiquetas: remuestreo de audio procesamiento de señal digital STM32 SpeexDSP filtro FIR

Publicado el 6-24 00:51