Skip to content

Commit 42b78af

Browse files
committed
audio_stream
1 parent b1a646d commit 42b78af

File tree

6 files changed

+85
-8
lines changed

6 files changed

+85
-8
lines changed

examples/basic_room/room.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
async def main():
1111
room = livekit.Room()
1212

13+
audio_stream = None
14+
video_stream = None
15+
1316
logging.info("connecting to %s", URL)
1417
try:
1518
await room.connect(URL, TOKEN)
@@ -41,11 +44,21 @@ def on_track_unpublished(publication: livekit.LocalTrackPublication, participant
4144
def on_track_subscribed(track: livekit.Track, publication: livekit.RemoteTrackPublication, participant: livekit.RemoteParticipant):
4245
logging.info("track subscribed: %s", publication.sid)
4346
if track.kind == livekit.TrackKind.KIND_VIDEO:
47+
nonlocal video_stream
4448
video_stream = livekit.VideoStream(track)
4549

4650
@video_stream.on("frame_received")
4751
def on_video_frame(frame: livekit.VideoFrame):
48-
# e.g: Do something with the frames here :)
52+
# received a video frame from the track
53+
pass
54+
elif track.kind == livekit.TrackKind.KIND_AUDIO:
55+
print("Subscribed to an Audio Track")
56+
nonlocal audio_stream
57+
audio_stream = livekit.AudioStream(track)
58+
59+
@audio_stream.on('frame_received')
60+
def on_audio_frame(frame: livekit.AudioFrame):
61+
# received an audio frame from the track
4962
pass
5063

5164
@room.on("track_unsubscribed")

examples/face_landmark/room.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ async def room():
7575
await room.connect(URL, TOKEN)
7676
print("connected to room: " + room.name)
7777

78+
video_stream = None
79+
7880
@room.on("track_subscribed")
7981
def on_track_subscribed(track: livekit.Track, publication: livekit.RemoteTrackPublication, participant: livekit.RemoteParticipant):
8082
if track.kind == livekit.TrackKind.KIND_VIDEO:
83+
nonlocal video_stream
8184
video_stream = livekit.VideoStream(track)
8285

8386
@video_stream.on("frame_received")

livekit/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@
1919
PlanarYuv8Buffer, PlanarYuv16Buffer, I420Buffer, I420ABuffer, I422Buffer, I010Buffer, NV12Buffer)
2020
from .video_stream import VideoStream
2121
from .video_source import VideoSource
22+
23+
from .audio_frame import AudioFrame
24+
from .audio_stream import AudioStream
25+
from .audio_source import AudioSource

livekit/audio_frame.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ctypes
2+
3+
4+
class AudioFrame():
5+
def __init__(self, sample_rate: int, num_channels: int, samples_per_channel: int, data=None, ffi_handle=None):
6+
self._ffi_handle = ffi_handle
7+
self.sample_rate = sample_rate
8+
self.num_channels = num_channels
9+
self.samples_per_channel = samples_per_channel
10+
if data is None:
11+
self.data = (ctypes.c_int16 *
12+
(num_channels * samples_per_channel))()
13+
else:
14+
self.data = data

livekit/audio_source.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
3+
class AudioSource():
4+
def __init__(self):
5+
pass

livekit/audio_stream.py

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
from pyee.asyncio import AsyncIOEventEmitter
22
from ._ffi_client import (FfiClient, FfiHandle)
3-
from livekit import (Track, Room, Participant)
4-
from ._proto import track_pb2 as proto_track
3+
from livekit import Track
54
from ._proto import ffi_pb2 as proto_ffi
6-
from ._proto import video_frame_pb2 as proto_video_frame
7-
from .video_frame import (VideoFrame, VideoFrameBuffer)
5+
from ._proto import audio_frame_pb2 as proto_audio_frame
6+
from .audio_frame import AudioFrame
7+
import ctypes
8+
import weakref
89

910

1011
class AudioStream(AsyncIOEventEmitter):
11-
_streams: dict[int, 'AudioStream'] = {}
12+
_streams: dict[int, weakref.ref['AudioStream']] = {}
1213
_initialized = False
1314

1415
@classmethod
@@ -23,9 +24,46 @@ def initalize(cls):
2324
cls._on_audio_stream_event)
2425

2526
@classmethod
26-
def _on_audio_stream_event(cls, event: proto_video_frame.AudioStreamEvent):
27-
pass
27+
def _on_audio_stream_event(cls, event: proto_audio_frame.AudioStreamEvent):
28+
stream = cls._streams.get(event.handle.id)
29+
if stream is None:
30+
return
31+
32+
stream = stream()
33+
if stream is None:
34+
return
35+
36+
which = event.WhichOneof('message')
37+
if which == 'frame_received':
38+
frame_info = event.frame_received.frame
39+
ffi_handle = FfiHandle(frame_info.handle.id)
40+
data_len = frame_info.num_channels * frame_info.samples_per_channel
41+
data = ctypes.cast(frame_info.data_ptr,
42+
ctypes.POINTER(ctypes.c_uint16 * data_len)).contents
43+
frame = AudioFrame(
44+
frame_info.sample_rate, frame_info.num_channels, frame_info.samples_per_channel, data, ffi_handle)
45+
stream._on_frame_received(frame)
2846

2947
def __init__(self, track: Track):
3048
super().__init__()
3149
self.initalize()
50+
51+
req = proto_ffi.FfiRequest()
52+
new_audio_stream = req.new_audio_stream
53+
new_audio_stream.track_handle.id = track._ffi_handle.handle
54+
new_audio_stream.type = proto_audio_frame.AudioStreamType.AUDIO_STREAM_NATIVE
55+
56+
ffi_client = FfiClient()
57+
resp = ffi_client.request(req)
58+
stream_info = resp.new_audio_stream.stream
59+
60+
self._streams[stream_info.handle.id] = weakref.ref(self)
61+
self._ffi_handle = FfiHandle(stream_info.handle.id)
62+
self._info = stream_info
63+
self._track = track
64+
65+
def _on_frame_received(self, frame: AudioFrame):
66+
self.emit('frame_received', frame)
67+
68+
def __del__(self):
69+
self._streams.pop(self._ffi_handle.handle, None)

0 commit comments

Comments
 (0)