rtcllm/rtcllm/rtc.py

247 lines
6.8 KiB
Python
Raw Normal View History

2024-09-09 18:53:01 +08:00
import os
2024-09-09 18:02:54 +08:00
import sys
sys.path.append('./mini_omni')
2024-08-27 18:39:25 +08:00
import asyncio
2024-09-02 18:43:58 +08:00
import random
2024-08-28 18:26:47 +08:00
import json
2024-09-03 16:12:34 +08:00
2024-08-28 18:26:47 +08:00
from functools import partial
2024-08-27 18:39:25 +08:00
from appPublic.dictObject import DictObject
2024-09-03 16:19:14 +08:00
from appPublic.hf import hf_socks5proxy
2024-09-09 18:50:44 +08:00
from appPublic.worker import awaitify
2024-09-11 10:37:16 +08:00
from aiortc import MediaStreamTrack, VideoStreamTrack, AudioStreamTrack, RTCPeerConnection, RTCSessionDescription, RTCIceCandidate
2024-09-03 15:45:34 +08:00
from aiortc.sdp import candidate_from_sdp, candidate_to_sdp
2024-08-26 18:11:08 +08:00
from aiortc.contrib.media import MediaBlackhole, MediaPlayer, MediaRecorder, MediaRelay
2024-08-27 18:39:25 +08:00
# from websockets.asyncio.client import connect
2024-09-06 18:16:24 +08:00
# from websockets.asyncio.client import connect
from websockets.client import connect
2024-08-27 18:39:25 +08:00
import websockets
2024-09-02 18:43:58 +08:00
from stt import asr
2024-08-28 18:26:47 +08:00
from vad import AudioTrackVad
2024-09-09 17:34:08 +08:00
from a2a import LLMAudioStreamTrack
2024-09-13 11:18:20 +08:00
from aav import MyMediaPlayer, MyAudioStreamTrack, MyVideoStreamTrack
2024-09-11 11:08:26 +08:00
2024-09-10 10:44:51 +08:00
from mini_omni.inference import OmniInference
2024-09-02 18:43:58 +08:00
videos = ['./1.mp4', './2.mp4']
2024-08-28 18:26:47 +08:00
2024-09-04 11:00:07 +08:00
async def pc_get_local_candidates(pc, peer):
2024-09-04 14:37:10 +08:00
its = pc.__dict__.get('_RTCPeerConnection__iceTransports')
coros = map(lambda t: t.iceGatherer.gather(), its )
2024-09-04 11:11:04 +08:00
await asyncio.gather(*coros)
2024-09-04 14:37:10 +08:00
ss = pc.__dict__.get('_RTCPeerConnection__iceTransport')
2024-09-04 11:11:04 +08:00
2024-09-04 11:00:07 +08:00
if not peer.l_candidates:
peer.l_candidates = []
2024-09-04 15:10:09 +08:00
peer.sdp_id = 0
2024-09-04 14:37:10 +08:00
for t in its:
for c in t._connection.local_candidates:
2024-09-04 11:11:04 +08:00
if c not in peer.l_candidates:
2024-09-11 13:40:45 +08:00
# print(f'{c=}, {dir(c)}')
2024-09-04 15:10:09 +08:00
c.sdpMid = str(peer.sdp_id)
peer.sdp_id += 1
2024-09-04 11:11:04 +08:00
peer.l_candidates.append(c)
pc.emit('icecandidate', c)
2024-09-04 11:00:07 +08:00
RTCPeerConnection.get_local_candidates = pc_get_local_candidates
2024-08-27 18:39:25 +08:00
class RTCLLM:
def __init__(self, ws_url, iceServers):
self.ws_url = ws_url
2024-09-10 10:39:38 +08:00
self.omni_infer = OmniInference(ckpt_dir='/d/models/mini-omni')
2024-08-27 18:39:25 +08:00
self.iceServers = iceServers
self.peers = DictObject()
2024-09-02 18:43:58 +08:00
self.vid = 0
2024-08-28 18:26:47 +08:00
self.info = {
'id':'rtcllm_agent',
'name':'rtcllm_agent'
}
self.dc = None
2024-09-02 18:43:58 +08:00
self.loop = asyncio.get_event_loop()
2024-08-27 18:39:25 +08:00
2024-09-03 15:45:34 +08:00
async def ws_send(self, s):
2024-09-03 16:21:02 +08:00
await self.ws.send(s)
2024-09-03 15:45:34 +08:00
2024-08-27 18:39:25 +08:00
def get_pc(self, data):
return self.peers[data['from'].id].pc
async def run(self):
async with connect(self.ws_url) as self.ws:
2024-08-28 18:26:47 +08:00
await self.login()
2024-08-27 18:39:25 +08:00
while True:
msg = await self.ws.recv()
data = DictObject(**json.loads(msg))
2024-08-28 18:26:47 +08:00
d = data.data
print(f'ws recv(): {d.type=}')
func = self.handlers.get(d.type)
if func:
f = partial(func, self)
await f(d)
2024-08-27 18:39:25 +08:00
self.ws.close()
async def login(self):
2024-09-03 15:45:34 +08:00
await self.ws_send(json.dumps({
2024-08-27 18:39:25 +08:00
'type':'login',
2024-08-28 18:26:47 +08:00
'info':self.info
2024-08-27 18:39:25 +08:00
}))
2024-09-04 14:37:10 +08:00
async def on_icecandidate(self, pc, to, candidate):
2024-08-28 18:26:47 +08:00
if candidate:
2024-09-04 15:14:37 +08:00
candi = {
'candidate':'candidate:' + candidate.to_sdp(),
'sdpMid':candidate.sdpMid,
'type': candidate.type
}
2024-09-11 13:40:45 +08:00
# print('***********on_icecandidate()', candi)
2024-09-03 15:45:34 +08:00
await self.ws_send(json.dumps({
2024-09-04 14:37:10 +08:00
"type":"iceCandidate",
"to":to,
2024-09-04 15:14:37 +08:00
"candidate":candi
2024-09-04 15:15:26 +08:00
}))
2024-08-28 18:26:47 +08:00
2024-08-27 18:39:25 +08:00
async def save_onlineList(self, data):
2024-09-11 13:40:45 +08:00
# print(f'{self}, {type(self)}')
2024-08-27 18:39:25 +08:00
self.onlineList = data.onlineList
2024-09-02 18:43:58 +08:00
async def vad_voiceend(self, peer, audio):
2024-09-09 18:55:02 +08:00
if audio is not None:
2024-09-10 10:39:38 +08:00
feed = awaitify(peer.llmtrack._feed)
ret = await feed(audio)
2024-09-09 18:55:02 +08:00
print(f'self.feed("{audio}") return {ret}')
2024-09-11 15:51:19 +08:00
# os.remove(audio)
2024-08-28 18:26:47 +08:00
2024-08-27 18:39:25 +08:00
async def auto_accept_call(self, data):
2024-08-28 18:26:47 +08:00
opts = DictObject(iceServers=self.iceServers)
pc = RTCPeerConnection(opts)
2024-09-11 10:34:18 +08:00
player = MyMediaPlayer('./1.mp4')
2024-09-10 10:42:06 +08:00
llmtrack = LLMAudioStreamTrack(self.omni_infer)
2024-08-27 18:39:25 +08:00
self.peers[data['from'].id] = DictObject(**{
'info':data['from'],
2024-09-10 10:39:38 +08:00
'llmtrack':llmtrack,
2024-09-11 10:34:18 +08:00
'player':player,
2024-08-27 18:39:25 +08:00
'pc':pc
})
2024-09-11 15:08:56 +08:00
pc.addTrack(llmtrack)
# pc.addTrack(MyAudioStreamTrack(player))
2024-09-11 14:53:53 +08:00
pc.addTrack(MyVideoStreamTrack(player))
# pc.addTrack(LoopingVideoTrack('./1.mp4'))
2024-09-11 13:41:20 +08:00
# pc.addTrack(player.video)
2024-09-03 15:45:34 +08:00
await self.ws_send(json.dumps({'type':'callAccepted', 'to':data['from']}))
2024-09-14 15:10:04 +08:00
print('auto_accept_call() end')
2024-08-27 18:39:25 +08:00
2024-08-28 18:26:47 +08:00
async def pc_track(self, peerid, track):
peer = self.peers[peerid]
pc = peer.pc
if track.kind == 'audio':
2024-09-02 18:43:58 +08:00
f = partial(self.vad_voiceend, peer)
2024-09-13 10:18:40 +08:00
vadtrack = AudioTrackVad(track, stage=3, onvoiceend=f)
2024-08-28 18:26:47 +08:00
peer.vadtrack = vadtrack
vadtrack.start_vad()
async def pc_connectionState_changed(self, peerid):
peer = self.peers[peerid]
pc = peer.pc
2024-09-03 16:28:31 +08:00
print(f'conn_state={pc.connectionState} ...........')
2024-08-28 18:26:47 +08:00
if pc.connectionState == 'connected':
2024-09-02 10:36:07 +08:00
peer.dc = pc.createDataChannel(peer.info.name)
2024-08-28 18:26:47 +08:00
return
if pc.connectionState == 'closed':
await pc.close()
if peer.dc:
await peer.dc.close()
2024-09-03 16:33:21 +08:00
await self.ws_send(json.dumps({
'type':'disconnect',
'to': peer.info
2024-09-03 16:35:03 +08:00
}))
2024-08-28 18:26:47 +08:00
peers = {
k:v for k,v in self.peers.items() if k != peerid
}
self.peers = peers
2024-08-27 18:39:25 +08:00
async def response_offer(self, data):
pc = self.get_pc(data)
2024-09-04 11:00:07 +08:00
peer = self.peers[data['from'].id]
2024-08-28 18:26:47 +08:00
if pc is None:
print(f'{self.peers=}, {data=}')
return
2024-09-02 10:36:07 +08:00
pc.on("connectionstatechange", partial(self.pc_connectionState_changed, data['from'].id))
2024-08-28 18:26:47 +08:00
pc.on('track', partial(self.pc_track, data['from'].id))
2024-09-04 14:37:10 +08:00
pc.on('icecandidate', partial(self.on_icecandidate, pc, data['from']))
2024-09-02 10:36:07 +08:00
2024-08-28 18:26:47 +08:00
offer = RTCSessionDescription(** data.offer)
2024-08-27 18:39:25 +08:00
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
2024-09-03 15:45:34 +08:00
await self.ws_send(json.dumps({
2024-08-27 18:39:25 +08:00
'type':'answer',
2024-08-28 18:26:47 +08:00
'answer':{'type':pc.localDescription.type, 'sdp':pc.localDescription.sdp},
2024-08-27 18:39:25 +08:00
'to':data['from']
}))
2024-09-04 11:00:07 +08:00
cands = await pc.get_local_candidates(peer)
2024-09-03 18:09:40 +08:00
2024-09-03 16:27:25 +08:00
"""
2024-09-03 15:45:34 +08:00
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
await self.ws_send(json.dumps({
'type':'offer',
2024-09-03 16:22:36 +08:00
'offer': {'type':pc.localDescription.type, 'sdp':pc.localDescription.sdp},
2024-09-03 15:45:34 +08:00
'to':data['from']
}))
2024-09-03 16:27:25 +08:00
"""
2024-08-27 18:39:25 +08:00
async def accept_answer(self, data):
pc = self.get_pc(data)
answer = RTCSessionDescription(data.answer);
pc.setRemoteDescription(answer)
async def accept_iceCandidate(self, data):
pc = self.get_pc(data)
2024-08-28 18:26:47 +08:00
candidate = data.candidate
2024-09-11 13:40:45 +08:00
# print('accepted candidate=', candidate)
2024-09-03 15:45:34 +08:00
"""
2024-08-28 18:26:47 +08:00
rtc_candidate = RTCIceCandidate(
ip=ip,
port=port,
protocol=protocol,
priority=priority,
foundation=foundation,
component=component,
type=type,
sdpMid=candidate['sdpMid'],
sdpMLineIndex=candidate['sdpMLineIndex']
)
2024-09-03 15:45:34 +08:00
"""
rtc_candidate = candidate_from_sdp(candidate['candidate'].split(":", 1)[1])
rtc_candidate.sdpMid = candidate['sdpMid']
rtc_candidate.sdpMLineIndex = candidate['sdpMLineIndex']
2024-08-28 18:26:47 +08:00
await pc.addIceCandidate(rtc_candidate)
2024-09-11 13:40:45 +08:00
# print('addIceCandidate ok')
2024-08-27 18:39:25 +08:00
handlers = {
'onlineList':save_onlineList,
'callRequest':auto_accept_call,
'offer':response_offer,
'answer':accept_answer,
'iceCandidate':accept_iceCandidate,
}
async def main():
2024-09-03 16:19:14 +08:00
hf_socks5proxy()
2024-09-06 14:48:27 +08:00
agent = RTCLLM(ws_url='wss://sage.open-computing.cn/wss/ws/rtc_signaling.ws',
2024-08-27 18:39:25 +08:00
iceServers=[{
2024-09-06 14:48:27 +08:00
'urls':'stun:stun.open-computing.cn:13478'},{
'urls':'turn:stun.open-computing.cn:13479',
2024-08-27 18:39:25 +08:00
'username':'turn',
'credential':'server'
}])
2024-08-28 18:26:47 +08:00
print('running ...')
2024-08-27 18:39:25 +08:00
await agent.run()
if __name__ == '__main__':
asyncio.run(main())