启用扬声器时麦克风音频失真 (Xamarin.iOS)

如何解决启用扬声器时麦克风音频失真 (Xamarin.iOS)

我正在维护一键通 VoIP 应用程序。 当 PTT 呼叫正在运行时,应用创建音频会话

m_AudioSession = AVAudioSession.SharedInstance();

NSError error;
if (!m_AudioSession.SetCategory(AVAudioSession.CategoryPlayAndRecord,AVAudioSessionCategoryOptions.DefaultToSpeaker | AVAudioSessionCategoryOptions.AllowBluetooth,out error))
{
    IOSErrorLogger.Log(dammLoggerLevel.Error,TAG,error,"Error setting the category");
}

if (!m_AudioSession.SetMode(AVAudioSession.ModeVoiceChat,"Error setting the mode");
}

if (!m_AudioSession.OverrideOutputAudioPort(AVAudioSessionPortOverride.Speaker,"Error redirecting the audio to the loudspeaker");
}

if (!m_AudioSession.SetPreferredioBufferDuration(0.06,out error)) // 60 milli seconds
{
    IOSErrorLogger.Log(dammLoggerLevel.Error,"Error setting the preferred buffer duration");
}

if (!m_AudioSession.SetPreferredSampleRate(8000,out error)) // kHz
{
    IOSErrorLogger.Log(dammLoggerLevel.Error,"Error setting the preferred sample rate");
}

if (!m_AudioSession.SetActive(true,"Error activating the audio session");
}

使用 OutputAudioQueue 播放接收到的音频,并使用语音处理 I/O 单元捕获麦克风音频(如 Apple 文档中所述:https://developer.apple.com/documentation/avfaudio/avaudiosession/mode/1616455-voicechat)。 语音处理I/O单元的初始化代码为:

            AudioStreamBasicDescription audioFormat = new AudioStreamBasicDescription()
            {
                SampleRate = SAMPLERATE_8000,Format = AudioFormatType.LinearPCM,FormatFlags = AudioFormatFlags.LinearPCMIsSignedInteger | AudioFormatFlags.LinearPCMIsPacked,FramesPerPacket = 1,ChannelsPerFrame = CHANNELS,BitsPerChannel = BITS_X_SAMPLE,BytesPerPacket = BYTES_X_SAMPLE,BytesPerFrame = BYTES_X_FRAME,Reserved = 0
            };

            AudioComponent audioComp = AudioComponent.FindComponent(AudioTypeOutput.VoiceProcessingIO);
            AudioUnit.AudioUnit voiceProcessing = new AudioUnit.AudioUnit(audioComp);

            AudioUnitStatus unitStatus = AudioUnitStatus.NoError;

            unitStatus = voiceProcessing.SetEnableIO(true,AudioUnitScopeType.Input,ELEM_Mic);
            if (unitStatus != AudioUnitStatus.NoError)
            {
                dammLogger.Log(dammLoggerLevel.Warn,"Audio Unit SetEnableIO(true,ELEM_Mic) returned: {0}",unitStatus);
            }

            unitStatus = voiceProcessing.SetEnableIO(true,AudioUnitScopeType.Output,ELEM_Speaker);
            if (unitStatus != AudioUnitStatus.NoError)
            {
                dammLogger.Log(dammLoggerLevel.Warn,"Audio Unit SetEnableIO(false,ELEM_Speaker) returned: {0}",unitStatus);
            }


            unitStatus = voiceProcessing.SetFormat(audioFormat,"Audio Unit SetFormat (MIC-OUTPUT) returned: {0}",unitStatus);
            }

            unitStatus = voiceProcessing.SetFormat(audioFormat,"Audio Unit SetFormat (ELEM 0-INPUT) returned: {0}",unitStatus);
            }

            unitStatus = voiceProcessing.SetRenderCallback(AudioUnit_RenderCallback,"Audio Unit SetRenderCallback returned: {0}",unitStatus);
            }
            
            ...
            
            voiceProcessing.Initialize();
            voiceProcessing.Start();

                
                

RenderCallback 函数是:

private AudioUnitStatus AudioUnit_RenderCallback(AudioUnitRenderActionFlags actionFlags,AudioTimeStamp timeStamp,uint busNumber,uint numberFrames,AudioBuffers data)
{
    AudioUnit.AudioUnit voiceProcessing = m_VoiceProcessing;
    if (voiceProcessing != null)
    {
        // getting microphone input signal
        var status = voiceProcessing.Render(ref actionFlags,timeStamp,ELEM_Mic,numberFrames,data);
        if (status != AudioUnitStatus.OK)
        {
            return status;
        }

        if (data.Count > 0)
        {
            unsafe
            {
                short* samples = (short*)data[0].Data.ToPointer();

                for (uint idxSrcFrame = 0; idxSrcFrame < numberFrames; idxSrcFrame++)
                {
                   ... send the collected microphone audio (samples[idxSrcFrame])
                }
            }
        }
    }

    return AudioUnitStatus.NoError;
}

    
    

我面临的问题是,如果启用了扬声器:m_AudioSession.OverrideOutputAudioPort(AVAudioSessionPortOverride.Speaker,out error) 那么麦克风音频已损坏(有时无法理解语音)。 如果未启用扬声器(未设置 AVAudioSessionPortOverride.Speaker),则音频非常好。

我已经验证过Render函数返回的AudioBuffer中的NumberChannels为1(单声道音频)。

任何帮助解决问题的点击都非常感谢。谢谢

更新: AudioUnit_RenderCallback 方法每 32 毫秒调用一次。当扬声器被禁用时,接收到的帧数是 256,这是准确的(采样率为 8000)。启用扬声器时,接收到的帧数为 85。 在这两种情况下,GetAudioFormat 都返回预期值:BitsPerChannel=16、BytesPerFrame=2、FramesPerPacket=1、ChannelsPerFrame=1、SampleRate=8000

更新: 我最终使用来自硬件的采样率并执行下采样自我。必须理解音频单元应该能够执行下采样 https://developer.apple.com/library/archive/documentation/MusicAudio/Conceptual/AudioUnitHostingGuide_iOS/AudioUnitHostingFundamentals/AudioUnitHostingFundamentals.html#//apple_ref/doc/uid/TP40009492-CH3-SW11)) 但在启用扬声器时我无法使其工作。

解决方法

我希望您是在实际设备而不是模拟器上进行测试。

在代码中,你有没有试过使用这个:

sampleRate = AudioSession.CurrentHardwareSampleRate;

与其强制采样率,不如从硬件检查采样率。可能是在扬声器使用过程中,它改变了采样率,从而产生了问题。

我建议根据上述更改进行录制,看看音频是否有所改善,然后尝试使用其他标志。

标准录音模式https://docs.microsoft.com/en-us/dotnet/api/audiotoolbox.audiostreambasicdescription?view=xamarin-ios-sdk-12#remarks

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?