[项目]行空板 AI虚拟人 精华

3005浏览
查看: 3005|回复: 2

[项目] 行空板 AI虚拟人

[复制链接]
本帖最后由 云天 于 2024-1-18 21:34 编辑

【项目背景】
       在2022年的北京冬奥会上,以中国天气节目主持人冯殊为原型的虚拟主播冯小殊亮相荧屏。一张写实的脸,自然的语气和举止,让不少网友感叹,真假难辨神。通过科技服务的方式更好地提升气象服务的质量,而“冯小殊”的加入,也为这场科技冬奥贡献了一份来自AI的力量。《虚拟数字人深度产业报告》显示,到2030年,我国虚拟数字人整体市场规模将达到2700亿元,而当前虚拟人产业还处于培育阶段。
       虚拟数字人:是人工智能产物,利用最新的信息科技技术对人体在不同水平的形态和功能进行虚拟仿真。它背后集成了多模态建模、语音识别、知识图谱、视觉技术等综合AI能力,具有一定的逼真效果。
       2012年的“虚拟偶像”洛天依,这是二次元的虚拟人,如今的超现实虚拟人,2021年5月,天猫合作打造的首个超写实数字人AYAYI出世,入住小红书一个月内收获了280万浏览量和11.1万点赞收藏,涨粉4.9万,站内千赞转化比达到5以上,9月8日,她正式成为阿里的数字人员工。
行空板 AI虚拟人图4

【项目设计】
       使用行空板结合讯飞的“AI虚拟人技术”(AI 自动预测表情、智能预测口型、实时处理唇形,表情真实,自然生动。支持正常播报和交互动作,并在动作库里为各个应用场景添加了场景特性动作,使虚拟人生动自然。),展示“虚拟人”的简单应用。
行空板 AI虚拟人图5


行空板 AI虚拟人图3

       项目1:使用两个按钮进行文本驱动,控制虚拟人的相应动作。如让她赞美一下我“这个程序写的真好,你真棒!”。
       项目2:使用语音驱动,与虚拟人进行对话。
【注册领取虚拟人】
       在讯飞开放平台https://console.xfyun.cn/,注册帐号,并领取虚拟人(7天免费使用)控制台-讯飞开放平台 (xfyun.cn)
行空板 AI虚拟人图6

【下载Python-Demo】
       AI虚拟人技术 API 文档提供了Python开发语言的demo:AI虚拟人技术 API 文档 | 讯飞开放平台文档中心 (xfyun.cn)
行空板 AI虚拟人图7

【电脑Mind+中测试】
       将文本上传给讯飞平台,使用Opencv库拉取讯飞虚拟人的RTMP流,进行播放。并使用讯飞语音合成,同时播放语音。
行空板 AI虚拟人图2


行空板 AI虚拟人图1


行空板 AI虚拟人图8


修改“avatar_id”更换虚拟人形象。

  1.   "avatar_id": "110021007",#110017006  110021007
复制代码
文本驱动“text_ctrl”,增加“action”参数,控制虚拟人“动作”。
  1.    # 文本驱动
  2.    def text_ctrl(self, text_url, session, text,action):
  3.        # 合成文本
  4.        encode_str = base64.encodebytes(text.encode("UTF8"))
  5.        txt = encode_str.decode()
  6.        if action==1:
  7.            #value="A_RLH_introduced_O"
  8.            value="A_RLH_emphasize_O"
  9.        elif action==2:
  10.            #value="A_RH_good_O"
  11.            value="A_RH_encourage_O"
  12.        action_text='{"avatar":[{"type": "action","value": "'+value+'","wb": 0}]}'
  13.        action_text=base64.encodebytes(action_text.encode("UTF8"))
  14.        action_text=action_text.decode()
  15.        print(value)
  16.        #action_text=""
  17.        data = {
  18.            "header": {
  19.                "app_id": self.app_id,
  20.                "session": session,
  21.                "uid": ""
  22.            },
  23.            "parameter": {
  24.                "tts": {
  25.                    "vcn": "x3_qianxue",
  26.                    "speed": 50,
  27.                    "pitch": 50,
  28.                    "volume": 50
  29.                }
  30.            },
  31.            "payload": {
  32.                "text": {
  33.                    "encoding": "utf8",
  34.                    "status": 3,
  35.                    "text": txt
  36.                },
  37.                "ctrl_w": {
  38.                    "encoding": "utf8",
  39.                    "format": "json",
  40.                    "status": 3,
  41.                    "text":action_text
  42.                 }
  43.            }
  44.        }
  45.        self.get_url_data(text_url, data)
  46.        print("请求参数:",json.dumps(data))
复制代码

       使用电脑键盘“a"、“b”,控制“虚拟人”执行相应动作。
       完整代码:
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # @Author : iflytek
  4. import requests
  5. import json
  6. import base64
  7. import hashlib
  8. import time
  9. from urllib.parse import urlencode
  10. import hmac
  11. from datetime import datetime
  12. from wsgiref.handlers import format_date_time
  13. from time import mktime
  14. from ws4py.client.threadedclient import WebSocketClient
  15. import logging
  16. import cv2
  17. from df_xfyun_speech import XfTts
  18. import pygame
  19. pygame.mixer.init()
  20. text = ""
  21. action=0
  22. logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
  23. ################init 参数######################
  24. HOST = "vms.cn-huadong-1.xf-yun.com"
  25. # 音频驱动参数
  26. STATUS_FIRST_FRAME = 0  # 第一帧的标识
  27. STATUS_CONTINUE_FRAME = 1  # 中间帧标识
  28. STATUS_LAST_FRAME = 2  # 最后一帧的标识
  29. # 用户参数,相关参数注意修改
  30. appId = "******************"
  31. apiKey ="******************"
  32. apiSecret = "******************"
  33. options = {}
  34. tts = XfTts(appId, apiKey, apiSecret, options)
  35. ###############################################
  36. APP_ID = "******************"
  37. API_SECRET = "******************"
  38. API_KEY = "******************"
  39. class RequestParam(object):
  40.     def __init__(self):
  41.         self.host = HOST
  42.         self.app_id = APP_ID
  43.         self.api_key = API_KEY
  44.         self.api_secret = API_SECRET
  45.     # 生成鉴权的url
  46.     def assemble_auth_url(self, path, method='POST', schema='http'):
  47.         params = self.assemble_auth_params(path, method)
  48.         # 请求地址
  49.         request_url = "%s://"%schema + self.host + path
  50.         # 拼接请求地址和鉴权参数,生成带鉴权参数的url
  51.         auth_url = request_url + "?" + urlencode(params)
  52.         return auth_url
  53.     # 生成鉴权的参数
  54.     def assemble_auth_params(self, path, method):
  55.         # 生成RFC1123格式的时间戳
  56.         format_date = format_date_time(mktime(datetime.now().timetuple()))
  57.         # 拼接字符串
  58.         signature_origin = "host: " + self.host + "\n"
  59.         signature_origin += "date: " + format_date + "\n"
  60.         signature_origin += method+ " " + path + " HTTP/1.1"
  61.         # 进行hmac-sha256加密
  62.         signature_sha = hmac.new(self.api_secret.encode('utf-8'), signature_origin.encode('utf-8'),
  63.                                  digestmod=hashlib.sha256).digest()
  64.         signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  65.         # 构建请求参数
  66.         authorization_origin = 'api_key="%s", algorithm="%s", headers="%s", signature="%s"' % (
  67.             self.api_key, "hmac-sha256", "host date request-line", signature_sha)
  68.         # 将请求参数使用base64编码
  69.         authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  70.         # 将请求的鉴权参数组合为字典
  71.         params = {
  72.             "host": self.host,
  73.             "date": format_date,
  74.             "authorization": authorization
  75.         }
  76.         return params
  77. class VmsApi(RequestParam):
  78.     # 接口data请求参数,字段具体含义见官网文档
  79.     # 启动
  80.     stream_url=""
  81.     def start(self, start_url):
  82.       
  83.         data = {
  84.             "header": {
  85.                 "app_id": self.app_id,
  86.                 "uid": ""
  87.             },
  88.             "parameter": {
  89.                 "vmr": {
  90.                     "stream": {
  91.                         "protocol": "rtmp"
  92.                     },
  93.                     "avatar_id": "110021007",#110017006  110021007
  94.                     "width": 600,
  95.                     "height": 800
  96.                 }
  97.             }
  98.         }
  99.         url_data = self.get_url_data(start_url, data)
  100.         session = ''
  101.         if url_data:
  102.             session = url_data.get('header', {}).get('session', '')
  103.             self.stream_url = url_data.get('header', {}).get('stream_url', '拉流地址获取失败')
  104.             print("拉流地址:%s" %self.stream_url)
  105.         return session
  106.     # 心跳
  107.     def ping(self, ping_url, session):
  108.       
  109.         data = {
  110.                 "header": {
  111.                     "app_id": self.app_id,
  112.                     "uid":"",
  113.                     "session": session
  114.                 }
  115.             }
  116.      
  117.         self.get_url_data(ping_url, data)
  118.     # 停止
  119.     def stop(self, stop_url, session):
  120.         data = {
  121.             "header": {
  122.                 "app_id": self.app_id,
  123.                 "session": session,
  124.                 "uid":""
  125.             }
  126.         }
  127.         self.get_url_data(stop_url, data)
  128.     # 文本驱动
  129.     def text_ctrl(self, text_url, session, text,action):
  130.         # 合成文本
  131.         encode_str = base64.encodebytes(text.encode("UTF8"))
  132.         txt = encode_str.decode()
  133.         if action==1:
  134.             #value="A_RLH_introduced_O"
  135.             value="A_RLH_emphasize_O"
  136.         elif action==2:
  137.             #value="A_RH_good_O"
  138.             value="A_RH_encourage_O"
  139.         action_text='{"avatar":[{"type": "action","value": "'+value+'","wb": 0}]}'
  140.         action_text=base64.encodebytes(action_text.encode("UTF8"))
  141.         action_text=action_text.decode()
  142.         print(value)
  143.         #action_text=""
  144.         data = {
  145.             "header": {
  146.                 "app_id": self.app_id,
  147.                 "session": session,
  148.                 "uid": ""
  149.             },
  150.             "parameter": {
  151.                 "tts": {
  152.                     "vcn": "x3_qianxue",
  153.                     "speed": 50,
  154.                     "pitch": 50,
  155.                     "volume": 50
  156.                 }
  157.             },
  158.             "payload": {
  159.                 "text": {
  160.                     "encoding": "utf8",
  161.                     "status": 3,
  162.                     "text": txt
  163.                 },
  164.                 "ctrl_w": {
  165.                     "encoding": "utf8",
  166.                     "format": "json",
  167.                     "status": 3,
  168.                     "text":action_text
  169.                  }
  170.             }
  171.         }
  172.         self.get_url_data(text_url, data)
  173.         print("请求参数:",json.dumps(data))
  174.     # 音频驱动
  175.     def audio_ctrl(self, audio_url, session, audio_file):
  176.         auth_audio_url = self.assemble_auth_url(audio_url, 'GET', 'ws')
  177.         ws = AudioCtrl(auth_audio_url, session, audio_file)
  178.         ws.connect()
  179.         ws.run_forever()
  180.     def get_url_data(self, url, data):
  181.         auth_url = self.assemble_auth_url(url)
  182.         print("示例url:",auth_url)
  183.         headers = {'Content-Type': 'application/json'}
  184.         try:
  185.             result = requests.post(url=auth_url, headers=headers, data=json.dumps(data))
  186.             result = json.loads(result.text)
  187.             print("response:",json.dumps(result))
  188.             code = result.get('header', {}).get('code')
  189.             if code == 0:
  190.                 logging.info("%s 接口调用成功" % url)
  191.                 return result
  192.             else:
  193.                 logging.error("%s 接口调用失败,错误码:%s" % (url, code))
  194.                 return {}
  195.         except Exception as e:
  196.             logging.error("%s 接口调用异常,错误详情:%s" %(url, e) )
  197.             return {}
  198. # websocket 音频驱动
  199. class AudioCtrl(WebSocketClient):
  200.     def __init__(self, url, session, file_path):
  201.         super().__init__(url)
  202.         self.file_path = file_path
  203.         self.session = session
  204.         self.app_id = APP_ID
  205.     # 收到websocket消息的处理
  206.     def received_message(self, message):
  207.         message = message.__str__()
  208.         try:
  209.             res = json.loads(message)
  210.             print("response:",json.dumps(res))
  211.             # 音频驱动接口返回状态码
  212.             code = res.get('header', {}).get('code')
  213.             # 状态码为0,音频驱动接口调用成功
  214.             if code == 0:
  215.                 logging.info("音频驱动接口调用成功")
  216.             # 状态码非0,音频驱动接口调用失败, 相关错误码参考官网文档
  217.             else:
  218.                 logging.info("音频驱动接口调用失败,返回状态码: %s" % code)
  219.         except Exception as e:
  220.             logging.info("音频驱动接口调用失败,错误详情:%s" % e)
  221.     # 收到websocket错误的处理
  222.     def on_error(self, error):
  223.         logging.error(error)
  224.     # 收到websocket关闭的处理
  225.     def closed(self, code, reason=None):
  226.         logging.info('音频驱动:websocket关闭')
  227.     # 收到websocket连接建立的处理
  228.     def opened(self):
  229.         logging.info('音频驱动:websocket连接建立')
  230.         frame_size = 1280  # 每一帧音频大小
  231.         interval = 0.04  # 发送音频间隔(单位:s)
  232.         status = STATUS_FIRST_FRAME  # 音频的状态信息,标识音频是第一帧,还是中间帧、最后一帧
  233.         count = 1
  234.         with open(self.file_path, 'rb') as file:
  235.             while True:
  236.                 buffer = file.read(frame_size)
  237.                 if len(buffer) < frame_size:
  238.                     status = STATUS_LAST_FRAME
  239.                 # 第一帧处理
  240.                 if status == STATUS_FIRST_FRAME:
  241.                     self.send_frame(status, buffer, count)
  242.                     status = STATUS_CONTINUE_FRAME
  243.                 # 中间帧处理
  244.                 elif status == STATUS_CONTINUE_FRAME:
  245.                     self.send_frame(status, buffer, count)
  246.                 # 最后一帧处理
  247.                 elif status == STATUS_LAST_FRAME:
  248.                     self.send_frame(status, buffer, count)
  249.                     break
  250.                 count += 1
  251.                 # 音频采样间隔
  252.                 time.sleep(interval)
  253.     # 发送音频
  254.     def send_frame(self, status, audio_buffer, seq):
  255.         data = {
  256.             "header": {
  257.                 "app_id": self.app_id,
  258.                 "session": self.session,
  259.                 "status": status,
  260.                 "uid":""
  261.             },
  262.             "payload": {
  263.                 "audio": {
  264.                     "encoding": "raw",
  265.                     "sample_rate": 16000,
  266.                     "status": status,
  267.                     "seq": seq,
  268.                     "audio": base64.encodebytes(audio_buffer).decode("utf-8")
  269.                 }
  270.             }
  271.         }
  272.         json_data = json.dumps(data)
  273.         print("请求参数:",json_data)
  274.         self.send(json_data)
  275. if __name__ == "__main__":
  276.     vms = VmsApi()
  277.     start_url = "/v1/private/vms2d_start"
  278.     print("启动")
  279.     session = vms.start(start_url)
  280.     if session:
  281.         # 文本驱动,自定义文本内容
  282.         time.sleep(10)
  283.         ping_url = "/v1/private/vms2d_ping"
  284.         print("\n文本驱动")
  285.         
  286.         text_url = "/v1/private/vms2d_ctrl"
  287.         
  288.         print("返回的地址:"+vms.stream_url)
  289.         if vms.stream_url!="":
  290.             rtmp_url =vms.stream_url
  291.             cap = cv2.VideoCapture(rtmp_url)
  292.             cap.set(cv2.CAP_PROP_FRAME_WIDTH,600)
  293.             cap.set(cv2.CAP_PROP_FRAME_HEIGHT,800)
  294.             if not cap.isOpened():
  295.                 print("无法连接到RTMP流")
  296.             else:
  297.                 print("成功连接到RTMP流")
  298.             bs=0
  299.             while True:
  300.                  for i in range(3):
  301.                     ret, frame = cap.read()
  302.                  ret, frame = cap.read()   
  303.                  #frame = cv2.resize(frame,( 240, 320))
  304.                  
  305.                  if not ret:
  306.                       break
  307.                  cv2.imshow("Windows", frame)
  308.                  if cv2.waitKey(1) & 0xFF == ord('q'):
  309.                     break
  310.                  
  311.                     
  312.                  if cv2.waitKey(1) & 0xFF == ord('a'):
  313.                      action=1
  314.                      text="大家好,我是讯飞虚拟人!"
  315.                      
  316.                      vms.text_ctrl(text_url, session, text,action)
  317.                      tts.synthesis(text, "speech.wav")
  318.                      pygame.mixer.Sound("speech.wav").play()
  319.                      
  320.                               
  321.                  if cv2.waitKey(1) & 0xFF == ord('b'):
  322.                      action=2
  323.                      text="这个程序写的真好,你真棒!"
  324.                      vms.text_ctrl(text_url, session, text,action)
  325.                      tts.synthesis(text, "speech.wav")
  326.                      pygame.mixer.Sound("speech.wav").play()
  327.                      
  328.    
  329.                                  
  330.                     
  331.         
  332.         # 停止
  333.         time.sleep(10)
  334.         print("\n停止")
  335.         stop_url = "/v1/private/vms2d_stop"
  336.         vms.stop(stop_url, session)
  337.       
复制代码
   演示视频

【行空板文本驱动】
       两个按钮分别接22和24引脚,使用两个按钮进行文本驱动控制虚拟人。文本一:“大家好,我是讯飞虚拟人!”,文本二:“这个程序写的真好,你真棒!”,并使用语音合成,通过蓝牙音箱播放语音。
  1. p_p22_in=Pin(Pin.P22, Pin.IN)
  2. p_p24_in=Pin(Pin.P24, Pin.IN)
复制代码
       在“终端”中使用“bluetoothctl”配置并连接蓝牙音箱。
       Python程序代码
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # @Author : iflytek
  4. import requests
  5. import json
  6. import base64
  7. import hashlib
  8. import time
  9. from urllib.parse import urlencode
  10. import hmac
  11. from datetime import datetime
  12. from wsgiref.handlers import format_date_time
  13. from time import mktime
  14. from ws4py.client.threadedclient import WebSocketClient
  15. import logging
  16. import cv2
  17. from pinpong.extension.unihiker import *
  18. from pinpong.board import Board,Pin
  19. from df_xfyun_speech import XfTts
  20. from unihiker import GUI
  21. u_gui=GUI()
  22. from unihiker import Audio
  23. u_audio = Audio()
  24. appId = "******************"
  25. apiKey ="******************"
  26. apiSecret = "******************"
  27. options = {}
  28. tts = XfTts(appId, apiKey, apiSecret, options)
  29. Board().begin()
  30. p_p22_in=Pin(Pin.P22, Pin.IN)
  31. p_p24_in=Pin(Pin.P24, Pin.IN)
  32. text = ""
  33. action=0
  34. logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
  35. ################init 参数######################
  36. HOST = "vms.cn-huadong-1.xf-yun.com"
  37. # 音频驱动参数
  38. STATUS_FIRST_FRAME = 0  # 第一帧的标识
  39. STATUS_CONTINUE_FRAME = 1  # 中间帧标识
  40. STATUS_LAST_FRAME = 2  # 最后一帧的标识
  41. # 用户参数,相关参数注意修改
  42. APP_ID = "******************"
  43. API_SECRET = "******************"
  44. API_KEY = "******************"
  45. ###############################################
  46. class RequestParam(object):
  47.     def __init__(self):
  48.         self.host = HOST
  49.         self.app_id = APP_ID
  50.         self.api_key = API_KEY
  51.         self.api_secret = API_SECRET
  52.     # 生成鉴权的url
  53.     def assemble_auth_url(self, path, method='POST', schema='http'):
  54.         params = self.assemble_auth_params(path, method)
  55.         # 请求地址
  56.         request_url = "%s://"%schema + self.host + path
  57.         # 拼接请求地址和鉴权参数,生成带鉴权参数的url
  58.         auth_url = request_url + "?" + urlencode(params)
  59.         return auth_url
  60.     # 生成鉴权的参数
  61.     def assemble_auth_params(self, path, method):
  62.         # 生成RFC1123格式的时间戳
  63.         format_date = format_date_time(mktime(datetime.now().timetuple()))
  64.         # 拼接字符串
  65.         signature_origin = "host: " + self.host + "\n"
  66.         signature_origin += "date: " + format_date + "\n"
  67.         signature_origin += method+ " " + path + " HTTP/1.1"
  68.         # 进行hmac-sha256加密
  69.         signature_sha = hmac.new(self.api_secret.encode('utf-8'), signature_origin.encode('utf-8'),
  70.                                  digestmod=hashlib.sha256).digest()
  71.         signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  72.         # 构建请求参数
  73.         authorization_origin = 'api_key="%s", algorithm="%s", headers="%s", signature="%s"' % (
  74.             self.api_key, "hmac-sha256", "host date request-line", signature_sha)
  75.         # 将请求参数使用base64编码
  76.         authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  77.         # 将请求的鉴权参数组合为字典
  78.         params = {
  79.             "host": self.host,
  80.             "date": format_date,
  81.             "authorization": authorization
  82.         }
  83.         return params
  84. class VmsApi(RequestParam):
  85.     # 接口data请求参数,字段具体含义见官网文档
  86.     # 启动
  87.     stream_url=""
  88.     def start(self, start_url):
  89.       
  90.         data = {
  91.             "header": {
  92.                 "app_id": self.app_id,
  93.                 "uid": ""
  94.             },
  95.             "parameter": {
  96.                 "vmr": {
  97.                     "stream": {
  98.                         "protocol": "rtmp"
  99.                     },
  100.                     "avatar_id": "110021007",
  101.                     "width": 300,
  102.                     "height": 400
  103.                 }
  104.             }
  105.         }
  106.         url_data = self.get_url_data(start_url, data)
  107.         session = ''
  108.         if url_data:
  109.             session = url_data.get('header', {}).get('session', '')
  110.             self.stream_url = url_data.get('header', {}).get('stream_url', '拉流地址获取失败')
  111.             print("拉流地址:%s" %self.stream_url)
  112.         return session
  113.     # 心跳
  114.     def ping(self, ping_url, session):
  115.       
  116.         data = {
  117.                 "header": {
  118.                     "app_id": self.app_id,
  119.                     "uid":"",
  120.                     "session": session
  121.                 }
  122.             }
  123.      
  124.         self.get_url_data(ping_url, data)
  125.     # 停止
  126.     def stop(self, stop_url, session):
  127.         data = {
  128.             "header": {
  129.                 "app_id": self.app_id,
  130.                 "session": session,
  131.                 "uid":""
  132.             }
  133.         }
  134.         self.get_url_data(stop_url, data)
  135.     # 文本驱动
  136.     def text_ctrl(self, text_url, session, text,action):
  137.         # 合成文本
  138.         encode_str = base64.encodebytes(text.encode("UTF8"))
  139.         txt = encode_str.decode()
  140.         if action==1:
  141.             #value="A_RLH_introduced_O"
  142.             value="A_RLH_emphasize_O"
  143.         elif action==2:
  144.             #value="A_RH_good_O"
  145.             value="A_RH_encourage_O"
  146.         action_text='{"avatar":[{"type": "action","value": "'+value+'","wb": 0}]}'
  147.         action_text=base64.encodebytes(action_text.encode("UTF8"))
  148.         action_text=action_text.decode()
  149.         print(value)
  150.         #action_text=""
  151.         data = {
  152.             "header": {
  153.                 "app_id": self.app_id,
  154.                 "session": session,
  155.                 "uid": ""
  156.             },
  157.             "parameter": {
  158.                 "tts": {
  159.                     "vcn": "x3_qianxue",
  160.                     "speed": 50,
  161.                     "pitch": 50,
  162.                     "volume": 50
  163.                 }
  164.             },
  165.             "payload": {
  166.                 "text": {
  167.                     "encoding": "utf8",
  168.                     "status": 3,
  169.                     "text": txt
  170.                 },
  171.                 "ctrl_w": {
  172.                     "encoding": "utf8",
  173.                     "format": "json",
  174.                     "status": 3,
  175.                     "text":action_text
  176.                  }
  177.             }
  178.         }
  179.         self.get_url_data(text_url, data)
  180.         print("请求参数:",json.dumps(data))
  181.     # 音频驱动
  182.     def audio_ctrl(self, audio_url, session, audio_file):
  183.         auth_audio_url = self.assemble_auth_url(audio_url, 'GET', 'ws')
  184.         ws = AudioCtrl(auth_audio_url, session, audio_file)
  185.         ws.connect()
  186.         ws.run_forever()
  187.     def get_url_data(self, url, data):
  188.         auth_url = self.assemble_auth_url(url)
  189.         print("示例url:",auth_url)
  190.         headers = {'Content-Type': 'application/json'}
  191.         try:
  192.             result = requests.post(url=auth_url, headers=headers, data=json.dumps(data))
  193.             result = json.loads(result.text)
  194.             print("response:",json.dumps(result))
  195.             code = result.get('header', {}).get('code')
  196.             if code == 0:
  197.                 logging.info("%s 接口调用成功" % url)
  198.                 return result
  199.             else:
  200.                 logging.error("%s 接口调用失败,错误码:%s" % (url, code))
  201.                 return {}
  202.         except Exception as e:
  203.             logging.error("%s 接口调用异常,错误详情:%s" %(url, e) )
  204.             return {}
  205. # websocket 音频驱动
  206. class AudioCtrl(WebSocketClient):
  207.     def __init__(self, url, session, file_path):
  208.         super().__init__(url)
  209.         self.file_path = file_path
  210.         self.session = session
  211.         self.app_id = APP_ID
  212.     # 收到websocket消息的处理
  213.     def received_message(self, message):
  214.         message = message.__str__()
  215.         try:
  216.             res = json.loads(message)
  217.             print("response:",json.dumps(res))
  218.             # 音频驱动接口返回状态码
  219.             code = res.get('header', {}).get('code')
  220.             # 状态码为0,音频驱动接口调用成功
  221.             if code == 0:
  222.                 logging.info("音频驱动接口调用成功")
  223.             # 状态码非0,音频驱动接口调用失败, 相关错误码参考官网文档
  224.             else:
  225.                 logging.info("音频驱动接口调用失败,返回状态码: %s" % code)
  226.         except Exception as e:
  227.             logging.info("音频驱动接口调用失败,错误详情:%s" % e)
  228.     # 收到websocket错误的处理
  229.     def on_error(self, error):
  230.         logging.error(error)
  231.     # 收到websocket关闭的处理
  232.     def closed(self, code, reason=None):
  233.         logging.info('音频驱动:websocket关闭')
  234.     # 收到websocket连接建立的处理
  235.     def opened(self):
  236.         logging.info('音频驱动:websocket连接建立')
  237.         frame_size = 1280  # 每一帧音频大小
  238.         interval = 0.04  # 发送音频间隔(单位:s)
  239.         status = STATUS_FIRST_FRAME  # 音频的状态信息,标识音频是第一帧,还是中间帧、最后一帧
  240.         count = 1
  241.         with open(self.file_path, 'rb') as file:
  242.             while True:
  243.                 buffer = file.read(frame_size)
  244.                 if len(buffer) < frame_size:
  245.                     status = STATUS_LAST_FRAME
  246.                 # 第一帧处理
  247.                 if status == STATUS_FIRST_FRAME:
  248.                     self.send_frame(status, buffer, count)
  249.                     status = STATUS_CONTINUE_FRAME
  250.                 # 中间帧处理
  251.                 elif status == STATUS_CONTINUE_FRAME:
  252.                     self.send_frame(status, buffer, count)
  253.                 # 最后一帧处理
  254.                 elif status == STATUS_LAST_FRAME:
  255.                     self.send_frame(status, buffer, count)
  256.                     break
  257.                 count += 1
  258.                 # 音频采样间隔
  259.                 time.sleep(interval)
  260.     # 发送音频
  261.     def send_frame(self, status, audio_buffer, seq):
  262.         data = {
  263.             "header": {
  264.                 "app_id": self.app_id,
  265.                 "session": self.session,
  266.                 "status": status,
  267.                 "uid":""
  268.             },
  269.             "payload": {
  270.                 "audio": {
  271.                     "encoding": "raw",
  272.                     "sample_rate": 16000,
  273.                     "status": status,
  274.                     "seq": seq,
  275.                     "audio": base64.encodebytes(audio_buffer).decode("utf-8")
  276.                 }
  277.             }
  278.         }
  279.         json_data = json.dumps(data)
  280.         print("请求参数:",json_data)
  281.         self.send(json_data)
  282. if __name__ == "__main__":
  283.     vms = VmsApi()
  284.     start_url = "/v1/private/vms2d_start"
  285.     print("启动")
  286.     session = vms.start(start_url)
  287.     if session:
  288.         # 文本驱动,自定义文本内容
  289.         time.sleep(10)
  290.         ping_url = "/v1/private/vms2d_ping"
  291.         print("\n文本驱动")
  292.         
  293.         text_url = "/v1/private/vms2d_ctrl"
  294.         
  295.         print("返回的地址:"+vms.stream_url)
  296.         if vms.stream_url!="":
  297.             rtmp_url =vms.stream_url
  298.             cap = cv2.VideoCapture(rtmp_url)
  299.             cap.set(cv2.CAP_PROP_FRAME_WIDTH,240)
  300.             cap.set(cv2.CAP_PROP_FRAME_HEIGHT,320)
  301.             cv2.namedWindow('Windows', cv2.WINDOW_NORMAL)
  302.             cv2.setWindowProperty('Windows', cv2.WND_PROP_FULLSCREEN, cv2.WINDOW_FULLSCREEN)
  303.             if not cap.isOpened():
  304.                 print("无法连接到RTMP流")
  305.             else:
  306.                 print("成功连接到RTMP流")
  307.             bs=0
  308.             while True:
  309.                  for i in range(3):
  310.                     ret, frame = cap.read()
  311.                  ret, frame = cap.read()   
  312.                  frame = cv2.resize(frame,( 240, 320))
  313.                  
  314.                  if not ret:
  315.                       break
  316.                  cv2.imshow("Windows", frame)
  317.                  if cv2.waitKey(1) & 0xFF == ord('q'):
  318.                     break
  319.                  
  320.                     
  321.                  if (p_p22_in.read_digital()==True):
  322.                      action=1
  323.                      text="大家好,我是讯飞虚拟人"
  324.                      tts.synthesis(text, "speech.wav")
  325.                      u_audio.start_play("speech.wav")
  326.                      vms.text_ctrl(text_url, session, text,action)
  327.                     
  328.                  if (p_p24_in.read_digital()==True):
  329.                      action=2
  330.                      text="这个程序写的真好,你真棒"
  331.                      tts.synthesis(text, "speech.wav")
  332.                      u_audio.start_play("speech.wav")
  333.                      vms.text_ctrl(text_url, session, text,action)
  334.                      
  335.                   
  336.    
  337.                                  
  338.                     
  339.                
  340.         # 停止
  341.         time.sleep(10)
  342.         print("\n停止")
  343.         stop_url = "/v1/private/vms2d_stop"
  344.         vms.stop(stop_url, session)
  345.       
复制代码
      演示视频

【语音对话】
       使用“讯飞语音识别”,将用户语音识别成文本与“星火认识大模型”进行对话,将大模型反馈的结果送给虚拟人,同时利用讯飞语音合成播放语音。(文本长时,口型最后有些对不上,大家共同交流学习)
1.演示视频

2.测试程序代码
(1)服务侧设置60秒超时,要求客户端每间隔一段时间发起一次心跳进行保活,否则停止会话。
  1.                 if time.time()-temtime>10:
  2.                   temtime=time.time()
  3.                   # 心跳
  4.                   print("\n心跳")
  5.                   ping_url = "/v1/private/vms2d_ping"
  6.                   vms.ping(ping_url, session)
复制代码
(2)使用多线程,同步录音。
  1. from threading import Thread
复制代码
(3)使用全局变量result1,判断文本驱动是否成功,同步播放合成语音。(4)完整代码
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. # @Author : iflytek
  4. import listening
  5. import requests
  6. import json
  7. import base64
  8. import hashlib
  9. import time
  10. from urllib.parse import urlencode
  11. import hmac
  12. from datetime import datetime
  13. from wsgiref.handlers import format_date_time
  14. from time import mktime
  15. from ws4py.client.threadedclient import WebSocketClient
  16. import logging
  17. import cv2
  18. from pinpong.extension.unihiker import *
  19. from pinpong.board import Board,Pin
  20. from df_xfyun_speech import XfTts
  21. from df_xfyun_speech import XfIat
  22. from threading import Thread
  23. from unihiker import Audio
  24. import SparkApi
  25. u_audio = Audio()
  26. appId = "*********************"
  27. apiKey ="*********************"
  28. apiSecret = "*********************"
  29. options = {}
  30. business_args = {"aue":"raw","vcn":"x4_xiaoxuan","tte":"utf8","speed":50,"volume":50,"pitch":50,"bgs":0}
  31. options["business_args"] = business_args
  32. iat = XfIat(appId, apiKey, apiSecret)
  33. tts = XfTts(appId, apiKey, apiSecret, options)
  34. appid2 ="*********************"    #填写控制台中获取的 APPID 信息
  35. api_secret2 = "*********************"  #填写控制台中获取的 APISecret 信息
  36. api_key2 ="*********************"    #填写控制台中获取的 APIKey 信息
  37. #用于配置大模型版本,默认“general/generalv2”
  38. domain = "generalv3"   # v1.5版本
  39. # domain = "generalv2"    # v2.0版本
  40. #云端环境的服务地址
  41. Spark_url = "ws://spark-api.xf-yun.com/v3.1/chat"  # v1.5环境的地址
  42. # Spark_url = "ws://spark-api.xf-yun.com/v2.1/chat"  # v2.0环境的地址
  43. text =[]
  44. # length = 0
  45. def getText(role,content):
  46.     jsoncon = {}
  47.     jsoncon["role"] = role
  48.     jsoncon["content"] = content
  49.     text.append(jsoncon)
  50.     return text
  51. def getlength(text):
  52.     length = 0
  53.     for content in text:
  54.         temp = content["content"]
  55.         leng = len(temp)
  56.         length += leng
  57.     return length
  58. def checklen(text):
  59.     while (getlength(text) > 8000):
  60.         del text[0]
  61.     return text
  62. def task():
  63.                      global result1
  64.                      listening.listen()
  65.                      Input= iat.recognition("record.wav")
  66.                      print(Input)
  67.                      if Input!="":
  68.                       question = checklen(getText("user",Input))
  69.                       SparkApi.answer =""
  70.                       print("星火:",end = "")
  71.                       SparkApi.main(appid2,api_key2,api_secret2,Spark_url,domain,question)
  72.                       if SparkApi.answer!="":
  73.                        getText("assistant",SparkApi.answer)
  74.                        tts.synthesis(SparkApi.answer, "speech.wav")
  75.                        time.sleep(1)
  76.                        text1=SparkApi.answer
  77.                        
  78.                        result1=vms.text_ctrl(text_url, session, text1)
  79.                        
  80.                        
  81.                       else:
  82.                           print("语音识别为空")
  83.   
  84. action=0
  85. logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO)
  86. ################init 参数######################
  87. HOST = "vms.cn-huadong-1.xf-yun.com"
  88. # 音频驱动参数
  89. STATUS_FIRST_FRAME = 0  # 第一帧的标识
  90. STATUS_CONTINUE_FRAME = 1  # 中间帧标识
  91. STATUS_LAST_FRAME = 2  # 最后一帧的标识
  92. # 用户参数,相关参数注意修改
  93. APP_ID = "*********************"
  94. API_SECRET = "*********************"
  95. API_KEY = "*********************"
  96. ###############################################
  97. class RequestParam(object):
  98.     def __init__(self):
  99.         self.host = HOST
  100.         self.app_id = APP_ID
  101.         self.api_key = API_KEY
  102.         self.api_secret = API_SECRET
  103.     # 生成鉴权的url
  104.     def assemble_auth_url(self, path, method='POST', schema='http'):
  105.         params = self.assemble_auth_params(path, method)
  106.         # 请求地址
  107.         request_url = "%s://"%schema + self.host + path
  108.         # 拼接请求地址和鉴权参数,生成带鉴权参数的url
  109.         auth_url = request_url + "?" + urlencode(params)
  110.         return auth_url
  111.     # 生成鉴权的参数
  112.     def assemble_auth_params(self, path, method):
  113.         # 生成RFC1123格式的时间戳
  114.         format_date = format_date_time(mktime(datetime.now().timetuple()))
  115.         # 拼接字符串
  116.         signature_origin = "host: " + self.host + "\n"
  117.         signature_origin += "date: " + format_date + "\n"
  118.         signature_origin += method+ " " + path + " HTTP/1.1"
  119.         # 进行hmac-sha256加密
  120.         signature_sha = hmac.new(self.api_secret.encode('utf-8'), signature_origin.encode('utf-8'),
  121.                                  digestmod=hashlib.sha256).digest()
  122.         signature_sha = base64.b64encode(signature_sha).decode(encoding='utf-8')
  123.         # 构建请求参数
  124.         authorization_origin = 'api_key="%s", algorithm="%s", headers="%s", signature="%s"' % (
  125.             self.api_key, "hmac-sha256", "host date request-line", signature_sha)
  126.         # 将请求参数使用base64编码
  127.         authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
  128.         # 将请求的鉴权参数组合为字典
  129.         params = {
  130.             "host": self.host,
  131.             "date": format_date,
  132.             "authorization": authorization
  133.         }
  134.         return params
  135. class VmsApi(RequestParam):
  136.     # 接口data请求参数,字段具体含义见官网文档
  137.     # 启动
  138.     stream_url=""
  139.     def start(self, start_url):
  140.       
  141.         data = {
  142.             "header": {
  143.                 "app_id": self.app_id,
  144.                 "uid": ""
  145.             },
  146.             "parameter": {
  147.                 "vmr": {
  148.                     "stream": {
  149.                         "protocol": "rtmp"
  150.                     },
  151.                     "avatar_id": "110021007",
  152.                     "width": 600,
  153.                     "height": 800
  154.                 }
  155.             }
  156.         }
  157.         url_data = self.get_url_data(start_url, data)
  158.         session = ''
  159.         if url_data:
  160.             session = url_data.get('header', {}).get('session', '')
  161.             self.stream_url = url_data.get('header', {}).get('stream_url', '拉流地址获取失败')
  162.             print("拉流地址:%s" %self.stream_url)
  163.         return session
  164.     # 心跳
  165.     def ping(self, ping_url, session):
  166.       
  167.         data = {
  168.                 "header": {
  169.                     "app_id": self.app_id,
  170.                     "uid":"",
  171.                     "session": session
  172.                 }
  173.             }
  174.      
  175.         self.get_url_data(ping_url, data)
  176.     # 停止
  177.     def stop(self, stop_url, session):
  178.         data = {
  179.             "header": {
  180.                 "app_id": self.app_id,
  181.                 "session": session,
  182.                 "uid":""
  183.             }
  184.         }
  185.         self.get_url_data(stop_url, data)
  186.     # 文本驱动
  187.     def text_ctrl(self, text_url, session, text):
  188.         # 合成文本
  189.         encode_str = base64.encodebytes(text.encode("UTF8"))
  190.         txt = encode_str.decode()
  191.         value="A_RLH_emphasize_O"
  192.       
  193.         action_text='{"avatar":[{"type": "action","value": "'+value+'","wb": 0}]}'
  194.         action_text=base64.encodebytes(action_text.encode("UTF8"))
  195.         action_text=action_text.decode()
  196.         print(value)
  197.         #action_text=""
  198.         data = {
  199.             "header": {
  200.                 "app_id": self.app_id,
  201.                 "session": session,
  202.                 "uid": ""
  203.             },
  204.             "parameter": {
  205.                 "tts": {
  206.                     "vcn": "x4_xiaoxuan",
  207.                     "speed": 50,
  208.                     "pitch": 50,
  209.                     "volume": 50
  210.                 }
  211.             },
  212.             "payload": {
  213.                 "text": {
  214.                     "encoding": "utf8",
  215.                     "status": 3,
  216.                     "text": txt
  217.                 },
  218.                 "ctrl_w": {
  219.                     "encoding": "utf8",
  220.                     "format": "json",
  221.                     "status": 3,
  222.                     "text":action_text
  223.                  }
  224.             }
  225.         }
  226.         result=""
  227.         result=self.get_url_data(text_url, data)
  228.         if result!="":
  229.             return 1
  230.         else:
  231.             return 0
  232.         print("请求参数:",json.dumps(data))
  233.     # 音频驱动
  234.     def audio_ctrl(self, audio_url, session, audio_file):
  235.         auth_audio_url = self.assemble_auth_url(audio_url, 'GET', 'ws')
  236.         ws = AudioCtrl(auth_audio_url, session, audio_file)
  237.         ws.connect()
  238.         ws.run_forever()
  239.     def get_url_data(self, url, data):
  240.         auth_url = self.assemble_auth_url(url)
  241.         print("示例url:",auth_url)
  242.         headers = {'Content-Type': 'application/json'}
  243.         try:
  244.             result = requests.post(url=auth_url, headers=headers, data=json.dumps(data))
  245.             result = json.loads(result.text)
  246.             print("response:",json.dumps(result))
  247.             code = result.get('header', {}).get('code')
  248.             if code == 0:
  249.                 logging.info("%s 接口调用成功" % url)
  250.                 return result
  251.             else:
  252.                 logging.error("%s 接口调用失败,错误码:%s" % (url, code))
  253.                 return {}
  254.         except Exception as e:
  255.             logging.error("%s 接口调用异常,错误详情:%s" %(url, e) )
  256.             return {}
  257. # websocket 音频驱动
  258. class AudioCtrl(WebSocketClient):
  259.     def __init__(self, url, session, file_path):
  260.         super().__init__(url)
  261.         self.file_path = file_path
  262.         self.session = session
  263.         self.app_id = APP_ID
  264.     # 收到websocket消息的处理
  265.     def received_message(self, message):
  266.         message = message.__str__()
  267.         try:
  268.             res = json.loads(message)
  269.             print("response:",json.dumps(res))
  270.             # 音频驱动接口返回状态码
  271.             code = res.get('header', {}).get('code')
  272.             # 状态码为0,音频驱动接口调用成功
  273.             if code == 0:
  274.                 logging.info("音频驱动接口调用成功")
  275.             # 状态码非0,音频驱动接口调用失败, 相关错误码参考官网文档
  276.             else:
  277.                 logging.info("音频驱动接口调用失败,返回状态码: %s" % code)
  278.         except Exception as e:
  279.             logging.info("音频驱动接口调用失败,错误详情:%s" % e)
  280.     # 收到websocket错误的处理
  281.     def on_error(self, error):
  282.         logging.error(error)
  283.     # 收到websocket关闭的处理
  284.     def closed(self, code, reason=None):
  285.         logging.info('音频驱动:websocket关闭')
  286.     # 收到websocket连接建立的处理
  287.     def opened(self):
  288.         logging.info('音频驱动:websocket连接建立')
  289.         frame_size = 1280  # 每一帧音频大小
  290.         interval = 0.04  # 发送音频间隔(单位:s)
  291.         status = STATUS_FIRST_FRAME  # 音频的状态信息,标识音频是第一帧,还是中间帧、最后一帧
  292.         count = 1
  293.         with open(self.file_path, 'rb') as file:
  294.             while True:
  295.                 buffer = file.read(frame_size)
  296.                 if len(buffer) < frame_size:
  297.                     status = STATUS_LAST_FRAME
  298.                 # 第一帧处理
  299.                 if status == STATUS_FIRST_FRAME:
  300.                     self.send_frame(status, buffer, count)
  301.                     status = STATUS_CONTINUE_FRAME
  302.                 # 中间帧处理
  303.                 elif status == STATUS_CONTINUE_FRAME:
  304.                     self.send_frame(status, buffer, count)
  305.                 # 最后一帧处理
  306.                 elif status == STATUS_LAST_FRAME:
  307.                     self.send_frame(status, buffer, count)
  308.                     break
  309.                 count += 1
  310.                 # 音频采样间隔
  311.                 time.sleep(interval)
  312.     # 发送音频
  313.     def send_frame(self, status, audio_buffer, seq):
  314.         data = {
  315.             "header": {
  316.                 "app_id": self.app_id,
  317.                 "session": self.session,
  318.                 "status": status,
  319.                 "uid":""
  320.             },
  321.             "payload": {
  322.                 "audio": {
  323.                     "encoding": "raw",
  324.                     "sample_rate": 16000,
  325.                     "status": status,
  326.                     "seq": seq,
  327.                     "audio": base64.encodebytes(audio_buffer).decode("utf-8")
  328.                 }
  329.             }
  330.         }
  331.         json_data = json.dumps(data)
  332.         print("请求参数:",json_data)
  333.         self.send(json_data)
  334. temtime=time.time()
  335. if __name__ == "__main__":
  336.     vms = VmsApi()
  337.     start_url = "/v1/private/vms2d_start"
  338.     print("启动")
  339.     session = vms.start(start_url)
  340.     result1=0
  341.     text.clear
  342.     if session:
  343.         # 文本驱动,自定义文本内容
  344.         time.sleep(10)
  345.         ping_url = "/v1/private/vms2d_ping"
  346.         print("\n文本驱动")
  347.         
  348.         text_url = "/v1/private/vms2d_ctrl"
  349.         
  350.         print("返回的地址:"+vms.stream_url)
  351.         if vms.stream_url!="":
  352.             rtmp_url =vms.stream_url
  353.             cap = cv2.VideoCapture(rtmp_url)
  354.             cap.set(cv2.CAP_PROP_FRAME_WIDTH,600)
  355.             cap.set(cv2.CAP_PROP_FRAME_HEIGHT,800)
  356.          
  357.             if not cap.isOpened():
  358.                 print("无法连接到RTMP流")
  359.             else:
  360.                 print("成功连接到RTMP流")
  361.             bs=0
  362.             while True:
  363.                
  364.                  ret, frame = cap.read()   
  365.                
  366.                  
  367.                  if not ret:
  368.                       break
  369.                  cv2.imshow("Windows", frame)
  370.                  if cv2.waitKey(1) & 0xFF == ord('q'):
  371.                     break
  372.                  
  373.                  if cv2.waitKey(1) & 0xFF == ord('a'):
  374.                        print("启动线程")
  375.                        time.sleep(3)
  376.                        t1 = Thread(target=task)
  377.                        t1.start()
  378.                  if result1==1:
  379.                     result1=0
  380.                     u_audio.start_play("speech.wav")
  381.                     #t1._stop()
  382.                  if time.time()-temtime>10:
  383.                    temtime=time.time()
  384.                    # 心跳
  385.                    print("\n心跳")
  386.                    ping_url = "/v1/private/vms2d_ping"
  387.                    vms.ping(ping_url, session)
复制代码










gray6666  初级技神

发表于 2024-1-19 08:07:17

声音不同步,试试离线语音合成
回复

使用道具 举报

云天  初级技神
 楼主|

发表于 2024-1-19 08:42:29

gray6666 发表于 2024-1-19 08:07
声音不同步,试试离线语音合成

好的,这样应该能解决
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail