Skip to content

Commit

Permalink
Let TrackSDL2Player use ArrayPool
Browse files Browse the repository at this point in the history
  • Loading branch information
hwsmm committed Dec 24, 2023
1 parent 4f341d2 commit 89ab405
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 73 deletions.
33 changes: 3 additions & 30 deletions osu.Framework/Audio/ResamplingPlayer.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using NAudio.Dsp;

namespace osu.Framework.Audio
Expand Down Expand Up @@ -31,8 +30,8 @@ public double RelativeRate
/// <summary>
/// Creates a new <see cref="ResamplingPlayer"/>.
/// </summary>
/// <param name="srcRate">Sampling rate of audio that's given from <see cref="GetRemainingRawFloats(float[], int, int)"/> or <see cref="GetRemainingRawBytes(byte[])"/></param>
/// <param name="srcChannels">Channels of audio that's given from <see cref="GetRemainingRawFloats(float[], int, int)"/> or <see cref="GetRemainingRawBytes(byte[])"/></param>
/// <param name="srcRate">Sampling rate of audio that's given from <see cref="GetRemainingRawFloats(float[], int, int)"/></param>
/// <param name="srcChannels">Channels of audio that's given from <see cref="GetRemainingRawFloats(float[], int, int)"/></param>
protected ResamplingPlayer(int srcRate, byte srcChannels)
{
SrcRate = srcRate;
Expand Down Expand Up @@ -105,32 +104,6 @@ public virtual int GetRemainingSamples(float[] data)
return 0;
}

// must implement either (preferably float one)

private byte[]? bytes;

protected virtual int GetRemainingRawFloats(float[] data, int offset, int needed)
{
if (bytes == null || needed * 4 != bytes.Length)
bytes = new byte[needed * 4];

int got = GetRemainingRawBytes(bytes);

if (got > 0) Buffer.BlockCopy(bytes, 0, data, offset * 4, got);
return got / 4;
}

private float[]? floats;

protected virtual int GetRemainingRawBytes(byte[] data)
{
if (floats == null || data.Length / 4 != floats.Length)
floats = new float[data.Length / 4];

int got = GetRemainingRawFloats(floats, 0, floats.Length);

if (got > 0) Buffer.BlockCopy(floats, 0, data, 0, got * 4);
return got * 4;
}
protected abstract int GetRemainingRawFloats(float[] data, int offset, int needed);
}
}
6 changes: 3 additions & 3 deletions osu.Framework/Audio/Track/TempoSDL2AudioPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,11 @@ private void setTempo(double tempo)
{
if (AudioData != null)
{
int latency = GetTempoLatencyInSamples() * 4 * SrcChannels;
long temp = !ReversePlayback ? AudioData.Position - latency : AudioData.Position + latency;
int latency = GetTempoLatencyInSamples() * SrcChannels;
long temp = !ReversePlayback ? AudioDataPosition - latency : AudioDataPosition + latency;

if (temp >= 0)
AudioData.Position = temp;
AudioDataPosition = temp;
}

Reset(false);
Expand Down
141 changes: 101 additions & 40 deletions osu.Framework/Audio/Track/TrackSDL2AudioPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See the LICENCE file in the repository root for full licence text.

using System;
using System.IO;
using System.Buffers;
using osu.Framework.Logging;

namespace osu.Framework.Audio.Track
Expand All @@ -26,21 +26,25 @@ internal class TrackSDL2AudioPlayer : ResamplingPlayer, IDisposable
/// </summary>
/// <param name="bytePos">A byte position to convert</param>
/// <returns></returns>
public double GetMsFromBytes(long bytePos) => bytePos * 1000.0d / SrcRate / SrcChannels / 4;
public double GetMsFromIndex(long bytePos) => bytePos * 1000.0d / SrcRate / SrcChannels;

/// <summary>
/// Returns a position in milliseconds converted from a byte position with configuration set for this player.
/// </summary>
/// <param name="seconds">A position in milliseconds to convert</param>
/// <returns></returns>
public long GetBytesFromMs(double seconds) => (long)(seconds / 1000.0d * SrcRate) * SrcChannels * 4;
public long GetIndexFromMs(double seconds) => (long)(seconds / 1000.0d * SrcRate) * SrcChannels;

/// <summary>
/// Stores raw audio data.
/// </summary>
protected MemoryStream? AudioData;
protected float[]? AudioData;

public long AudioDataLength => AudioData?.Length ?? 0;
protected long AudioDataPosition;

private bool dataRented;

public long AudioDataLength { get; private set; }

/// <summary>
/// Play backwards if set to true.
Expand All @@ -59,16 +63,55 @@ public TrackSDL2AudioPlayer(int rate, byte channels)
isLoaded = false;
}

// To copy data with long offset with one method
private unsafe void copyData(float[] src, long srcOffset, float[] dst, long dstOffset, long length)
{
if (length <= 0)
return;

fixed (float* srcPtr = src)
fixed (float* dstPtr = dst)
{
Buffer.MemoryCopy(srcPtr + srcOffset, dstPtr + dstOffset, (dst.LongLength - dstOffset) * sizeof(float), length * sizeof(float));
}
}

private void prepareArray(long wanted)
{
if (wanted <= AudioData?.LongLength)
return;

float[] temp;
bool rent;

if (wanted > int.MaxValue)
{
rent = false;
temp = new float[wanted];
}
else
{
rent = true;
temp = ArrayPool<float>.Shared.Rent((int)wanted);
}

if (AudioData != null)
copyData(AudioData, 0, temp, 0, AudioDataLength);

if (dataRented && AudioData != null)
ArrayPool<float>.Shared.Return(AudioData);

AudioData = temp;
dataRented = rent;
}

internal void PrepareStream(long byteLength)
{
if (disposedValue)
return;

if (AudioData == null)
{
int len = byteLength > int.MaxValue ? int.MaxValue : (int)byteLength;
AudioData = new MemoryStream(len);
}
prepareArray(byteLength / 4);

isLoading = true;
}
Expand All @@ -81,10 +124,22 @@ internal void PutSamplesInStream(byte[] next, int length)
if (AudioData == null)
throw new InvalidOperationException($"Use {nameof(PrepareStream)} before calling this");

long save = AudioData.Position;
AudioData.Position = AudioData.Length;
AudioData.Write(next, 0, length);
AudioData.Position = save;
int floatLen = length / sizeof(float);

if (AudioDataLength + floatLen > AudioData.LongLength)
prepareArray(AudioDataLength + floatLen);

unsafe // Most standard functions doesn't support long
{
fixed (float* dest = AudioData)
fixed (void* ptr = next)
{
float* src = (float*)ptr;
Buffer.MemoryCopy(src, dest + AudioDataLength, (AudioData.LongLength - AudioDataLength) * sizeof(float), length);
}
}

AudioDataLength += floatLen;
}

internal void DonePutting()
Expand All @@ -100,12 +155,12 @@ internal void DonePutting()
isLoaded = true;
}

protected override int GetRemainingRawBytes(byte[] data)
protected override int GetRemainingRawFloats(float[] data, int offset, int needed)
{
if (AudioData == null)
return 0;

if (AudioData.Length <= 0)
if (AudioDataLength <= 0)
{
done = true;
return 0;
Expand All @@ -114,13 +169,13 @@ protected override int GetRemainingRawBytes(byte[] data)
if (SaveSeek > 0)
{
// set to 0 if position is over saved seek
if (AudioData.Position > SaveSeek)
if (AudioDataPosition > SaveSeek)
SaveSeek = 0;

// player now has audio data to play
if (AudioData.Length > SaveSeek)
if (AudioDataLength > SaveSeek)
{
AudioData.Position = SaveSeek;
AudioDataPosition = SaveSeek;
SaveSeek = 0;
}

Expand All @@ -129,35 +184,34 @@ protected override int GetRemainingRawBytes(byte[] data)
return 0;
}

int read = data.Length;
int read;

if (ReversePlayback)
{
int frameSize = SrcChannels * 4;

if (AudioData.Position < read)
read = (int)AudioData.Position;

byte[] temp = new byte[read];

AudioData.Position -= read;
read = AudioData.Read(temp, 0, read);
AudioData.Position -= read;

for (int e = 0; e < read / frameSize; e++)
for (read = 0; read < needed; read++)
{
Buffer.BlockCopy(temp, read - frameSize * (e + 1), data, frameSize * e, frameSize);
if (AudioDataPosition < 0)
{
AudioDataPosition = 0;
break;
}

data[read + offset] = AudioData[AudioDataPosition--];
}
}
else
{
read = AudioData.Read(data, 0, read);
long remain = AudioDataLength - AudioDataPosition;
read = remain > needed ? needed : (int)remain;

copyData(AudioData, AudioDataPosition, data, offset, read);
AudioDataPosition += read;
}

if (read < data.Length && isLoading)
if (read < needed && isLoading)
Logger.Log("Track underrun!");

if (ReversePlayback ? AudioData.Position <= 0 : AudioData.Position >= AudioData.Length && !isLoading)
if (ReversePlayback ? AudioDataPosition <= 0 : AudioDataPosition >= AudioDataLength && !isLoading)
done = true;

return read;
Expand Down Expand Up @@ -187,8 +241,8 @@ public double GetCurrentTime()
return 0;

return !ReversePlayback
? GetMsFromBytes(AudioData.Position) - GetProcessingLatency()
: GetMsFromBytes(AudioData.Position) + GetProcessingLatency();
? GetMsFromIndex(AudioDataPosition) - GetProcessingLatency()
: GetMsFromIndex(AudioDataPosition) + GetProcessingLatency();
}

protected long SaveSeek;
Expand All @@ -201,7 +255,7 @@ public double GetCurrentTime()
/// <param name="seek">Position in milliseconds</param>
public virtual void Seek(double seek)
{
long tmp = GetBytesFromMs(seek);
long tmp = GetIndexFromMs(seek);

if (!isLoaded && tmp > AudioDataLength)
{
Expand All @@ -210,7 +264,7 @@ public virtual void Seek(double seek)
else if (AudioData != null)
{
SaveSeek = 0;
AudioData.Position = Math.Clamp(tmp, 0, AudioDataLength - 1);
AudioDataPosition = Math.Clamp(tmp, 0, AudioDataLength - 1);
Flush();
}
}
Expand All @@ -223,14 +277,21 @@ protected virtual void Dispose(bool disposing)
{
if (disposing)
{
AudioData?.Dispose();
if (dataRented && AudioData != null)
ArrayPool<float>.Shared.Return(AudioData);

AudioData = null;
}

disposedValue = true;
}
}

~TrackSDL2AudioPlayer()
{
Dispose(false);
}

public void Dispose()
{
Dispose(true);
Expand Down

0 comments on commit 89ab405

Please sign in to comment.