返回

Android TV遥控器语音输入问题排查与解决

IOS

Android TV 遥控器语音输入问题排查与解决

最近在用 iOS 搞 Android TV 遥控器的语音搜索功能,碰到了个问题。虽然能连上电视、发命令、改输入框里的字,但就是没法启动语音助手。 我琢磨着,应该得发一个 RemoteVoiceBegin 包,这我也做了。但语音助手就是不出来,而且 Socket 连接还断了,sessionID 是 -1。下面是我解决这个问题的完整过程。

一、问题现象

连接 Android TV 遥控器 v2 协议后,尝试启动语音输入,但语音助手没有响应,Socket 连接断开,sessionID 为 -1。

二、原因分析

这种情况可能有几个原因:

  1. RemoteVoiceBegin 包构建错误: 虽然发送了 RemoteVoiceBegin 包,但里面的参数,比如 voiceConfig 的设置,可能不正确。电视盒子接收到错误的数据,不知道咋处理,就断开了连接。
  2. sessionID 处理不当: sessionID 为 -1 通常表示初始状态或者会话无效。在语音开始的时候, 可能不需要设定 sessionID,应该在后续的交互中使用有效的ID。
  3. 音频配置不兼容: RemoteVoiceConfig 中的音频参数(采样率、通道配置、音频格式)可能与电视支持的格式不匹配。
  4. Socket 连接管理问题: 在发送 RemoteVoiceBegin 之后,Socket 连接可能没有正确保持,导致后续操作失败。
  5. 权限问题: App 可能没有录音权限。
  6. 设备兼容性问题: 有些 Android TV 设备可能对语音输入协议的实现有细微差别。

三、解决方案

下面是我尝试的几种解决方案, 以及详细的步骤和说明:

1. 检查并修正 RemoteVoiceBegin

仔细检查构建 RemoteVoiceBegin 的代码,确保所有字段都正确设置。

原理: 确保发送给电视的 RemoteVoiceBegin 消息包含了正确的信息,让电视知道如何处理接下来的语音数据。

代码示例:

func createVoiceMessage() -> Data? {

    // 创建 voice begin with config
    var message = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteMessage()
    let voiceBegin = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteVoiceBegin.with {

        $0.voiceConfig = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteVoiceConfig.with {
             // 确保这里的参数和你的录音配置匹配
            $0.sampleRate = 16000  // 常见的采样率
            $0.channelConfig = 1 // 单声道。 对应 AudioFormatFlags. (注意这里容易出错.)
            $0.audioFormat = 2 // 常见格式: PCM 16 位.  对应 AVAudioPCMFormatInt16.rawValue (注意这里容易出错)
        }
         // 重要:开始时不要设置 sessionID,或者设置为 0。让服务端分配。
        //$0.sessionID = -1 // 注释掉或者设置 $0.sessionID=0
    }

    // 创建最终的消息
    message.remoteVoiceBegin = voiceBegin

    print("Voice message created: \(message)")

    // 序列化消息并处理错误
    do {
        return try message.serializedData()
    } catch {
        print("Failed to serialize voice message: \(error)")
        return nil
    }
}

重点说明:

  • sampleRatechannelConfigaudioFormat 的值要跟实际使用的录音配置对应。一般可以尝试常见的配置,比如 16000Hz 采样率、单声道、PCM 16 位格式。channelConfig的值为1代表单声道,根据使用的音频库可能会使用其他的flag. 要对应上.
  • sessionID 在开始时不要设置,或者设为 0。让服务器端来分配 sessionID

2. 正确处理 sessionID

原理: sessionID 用于标识一个语音会话。服务端会在响应 RemoteVoiceBegin 后,在 RemoteVoiceBegin 的返回信息里包含一个有效的 sessionID。后续的 RemoteVoiceDataRemoteVoiceEnd 包需要使用这个 sessionID

进阶处理(根据服务器返回修改):

在收到服务器的响应后,解析出 sessionID 并保存。在发送后续的语音数据包(RemoteVoiceData)和结束包(RemoteVoiceEnd)时,使用这个 sessionID

代码示例(假设已在别处实现了接收和解析响应):

// 假设这是你从服务器响应中获取到的 sessionID
var currentSessionID: Int32 = 0

func sendVoiceData(data: Data) {
        //发送voice Data时候, 要带上服务端回应的 SessionID
       var message = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteMessage()

        let voiceData = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteVoiceData.with {
                $0.data = data //这是pcm 数据
                $0.sessionID = currentSessionID  // 使用有效的 sessionID
         }
    
        message.remoteVoiceData = voiceData
    
    
     do {
        let messageData =  try message.serializedData()
        let varintData = Data(Encoder.encodeVarint(UInt(messageData.count)))

           var combinedData = Data()
            combinedData.append(varintData)
            combinedData.append(messageData)
        
           //发送
            self.remoteManager.send(varintData, messageData)

         print("Data sent successfully, with SessionID: \(currentSessionID)")
    
    
     }
       catch {
           print("Failed to serialize voice message: \(error)")
            
      }
        

}

func sendVoiceEnd(){

 //发送voice end, 表明语音输入结束.
   var message = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteMessage()
        let voiceEnd = Com_Multi_Tv_Utils_AndroidTvRemote_RemoteCommunicationTv_RemoteVoiceEnd.with {

                $0.sessionID = currentSessionID  // 使用有效的 sessionID
         }
    message.remoteVoiceEnd = voiceEnd
   do {
       let messageData = try message.serializedData()
        let varintData = Data(Encoder.encodeVarint(UInt(messageData.count)))

           var combinedData = Data()
            combinedData.append(varintData)
            combinedData.append(messageData)
              //发送
           self.remoteManager.send(varintData, messageData)
         print("voice End sent successfully, with SessionID: \(currentSessionID)")
     }
      catch {
         print("Failed to serialize voice end message: \(error)")

   }
}

关键修改 : 添加一个全局/类变量(例子中的 currentSessionID)用于储存有效的sessionID, 然后, 在发送VoiceDataVoiceEnd包时, 使用这个变量的值.

3. 确认音频配置

原理: 与电视支持的音频格式兼容,才能正确处理语音数据。

操作步骤:

  1. 查阅电视的文档或者开发者选项,看看它支持哪些音频格式。
  2. 在应用中尝试不同的 RemoteVoiceConfig 设置,找到能用的配置。
  3. 可以尝试 log, 查看每次传输的数据.
    • 如果数据传输正常, 但依旧不能调出语音助手, 大概率是 RemoteVoiceConfig 参数不对, 仔细阅读对应平台的官方文档关于这几个参数的具体定义.
    • 注意对比 Android 原生应用的配置(可以使用调试工具) 与 自己应用配置的差异, 缩小排查范围。

4. 确保 Socket 连接稳定

原理: 稳定的 Socket 连接是持续语音输入的基础。

操作步骤:

  1. 在发送 RemoteVoiceBegin 之后,保持 Socket 连接处于活跃状态。
  2. 如果使用第三方库管理 Socket 连接,查阅文档,确保正确处理了连接的生命周期。
  3. 可以添加心跳包机制,定期发送小数据包来检测连接是否正常。

5. 检查录音权限

原理: 没有录音权限,应用无法获取麦克风数据,自然无法进行语音输入。

操作步骤:

  1. 在 iOS 的 Info.plist 文件中,添加 NSMicrophoneUsageDescription 键,并提供一个,说明为什么需要录音权限。
  2. 在代码中请求录音权限。

代码示例:

import AVFoundation

func requestMicrophonePermission() {
    AVAudioSession.sharedInstance().requestRecordPermission { granted in
        if granted {
            print("Microphone permission granted")
            // 权限已获取,可以开始录音
        } else {
            print("Microphone permission denied")
            // 提示用户去设置里开启权限
        }
    }
}

6. 处理设备兼容性

原理: 不同的 Android TV 设备可能存在细微差异。

操作步骤:

  1. 在多台不同型号的 Android TV 设备上测试。
  2. 如果发现特定设备有问题,尝试查找该设备的特定文档或者开发者论坛,看看有没有相关的兼容性信息。

通过以上步骤的排查和调整,应该能解决 Android TV 遥控器语音输入的问题。记住,细心、耐心是解决这类问题的关键。遇到问题多尝试、多分析、多查资料,总能找到解决办法!