Skip to content

Commit 51c596b

Browse files
committed
Integrate apm
1 parent 5569acc commit 51c596b

File tree

4 files changed

+99
-13
lines changed

4 files changed

+99
-13
lines changed

Runtime/Scripts/ApmReverseStream.cs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.Threading;
2+
using LiveKit.Internal;
3+
using UnityEngine;
4+
5+
namespace LiveKit
6+
{
7+
/// <summary>
8+
/// Captures and processes the reverse audio stream using an <see cref="AudioProcessingModule"/>.
9+
/// </summary>
10+
/// <remarks>
11+
/// The reverse stream is captured from the scene's audio listener.
12+
/// </remarks>
13+
internal class ApmReverseStream
14+
{
15+
private readonly AudioBuffer _captureBuffer = new AudioBuffer();
16+
private readonly AudioProcessingModule _apm; // APM is thread safe
17+
private Thread _thread;
18+
private AudioFilter _audioFilter;
19+
20+
internal ApmReverseStream(AudioProcessingModule apm)
21+
{
22+
_apm = apm;
23+
}
24+
25+
internal void Start()
26+
{
27+
var audioListener = GameObject.FindObjectOfType<AudioListener>();
28+
if (audioListener == null)
29+
{
30+
Utils.Error("AudioListener not found in scene");
31+
return;
32+
}
33+
_audioFilter = audioListener.gameObject.AddComponent<AudioFilter>();
34+
_audioFilter.AudioRead += OnAudioRead;
35+
36+
_thread = new Thread(ProcessReverseStream);
37+
_thread.Start();
38+
}
39+
40+
internal void Stop()
41+
{
42+
_thread?.Abort();
43+
if (_audioFilter != null)
44+
Object.Destroy(_audioFilter);
45+
}
46+
47+
private void ProcessReverseStream()
48+
{
49+
while (true)
50+
{
51+
Thread.Sleep(Constants.TASK_DELAY);
52+
using var frame = _captureBuffer.ReadDuration(AudioProcessingModule.FRAME_DURATION_MS);
53+
if (frame == null) continue;
54+
_apm.ProcessReverseStream(frame);
55+
}
56+
}
57+
58+
private void OnAudioRead(float[] data, int channels, int sampleRate)
59+
{
60+
_captureBuffer.Write(data, (uint)channels, (uint)sampleRate);
61+
}
62+
}
63+
}

Runtime/Scripts/ApmReverseStream.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Scripts/AudioProcessingModule.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,5 +126,10 @@ public void SetStreamDelayMs(int delayMs)
126126
throw new Exception(res.ApmSetStreamDelay.Error);
127127
}
128128
}
129+
130+
/// <summary>
131+
/// The required duration for audio frames being processed.
132+
/// </summary>
133+
public const uint FRAME_DURATION_MS = 10;
129134
}
130135
}

Runtime/Scripts/RtcAudioSource.cs

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -44,30 +44,26 @@ public abstract class RtcAudioSource : IRtcSource
4444
// Possibly used on the AudioThread
4545
private Thread _readAudioThread;
4646
private AudioBuffer _captureBuffer = new AudioBuffer();
47+
private readonly AudioProcessingModule _apm;
48+
private readonly ApmReverseStream _apmReverseStream;
4749

4850
private bool _muted = false;
4951
public override bool Muted => _muted;
5052

5153
protected RtcAudioSource(int channels = 2, RtcAudioSourceType audioSourceType = RtcAudioSourceType.AudioSourceCustom)
5254
{
55+
var isMicrophone = audioSourceType == RtcAudioSourceType.AudioSourceMicrophone;
5356
_sourceType = audioSourceType;
57+
_apm = new AudioProcessingModule(isMicrophone, true, true, true);
58+
if (isMicrophone)
59+
_apmReverseStream = new ApmReverseStream(_apm);
5460

5561
using var request = FFIBridge.Instance.NewRequest<NewAudioSourceRequest>();
5662
var newAudioSource = request.request;
5763
newAudioSource.Type = AudioSourceType.AudioSourceNative;
5864
newAudioSource.NumChannels = (uint)channels;
59-
if(_sourceType == RtcAudioSourceType.AudioSourceMicrophone)
60-
{
61-
newAudioSource.SampleRate = DefaultMirophoneSampleRate;
62-
}
63-
else
64-
{
65-
newAudioSource.SampleRate = DefaultSampleRate;
66-
}
67-
newAudioSource.Options = request.TempResource<AudioSourceOptions>();
68-
newAudioSource.Options.EchoCancellation = true;
69-
newAudioSource.Options.AutoGainControl = true;
70-
newAudioSource.Options.NoiseSuppression = true;
65+
newAudioSource.SampleRate = isMicrophone ? DefaultMirophoneSampleRate : DefaultSampleRate;
66+
7167
using var response = request.Send();
7268
FfiResponse res = response;
7369
_info = res.NewAudioSource.Source.Info;
@@ -85,13 +81,15 @@ public void Start()
8581
Stop();
8682
_readAudioThread = new Thread(Update);
8783
_readAudioThread.Start();
84+
_apmReverseStream?.Start();
8885
AudioRead += OnAudioRead;
8986
Play();
9087
}
9188

9289
public virtual void Stop()
9390
{
9491
_readAudioThread?.Abort();
92+
_apmReverseStream?.Stop();
9593
AudioRead -= OnAudioRead;
9694
}
9795

@@ -100,8 +98,17 @@ private void Update()
10098
while (true)
10199
{
102100
Thread.Sleep(Constants.TASK_DELAY);
103-
var frame = _captureBuffer.ReadDuration(10); // 10ms
101+
var frame = _captureBuffer.ReadDuration(AudioProcessingModule.FRAME_DURATION_MS);
104102
if (_muted || frame == null) continue;
103+
104+
if (_apmReverseStream != null)
105+
{
106+
// TODO: calculate stream delay
107+
var delayMs = 0;
108+
_apm.SetStreamDelayMs(delayMs);
109+
}
110+
_apm.ProcessStream(frame);
111+
105112
Capture(frame);
106113
}
107114
}

0 commit comments

Comments
 (0)