HarmonyOS鸿蒙Next中华为4.2以下版本回声抑制 voip发送上行数据为什么会导致AudioTrack的声音被压制

HarmonyOS鸿蒙Next中华为4.2以下版本回声抑制 voip发送上行数据为什么会导致AudioTrack的声音被压制 录制端

private const val SAMPLE_RATE = 16000  // 采集采样率:16kHz
private const val CHANNEL_CONFIG = AudioFormat.CHANNEL_IN_MONO
private const val AUDIO_FORMAT = AudioFormat.ENCODING_PCM_16BIT
audioRecord = AudioRecord(
    MediaRecorder.AudioSource.VOICE_COMMUNICATION,
    SAMPLE_RATE,
    CHANNEL_CONFIG,
    AUDIO_FORMAT,
    BUFFER_SIZE
).apply {
    if (AcousticEchoCanceler.isAvailable()) {
        aec = AcousticEchoCanceler.create(audioSessionId)
        aec?.enabled = true
    }
    if (AutomaticGainControl.isAvailable()) {
        agc = AutomaticGainControl.create(audioSessionId)
        agc?.enabled = false
    }
    if (NoiseSuppressor.isAvailable()) {
        ns = NoiseSuppressor.create(audioSessionId)
        ns?.enabled = false
    }
    startRecording()
}

播放端

int channelMask = AudioFormat.CHANNEL_OUT_MONO;
if (channelCount == 2) {
    channelMask = AudioFormat.CHANNEL_OUT_STEREO;
}

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioAttributes audioAttributes =
        new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
                .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                .build();
AudioFormat audioFormat = new AudioFormat.Builder()
        .setSampleRate(16000)
        .setChannelMask(channelMask)
        .setEncoding(format)
        .build();
int bufferSize = AudioTrack.getMinBufferSize(16000, channelMask, format);
AudioTrack audioTrack = new AudioTrack.Builder().setAudioAttributes(audioAttributes)
        .setAudioFormat(audioFormat)
        .setPerformanceMode(AudioTrack.PERFORMANCE_MODE_LOW_LATENCY)
        .setTransferMode(AudioTrack.MODE_STREAM)
        .setBufferSizeInBytes(bufferSize)
        .build();
if (audioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
    throw new RuntimeException("无法创建音频播放器");
}
audioManager.setMode(AudioManager.MODE_IN_COMMUNICATION);

AudioFocusRequest build = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)
        .setAudioAttributes(audioAttributes)
        .setAcceptsDelayedFocusGain(true)
        .setOnAudioFocusChangeListener(i -> {

        })
        .build();
audioManager.requestAudioFocus(build);

只有发送了上行数据形成VOIP 就会导致AudioTrack 播放的声音被压制的很低 但是写入的 mAudioTrack的数据是正常的 这个怎么处理

mAudioTrack.write(pcm16k,0, pcm16k.length, AudioTrack.WRITE_BLOCKING);

Historical Thread Log 01-23 10:51:01.486 -

  • Input thread 0x7a74ecaa00, name AudioIn_2D6, tid 16062, type 3 (RECORD):
  • I/O handle: 726
  • Standby: yes
  • Sample rate: 16000 Hz
  • HAL frame count: 320
  • HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • HAL buffer size: 640 bytes
  • Channel count: 1
  • Channel mask: 0x00000010 (front)
  • Processing format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • Processing frame size: 2 bytes
  • Pending config events: none
  • Output device: 0x2 (AUDIO_DEVICE_OUT_SPEAKER)
  • Input device: 0 (AUDIO_DEVICE_NONE)
  • Audio source: 7 (AUDIO_SOURCE_VOICE_COMMUNICATION)
  • Timestamp stats: n=0 disc=0 cold=0 nRdy=0 err=7804 jitterMs(unavail) localSR(nan, nan) correctedJitterMs(unavail)
  • Timestamp corrected: no
  • Last read occurred (msecs): 107
  • Process time ms stats: ave=0.260689 std=0.170366 min=0.078646 max=3.33542
  • Hal read jitter ms stats: ave=0.00103881 std=0.5581 min=-14.3167 max=12.3552
  • AudioStreamIn: 0x7a8a016520 flags 0 (AUDIO_INPUT_FLAG_NONE)
  • Frames read: 2497280
  • No active record clients
  • Hal stream dump:
  • device: 80, standby: 1, frames_in: 0

Historical Thread Log 01-23 10:51:07.292 -

  • Output thread 0x7a8972f800, name AudioOut_5ED, tid 16039, type 1 (DIRECT):
  • I/O handle: 1517
  • Standby: yes
  • Sample rate: 16000 Hz
  • HAL frame count: 320
  • HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • HAL buffer size: 640 bytes
  • Channel count: 1
  • Channel mask: 0x00000001 (front-left)
  • Processing format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • Processing frame size: 2 bytes
  • Pending config events: none
  • Output device: 0 (AUDIO_DEVICE_NONE)
  • Input device: 0 (AUDIO_DEVICE_NONE)
  • Audio source: 0 (AUDIO_SOURCE_DEFAULT)
  • Timestamp stats: n=8472 disc=2 cold=0 nRdy=9 err=9 rate=0.999668 jitterMs(ave=-0.00116201 std=1.17161 min=-18.913 max=10.5344) localSR(15977.7, 1.53867e-05) correctedJitterMs(ave=0.000604393 std=0.0425864 min=-0.20052 max=0.6625)
  • Timestamp corrected: no
  • Last write occurred (msecs): 55
  • Process time ms stats: ave=0.266891 std=0.0708517 min=0.073959 max=0.670833
  • Hal write jitter ms stats: ave=-0.389735 std=2.80415 min=-19.787 max=1.71302
  • Threadloop write latency stats: ave=-9.32669e+06 std=17734.3 min=-9.49175e+06 max=-9.3248e+06
  • Master mute: off
  • Normal frame count: 320
  • Total writes: 8466
  • Delayed writes: 0
  • Blocked in write: no
  • Suspend count: 0
  • Sink buffer : 0x7a896e4e80
  • Mixer buffer: 0x7a8a05e900
  • Effect buffer: 0x7a8a05f800
  • Fast track availMask=0xfe
  • Standby delay ns=160000000
  • AudioStreamOut: 0x7a8a058f00 flags 0xa001 (AUDIO_OUTPUT_FLAG_DIRECT|AUDIO_OUTPUT_FLAG_DIRECT_PCM|AUDIO_OUTPUT_FLAG_VOIP_RX)
  • Frames written: 2709120
  • Suspended frames: 0
  • Hal stream dump:
  • device: 2, disabled: 0,standby: 0, written: 154578528

Historical Thread Log 01-23 10:53:47.135 -

  • Input thread 0x7a766312c0, name AudioIn_2DE, tid 16801, type 3 (RECORD):
  • I/O handle: 734
  • Standby: yes
  • Sample rate: 16000 Hz
  • HAL frame count: 320
  • HAL format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • HAL buffer size: 640 bytes
  • Channel count: 1
  • Channel mask: 0x00000010 (front)
  • Processing format: 0x1 (AUDIO_FORMAT_PCM_16_BIT)
  • Processing frame size: 2 bytes
  • Pending config events: none
  • Output device: 0x2 (AUDIO_DEVICE_OUT_SPEAKER)
  • Input device: 0 (AUDIO_DEVICE_NONE)
  • Audio source: 7 (AUDIO_SOURCE_VOICE_COMMUNICATION)
  • Timestamp stats: n=0 disc=0 cold=0 nRdy=0 err=1572 jitterMs(unavail) localSR(nan, nan) correctedJitterMs(unavail)
  • Timestamp corrected: no
  • Last read occurred (msecs): 99
  • Process time ms stats: ave=0.252897 std=0.222506 min=0.111458 max=2.48177
  • Hal read jitter ms stats: ave=-0.00138235 std=0.382447 min=-4.35833 max=4.4651
  • AudioStreamIn: 0x7a8b519360 flags 0 (AUDIO_INPUT_FLAG_NONE)
  • Frames read: 503040
  • No active record clients
  • Hal stream dump:
  • device: 80, standby: 1, frames_in: 0

更多关于HarmonyOS鸿蒙Next中华为4.2以下版本回声抑制 voip发送上行数据为什么会导致AudioTrack的声音被压制的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

HarmonyOS Next中,华为4.2以下版本回声抑制模块在处理VoIP上行数据时,会错误地将系统AudioTrack播放的音频识别为需要抑制的回声源。这是由于该版本音频框架在处理多路音频流时,内部优先级和回声参考信号设置存在缺陷,导致系统播放通道的音频数据被误纳入回声消除处理流程,从而被压制。

更多关于HarmonyOS鸿蒙Next中华为4.2以下版本回声抑制 voip发送上行数据为什么会导致AudioTrack的声音被压制的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


在HarmonyOS Next中,当使用VOICE_COMMUNICATION音频源进行上行采集并启用回声抑制(AEC)时,AudioTrack播放声音被压制,这是系统音频策略的预期行为,而非Bug。

核心原因是通信模式下的音频通路管理。当您设置AudioManager.MODE_IN_COMMUNICATION并创建了USAGE_VOICE_COMMUNICATION的AudioTrack时,系统会将其识别为一条完整的VOIP通信通路(包含上行和下行)。一旦上行音频流(AudioRecord)被激活,系统音频服务会为了确保通话清晰度,自动启用回声消除处理。

问题根源分析:

  1. 回声消除(AEC)工作逻辑:AEC算法需要同时捕获扬声器播放的“参考信号”(即您AudioTrack播放的声音)和麦克风采集的“含回声信号”。为了准确建模并消除回声,系统在内部会衰减或压制发送给扬声器的原始“参考信号”音量。这就是您听到AudioTrack声音变小的直接原因——声音数据本身正常,但被系统音频处理管线在混音后压低了增益。
  2. 音频焦点与策略MODE_IN_COMMUNICATION模式下,系统优先保障上行语音的清晰度和信噪比。下行播放(AudioTrack)的音量可能会被动态管理,以避免啸叫并优化整体通信体验。

解决方案: 针对此预期行为,如果您需要保障下行播放音量,可以尝试以下技术方案:

  1. 调整AudioTrack音频属性:这是最直接的方法。尝试将AudioTrack的AudioAttributes中的UsageUSAGE_VOICE_COMMUNICATION更改为USAGE_MEDIA。同时,将AudioManager的模式从MODE_IN_COMMUNICATION改为MODE_NORMAL。这会将播放流与上行采集流在系统层面解耦,AEC可能不会主动压制这条媒体播放流。但请注意,这可能会影响系统对通话场景的整体音频优化。

    // 修改播放端 AudioAttributes
    new AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_MEDIA) // 改为 MEDIA 用途
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .build();
    // 修改模式
    audioManager.setMode(AudioManager.MODE_NORMAL);
    
  2. 管理音频会话ID:确保上行AudioRecord和下行AudioTrack使用不同的 audioSessionId。在创建AudioTrack时,通过setSessionId方法显式设置一个与AudioRecord不同的ID。这可以向音频框架提示这是两个独立的音频上下文,可能减轻自动增益调整的关联性。

    int playbackSessionId = audioManager.generateAudioSessionId(); // 生成新ID
    new AudioTrack.Builder()
            .setSessionId(playbackSessionId) // 设置独立的会话ID
            // ... 其他参数
            .build();
    
  3. 检查并配置AEC:确认AcousticEchoCanceler是否正确附加到了AudioRecord的音频会话上。虽然您已启用AEC,但可以尝试在播放端也为同一个音频会话ID创建并启用一个AEC实例(如果API支持),这有助于系统更精确地协调回声消除处理,有时可以改善音量平衡。

    // 在播放端尝试(需验证API可用性)
    if (AcousticEchoCanceler.isAvailable()) {
        Aec aec = AcousticEchoCanceler.create(audioTrack.getAudioSessionId());
        if (aec != null) {
            aec.setEnabled(true);
        }
    }
    
  4. 音量独立控制:在通信过程中,通过AudioManagersetStreamVolume接口,独立调整STREAM_VOICE_CALLSTREAM_MUSIC(取决于您最终设置的Usage)的音量级别,以补偿被压制的部分。

总结:您遇到的现象是通信音频栈中AEC工作的副作用。请优先尝试方案1(更改Usage和Mode)和方案2(分离会话ID),这通常能有效解决问题。方案3和4可作为补充调整。由于这是底层音频策略行为,应用层需要通过正确的参数配置来引导系统做出符合预期的音频处理。

回到顶部