|
@@ -1,6 +1,6 @@
|
|
|
#!/usr/bin/env python3
|
|
|
# encoding:utf-8
|
|
|
-
|
|
|
+import os
|
|
|
import time
|
|
|
import json
|
|
|
import wave
|
|
@@ -12,7 +12,8 @@ from enum import Enum
|
|
|
|
|
|
calls = {}
|
|
|
recording_file = '/code/src/core/voip/incoming_call.wav'
|
|
|
-player_file = '/code/src/core/voip/test111.wav'
|
|
|
+player_file = '/code/src/core/voip/test222.wav'
|
|
|
+player_script_dir = '/code/src/core/voip/scripts/'
|
|
|
|
|
|
|
|
|
class BotStatus(Enum):
|
|
@@ -36,13 +37,14 @@ class UserStatus(Enum):
|
|
|
|
|
|
|
|
|
class MyAudioMediaPort(pj.AudioMediaPort):
|
|
|
- def __init__(self, asr=None):
|
|
|
+ def __init__(self, call, asr=None):
|
|
|
pj.AudioMediaPort.__init__(self)
|
|
|
# 打开一个 .pcm 文件来保存音频流(可选:保存为 .wav)
|
|
|
self.wav = wave.open(f"{recording_file}", "wb")
|
|
|
self.wav.setnchannels(1)
|
|
|
self.wav.setsampwidth(2) # 假设每个样本是 16 位(2 字节)
|
|
|
self.wav.setframerate(16000)
|
|
|
+ self.call = call
|
|
|
self.asr = asr
|
|
|
|
|
|
def onFrameRequested(self, frame):
|
|
@@ -53,17 +55,26 @@ class MyAudioMediaPort(pj.AudioMediaPort):
|
|
|
# print("Received audio frame:", frame.buf, frame.size)
|
|
|
if self.asr: # 如果ASR实例存在,则发送音频数据
|
|
|
self.asr.send_audio(frame.buf)
|
|
|
+ try:
|
|
|
+ asr_text = self.call.user_asr_text_queue.get(block=False)
|
|
|
+ print(asr_text)
|
|
|
+ if asr_text:
|
|
|
+ self.call.send_bot_speaker(self.call.scripts.get())
|
|
|
+ except:
|
|
|
+ pass
|
|
|
|
|
|
|
|
|
class MyAudioMediaPlayer(pj.AudioMediaPlayer):
|
|
|
|
|
|
- def __init__(self, sink):
|
|
|
+ def __init__(self, sink, on_complete=None):
|
|
|
pj.AudioMediaPlayer.__init__(self)
|
|
|
self.sink = sink
|
|
|
+ self.on_complete = on_complete
|
|
|
|
|
|
def onEof2(self):
|
|
|
- print('player complete')
|
|
|
self.stopTransmit(self.sink)
|
|
|
+ if self.on_complete:
|
|
|
+ self.on_complete()
|
|
|
|
|
|
|
|
|
# Subclass to extend the Account and get notifications etc.
|
|
@@ -99,13 +110,24 @@ class MyCall(pj.Call):
|
|
|
self.call_id = call_id
|
|
|
self.kwargs = kwargs
|
|
|
self.audio_port = None
|
|
|
- self.aud_med = None
|
|
|
self.recorder = None
|
|
|
+ self.aud_med = None
|
|
|
self.player = None # 用于播放录音的媒体播放器
|
|
|
self.asr = None
|
|
|
- # from src.core.voip.asr import TestSt
|
|
|
- # self.asr = TestSt(call_id) # 创建ASR实例
|
|
|
- # self.asr.start() # 启动ASR线程
|
|
|
+ self.user_asr_text_queue = queue.Queue(maxsize=100)
|
|
|
+ self.scripts = self.build_demo_script()
|
|
|
+
|
|
|
+ from src.core.voip.asr import TestSt
|
|
|
+ self.asr = TestSt(call_id, message_receiver=self.on_receiver_asr_result) # 创建ASR实例
|
|
|
+ self.asr.start() # 启动ASR线程
|
|
|
+
|
|
|
+ def build_demo_script(self):
|
|
|
+ res = queue.Queue(maxsize=10)
|
|
|
+ for file in os.listdir(player_script_dir):
|
|
|
+ file = os.path.join(player_script_dir, file)
|
|
|
+ print('build_demo_script::', file)
|
|
|
+ res.put(file)
|
|
|
+ return res
|
|
|
|
|
|
def onDtmfDigit(self, prm):
|
|
|
digit = prm.digit
|
|
@@ -139,30 +161,33 @@ class MyCall(pj.Call):
|
|
|
for media in call_info.media:
|
|
|
if media.type == pj.PJMEDIA_TYPE_AUDIO and \
|
|
|
(media.status == pj.PJSUA_CALL_MEDIA_ACTIVE):
|
|
|
+ print("Call Media state 111: ", call_info.stateText)
|
|
|
self.aud_med = self.getAudioMedia(media.index)
|
|
|
try:
|
|
|
- self.audio_port = MyAudioMediaPort(self.asr)
|
|
|
- self.audio_port.createPort("Incoming Call Port", self.build_audio_format())
|
|
|
- self.aud_med.startTransmit(self.audio_port)
|
|
|
-
|
|
|
- # 录制来电声音
|
|
|
- # self.recorder = pj.AudioMediaRecorder()
|
|
|
- # self.recorder.createRecorder(recording_file)
|
|
|
- # self.aud_med.startTransmit(self.recorder)
|
|
|
- # self.send_audio_to_asr()
|
|
|
-
|
|
|
- # 播放其它录音文件
|
|
|
- self.player = MyAudioMediaPlayer(self.aud_med)
|
|
|
- #self.player = pj.AudioMediaPlayer()
|
|
|
- self.player.createPlayer(player_file)
|
|
|
- self.player.startTransmit(self.aud_med)
|
|
|
-
|
|
|
- # 显示播放进度
|
|
|
- #threading.Thread(target=self.display_playback_progress, args=()).start()
|
|
|
- # self.display_playback_progress()
|
|
|
+ # 建立双向通道
|
|
|
+ self.receive_user_speaker()
|
|
|
+ self.send_bot_speaker(self.scripts.get())
|
|
|
except Exception as e:
|
|
|
traceback.print_exc()
|
|
|
|
|
|
+ def receive_user_speaker(self):
|
|
|
+ self.audio_port = MyAudioMediaPort(self, self.asr)
|
|
|
+ self.audio_port.createPort("Incoming Call Port", self.build_audio_format())
|
|
|
+ self.aud_med.startTransmit(self.audio_port)
|
|
|
+
|
|
|
+ def send_bot_speaker(self, player_file):
|
|
|
+ if not player_file or not os.path.exists(player_file):
|
|
|
+ return
|
|
|
+ self.player = MyAudioMediaPlayer(self.aud_med, on_complete=self.on_media_player_complete)
|
|
|
+ self.player.createPlayer(player_file)
|
|
|
+ self.player.startTransmit(self.aud_med)
|
|
|
+
|
|
|
+ def on_receiver_asr_result(self, message, *args):
|
|
|
+ self.user_asr_text_queue.put(message)
|
|
|
+
|
|
|
+ def on_media_player_complete(self):
|
|
|
+ print('player complete')
|
|
|
+
|
|
|
def build_audio_format(self):
|
|
|
fmt = pj.MediaFormatAudio()
|
|
|
fmt.type = pj.PJMEDIA_TYPE_AUDIO
|
|
@@ -173,31 +198,13 @@ class MyCall(pj.Call):
|
|
|
fmt.bitsPerSample = 16 # 每个采样的位数
|
|
|
return fmt
|
|
|
|
|
|
- def display_playback_progress(self):
|
|
|
- while self.player:
|
|
|
- # 获取当前播放位置和总时长
|
|
|
- current_pos = self.player.getPos()
|
|
|
- player_info = self.player.getInfo()
|
|
|
- # print(current_pos, player_info.sizeBytes, player_info.sizeSamples)
|
|
|
- total_duration = player_info.sizeBytes
|
|
|
- if current_pos >= total_duration:
|
|
|
- self.player.stopTransmit(self.aud_med)
|
|
|
- # if total_duration > 0:
|
|
|
- # print(current_pos, total_duration)
|
|
|
- # progress = (current_pos / total_duration) * 100
|
|
|
- # print(f"播放进度: {progress:.2f}%")
|
|
|
- # if current_pos >= total_duration:
|
|
|
- # self.player.stop()
|
|
|
-
|
|
|
- # time.sleep(0.1) # 每隔1秒更新一次进度
|
|
|
-
|
|
|
|
|
|
class BotAgent:
|
|
|
|
|
|
def __init__(self, logger, user_part_range=range(1001, 1011), host="192.168.100.159", port="5060", password="slibra@#123456"):
|
|
|
self.logger = logger
|
|
|
self.user_part_range, self.host, self.port, self.password = user_part_range, host, port, password
|
|
|
- self.pool = queue.Queue(maxsize=len(user_part_range))
|
|
|
+ self.user_part_pool = queue.Queue(maxsize=len(user_part_range))
|
|
|
self.accounts = {}
|
|
|
self.calls = {}
|
|
|
self.ep = pj.Endpoint()
|
|
@@ -207,10 +214,11 @@ class BotAgent:
|
|
|
def create_pjsua2(self):
|
|
|
# Create and initialize the library
|
|
|
ep_cfg = pj.EpConfig()
|
|
|
- ep_cfg.uaConfig.threadCnt = 4
|
|
|
- ep_cfg.uaConfig.mainThreadOnly = False
|
|
|
+ ep_cfg.uaConfig.threadCnt = 0
|
|
|
+ ep_cfg.uaConfig.mainThreadOnly = True
|
|
|
ep_cfg.uaConfig.maxCalls = 10
|
|
|
ep_cfg.uaConfig.maxAccounts = 10
|
|
|
+ ep_cfg.medConfig.noVad = True
|
|
|
ep_cfg.logConfig.level = 5
|
|
|
ep_cfg.logConfig.consoleLevel = 5
|
|
|
self.ep.libCreate()
|
|
@@ -240,14 +248,14 @@ class BotAgent:
|
|
|
acfg.natConfig.iceEnabled = True
|
|
|
acfg.natConfig.turnEnabled = True
|
|
|
acfg.natConfig.turnServer = "stun:192.168.100.159:3478"
|
|
|
- #acfg.natConfig.turnUsername = "username"
|
|
|
- #acfg.natConfig.turnPassword = "password"
|
|
|
+ # acfg.natConfig.turnUsername = "username"
|
|
|
+ # acfg.natConfig.turnPassword = "password"
|
|
|
|
|
|
# Create the account
|
|
|
acc = Account(self, user_part)
|
|
|
acc.create(acfg)
|
|
|
|
|
|
- self.pool.put(user_part)
|
|
|
+ self.user_part_pool.put(user_part)
|
|
|
self.accounts[user_part] = acc
|
|
|
|
|
|
while not self.is_stopping:
|
|
@@ -267,9 +275,9 @@ class BotAgent:
|
|
|
self.release(user_part)
|
|
|
|
|
|
def register(self, **kwargs):
|
|
|
- user_part = self.pool.get()
|
|
|
+ user_part = self.user_part_pool.get()
|
|
|
acc = self.accounts.get(user_part)
|
|
|
- self.logger.info('register, user_part :%d, pool.size :%d', user_part, self.pool.qsize())
|
|
|
+ self.logger.info('register, user_part :%d, pool.size :%d', user_part, self.user_part_pool.qsize())
|
|
|
if acc:
|
|
|
print('register==========>', acc.getId())
|
|
|
# ps = pj.PresenceStatus()
|
|
@@ -296,10 +304,10 @@ class BotAgent:
|
|
|
return True
|
|
|
return False
|
|
|
|
|
|
- if element_in_queue(self.pool, user_part):
|
|
|
+ if element_in_queue(self.user_part_pool, user_part):
|
|
|
return
|
|
|
- self.pool.put(user_part)
|
|
|
- self.logger.info("release, user_part :%d, pool.size :%d", user_part, self.pool.qsize())
|
|
|
+ self.user_part_pool.put(user_part)
|
|
|
+ self.logger.info("release, user_part :%d, pool.size :%d", user_part, self.user_part_pool.qsize())
|
|
|
|
|
|
def destroy(self):
|
|
|
self.is_stopping = True
|