Skip to content

Commit ee150d7

Browse files
authored
more docstrings & add rtc.combine_audio_frames (#268)
1 parent d37e8eb commit ee150d7

File tree

7 files changed

+557
-20
lines changed

7 files changed

+557
-20
lines changed

livekit-rtc/livekit/rtc/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
from .video_source import VideoSource
7171
from .video_stream import VideoFrameEvent, VideoStream
7272
from .audio_resampler import AudioResampler, AudioResamplerQuality
73+
from .utils import combine_audio_frames
7374

7475
__all__ = [
7576
"ConnectionQuality",
@@ -130,5 +131,6 @@
130131
"ChatMessage",
131132
"AudioResampler",
132133
"AudioResamplerQuality",
134+
"combine_audio_frames",
133135
"__version__",
134136
]

livekit-rtc/livekit/rtc/audio_source.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@
2424

2525

2626
class AudioSource:
27+
"""
28+
Represents a real-time audio source with an internal audio queue.
29+
30+
The `AudioSource` class allows you to push audio frames into a real-time audio
31+
source, managing an internal queue of audio data up to a maximum duration defined
32+
by `queue_size_ms`. It supports asynchronous operations to capture audio frames
33+
and to wait for the playback of all queued audio data.
34+
"""
35+
2736
def __init__(
2837
self,
2938
sample_rate: int,
@@ -35,11 +44,12 @@ def __init__(
3544
Initializes a new instance of the audio source.
3645
3746
Args:
38-
sample_rate (int): The sample rate of the audio source in Hz
39-
num_channels (int): The number of audio channels
47+
sample_rate (int): The sample rate of the audio source in Hz.
48+
num_channels (int): The number of audio channels.
4049
queue_size_ms (int, optional): The buffer size of the audio queue in milliseconds.
4150
Defaults to 1000 ms.
42-
loop (asyncio.AbstractEventLoop, optional): The event loop to use. Defaults to asyncio.get_event_loop().
51+
loop (asyncio.AbstractEventLoop, optional): The event loop to use. Defaults to
52+
`asyncio.get_event_loop()`.
4353
"""
4454
self._sample_rate = sample_rate
4555
self._num_channels = num_channels
@@ -63,29 +73,48 @@ def __init__(
6373

6474
@property
6575
def sample_rate(self) -> int:
76+
"""The sample rate of the audio source in Hz."""
6677
return self._sample_rate
6778

6879
@property
6980
def num_channels(self) -> int:
81+
"""The number of audio channels."""
7082
return self._num_channels
7183

7284
@property
7385
def queued_duration(self) -> float:
86+
"""The current duration (in seconds) of audio data queued for playback."""
7487
return max(self._q_size - time.monotonic() + self._last_capture, 0.0)
7588

7689
def clear_queue(self) -> None:
77-
"""Clears the audio queue, discarding all buffered audio data."""
90+
"""
91+
Clears the internal audio queue, discarding all buffered audio data.
92+
93+
This method immediately removes all audio data currently queued for playback,
94+
effectively resetting the audio source's buffer. Any audio frames that have been
95+
captured but not yet played will be discarded. This is useful in scenarios where
96+
you need to stop playback abruptly or prevent outdated audio data from being played.
97+
"""
7898
req = proto_ffi.FfiRequest()
7999
req.clear_audio_buffer.source_handle = self._ffi_handle.handle
80100
_ = FfiClient.instance.request(req)
81101
self._release_waiter()
82102

83103
async def capture_frame(self, frame: AudioFrame) -> None:
84-
"""Captures an AudioFrame.
104+
"""
105+
Captures an `AudioFrame` and queues it for playback.
85106
86-
Used to push new audio data into the published Track. Audio data will
87-
be pushed in chunks of 10ms. It'll return only when all of the data in
88-
the buffer has been pushed.
107+
This method is used to push new audio data into the audio source. The audio data
108+
will be processed and queued. If the size of the audio frame exceeds the internal
109+
queue size, the method will wait until there is enough space in the queue to
110+
accommodate the frame. The method returns only when all of the data in the buffer
111+
has been pushed.
112+
113+
Args:
114+
frame (AudioFrame): The audio frame to capture and queue.
115+
116+
Raises:
117+
Exception: If there is an error during frame capture.
89118
"""
90119

91120
now = time.monotonic()
@@ -123,7 +152,13 @@ async def capture_frame(self, frame: AudioFrame) -> None:
123152
raise Exception(cb.capture_audio_frame.error)
124153

125154
async def wait_for_playout(self) -> None:
126-
"""Waits for the audio source to finish playing out all audio data."""
155+
"""
156+
Waits for the audio source to finish playing out all audio data.
157+
158+
This method ensures that all queued audio data has been played out before returning.
159+
It can be used to synchronize events after audio playback or to ensure that the
160+
audio queue is empty.
161+
"""
127162

128163
if self._join_fut is None:
129164
return

livekit-rtc/livekit/rtc/audio_stream.py

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,22 @@
3030

3131
@dataclass
3232
class AudioFrameEvent:
33+
"""An event representing a received audio frame.
34+
35+
Attributes:
36+
frame (AudioFrame): The received audio frame.
37+
"""
38+
3339
frame: AudioFrame
3440

3541

3642
class AudioStream:
37-
"""AudioStream is a stream of audio frames received from a RemoteTrack."""
43+
"""An asynchronous audio stream for receiving audio frames from a participant or track.
44+
45+
The `AudioStream` class provides an asynchronous iterator over audio frames received from
46+
a specific track or participant. It allows you to receive audio frames in real-time with
47+
customizable sample rates and channel configurations.
48+
"""
3849

3950
def __init__(
4051
self,
@@ -45,6 +56,32 @@ def __init__(
4556
num_channels: int = 1,
4657
**kwargs,
4758
) -> None:
59+
"""Initialize an `AudioStream` instance.
60+
61+
Args:
62+
track (Optional[Track]): The audio track from which to receive audio. If not provided,
63+
you must specify `participant` and `track_source` in `kwargs`.
64+
loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use.
65+
Defaults to the current event loop.
66+
capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
67+
sample_rate (int, optional): The sample rate for the audio stream in Hz.
68+
Defaults to 48000.
69+
num_channels (int, optional): The number of audio channels. Defaults to 1.
70+
Example:
71+
```python
72+
audio_stream = AudioStream(
73+
track=audio_track,
74+
sample_rate=44100,
75+
num_channels=2,
76+
)
77+
78+
audio_stream = AudioStream.from_track(
79+
track=audio_track,
80+
sample_rate=44100,
81+
num_channels=2,
82+
)
83+
```
84+
"""
4885
self._track: Track | None = track
4986
self._sample_rate = sample_rate
5087
self._num_channels = num_channels
@@ -76,6 +113,29 @@ def from_participant(
76113
sample_rate: int = 48000,
77114
num_channels: int = 1,
78115
) -> AudioStream:
116+
"""Create an `AudioStream` from a participant's audio track.
117+
118+
Args:
119+
participant (Participant): The participant from whom to receive audio.
120+
track_source (TrackSource.ValueType): The source of the audio track (e.g., microphone, screen share).
121+
loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use. Defaults to the current event loop.
122+
capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
123+
sample_rate (int, optional): The sample rate for the audio stream in Hz. Defaults to 48000.
124+
num_channels (int, optional): The number of audio channels. Defaults to 1.
125+
126+
Returns:
127+
AudioStream: An instance of `AudioStream` that can be used to receive audio frames.
128+
129+
Example:
130+
```python
131+
audio_stream = AudioStream.from_participant(
132+
participant=participant,
133+
track_source=TrackSource.MICROPHONE,
134+
sample_rate=24000,
135+
num_channels=1,
136+
)
137+
```
138+
"""
79139
return AudioStream(
80140
participant=participant,
81141
track_source=track_source,
@@ -96,6 +156,27 @@ def from_track(
96156
sample_rate: int = 48000,
97157
num_channels: int = 1,
98158
) -> AudioStream:
159+
"""Create an `AudioStream` from an existing audio track.
160+
161+
Args:
162+
track (Track): The audio track from which to receive audio.
163+
loop (Optional[asyncio.AbstractEventLoop], optional): The event loop to use. Defaults to the current event loop.
164+
capacity (int, optional): The capacity of the internal frame queue. Defaults to 0 (unbounded).
165+
sample_rate (int, optional): The sample rate for the audio stream in Hz. Defaults to 48000.
166+
num_channels (int, optional): The number of audio channels. Defaults to 1.
167+
168+
Returns:
169+
AudioStream: An instance of `AudioStream` that can be used to receive audio frames.
170+
171+
Example:
172+
```python
173+
audio_stream = AudioStream.from_track(
174+
track=audio_track,
175+
sample_rate=44100,
176+
num_channels=2,
177+
)
178+
```
179+
"""
99180
return AudioStream(
100181
track=track,
101182
loop=loop,
@@ -152,6 +233,11 @@ async def _run(self):
152233
FfiClient.instance.queue.unsubscribe(self._ffi_queue)
153234

154235
async def aclose(self) -> None:
236+
"""Asynchronously close the audio stream.
237+
238+
This method cleans up resources associated with the audio stream and waits for
239+
any pending operations to complete.
240+
"""
155241
self._ffi_handle.dispose()
156242
await self._task
157243

0 commit comments

Comments
 (0)