AudioTrack:播放通过 WiFi 传入的声音

Posted

技术标签:

【中文标题】AudioTrack:播放通过 WiFi 传入的声音【英文标题】:AudioTrack: Playing sound coming in over WiFi 【发布时间】:2011-03-14 11:25:44 【问题描述】:

我的应用程序中有一个 AudioTrack,它设置为流模式。我想编写通过无线连接接收的音频。 AudioTrack 是这样声明的:

mPlayer = new AudioTrack(STREAM_TYPE,
                         FREQUENCY,
                         CHANNEL_CONFIG_OUT,
                         AUDIO_ENCODING,
                         PLAYER_CAPACITY,
                         PLAY_MODE);

参数定义如下:

private static final int FREQUENCY = 8000,
                         CHANNEL_CONFIG_OUT = AudioFormat.CHANNEL_OUT_MONO,
                         AUDIO_ENCODING = AudioFormat.ENCODING_PCM_16BIT,
                         PLAYER_CAPACITY = 2048,
                         STREAM_TYPE = AudioManager.STREAM_MUSIC,
                         PLAY_MODE = AudioTrack.MODE_STREAM;

但是,当我使用 write() 将数据写入 AudioTrack 时,它会播放断断续续...调用

byte[] audio = packet.getData();
mPlayer.write(audio, 0, audio.length);

每当通过网络连接接收到数据包时都会生成。有没有人知道为什么它听起来波涛汹涌?也许它与WiFi连接本身有关?我不这么认为,因为当我通过 UDP 将数据从 android 手机发送到另一个源时,声音听起来并不可怕。然后声音听起来很完整,一点也不断断续续……那么有人知道为什么会发生这种情况吗?

【问题讨论】:

你试过玩PLAYER_CAPACITY吗?我相信您应该使用getMinBufferSize() 来获得正确的值,如下所示:developer.android.com/reference/android/media/AudioTrack.html 我已经尝试过了...但是我从网络获得的数据通常小于我(当前)从 getMinBufferSize 在此设备上获得的 4096。将其设置为 buffersize 我再次查看了这个,在我的情况下,波动是由于大量的数据包丢失/重新传输。在一些资源有限的系统(如 Android)中,程序必须足够快地处理每个单独的 UDP 数据包,以防止数据包丢失。 【参考方案1】:

您知道每秒接收多少字节、比较数据包之间的平均时间以及数据包之间的最长时间吗?如果没有,你可以添加代码来计算它吗?

您需要平均 8000 个样本/秒 * 2 个字节/样本 = 16,000 个字节/秒,以保持流充满。

传入数据包之间的间隔超过 2048 字节/(16000 字节/秒)= 128 毫秒 将导致您的流干涸和音频断断续续。

防止它的一种方法是增加缓冲区大小(PLAYER_CAPACITY)。更大的缓冲区将更能处理传入数据包大小和速率的变化。额外稳定性的代价是在等待缓冲区初始填充时开始播放的延迟更大。

【讨论】:

粗略估计我每秒得到大约 67,200 字节。大约(大约)70 个包,价值 960 字节。我粗略地说是因为这是通过有线连接。当我通过无线时,我想我每秒会收到大约 50 个(也许多一点,也许少一点)包,每秒给我 48,000 字节。我会说这足以让它充满。 :o 经过一些调试并查看了几个 logcat,我注意到这是一个“obtainBuffer 超时”错误......我仍在尝试修复它...... 你解决过这个问题吗?请发布更新。我也有类似的问题....谢谢!!! 我最终使用了 OpenSL ES 音频 API。它包含在 NDK 开发套件中。它更底层,但在我看来它比 Java API 好一点...... @ThaMe90,OpenSL ES 解决波涛汹涌的问题有多好,你能发表一些反馈意见吗?【参考方案2】:

我通过将mPlayer.write(audio, 0, audio.length); 放在它自己的Thread 中已经部分解决了这个问题。这确实消除了一些不稳定的情况(因为 write 是一个阻塞调用),但在一两秒后它仍然听起来不稳定。它仍然有 2-3 秒的显着延迟。

new Thread()
    public void run()
        byte[] audio = packet.getData();
        mPlayer.write(audio, 0, audio.length);
    
.start();

只是一个匿名的Thread 现在正在写作......

有人知道如何解决这个问题吗?


编辑:

经过进一步的检查和调试,我注意到这是 gainBuffer 的问题。 我查看了java code of the AudioTrack 和C++ code of AudioTrack 我注意到它只能出现在 C++ 代码中。

if (__builtin_expect(result!=NO_ERROR, false)) 
    LOGW(   "obtainBuffer timed out (is the CPU pegged?) "
            "user=%08x, server=%08x", u, s);
    mAudioTrack->start(); // FIXME: Wake up audioflinger
    timeout = 1;

我注意到这段代码中有一个FIXME。 :


编辑 2:

我现在尝试了一些不同的方法,不同之处在于我缓冲接收到的数据,然后当缓冲区充满一些数据时,它被写入播放器。但是,播放器持续消耗几个周期,然后obtainBuffer timed out (is the CPU pegged?) 警告启动,并且根本没有数据写入播放器,直到它开始恢复生命......之后,它将不断将数据写入其中,直到缓冲区清空。

另一个细微的区别是我现在将文件流式传输到播放器。也就是说,以块的形式读取它,将这些块写入缓冲区。这模拟了通过 wifi 接收的包裹...

我开始怀疑这是否只是 Android 的操作系统问题,而这不是我可以自己解决的问题……有人对此有任何想法吗?


编辑 3:

我做了更多的测试,但这对我没有任何帮助。这个测试告诉我,我只有在我第一次尝试写入 AudioTrack 时才会出现延迟。这需要 1 到 3 秒才能完成。我通过使用以下代码来做到这一点:

long beforeTime = Utilities.getCurrentTimeMillis(), afterTime = 0;
mPlayer.write(data, 0, data.length);
afterTime = Utilities.getCurrentTimeMillis();
Log.e("WriteToPlayerThread", "Writing a package took " + (afterTime - beforeTime) + " milliseconds");

但是,我得到以下结果: Logcat Image http://img810.imageshack.us/img810/3453/logcatimage.png

这些表明滞后最初发生在开始,之后AudioTrack不断获取数据......我真的需要修复这个......

【讨论】:

__builtin_expect 是一个优化提示。逻辑归结为“如果出错,则写入日志,在音轨上调用 start(),并设置超时标志。” result 被设置在上一行 result = cblk->cv.waitRelative(cblk->lock, seconds(1)); 中,这似乎是对条件变量的等待。所以看起来线程安全方案有问题。 此代码中线程安全错误的进一步证据是同一函数后面的这一行:`LOGW_IF(超时,“***严重警告***获得缓冲区()超时但没有'不需要被锁定。我们恢复了,但这不应该发生 (user=%08x, server=%08x)", u, s);`

以上是关于AudioTrack:播放通过 WiFi 传入的声音的主要内容,如果未能解决你的问题,请参考以下文章

调节 Android AudioTrack 播放速度

通过WiFi在Android手机之间流式传输语音

音视频 — AudioRecorder 和 AudioTrack

Android 音视频深入 二 AudioTrack播放pcm(附源码下载)

Android音视频之AudioTrack播放音频

基于AudioTrack、AudioRecord获取分贝值、录制时长、PCM解码与编码