You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于Superpowered Audio Kit的音频切换爆音问题排查求助

解决Superpowered音频切换时的爆音问题

你遇到的切换曲目时的尖锐爆音,大概率是音频资源销毁与新实例创建的时序冲突,或是旧音频线程未完全终止就启动新播放导致的。结合你的代码和Superpowered的工作机制,我整理了几个核心修复点:

一、核心问题拆解

  1. 资源释放不彻底:你直接删除audioSystem(SuperpoweredAndroidAudioIO实例),但Superpowered的音频线程不会立即停止,此时立刻创建新实例,旧线程可能还在向输出缓冲区写数据,和新线程的输出叠加产生爆音。
  2. 缓冲区残留旧数据:新实例的stereoBuffer没有初始化清零,残留的旧音频数据会在新曲目启动时先被输出,引发爆音。
  3. 切换时序不同步:Java端调用销毁方法后立刻启动新播放,没有给旧线程留出足够的终止时间。

二、具体修复方案

1. 优化资源销毁逻辑,确保线程完全停止

修改setDestroyAllThreads方法,先停止音频IO再释放资源,给线程留足终止时间:

void Eve::setDestroyAllThreads(){
    // 先停止音频IO,等待线程终止
    if (audioSystem) {
        audioSystem->stop();
        usleep(10000); // 等待10ms,确保线程处理完当前缓冲区
    }
    // 释放资源并置空指针,避免野指针
    delete playerBass; playerBass = nullptr;
    delete playerB; playerB = nullptr;
    delete spatializer; spatializer = nullptr;
    delete filterb; filterb = nullptr;
    delete audioSystem; audioSystem = nullptr;
    // 重置状态变量
    finishedPlaying = false;
    playingNow = false;
    pausedNow = false;
    processed = false;
    hd = false;
}

2. 初始化时清空音频缓冲区

在Eve构造函数中,给新创建的stereoBuffer清零,避免残留旧数据:

Eve::Eve(const char *path,unsigned int samplerate,unsigned int buffersize) : activeFx(0), crossValue(0.0f), volBass(1.0f * headroom), volB(1.0f * headroom) {
    stereoBuffer = (float *)memalign(16,(buffersize + 16) * sizeof(float) * 2);
    // 初始化缓冲区为静音
    memset(stereoBuffer, 0, (buffersize + 16) * sizeof(float) * 2);
    
    playerBass = new SuperpoweredAdvancedAudioPlayer(&playerBass , playerEventCallbackA, samplerate, 0);
    playerB = new SuperpoweredAdvancedAudioPlayer(&playerB, playerEventCallbackB, samplerate, 0);
    filterb = new SuperpoweredFilter(SuperpoweredFilter_Resonant_Lowpass, samplerate*0.98);
    spatializer = new SuperpoweredSpatializer(samplerate);
    
    playerBass->open(path);
    playerB->open(path);
    finishedPlaying = false;
    playerBass->syncMode = playerB->syncMode = SuperpoweredAdvancedAudioPlayerSyncMode_TempoAndBeat;
    
    audioSystem = new SuperpoweredAndroidAudioIO(samplerate, buffersize, false, true, audioProcessing, this, -1, SL_ANDROID_STREAM_MEDIA, buffersize * 2);
}

3. 调整Java端切换时序,增加等待

在Java端调用销毁方法后,等待一小段时间再启动新播放:

// 修改Java端切换逻辑
PlayerClass.setDestroyAllThreads();
// 等待旧线程完全终止
try {
    Thread.sleep(50);
} catch (InterruptedException e) {
    e.printStackTrace();
}
PlayerClass.open("new_path", sampleRate, bufferSize);
PlayerClass.play();

4. 在process方法中处理初始静音

确保新曲目启动前输出静音,避免异常数据:

bool Eve::process(short int *output, unsigned int numberOfSamples) {
    // 未播放时直接输出静音
    if (!playingNow || (!playerBass->isPlaying && !playerB->isPlaying)) {
        memset(output, 0, numberOfSamples * 2 * sizeof(short));
        return false;
    }
    
    bool masterIsA = (crossValue <= 0.5f);
    double masterBpm = masterIsA ? playerBass->currentBpm : playerB->currentBpm;
    double msElapsedSinceLastBeatA = playerBass->msElapsedSinceLastBeat;
    
    bool silence = !playerBass->process(stereoBuffer, false, numberOfSamples*1, volBass, masterBpm, playerB->msElapsedSinceLastBeat);
    processed = true;
    
    filterb->process(stereoBuffer, stereoBuffer, numberOfSamples);
    
    if (playerB->process(stereoBuffer, !silence, numberOfSamples, volB, masterBpm, msElapsedSinceLastBeatA)) silence = false;
    
    spatializer->process(stereoBuffer, stereoBuffer, (float*)output, (float*)output, numberOfSamples, true);
    
    SuperpoweredFloatToShortInt(stereoBuffer, output, numberOfSamples);
    return !silence;
}

5. 修复全局指针的空指针问题

在JNI销毁方法中,销毁eve实例并置空:

extern "C" JNIEXPORT void Java_com_EvePlayer_setDestroyAllThreads(JNIEnv *javaEnvironment, jobject self) {
    if (eve) {
        eve->setDestroyAllThreads();
        delete eve;
        eve = nullptr;
    }
}

内容的提问来源于stack exchange,提问作者Isaac Oyewole

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:38:03