You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Xamarin.Forms(仅Android)如何播放TCP/UDP网络传输的音频流

完全可以实现,你推测的基于AudioTrack封装的方案就是最优方案,不需要引入额外第三方依赖,性能和可控性都最好,逻辑和你之前在WPF中用NAudio的实现完全对齐,具体实现如下:

实现逻辑对齐

你之前WPF端的逻辑可以直接映射到Android端:

  • WaveOut 对应 Android 原生的 AudioTrack
  • BufferedWaveProvider 对应自行实现的线程安全环形缓冲区
  • 接收TCP/UDP数据解码后喂到缓冲区的逻辑完全不变

具体实现步骤

因为你的项目仅需适配Android,直接在Xamarin.Android平台项目中实现逻辑,再通过Xamarin.Forms依赖服务暴露给共享层调用即可。

1. 声明核心变量

private AudioTrack _audioTrack;
private CircularBuffer _circularBuffer; // 线程安全环形缓冲区
private bool _isPlaying;
private Thread _playbackThread;

// 以下参数需和你解码器输出的PCM参数完全匹配,此处为示例值
private const int SampleRate = 44100;
private readonly ChannelOut ChannelConfig = ChannelOut.Mono;
private readonly Encoding AudioEncoding = Encoding.Pcm16bit;

2. 初始化播放组件

对应WPF端的WaveOut初始化逻辑:

public void InitPlayback()
{
    // 计算AudioTrack所需的最小缓冲区大小
    int minBufferSize = AudioTrack.GetMinBufferSize(SampleRate, ChannelConfig, AudioEncoding);
    // 环形缓冲区大小建议设为最小缓冲区的2-4倍,避免播放卡顿
    _circularBuffer = new CircularBuffer(minBufferSize * 3);
    // 初始化AudioTrack,使用流模式适配实时播放场景
    _audioTrack = new AudioTrack(
        streamType: Stream.Music,
        sampleRateInHz: SampleRate,
        channelConfig: ChannelConfig,
        audioFormat: AudioEncoding,
        bufferSizeInBytes: minBufferSize,
        mode: Mode.Stream);
    
    // 启动播放线程
    _isPlaying = true;
    _playbackThread = new Thread(PlaybackLoop)
    {
        IsBackground = true
    };
    _playbackThread.Start();
    _audioTrack.Play();
}

3. 实现播放循环

单独开线程从环形缓冲区拉取数据喂给AudioTrack:

private void PlaybackLoop()
{
    var readBuffer = new byte[1024];
    while (_isPlaying)
    {
        if (_circularBuffer.AvailableBytes >= readBuffer.Length)
        {
            int readCount = _circularBuffer.Read(readBuffer, 0, readBuffer.Length);
            _audioTrack.Write(readBuffer, 0, readCount);
        }
        else
        {
            // 缓冲区数据不足时休眠,避免空转占用CPU
            Thread.Sleep(10);
        }
    }
}

4. 接收网络数据后的处理逻辑

和你WPF端的逻辑几乎完全一致:

// 从TCP/UDP套接字拿到原始字节后,先走你原有解码器逻辑
byte[] decodedPcm = codec.Decode(receivedData, 0, receivedData.Length);
// 把解码后的PCM数据写入环形缓冲区即可
_circularBuffer.Write(decodedPcm, 0, decodedPcm.Length);

5. 资源释放逻辑

停止播放或页面销毁时需要主动释放资源:

public void StopPlayback()
{
    _isPlaying = false;
    _playbackThread?.Join();
    
    if (_audioTrack != null)
    {
        if (_audioTrack.PlayState == PlayState.Playing)
        {
            _audioTrack.Stop();
        }
        _audioTrack.Release();
        _audioTrack.Dispose();
    }
    
    _circularBuffer?.Dispose();
}

注意事项

  • 环形缓冲区需要保证线程安全,因为写操作运行在网络接收线程,读操作运行在播放线程,操作时需要加锁避免并发冲突。如果不想自行实现,可以直接使用System.Buffers库提供的线程安全环形缓冲区实现。
  • AudioTrack初始化参数(采样率、声道数、位深)必须和解码器输出的PCM参数完全匹配,否则会出现杂音、播放速度异常等问题。
  • 网络接收和解码逻辑建议放在独立的后台线程运行,避免阻塞主线程导致播放卡顿。

内容的提问来源于stack exchange,提问作者nighthawk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 23:45:04