Browse Source

feat:init

余尚辉 5 months ago
parent
commit
673451c690
3 changed files with 113 additions and 48 deletions
  1. 36 0
      .gitignore
  2. 1 1
      Dockerfile
  3. 76 47
      src/core/voip/bot.py

+ 36 - 0
.gitignore

@@ -0,0 +1,36 @@
+.vscode
+*.mar
+*.zip
+/target/
+target
+/.classpath
+/.project
+/.settings/
+/*/logs/
+.idea/misc.xml
+.idea/workspace.xml
+/.idea/
+.idea
+*.iml
+logs/
+*.xml.versionsBackup
+.DS_Store
+static
+templates
+venv
+__**__
+test/*.csv
+test/*.xlsx
+test/*.txt
+test/*test.py
+test/*mining.py
+test/*online.py
+test/*corpus.py
+test/data
+data
+resources
+output/*
+config/*.xlsx
+**.log
+**.ipynb
+*.pyc

+ 1 - 1
Dockerfile

@@ -7,7 +7,7 @@ WORKDIR /code
 ADD . .
 
 # pjsua2
-RUN cd /code/pjproject && ./configure CFLAGS="-fPIC" && make dep && make
+RUN cd /code/pjproject && chmod +x configure aconfigure && ./configure CFLAGS="-fPIC" && make dep && make
 RUN cd /code/pjproject/pjsip-apps/src/swig/python && make && make install
 # pyESL
 RUN cd /code/python-ESL-1.4.18 && python setup.py install

+ 76 - 47
src/core/voip/bot.py

@@ -7,13 +7,14 @@ import wave
 import queue
 import threading
 import traceback
+
 import pjsua2 as pj
 from enum import Enum
+from src.core.voip.constant import *
 
 calls = {}
-recording_file = '/code/src/core/voip/incoming_call.wav'
-player_file = '/code/src/core/voip/test222.wav'
-player_script_dir = '/code/src/core/voip/scripts/'
+# recording_file = '/code/src/core/voip/incoming_call.wav'
+# player_file = '/code/src/core/voip/test222.wav'
 
 
 class BotStatus(Enum):
@@ -37,44 +38,80 @@ class UserStatus(Enum):
 
 
 class MyAudioMediaPort(pj.AudioMediaPort):
-    def __init__(self, call, asr=None):
+    def __init__(self, call, aud_med=None, 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)
+        # # 打开一个 .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.aud_med = aud_med
         self.asr = asr
 
+        self.user_asr_texts = []
+        self.cur_player_file = None
+
     def onFrameRequested(self, frame):
         print("Request audio frame:", frame)
 
     def onFrameReceived(self, frame):
-        self.wav.writeframes(bytes(frame.buf))
+        # self.wav.writeframes(bytes(frame.buf))
         # print("Received audio frame:", frame.buf, frame.size)
         if self.asr:  # 如果ASR实例存在,则发送音频数据
             self.asr.send_audio(frame.buf)
+
+        try:
+            asr_text = self.get_asr_text()
+            play_complete = self.is_play_complete()
+            if asr_text and not play_complete:
+                self.user_asr_texts.append(asr_text)
+            if asr_text and play_complete:
+                self.user_asr_texts.append(asr_text)
+                self.call.chat('###'.join(self.user_asr_texts))
+
+            player_file = self.get_player_file()
+            if player_file and play_complete:
+                self.cur_player_file = player_file
+                self.call.send_bot_speaker(player_file)
+        except:
+            pass
+
+    def is_play_complete(self):
+        if self.cur_player_file:
+            player_id = murmur3_32(self.cur_player_file)
+            return self.call.player_complete_dict.get(player_id)
+
+    def get_asr_text(self):
         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())
+            return asr_text
+        except:
+            pass
+
+    def get_player_file(self):
+        try:
+            player_file = self.call.player_queue.get(block=False)
+            print(player_file)
+            return player_file
         except:
             pass
 
 
 class MyAudioMediaPlayer(pj.AudioMediaPlayer):
 
-    def __init__(self, sink, on_complete=None):
+    def __init__(self, player_id, sink, on_complete=None):
         pj.AudioMediaPlayer.__init__(self)
+        self.player_id = player_id
         self.sink = sink
         self.on_complete = on_complete
 
     def onEof2(self):
         self.stopTransmit(self.sink)
         if self.on_complete:
-            self.on_complete()
+            self.on_complete(self.player_id)
 
 
 # Subclass to extend the Account and get notifications etc.
@@ -109,25 +146,18 @@ class MyCall(pj.Call):
         self.user_part = user_part
         self.call_id = call_id
         self.kwargs = kwargs
-        self.audio_port = None
-        self.recorder = None
         self.aud_med = None
-        self.player = None  # 用于播放录音的媒体播放器
         self.asr = None
+
+        self.scripts = build_demo_script()
         self.user_asr_text_queue = queue.Queue(maxsize=100)
-        self.scripts = self.build_demo_script()
+        self.player_queue = queue.Queue(maxsize=3)
+        self.player_complete_dict = {}
 
         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
@@ -145,11 +175,10 @@ class MyCall(pj.Call):
         # pj.PJSIP_INV_STATE_CONFIRMED
         # pj.PJSIP_INV_STATE_DISCONNECTED
 
-        # if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
-        #     # 当呼叫状态为已确认(即接通)
-        #     audio_media = self.getAudioMedia()  # 获取音频媒体对象
-        #     audio_media.startTransmit(audio_media)  # 这里可以对音频流进行处理
-        #     print ('11111111111')
+        if call_info.state == pj.PJSIP_INV_STATE_CONFIRMED:
+            # 当呼叫状态为已确认(即接通)
+            self.dbot_say_hello()
+
         if call_info.state == pj.PJSIP_INV_STATE_DISCONNECTED:
             print("通话结束")
             # 远程挂机之后要将分机号回收
@@ -166,37 +195,37 @@ class MyCall(pj.Call):
                 try:
                     # 建立双向通道
                     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)
+        audio_port = MyAudioMediaPort(self, self.aud_med, self.asr)
+        audio_port.createPort("Incoming Call Port", build_audio_format())
+        self.aud_med.startTransmit(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)
+        player_id = murmur3_32(player_file)
+        player = MyAudioMediaPlayer(player_id, self.aud_med, on_complete=self.on_media_player_complete)
+        player.createPlayer(player_file)
+        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):
+    def on_media_player_complete(self, player_id):
         print('player complete')
+        self.player_complete_dict[player_id] = 1
+
+    def bot_say_hello(self):
+        self.chat()
 
-    def build_audio_format(self):
-        fmt = pj.MediaFormatAudio()
-        fmt.type = pj.PJMEDIA_TYPE_AUDIO
-        fmt.id = pj.PJMEDIA_FORMAT_PCM
-        fmt.clockRate = 16000  # 采样率
-        fmt.channelCount = 1  # 通道数
-        fmt.frameTimeUsec = 20000  # 每帧的时间(20 毫秒)
-        fmt.bitsPerSample = 16  # 每个采样的位数
-        return fmt
+    def chat(self, user_asr_text=None):
+        # TODO 调用文本机器人接口
+        message = {'player_file', self.scripts.get()}
+        player = message.get('player_file')
+        self.player_queue.put(player)
 
 
 class BotAgent: