- 这是一个 Python SDK 封装的 Agora RTC SDK。
- 支持Linux和Mac平台。
- examples只是作为非常简单的演示,不建议在生产环境中使用。
- 一个进程只能有一个实例
- 一个实例,可以有多个connection
- 所有的observer或者是回调中,都不能在调用sdk自身的api,也不能在回调中做cpu耗时的工作,数据拷贝是可以的。
-
支持的 Linux 版本:
- Ubuntu 18.04 LTS 及以上
- CentOS 7.0 及以上
-
支持的 Mac 版本(仅支持开发测试):
- MacOS 13 及以上
-
Python 版本:
- Python 3.10及以上
pip install agora_python_server_sdk
- 下载并解压 test_data.zip 到Agora-Python-Server-SDK目录
python agora_rtc/examples/example_audio_pcm_send.py --appId=xxx --channelId=xxx --userId=xxx --audioFile=./test_data/demo.pcm --sampleRate=16000 --numOfChannels=1
-- 增加: -增加了AduioVadManger,用来管理vad instance -将vad 功能内置在sdk内部,开发者不在需要关注如何使用vad,只需要关注设置合适的参数就可以。参考: sample_audio_vad.py
- 修改: -- register_audio_frame_observer中,增加了2个参数,用于设置vad的参数,参考: sample_audio_vad.py -- 在on_playback_audio_frame_before_mixing中,返回值增加了2个参数: vad_result_state 和 vad_result_bytearray。 state: < 0 没有设置内部自动出来vad;0: nospeaking, 1: startspeakong; 2 speaking; 3 stopspeaking. vad_result_bytearray 在vad状态下返回的vad处理后的结果。 -- 如果启动了自动处理vad: . 开发者需要用vad_result_bytearray来做后续的业务处理,比如发送给ASR/STT, 而不是用frame 来做处理 参考: sample_audio_vad.py
- 优化: -- 在推送pcm中,不在使用pacer,而是使用Audioconsumer 进行推送。
- 更新: --修改了和Pacer、vad有关的sample
- 修改: LocalUser/audioTrack:
- 当场景为chorus的时候,开发者不需要调用setsenddelayinms;
- 当场景为chorus的时候,开发者不需要调用track的setaudioscnario 为chorus
- NOTE: 可以降低开发者难度。在ai场景下,开发者只需要设置service为chorus就可以。
- 增加:VadDump 类,在测试环境下可以协助排查vad的问题。但在线上环境中,不要开启
- 移除: Vad V1版本,只保留v2 版本。参考voice_detection.py,sample_audio_vad.py
- 增加: on_volume_indication 回调
- 增加: on_remote_video_track_state_changed 回调
- 更新:更新有关的samples:audioconsume, vad sample
- 修改videoFrame中的metadata的类型从str修改bytes类型,和c++保持一致;从而可以支持字节流;
- 修改了内部对ExteranlVideoFrame的封装,从而支持字节流;对alpha编码的支持,做了逻辑判断,如果fill_alpha_buffer 为0 ,则不处理
- 增加了一个sample:example_jpeg_send.py 可以将jpeg文件或者jpeg 流 推送到频道中
- 性能耗费参考example中的注释,可以简单总结为对1920*1080对jpeg文件,从读取文件到转换为RGBA bytearry,耗费在11ms
- 对AudioVolumeInfoInner 以及 AudioVolumeInfo 结构中的user_id更新为str 类型
- 修复了_on_audio_volume_indication中回调的bug,原来只能回调一个;修改成可以回调speaker_number个 ():
- 修复了IRTCLocalUserObserver::on_audio_volume_indication中回调的参数类型为list类型
- 添加V2版本的音频 VAD 接口及相应的示例。
- 修复了一些 bug
- 一个进程只能有一个service,只能对service做一次初始化;
- 一个service,只能有一个media_node_factory;
- 一个service,可以有多个connection;
- 在进程退出去的时候,再释放:media_node_factory.release() 和 service.release()
- 这个时候就在进程启动的时候,创建service/media_node_factory 和 connection;
- 在进程退出的时候,释放service/media_node_factory 和 connedtion,这样就可以保证
- 这个情况下,我们推荐用connection pool的概念
- 在进程启动的时候,创建service/media_node_factory 和 connection pool(只是new connection,并不初始化);
- 当有用户进来的时候,就从connection pool中获取一个connection,然后初始化,执行 con.connect()并且设置好回调,然后加入频道;
- 处理业务
- 当用户退出的时候,con.disconnect()并释放跟随该conn 的audio/video track,observer等,但不调用con.release();然后将该con 放回connection pool中;
- 在进程退出的时候,释放和 connedtion pool(对每一个con.release() 释放 service/media_node_factory 和 connedtion pool(对每一个con.release()),这样就可以保证资源的释放和性能最优
-
推荐用VAD V2版本,类为: AudioVadV2; 参考:voice_detection.py;
-
VAD 的使用:
-
- 调用 _vad_instance.init(AudioVadConfigV2) 初始化vad实例.参考:voice_detection.py。 实例假如为: _vad_instance
-
- 在audio_frame_observer::on_playback_audio_frame_before_mixing(audio_frame) 中:
-
- 调用 vad模块的process: state, bytes = _vad_instance.process(audio_frame)
-
- 根据返回的state,判断state的值,并做相应的处理
- A. 如果state为 _vad_instance._vad_state_startspeaking,则表明当前“开始说话”,可以开始进行语音识别(STT/ASR)等操作。记住:一定要将返回的bytes 交给识别模块,而不是原始的audio_frame,否则会导致识别结果不正确。
- B. 如果state为 _vad_instance._vad_state_stopspeaking,则表明当前“停止说话”,可以停止语音识别(STT/ASR)等操作。记住:一定要将返回的bytes 交给识别模块,而不是原始的audio_frame,否则会导致识别结果不正确。
- C. 如果state为 _vad_instance._vad_state_speaking,则表明当前“说话中”,可以继续进行语音识别(STT/ASR)等操作。记住:一定要将返回的bytes 交给识别模块,而不是原始的audio_frame,否则会导致识别结果不正确。 备注:如果使用了vad模块,并且希望用vad模块进行语音识别(STT/ASR)等操作,那么一定要将返回的bytes 交给识别模块,而不是原始的audio_frame,否则会导致识别结果不正确。
-
-
如何更好的排查VAD的问题:包含2个方面,配置和调试。
-
- 确保vad模块的初始化参数正确,参考:voice_detection.py。
-
- 在state,bytes = on_playback_audio_frame_before_mixing(audio_frame) 中,
-
- 将audio_frame的data 的data 保存到本地文件,参考:example_audio_pcm_send.py。这个就是录制原始的音频数据。比如可以命名为:source_{time.time()*1000}.pcm
-
- 保存每一次vad 处理的结果:
- A state==start_speaking的时候:新建一个二进制文件,比如命名为:vad_{time.time()*1000}.pcm,并将bytes 写入到文件中。
- B state==speaking的时候:将bytes 写入到文件中。
- C state==stop_speaking的时候:将bytes 写入到文件中。并关闭文件。 备注:这样就可以根据原始音频文件和vad处理后的音频文件,进行排查问题。生产环境的时候,可以关闭这个功能
-
localuser.unpublish_audio(audio_track)
localuser.unpublish_video(video_track)
audio_track.set_enabled(0)
video_track.set_enabled(0)
localuser.unregister_audio_frame_observer()
localuser.unregister_video_frame_observer()
localuser.unregister_local_user_observer()
connection.disconnect()
connection.unregister_observer()
localuser.release()
connection.release()
audio_track.release()
video_track.release()
pcm_data_sender.release()
video_data_sender.release()
audio_consumer.release()
media_node_factory.release()
agora_service.release()
#set to None
audio_track = None
video_track = None
audio_observer = None
video_observer = None
local_observer = None
localuser = None
connection = None
agora_service = None
-
打断的定义 在人机对话中,打断是指用户在对话过程中突然打断机器人的回答,要求机器人立即停止当前回答并转而回答用户的新问题。这个行为就叫打断
-
打断触发的条件 打断根据不同的产品定义,一般有两种方式:
- 模式1:语音激励模式. 当检测到有用户说话,就执行打断策略,比如用户说话时,识别到用户说话,就执行打断策略。
- 模式2:ASR激励模式. 当检测到有用户说话、并且asr/STT的识别返回有结果的时候,就执行打断策略。
-
不同打断策略的优点
-
- 语音激励打断:
- 优点:
-
- 减少用户等待时间,减少用户打断的概率。因为用户说话时,机器人会立即停止回答,用户不需要等待机器人回答完成。
- 缺点:
-
- 因为是语音激励模式,有可能会被无意义的语音信号给打断,依赖于VAD判断的准确性。比如AI在回答的时候,如果有人敲击键盘,就可能触发语音激励,将AI打断。 -2 . ASR激励打断:
- 优点:
-
- 降低用户打断的概率。因为用户说话时,asr/STT识别到用户说话,才会触发打断策略。
- 缺点:
-
- 因为是asr/STT激励模式,需要将语音信号转换成文本,会增加打断的延迟。
-
-
推荐模式 如果VAD能过滤掉非人声,只是在有人声的时候,才触发VAD判断,建议用语音激励模式;或者是对打断要求延迟敏感的时候,用改模式 如果对打断延迟不敏感,建议用ASR激励模式,因为ASR激励模式,可以过滤掉非人声,降低用户打断的概率。
-
如何实现打断?打断需要做哪些操作? 定义:人机对话,通常可以理解为对话轮的方式来进行。比如用户问一个问题,机器人回答一个问题;然后用户再问一个问题,机器人再回答一个问题。这样的模式就是对话轮。我们假设给对话轮一个roundId,每轮对话,roundId+1。 一个对话轮包含了这样的3个阶段/组成部分:vad、asr、LLM、TTS、rtc推流。
- vad: 是指人机对话的开始,通过vad识别出用户说话的开始和结束,然后根据用户说话的开始和结束,交给后续的ASR。
- asr: 是指人机对话的识别阶段,通过asr识别出用户说的话,然后交给LLM。
- LLM: 是指人机对话的生成阶段,LLM根据用户说的话,生成一个回答。
- TTS: 是指人机对话的合成阶段,LLM根据生成的回答,合成一个音频。
- rtc推流: 是指人机对话的推流阶段,将合成后的音频推流到rtc,然后机器人播放音频。
因此,所谓的打断,就是在(roundid+1)轮的时候,无论是用语音激励(VAD阶段触发)还是用ASR激励(就是在ASR识别出用户说的话)打断,都需要做如下的操作:
- 停止当前轮roundID轮的LLM生成。
- 停止当前轮roundID轮的TTS合成。
- 停止当前轮roundID轮的RTC推流。 API调用参考: a 调用:AudioConsumer.clear(); b 调用:LocalAudioTrack.clear_sender_buffer(); c 业务层:清除TTS返回来保留的数据(如果有)
LLM的结果是异步返回的,而且都是流式返回的。应该按照什么时机将LLM的结果交给TTS做合成呢? 需要考虑2个因素:
- 无歧义、连续、流畅:确保TTS合成的语音是没有歧义、而且是完整、连续的。比如LLM返回的文本是:"中间的首都是北京吗?"如果我们给TTS的是:中 然后是:国首 然后是:是北 然后是:京吗? 这样合成会有歧义,因为"中"和"国"之间没有空格,"首"和"是"之间没有空格,"京"和"吗"之间没有空格。
- 确保整个流程延迟最低。LLM 生成完成后,在交给TTS,这样的处理方式,合成的语音一定是没有歧义,而且是连续的。但延迟会很大,对用户体验不友好。 推荐的方案: 将有LLM返回数据的时候: a LLM返回的结果存放在缓存中 b 对缓存中的数据做逆序扫描,找到最近的一个标点符号 c 将缓存中的数据,从头开始到最尾的一个标点符号截断,然后交给TTS做合成。 d 将截断后的数据,从缓存中删除。剩余的数据,移动到缓存头位置,继续等待LLM返回数据。