如何在.NET Android平台上使用AudioTrack实现PCM播放

移动开发 2026-07-11

我在尝试实现一个简单的PCM播放器,用来独立播放一些合成音符。

  public void Play()
  {
      var playToken = new CancellationTokenSource();
      playToken.CancelAfter(3000);
      Task.Run(() =>
      {
          var left = new float[1024]; //smaller buffer because rendering to larger ones causes android to freeze
          var right = new float[1024];

          var minBufferSize = AudioTrack.GetMinBufferSize(
              SAMPLE_RATE,
              ChannelOut.Stereo,
              AndroidMedia.Encoding.Pcm16bit);

          var audioAttributes = new AudioAttributes.Builder()
              .SetUsage(AudioUsageKind.Media)
              .SetContentType(AudioContentType.Music)
              .Build()!;

          var audioFormat = new AudioFormat.Builder()
              .SetEncoding(AndroidMedia.Encoding.Pcm16bit)
              .SetSampleRate(SAMPLE_RATE)
              .SetChannelMask(ChannelOut.Stereo)
              .Build()!;

          _audioTrack = new AudioTrack.Builder()
              .SetAudioAttributes(audioAttributes)
              .SetAudioFormat(audioFormat)
              .SetTransferMode(AudioTrackMode.Stream)
              .SetBufferSizeInBytes(minBufferSize)
              .Build();

          _audioTrack.Play();

          _inner.NoteOn(0, 60, 100); //generate noteon event

          while (!playToken.IsCancellationRequested)
          {
            lock(mutex){
              _inner.Render(left, right); //render to left and right
              var pcm = Helper.InterlacePcm(left, right); //interlace to one buffer
              _audioTrack.Write(pcm, 0, pcm.Length); //write to AudioTrack sync
            }
          }
      });
  }

public static short[] InterlacePcm(float[] left, float[] right)
{
    var pcm = new short[left.Length * 2];
    for (int i = 0; i < left.Length; i++)
    {
        pcm[i * 2] = (short)(Math.Clamp(left[i], -1f, 1f) * short.MaxValue);
        pcm[i * 2 + 1] = (short)(Math.Clamp(right[i], -1f, 1f) * short.MaxValue);
    }

    return pcm;
}

我们的目标是在需要的时候多次调用Play,因此每次调用都会创建它自己的AudioTrack实例。synth_inner是 MetylSynth的一个实例,用于生成一些简单的PCM波形。它的表现相当不稳定,声音会不断缓冲,在连续多次调用Play时无论间隔多久都会卡顿。如果您能帮忙把这个问题解决,我们将不胜感激。

解决方案

结果发现主要的问题是.NET调试器让运行变慢。未调试时听起来没问题。下面是改进后的代码:

internal class AudioTrackDriver : IAudioDriver
{
    public static int SAMPLE_RATE = 44100;
    private MeltySynth.Synthesizer _inner;
    private Task PlayTask;
    private AudioTrack _audioTrack;
    object mutex = new object();
    private float[] _left;
    private float[] _right;
    private float[] _interlaced;

    public AudioTrackDriver(string soundFont)
    {
        _inner = new MeltySynth.Synthesizer(soundFont, SAMPLE_RATE);
        var minBufferSize = AudioTrack.GetMinBufferSize(
             SAMPLE_RATE,
             ChannelOut.Stereo,
             AndroidMedia.Encoding.PcmFloat);
        var audioAttributes = new AudioAttributes.Builder()
            .SetUsage(AudioUsageKind.Media)
            .SetContentType(AudioContentType.Music)
            .Build()!;

        var audioFormat = new AudioFormat.Builder()
            .SetEncoding(AndroidMedia.Encoding.PcmFloat)
            .SetSampleRate(SAMPLE_RATE)
            .SetChannelMask(ChannelOut.Stereo)
            .Build()!;
        int blocksize = 1024;
        Debug.WriteLine($"Min buffer size of {minBufferSize}");
        _audioTrack = new AudioTrack.Builder()
            .SetAudioAttributes(audioAttributes)
            .SetAudioFormat(audioFormat)
            .SetTransferMode(AudioTrackMode.Stream)
            .SetBufferSizeInBytes(minBufferSize * 4)
            .Build();
        _left = new float[blocksize];
        _right = new float[blocksize];
        _interlaced = new float[blocksize * 2];
        _audioTrack.Play();
        Task.Factory.StartNew(WriteToSink, TaskCreationOptions.LongRunning);

    }

    public void Play()
    {
        Task.Run(async () =>
        {
            lock (mutex)
            {
                _inner.NoteOn(0, 60, 100);
            }
            await Task.Delay(1500);
            lock (mutex)
            {
                _inner.NoteOffAll(false);
            }
        });
    }
    public void WriteToSink()
    {
        while (true)
        {
            lock (mutex)
            {
                _inner.RenderInterleaved(_interlaced);
            }
            _audioTrack.Write(_interlaced, 0, _interlaced.Length, WriteMode.Blocking);
        }
    }
    public void Stop()
    {

    }
}
站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章