1

在开发具有 VoIP 功能的 Android Unity 应用程序时,我们发现在某些设备(特别是 Galaxy S6)上,麦克风会拾取扬声器输出的声音,从而导致大量的静电、失真和回声。正因为如此,我开始着手开发一个原生解决方案,以利用 Android 的一些 AEC 和 NS 功能。

我编写了以下测试平台,它可以在某些设备上完美运行,例如 Note 4。当相同的应用程序在 Galaxy S6 上运行时,就好像 ARC 和 NS 没有很好地应用或根本没有应用。

public class MainActivity extends Activity
{
    private AudioRecord mAudioRecorder = null;
    private AudioTrack mStreamingPlayer = null;

    private Thread playbackThread = null;

    private boolean useVoip = true;

    int bufferSize;
    int chunkSize;

    private AutomaticGainControl agc;
    private NoiseSuppressor suppressor;
    private  AcousticEchoCanceler aec;

    private boolean manuallyAttachEffects = false;

    private void StartRecordingAudio()
    {
        int sampleRate = 44100;
        int channelConfig = AudioFormat.CHANNEL_IN_MONO;
        int audioFormat = AudioFormat.ENCODING_PCM_16BIT;

        chunkSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
        bufferSize = chunkSize * 10;

        int audioSource = MediaRecorder.AudioSource.MIC;

        if ( useVoip && !manuallyAttachEffects )
        {
            audioSource = MediaRecorder.AudioSource.VOICE_COMMUNICATION;
        }

        mAudioRecorder = new AudioRecord( audioSource, 
                                        sampleRate,
                                        channelConfig,
                                        audioFormat,
                                        bufferSize );

        if ( useVoip && manuallyAttachEffects )
        {
            SetupVoipEffects( mAudioRecorder.getAudioSessionId() );
        }

        mAudioRecorder.startRecording();

        mStreamingPlayer = new AudioTrack(  AudioManager.STREAM_MUSIC,
                                            sampleRate,
                                            AudioFormat.CHANNEL_OUT_MONO,
                                            audioFormat,
                                            bufferSize,
                                            AudioTrack.MODE_STREAM );

        mStreamingPlayer.play();

        playbackThread = new Thread(new Runnable()
        {
            @Override
            public void run() {
                readAudioData();
            }

        });

        playbackThread.start();
    }

    private void readAudioData()
    {
        Thread thisThread = Thread.currentThread();

        byte[] buffer = new byte[chunkSize];

        while ( !thisThread.isInterrupted() )
        {
            if ( mAudioRecorder == null )
                break;

            int bytesRead = mAudioRecorder.read(buffer, 0, chunkSize);
            if ( bytesRead > 0 )
            {
                if ( mStreamingPlayer == null )
                    break;

                mStreamingPlayer.write(buffer, 0, chunkSize);   
            }
        }
    }

    private void SetupVoipEffects( int sessionId )
    {
        if(AutomaticGainControl.isAvailable())
        {
            agc = AutomaticGainControl.create( sessionId );
            Log.d(LOG_TAG, "AGC is " + (agc.getEnabled()?"enabled":"disabled"));

            if ( !agc.getEnabled() )
            {
                agc.setEnabled(true);
                Log.d(LOG_TAG, "AGC is " + (agc.getEnabled()?"enabled":"disabled" +" after trying to enable"));
            }
        }

        if(NoiseSuppressor.isAvailable())
        {
            suppressor = NoiseSuppressor.create( sessionId );
            Log.d(LOG_TAG, "NS is " + (suppressor.getEnabled()?"enabled":"disabled"));

            if ( !suppressor.getEnabled() )
            {
                suppressor.setEnabled(true);
                Log.d(LOG_TAG, "NS is " + (suppressor.getEnabled()?"enabled":"disabled" +" after trying to disable"));  
            }

        }
        if(AcousticEchoCanceler.isAvailable())
        {
            aec = AcousticEchoCanceler.create( sessionId );
            Log.d(LOG_TAG, "AEC is " + (aec.getEnabled()?"enabled":"disabled"));

            if ( !aec.getEnabled() )
            {
                aec.setEnabled(true);
                Log.d(LOG_TAG, "AEC is " + (aec.getEnabled()?"enabled":"disabled" +" after trying to disable"));
            }
        }
    }
}

我已经尝试了很多方法,例如将 VOICE_COMMUNICATION 用于 AudioRecord、使用 MIC 并手动创建 NoiseSuppressor 和 AcousticEchoCanceller、禁用/启用 NuPlayer/AwesomePlayer 等等。这些设备显示支持并启用了 AEC,但它似乎并没有发生。使用耳机或使用听筒代替免提电话时,问题完全消失(因此麦克风无法拾取扬声器声音)。我是否在这里忽略了一些巨大的东西,或者是否有可能要报告一个错误?

如果 AEC 和 NS 在其他设备上不能完美运行,我不会怀疑。

4

1 回答 1

0

这是 Android 中的一个已知问题。硬件 AEC 不可靠,许多硬件都没有实现/支持它,即使他们以其他方式报告它也是如此。我建议同时部署软件 AEC 和硬件 AEC,以使此功能在您的软件中更加稳定。唯一的问题是软件AEC不是那么容易正确完成的,所以要做好大量测试的准备。

于 2015-11-10T22:43:08.090 回答