Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 42 additions & 5 deletions Runtime/Scripts/AudioStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ private void OnAudioStreamEvent(AudioStreamEvent e)
if (e.MessageCase != AudioStreamEvent.MessageOneofCase.FrameReceived)
return;

var frame = new AudioFrame(e.FrameReceived.Frame);
//var frame = new AudioFrame(e.FrameReceived.Frame);

lock (_lock)
{
Expand All @@ -105,15 +105,52 @@ private void OnAudioStreamEvent(AudioStreamEvent e)

unsafe
{
var uFrame = _resampler.RemixAndResample(frame, _numChannels, _sampleRate);
if (uFrame != null)
// Change deal with issue in newer LiveKit where channels are returned incorrectly
// -https://github.com/livekit/client-sdk-unity/issues/169
// (plus some new changes to reduce garbage creation)
if (e.FrameReceived.Frame.Info.NumChannels == 1 && _numChannels == 2)
{
var data = new Span<byte>(uFrame.Data.ToPointer(), uFrame.Length);
_buffer?.Write(data);

int samplesPerChannel = (int)e.FrameReceived.Frame.Info.SamplesPerChannel;
int monoLengthBytes =
(int)(e.FrameReceived.Frame.Info.SamplesPerChannel *
e.FrameReceived.Frame.Info.NumChannels *
sizeof(short));

// Span over the incoming mono audio bytes
var monoByteSpan = new ReadOnlySpan<byte>(
(void*)e.FrameReceived.Frame.Info.DataPtr,
monoLengthBytes);

// Treat them as 16-bit samples
var monoSamples = MemoryMarshal.Cast<byte, short>(monoByteSpan);

// Allocate stereo buffer on the stack: 2 channels * samples * sizeof(short)
Span<short> stereoSamples = stackalloc short[samplesPerChannel * 2];

for (int i = 0; i < samplesPerChannel; i++)
{
short sample = monoSamples[i]; // mono
int dstIndex = i * 2;

stereoSamples[dstIndex] = sample; // Left
stereoSamples[dstIndex + 1] = sample; // Right
}

// Cast the stereo short span to bytes and write to the buffer
ReadOnlySpan<byte> stereoBytes = MemoryMarshal.AsBytes(stereoSamples);
_buffer?.Write(stereoBytes);
}
else
{
//TODO add support here if they fix the above bug
}

}
}
// Change - need to drop handle here because it would normally be done
// within AudioFrame, without memory will be leaked on the plugin side
NativeMethods.FfiDropHandle((IntPtr)e.FrameReceived.Frame.Handle.Id);
}

public void Dispose()
Expand Down
17 changes: 13 additions & 4 deletions Runtime/Scripts/Internal/FFIClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public void Release(FfiResponse response)
ffiResponsePool.Release(response);
}

public FfiResponse SendRequest(FfiRequest request)
public FfiResponse SendRequest(FfiRequest request, bool requiresResponse = true)
{
try
{
Expand All @@ -203,9 +203,18 @@ public FfiResponse SendRequest(FfiRequest request)
out UIntPtr dataLen
);
var dataSpan = new Span<byte>(dataPtr, (int)dataLen.ToUInt64());
var response = responseParser.ParseFrom(dataSpan)!;
NativeMethods.FfiDropHandle(handle);
return response;

if (requiresResponse)
{
var response = responseParser.ParseFrom(dataSpan)!;
NativeMethods.FfiDropHandle(handle);
return response;
}
else
{
NativeMethods.FfiDropHandle(handle);
return null;
}
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion Runtime/Scripts/Internal/FFIClients/IFFIClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace LiveKit.Internal.FFIClients
/// </summary>
public interface IFFIClient : IDisposable
{
FfiResponse SendRequest(FfiRequest request);
FfiResponse SendRequest(FfiRequest request, bool requireResponse = true);

void Release(FfiResponse response);
}
Expand Down
4 changes: 2 additions & 2 deletions Runtime/Scripts/Internal/FFIClients/Requests/FFIBridge.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ public class FFIBridge : IFFIBridge

public static FFIBridge Instance => instance.Value;

private readonly IFFIClient ffiClient;
private readonly IMultiPool multiPool;
public readonly IFFIClient ffiClient;
public readonly IMultiPool multiPool;

public FFIBridge(IFFIClient client, IMultiPool multiPool)
{
Expand Down
38 changes: 30 additions & 8 deletions Runtime/Scripts/RtcAudioSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,26 +126,37 @@ static short FloatToS16(float v)
for (int i = 0; i < data.Length; i++)
_frameData[i] = FloatToS16(data[i]);

//Change - hand to make FFIBridge properties public to do this which isn't great
// Capture the frame.
using var request = FFIBridge.Instance.NewRequest<CaptureAudioFrameRequest>();
using var audioFrameBufferInfo = request.TempResource<AudioFrameBufferInfo>();
//using var request = FFIBridge.Instance.NewRequest<CaptureAudioFrameRequest>();
CaptureAudioFrameRequest pushFrame = FFIBridge.Instance.multiPool.Get<CaptureAudioFrameRequest>();
AudioFrameBufferInfo audioFrameBufferInfo = FFIBridge.Instance.multiPool.Get<AudioFrameBufferInfo>();

var pushFrame = request.request;
FfiRequest ffiRequest = FFIBridge.Instance.multiPool.Get<FfiRequest>();

// using var audioFrameBufferInfo = request.TempResource<AudioFrameBufferInfo>();

// var pushFrame = request.request;
pushFrame.SourceHandle = (ulong)Handle.DangerousGetHandle();
pushFrame.Buffer = audioFrameBufferInfo;
unsafe
{
pushFrame.Buffer.DataPtr = (ulong)NativeArrayUnsafeUtility
.GetUnsafePtr(_frameData);
pushFrame.Buffer.DataPtr = (ulong)NativeArrayUnsafeUtility
.GetUnsafePtr(_frameData);
}
pushFrame.Buffer.NumChannels = (uint)channels;
pushFrame.Buffer.SampleRate = (uint)sampleRate;
pushFrame.Buffer.SamplesPerChannel = (uint)data.Length / (uint)channels;

using var response = request.Send();
FfiResponse res = response;
// using var response = request.Send();
// FfiResponse res = response;
ffiRequest.CaptureAudioFrame = pushFrame;
FFIBridge.Instance.ffiClient.SendRequest(ffiRequest, false);

// Wait for async callback, log an error if the capture fails.

// Changes - this was creating memory because of the callback being created,
// might be able to make a call back and cache the asyncID to get around that problem
/*
var asyncId = res.CaptureAudioFrame.AsyncId;
void Callback(CaptureAudioFrameCallback callback)
{
Expand All @@ -155,6 +166,17 @@ void Callback(CaptureAudioFrameCallback callback)
FfiClient.Instance.CaptureAudioFrameReceived -= Callback;
}
FfiClient.Instance.CaptureAudioFrameReceived += Callback;
*/

ffiRequest.CaptureAudioFrame = null;
pushFrame.Buffer.ClearDataPtr();
pushFrame.Buffer = null;
ffiRequest.ClearMessage();


FFIBridge.Instance.multiPool.Release(ffiRequest);
FFIBridge.Instance.multiPool.Release(pushFrame);
FFIBridge.Instance.multiPool.Release(audioFrameBufferInfo);
}

/// <summary>
Expand Down
Loading