喊话器控件
概述
DJI PSDK 为开发者能够开发出满足在各种搜救、巡逻场景下的喊话器负载,提供了标准的喊话器解决方案,包括了实时喊话、文字转语音喊话(TTS)、音量控制和播放模式控制等功能。开发者无需额外单独开发APP,可以直接通过DJI Pilot进行喊话器集成开发。
基础概念
TTS
TTS是Text To Speech的缩写,即“从文本到语音”,是人机对话的一部分,让机器能够说话的一种文本转语音技术。目前TTS技术发展较为成熟,主要包括两类,一种是通过专用的TTS硬件芯片进行音频合成,另外一种可以通过在线或者离线的软件库进行TTS转换合成,如讯飞离线语音合成SDK。
PCM数据
音频的裸数据格式就是脉冲编码调制(Pulse Code Modulation,PCM)数据。是对连续变化的模拟信号进行抽样、量化和编码产生的数字信号。描述一段PCM数据一般需要以下几个概念:量化格式、采样率(sampleRate)、声道数(channel)。
Opus音频编解码器
Opus是一款完全开放、免版税、功能多样的音频编解码器。它适用于互联网上的交互式语音和音乐传输,但也适用于存储和流媒体应用。
Opus的前身是celt编码器。在当今的有损音频格式争夺上,拥有众多不同编码器的AAC格式打败了同样颇有潜力的Musepack、Vorbis等格式,而在Opus格式诞生后,情况似乎不同了。通过诸多的对比测试,低码率下Opus完胜曾经优势明显的HE AAC,中码率就已经可以媲敌码率高出30%左右的AAC格式,而高码率下更接近原始音频。
Opus可以处理各种音频应用,包括IP语音,视频会议,游戏内聊天,甚至远程现场音乐表演。它可以从低比特率窄带语音扩展到非常高质量的立体声音乐。支持的特性包括:
- 比特率从 6kb/s 到 510 kb/s
- 采样率从 8kHz(窄带)到 48kHz(全频段)
- 帧大小从 2.5ms 到 60ms
- 支持恒定比特率(CBR)和可变比特率(VBR)
- 从窄带到全频带的音频带宽
- 支持语音和音乐
- 支持单声道和立体声
- 支持多达255个通道(多流帧)
- 动态可调比特率,音频带宽和帧大小
- 良好的稳健性和隐蔽性
- 浮点和定点实现
采样率
音频采样率是指录音设备在一秒钟内对声音信号的采样次数,采样频率越高声音的还原就越真实越自然。在当今的主流采集卡上,采样频率一般共分为11025Hz、22050Hz、24000Hz、44100Hz、48000Hz五个等级
频率对应于时间轴线,振幅对应于电平轴线。波是无限光滑的,弦线可以看成由无数点组成,由于存储空间是相对有限的,数字编码过程中,必须对弦线的点进行采样。采样的过程就是抽取某点的频率值,很显然,在一秒中内抽取的点越多,获取得频率信息更丰富,为了复原波形,一次振动中,必须有2个点的采样,人耳能够感觉到的最高频率为20kHz,因此要满足人耳的听觉要求,则需要至少每秒进行40k次采样,用40kHz表达,这个40kHz就是采样率。我们常见的CD,采样率为44.1kHz。
对于挂载在无人机上的喊话器负载,需要从地面端遥控器进行音频编码上传至飞机端,为了最大化传输效率,需要在音质和采样率之间进行权衡。
声道
声道是指声音在录制或播放时在不同空间位置采集或回放的相互独立的音频信号,所以声道数也就是声音录制时的音源数量或回放时相应的扬声器数量。
对于挂载在无人机上的喊话器负载,由于受无人机载重和功率限制,一般选择单扬声器的方案,即单通道。
喊话器控件介绍
DJI Pilot 2支持显示标准喊话器控件,用户可以通过配置自定义控件中的喊话器json字段,来控制是否进行图标显示。
注意: 在使用喊话器控件之前,需要先使能自定义控件。
语音喊话
用户可以通过“点击说话”按钮,录制需要喊话的内容。在录制过程中,Pilot端会有时间计时,当即将到最大录音时间时,遥控器会震动并有其他交互提示。当录制完成后,用户点击”结束“按钮,可以选择”本地试听“或者”喊话“进行播放。此外,当录制效果不理想时,可以再次重录,也可以选择保存当次的录音音频文件,通过音频列表进行管理(支持试听、文件信息显示、删除、重命名、上传播放等)。
时间限制 | 音频参数 | 编码参数 |
---|---|---|
最大3min | 16khz,mono,16bit | opus@16kbps,单帧数据160字节 |
快捷键喊话
在应急的场景下,Pilot支持通过遥控器自定义按键映射快速呼出录音喊话窗口,并可以通过遥控器按键一键“录音-上传-播放”。
TTS喊话
用户可以在控件中的文本输入框直接输入需要喊话的文本,DJI Pilot 2会将文本内容直接传输给喊话器端,喊话器端可以结合自身情况自行实现TTS功能。
导入音频播放
喊话器功能支持用户直接从 Pilot 端导入音频并上传到喊话器端进行播放,用户可以很方便的管理音频列表(支持试听、文件信息显示、删除、重命名、上传播放等)
格式限制 | 大小限制 | 最大数量 |
---|---|---|
MP3/WAV/AAC | 10MB | 20 |
导入文本喊话
喊话器功能支持用户直接从 Pilot 端导入文本内容并上传到喊话器端进行TTS转换播放,用户可以很方便的管理文本列表(预览、删除、上传播放等)
字符限制:1000个字符(汉字算一个字符)
喊话器音频参数
由于受限于无人机的载重、功率和上行链路限制,喊话器功能限制了音频的相关参数,以便满足各种使用场景的需求。
- 声道数:1
- 采样率:16 kHZ
- 位宽:16 bit
使用喊话器功能
1. 初始化控件模块
对于喊话器的控件的显示,可以通过speaker选项中的json字段来控制DJI Pilot端的喊话器TTS图标和语音喊话图标的显示。
说明:使用喊话器功能前需要使能widget功能。
"main_interface": {
"floating_window": {
"is_enable": true
},
"speaker": {
"is_enable_tts": true,
"is_enable_voice": true
},
2. 注册喊话器回调函数
为实现喊话器功能,需要调用DjiWidget_RegSpeakerHandler
接口,注册喊话器具体功能的实现方法。
T_DjiReturnCode returnCode;
T_DjiOsalHandler *osalHandler = DjiPlatform_GetOsalHandler();
s_speakerHandler.GetSpeakerState = GetSpeakerState;
s_speakerHandler.SetWorkMode = SetWorkMode;
s_speakerHandler.StartPlay = StartPlay;
s_speakerHandler.StopPlay = StopPlay;
s_speakerHandler.SetPlayMode = SetPlayMode;
s_speakerHandler.SetVolume = SetVolume;
s_speakerHandler.ReceiveTtsData = ReceiveTtsData;
s_speakerHandler.ReceiveVoiceData = ReceiveAudioData;
returnCode = DjiWidget_RegSpeakerHandler(&s_speakerHandler);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
USER_LOG_ERROR("Register speaker handler error: 0x%08llX", returnCode);
return returnCode;
}
3. 实现喊话器音频传输
喊话器音频传输需要通过对应的回调接口实现,当喊话器处于TTS模式下时,在ReceiveTtsData
回调中可接收到TTS文本数据;当喊话器处于实时喊话模式下时,在ReceiveAudioData
回调中可接收到Opus音频编码后的数据。
接收数据的过程中,需要进行开始接收、传输数据和完成接收三个阶段:
- 开始接收:APP会下发命令到喊话器,喊话器此时准备开始接收音频数据。
- 传输数据:APP会下发音频数据的序号、长度和编码后的数据。
- 完成接收:APP会下发该段音频数据的MD5值,提供给喊话器负载对音频数据进行校验。
实现如下:
static T_DjiReturnCode ReceiveAudioData(E_DjiWidgetTransmitDataEvent event,
uint32_t offset, uint8_t *buf, uint16_t size)
{
uint16_t writeLen;
T_DjiReturnCode returnCode;
if (event == DJI_WIDGET_TRANSMIT_DATA_EVENT_START) {
USER_LOG_INFO("Create voice file.");
audioFile = fopen(WIDGET_SPEAKER_AUDIO_OPUS_FILE_NAME, "wb");
if (audioFile == NULL) {
USER_LOG_ERROR("Create tts file error.");
}
} else if (event == DJI_WIDGET_TRANSMIT_DATA_EVENT_TRANSMIT) {
USER_LOG_INFO("Transmit voice file, offset: %d, size: %d", offset, size);
if (audioFile != NULL) {
fseek(audioFile, offset, SEEK_SET);
writeLen = fwrite(buf, 1, size, audioFile);
if (writeLen != size) {
USER_LOG_ERROR("Write tts file error %d", writeLen);
}
}
} else if (event == DJI_WIDGET_TRANSMIT_DATA_EVENT_FINISH) {
USER_LOG_INFO("Close voice file.");
if (audioFile != NULL) {
fclose(audioFile);
}
returnCode = DjiTest_CheckFileMd5Sum(WIDGET_SPEAKER_AUDIO_OPUS_FILE_NAME, buf, size);
if (returnCode != DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS) {
USER_LOG_ERROR("File md5 sum check failed");
}
DjiTest_DecodeAudioData();
}
return DJI_ERROR_SYSTEM_MODULE_CODE_SUCCESS;
}
4. 实现音频解码
对于编码后的音频数据,需要进行数据解码成PCM格式的RAW数据。如下为Linux平台的示例代码,需要创建解码器,按照每帧160字节读取Opus数据,将解码后的数据存储为PCM格式用于播放。
说明:Opus解码器的详细使用说明,请参考Opus API 文档。
FILE *fin;
FILE *fout;
OpusDecoder *decoder;
opus_int16 out[WIDGET_SPEAKER_AUDIO_OPUS_MAX_FRAME_SIZE * WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS];
uint8_t cbits[WIDGET_SPEAKER_AUDIO_OPUS_MAX_PACKET_SIZE];
int32_t nbBytes;
int32_t err;
/*! Attention: you can use "ffmpeg -i xxx.mp3 -ar 16000 -ac 1 out.wav" and use opus-tools to generate opus file for test */
fin = fopen(WIDGET_SPEAKER_AUDIO_OPUS_FILE_NAME, "r");
if (fin == NULL) {
fprintf(stderr, "failed to open input file: %s\n", strerror(errno));
return EXIT_FAILURE;
}
/* Create a new decoder state. */
decoder = opus_decoder_create(WIDGET_SPEAKER_AUDIO_OPUS_SAMPLE_RATE, WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS, &err);
if (err < 0) {
fprintf(stderr, "failed to create decoder: %s\n", opus_strerror(err));
return EXIT_FAILURE;
}
fout = fopen(WIDGET_SPEAKER_AUDIO_PCM_FILE_NAME, "w");
if (fout == NULL) {
fprintf(stderr, "failed to open output file: %s\n", strerror(errno));
return EXIT_FAILURE;
}
USER_LOG_INFO("Decode Start...");
while (1) {
int i;
unsigned char pcm_bytes[WIDGET_SPEAKER_AUDIO_OPUS_MAX_FRAME_SIZE * WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS * 2];
int frame_size;
/* Read a 16 bits/sample audio frame. */
nbBytes = fread(cbits, 1, WIDGET_SPEAKER_AUDIO_OPUS_DECODE_FRAME_SIZE, fin);
if (feof(fin))
break;
/* Decode the data. In this example, frame_size will be constant because
the encoder is using a constant frame size. However, that may not
be the case for all encoders, so the decoder must always check
the frame size returned. */
frame_size = opus_decode(decoder, cbits, nbBytes, out, WIDGET_SPEAKER_AUDIO_OPUS_MAX_FRAME_SIZE, 0);
if (frame_size < 0) {
fprintf(stderr, "decoder failed: %s\n", opus_strerror(frame_size));
return EXIT_FAILURE;
}
USER_LOG_DEBUG("decode data to file: %d\r\n", frame_size * WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS);
/* Convert to little-endian ordering. */
for (i = 0; i < WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS * frame_size; i++) {
pcm_bytes[2 * i] = out[i] & 0xFF;
pcm_bytes[2 * i + 1] = (out[i] >> 8) & 0xFF;
}
/* Write the decoded audio to file. */
fwrite(pcm_bytes, sizeof(short), frame_size * WIDGET_SPEAKER_AUDIO_OPUS_CHANNELS, fout);
}
/*Destroy the encoder state*/
opus_decoder_destroy(decoder);
fclose(fin);
fclose(fout);
5. 实现音频音量设置
对于喊话器音频音量的控制,需要结合具体的喊话器负载添加实现方式。如下为示例程序通过直接pactl set-sink-volume
命令,对声卡进行设置,实现音频音量控制的效果。
T_DjiReturnCode returnCode;
T_DjiOsalHandler *osalHandler = DjiPlatform_GetOsalHandler();
char cmdStr[128];
int32_t ret = 0;
float realVolume;
realVolume = 1.5f * (float) volume;
USER_LOG_INFO("Set widget speaker volume: %d", volume);
s_speakerState.volume = volume;
memset(cmdStr, 0, sizeof(cmdStr));
snprintf(cmdStr, sizeof(cmdStr), "pactl set-sink-volume %s %d%%", WIDGET_SPEAKER_USB_AUDIO_DEVICE_NAME,
(int32_t) realVolume);
returnCode = DjiUserUtil_RunSystemCmd(cmdStr);
if (returnCode < 0) {
USER_LOG_ERROR("Set widget speaker volume error: %d", ret);
}
6. 实现喊话器音频播放
对于喊话器音频的播放,需要结合具体的喊话器负载添加实现方式。常用的有两种方式:
- 数字转模拟I2S输出:如RTOS平台上一般选用专用数字PA芯片,通过I2S的方式完成音频的播放。
- 系统音频输出:如Linux平台默认支持了音频相关的驱动,只需要通过上层软件操作声卡进行音频播放,如ffplay。
static T_DjiReturnCode DjiTest_PlayAudioData(void)
{
char cmdStr[128];
memset(cmdStr, 0, sizeof(cmdStr));
snprintf(cmdStr, sizeof(cmdStr), "ffplay -nodisp -autoexit -ar 16000 -ac 1 -f s16le -i %s 2>/dev/null",
WIDGET_SPEAKER_AUDIO_PCM_FILE_NAME);
return DjiUserUtil_RunSystemCmd(cmdStr);
}
7. 实现TTS音频合成
对于喊话器TTS音频合成,需要结合具体的喊话器负载添加实现方式。常用的有两种方式:
- 专用TTS硬件芯片:该类芯片一般提供I2C或UART接口的协议,需按照协议格式将文本进行转化发送,即可完成音频的播放。
- 软件合成库:通过软件库的方式,对输入的文本进行合成,转化成音频数据,该类库对系统资源占用要求较高,方案较为成熟,如如讯飞离线语音合成SDK。
在喊话器Sample使用了一款开源的TTS语言库-Ekho,此处仅作为示例演示,为达到更好的TTS效果,建议使用其他方式。
memset(cmdStr, 0, sizeof(cmdStr));
/*! Attention: you can use other tts opensource function to convert txt to speech, example used ekho v7.5 */
snprintf(cmdStr, sizeof(cmdStr), " ekho %s -s 20 -p 20 -a 100", data);
return DjiUserUtil_RunSystemCmd(cmdStr);