音轨采样率不一致

30

使用AudioTrack进行播放时,有时需要重新采样不符合AudioTrack支持的采样率的音频。在这样做时,我需要确定当前设备下当前音频配置下AudioTrack支持的最大采样率。

由于AudioTrack允许的采样率文档记录不够完整,因此我决定查看AudioTrack源代码,并发现了这一惊人的代码行:

private static final int SAMPLE_RATE_HZ_MAX = 96000;
似乎“AudioTrack”实例正在应用一个96 KHz的硬限制,而不考虑设备的实际播放能力。 更令人困惑的是,在“AudioFormat”类中,我传递给“AudioTrack”的构造函数(API 21)的内容中包含这行代码:
if ((sampleRate <= 0) || (sampleRate > 192000)) {

setSampleRate()方法中,设定了最高的采样率为192 KHz。因此,试图将大于192 KHz的采样率传递给AudioFormat(或其构建器)将导致AudioFormat抛出IllegalArgumentException异常,而将配置为192 KHz<x<96 KHz采样率的AudioFormat传递到AudioTrack也会抛出IllegalArgumentException异常。


至今为止,我发现最令人困惑的是AudioTrack中的getNativeOutputSampleRate()方法,它实际上返回正确的输出采样率(出于直接从本地层运行的原因,并不令人惊讶,但仍然非常不一致)。

为了更完整地说明这个问题,让我们来看一下声称:

有效的采样率范围从1 Hz到由getNativeOutputSampleRate(int)返回值的两倍。

实际上,我尝试了一下,它确实可行?请考虑以下代码片段:

int nativeRate = AudioTrack.getNativeOutputSampleRate(AudioManager.STREAM_MUSIC);

android.util.Log.i("UI", "Native stream rate: " + nativeRate + " Hz");

// Build audio attributes

AudioAttributes.Builder attribBuilder = new AudioAttributes.Builder();

attribBuilder.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC);
attribBuilder.setUsage(AudioAttributes.USAGE_MEDIA);

AudioAttributes attrib = attribBuilder.build();

// Build audio format

AudioFormat.Builder afBuilder = new AudioFormat.Builder();

afBuilder.setChannelMask(AudioFormat.CHANNEL_OUT_STEREO);
afBuilder.setEncoding(AudioFormat.ENCODING_PCM_16BIT);
afBuilder.setSampleRate(nativeRate);

try{
    AudioTrack trackTest = new AudioTrack(attrib, afBuilder.build(), nativeRate, AudioTrack.MODE_STREAM, 0);

    android.util.Log.i("UI", "Track created successfully (direct)");
}catch(Exception ex){
    android.util.Log.w("UI", "Failed to create AudioTrack at native rate!");

    // Use a random supported samplerate to get pass constructor
    afBuilder.setSampleRate(48000);

    try{
        AudioTrack trackTest = new AudioTrack(attrib, afBuilder.build(), nativeRate, AudioTrack.MODE_STREAM, 0);

        trackTest.setPlaybackRate(nativeRate);

        android.util.Log.i("UI", "Track created successfully (indirect)");
    }catch(Exception e){
        android.util.Log.w("UI", "Failed to create AudioTrack at 48 KHz");
    }
}

根据程序流程,当本地采样率< 96 KHz时,代码会打印出:

本机流速率:48000 Hz
轨道创建成功(直接)

但是,当我连接一个支持最高播放速度为192 KHz的外部DAC时,输出为:

本机流速率:192000 Hz
无法以本机速率创建AudioTrack!
轨道创建成功(间接)

这些差异是什么原因?而且,setPlaybackRate()是否与传递到构造函数中的采样率相同?


6
您可以考虑向https://code.google.com/p/android/issues/list提交问题,反映这些不一致性(我可以确认,在API 22的源代码中也存在这些问题)。关于“setPlaybackRate()”,它明显绕过了您提到的(不一致的)检查,并直接在本地端设置播放速度。此外,查看核心/jni/android_media_AudioTrack.cpp的历史记录可能会很有趣。 - Aladin Q
1
@AladinQ 感谢您提供源链接,我不知道它们包含本地层源代码,这应该有助于调试问题。考虑到可能没有任何问题(仅是文档错误或未记录的行为),我将暂时不将其列为问题。这种情况以前也发生过... 我会从本地源代码中看看能得出什么。 - initramfs
1个回答

2
目前市场上大多数安卓手机仅支持一种采样率。我相信某些三星手机以48kHz播放,几乎所有其他手机以44.1kHz播放。这些值由硬件决定,虽然有一个更改本机速率的设施,但其主要功能是在运行时对所有音频进行重新采样,以备未来使用。这是一项昂贵的任务,并且也有些破坏性。192kHz(= 2 * 96kHz)存在硬性限制的原因很可能是因为发送超过最大频率(96kHz)的两倍是一种巨大的资源浪费,因为您可以将每个第二个样本直接丢弃以有效地降低采样率至该范围内。

最好避免指定非本机采样率。它将在软件中重新采样,并且最好情况下是资源浪费,最坏情况下是延迟的来源。

或者听听Google工程师的说法


感谢提供链接,我很快就会观看。然而,正如我所提到的,Android设备能够将音频投射到外部源,例如USB外部DAC。我拥有这样的DAC,当我连接它时,Android会协商出一个本地速率为192 KHz(通过系统日志以及我的DAC上的采样率指示器来表示)。确定本地速率的努力是为了在不必要时最小化重新采样的性能成本。如果可能的话,应该保持96 KHz+的源文件不变(即直接通过而不进行重新采样),以最大程度地延长电池寿命。 - initramfs
啊,当你谈论USB时,我确实认为自己可能错过了什么。 https://gitlab.com/SaberMod/pa-android-frameworks-base/commit/70b395e8c1d06ca9288afd418b9e889df4060eab 哈哈,去年已经“修复”了48kHz的限制,修复为96kHz。 我想Aladin Q的评论是正确的答案! 原因在于: if (sampleRateInHz < SAMPLE_RATE_HZ_MIN || sampleRateInHz > SAMPLE_RATE_HZ_MAX) { throw new IllegalArgumentException(sampleRateInHz + "Hz is not a supported sample rate."); } - Andrew Gallasch

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接