iOS音频重采样算法

95 阅读3分钟

简单线性重采样

/// 简单线性重采样(单声道16位)
- (NSData *)resamplePCMData:(NSData *)pcmData
             fromSampleRate:(double)srcRate
               toSampleRate:(double)dstRate {
    // 参数校验
    if (!pcmData || pcmData.length < 2 || srcRate <= 0 || dstRate <= 0) {
        return [NSData data];
    }
    if (srcRate == dstRate) return pcmData;

    // 计算采样点数量(16位单声道)
    NSUInteger srcCount = pcmData.length / 2;
    NSUInteger dstCount = (NSUInteger)ceil((double)srcCount * dstRate / srcRate);
    if (dstCount == 0) return [NSData data];

    // 准备缓冲区
    const int16_t *srcPCM = (const int16_t *)pcmData.bytes;
    int16_t *dstPCM = malloc(sizeof(int16_t) * dstCount);
    if (!dstPCM) return [NSData data];

    // 使用定点数运算提高效率(16位小数精度)
    uint64_t step = ((uint64_t)srcCount << 32) / dstCount;  // 64位步长防止溢出
    uint64_t current = 0;
    const NSUInteger lastIndex = srcCount - 1;

    for (NSUInteger i = 0; i < dstCount; i++) {
        uint32_t pos = (uint32_t)(current >> 32);  // 高32位作为整数位置
        uint32_t frac = (uint32_t)(current >> 16) & 0xFFFF;  // 中16位作为小数部分
        
        // 边界安全处理
        if (pos >= lastIndex) {
            dstPCM[i] = srcPCM[lastIndex];
        } else {
            int32_t s1 = srcPCM[pos];
            int32_t s2 = srcPCM[pos + 1];
            
            // 定点数插值: s1 + (s2 - s1) * frac
            int32_t val = s1 + (((s2 - s1) * (int32_t)frac) >> 16);
            
            // 限制在16位有符号范围内
            dstPCM[i] = (int16_t)MAX(INT16_MIN, MIN(INT16_MAX, val));
        }
        
        current += step;
    }

    NSData *result = [NSData dataWithBytes:dstPCM length:dstCount * sizeof(int16_t)];
    free(dstPCM);
    return result;
}

优化进阶版 这个方案包含:

  • 抗混叠滤波器:防止高频失真
  • 汉明窗:减少振铃效应
  • 边界镜像:正确处理音频边界
  • 整形滤波:保证输出质量
#define MAX_FILTER_LENGTH 256

- (NSData *)resamplePCMData:(NSData *)pcmData
             fromSampleRate:(double)srcRate
               toSampleRate:(double)dstRate {
    // 参数验证
    if (!pcmData || srcRate <= 0 || dstRate <= 0) return nil;
    if (srcRate == dstRate) return pcmData;
    
    // 1. 准备参数
    const int16_t *src = (const int16_t *)pcmData.bytes;
    size_t srcCount = pcmData.length / sizeof(int16_t);
    
    // 2. 计算输出大小
    size_t dstCount = ceil(srcCount * dstRate / srcRate);
    NSMutableData *outputData = [NSMutableData dataWithLength:dstCount * sizeof(int16_t)];
    int16_t *dst = outputData.mutableBytes;
    
    // 3. 计算重采样比例
    double ratio = srcRate / dstRate;
    double pos = 0.0;
    
    // 4. 创建抗混叠滤波器
    const size_t filterLength = 64; // 滤波器长度
    float filter[MAX_FILTER_LENGTH] = {0};
    [self createLowPassFilter:filter length:filterLength cutoff:MIN(0.45, 0.45 * MIN(1.0, ratio))];
    
    // 5. 执行高质量重采样
    for (size_t i = 0; i < dstCount; i++) {
        // 计算当前采样位置
        double floatIndex = pos;
        size_t baseIndex = (size_t)floatIndex;
        double frac = floatIndex - baseIndex;
        
        // 应用FIR滤波器
        double sum = 0.0;
        for (int j = 0; j < filterLength; j++) {
            int index = (int)baseIndex - filterLength/2 + j;
            
            // 边界处理
            if (index < 0) {
                // 镜像边界
                index = -index;
            } else if (index >= srcCount) {
                // 镜像边界
                index = 2 * srcCount - index - 2;
            }
            
            if (index >= 0 && index < srcCount) {
                sum += src[index] * filter[j];
            }
        }
        
        // 存储结果并限制范围
        int32_t sample = (int32_t)round(sum);
        dst[i] = (int16_t)MAX(-32768, MIN(32767, sample));
        
        // 更新位置
        pos += ratio;
    }
    
    return outputData;
}

- (void)createLowPassFilter:(float *)filter length:(size_t)length cutoff:(double)cutoff {
    // 创建sinc函数滤波器
    for (size_t n = 0; n < length; n++) {
        double x = n - (length-1)/2.0;
        
        if (x == 0) {
            filter[n] = 2.0 * M_PI * cutoff; // 避免除以零
        } else {
            filter[n] = sin(2.0 * M_PI * cutoff * x) / (M_PI * x);
        }
    }
    
    // 应用汉明窗减少振铃
    for (size_t n = 0; n < length; n++) {
        double window = 0.54 - 0.46 * cos(2.0 * M_PI * n / (length-1));
        filter[n] *= window;
    }
    
    // 归一化滤波器
    float sum = 0.0;
    for (size_t n = 0; n < length; n++) {
        sum += filter[n];
    }
    
    for (size_t n = 0; n < length; n++) {
        filter[n] /= sum;
    }
}