如何在.NET Android平台上使用AudioTrack实现PCM播放
我在尝试实现一个简单的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导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。