whatsapp 语音通话 音频编码(五)

news2024/11/26 19:23:26

Whatsapp VoiceCall

客户端通过websocket连接到服务器,客户端发起语音通话请求,并且完成必要的协商之后,就可以直接将语音数据发送给服务器,服务器接收到对方的语音数据之后也会通过websocket将语音数据转发给客户端

获取协商秘钥

XMPP 在发起语音通话请求的时候,需要带上一个秘钥,这个秘钥长32字节,通过特殊算法生成。这个算法需要三个参数:

  1. 自身jid
  2. 对方jid
  3. 时间戳(服务端自动获取,不需要生成)
        //发送获取秘钥请求
        JSONObject result = new JSONObject();
        result.put("command", "GetSecret");
        result.put("selfjid", "自己的@whatsapp.com");
        result.put("otherjid", "对方@whatsapp.com");
        SendCommand(result);
    
        //接收到服务器返回的消息, secret 字段是经过base64 编码,需要解码,解码之后是32字节
        {
            "secret": "Xh+LtW/gRxC92B4UK/gLAzqERAqL9U2ArNetO3Zy0h0=",
            "command": "ResponseSecret"
        }
    
    

发起XMPP 语音请求

  1. 发起语音请求。这个请求需要通过xmpp 通道发送出去,发出去之后,WA服务器会回一个ack包,这个ack包需要通过websocket发给中转服务器
 <call to='接收方@s.whatsapp.net' id='随机生成32字节'>
        <offer call-creator='发送方.0:0@s.whatsapp.net' call-id='随机生成32字节' device_class='2015'>
            <privacy>联系人的token,  同步联系人的时候 privacy_token节点下 trusted_contact 数据 </privacy>
            <audio rate='16000' enc='opus'/>
            <net medium='3'/>
            <capability ver='1'>AQT3CcT6</capability>
            <enc v='2' type='msg'>从服务器获取的32字节秘钥序列化成pb之后加密</enc>
            <encopt keygen='2'/>
        </offer>
    </call>
 //下面是消息pb 结构的一部分,需要将返回的32字节秘钥 设置到 Call->callKey 中,序列化之后加密
    message Message {
        optional string conversation = 1;
        optional SenderKeyDistributionMessage senderKeyDistributionMessage = 2;
        optional ImageMessage imageMessage = 3;
        optional ContactMessage contactMessage = 4;
        optional LocationMessage locationMessage = 5;
        optional ExtendedTextMessage extendedTextMessage = 6;
        optional DocumentMessage documentMessage = 7;
        optional AudioMessage audioMessage = 8;
        optional VideoMessage videoMessage = 9;
        optional Call call = 10;
        ... ...
        ... ...
    }

    message Call {
        optional bytes callKey = 1;
        optional string conversionSource = 2;
        optional bytes conversionData = 3;
        optional uint32 conversionDelaySeconds = 4;
    }
  1. 处理ack 回包。
    发送完第一个包之后,服务器会返回一个ack包, 需要将这个ack包转成xml格式,然后通过websocket 发送给服务器
      //xmpp 转xml 需要注意, 节点部分的值需要base64 之后再发过来
    <ack from='对方@s.whatsapp.net' class='call' type='offer' id='xxxx'>
        <relay attribute_padding='1' peer_pid='0' self_pid='1' uuid='xxx' call-creator='xxx@s.whatsapp.net' call-id='xxx' joinable='1'>
            <participant pid='0' jid='xxx@s.whatsapp.net'/>
            <token id='0'>base64的内容</token>
            <token id='1'>xxx</token>
            <token id='2'>xxx</token>
            <token id='3'>xxx</token>
            <token id='4'>xxxx</token>
            <key>xxxx</key>
            <te2 protocol='1' relay_id='0' token_id='0'>base64的内容</te2>
            <te2 protocol='1' relay_id='0' token_id='0'>base64的内容</te2>
            <te2 relay_id='0' token_id='0'>xxx</te2>
            <te2 relay_id='0' token_id='0'>xxx</te2>
            <te2 protocol='1' relay_id='1' token_id='1'>xxx</te2>
            <te2 protocol='1' relay_id='1' token_id='1'>xx</te2>
            <te2 relay_id='1' token_id='1'>xxx</te2>
            <te2 relay_id='1' token_id='1'>xxx</te2>
            <te2 protocol='1' relay_id='2' token_id='3'>xxx</te2>
            <te2 protocol='1' relay_id='2' token_id='3'>xxx</te2>
            <te2 relay_id='2' token_id='3'>xxx</te2>
            <te2 relay_id='2' token_id='3'>xxx</te2>
            <te2 protocol='1' relay_id='3' token_id='2'>xxx</te2>
            <te2 protocol='1' relay_id='3' token_id='2'>xxx</te2>
            <te2 relay_id='3' token_id='2'>xxx</te2>
            <te2 relay_id='3' token_id='2'>xxx</te2>
            <te2 protocol='1' relay_id='4' token_id='4'>xxx</te2>
            <te2 protocol='1' relay_id='4' token_id='4'>xxx</te2>
            <te2 relay_id='4' token_id='4'>xxx</te2>
            <te2 relay_id='4' token_id='4'>xxx</te2>
            <hbh_key>xxx</hbh_key>
        </relay>
        <user jid='xxx@s.whatsapp.net'>
            <device jid='xxx@s.whatsapp.net'/>
        </user>
        <rte>xxx</rte>
        <uploadfieldstat/>
        <userrate/>
        <voip_settings uncompressed='1'>xxxx</voip_settings>
    </ack>
 //将服务器回的ack 包发给中转服务器
    JSONObject result = new JSONObject();
    result.put("command", "VoiceAck");
    // 用于测试的音频文件ID,固定,正式部署的时候需要换成上传的文件
    result.put("file_uuid", "aee4d52d-6ba7-4a65-80d4-b7341b1115f0");
    result.put("ack", "服务器回的ack包打包成xml格式");
    SendCommand(result);
  1. 接收到的服务器的包必须回复ack,否则会被踢下线,下面几个常用的ack
		//接收的包
        <receipt from='xxx@s.whatsapp.net' id='xxx' t='xxx'>
            <offer call-id='xxx' call-creator='xxx@s.whatsapp.net'/>
        </receipt>

        //需要回复ack
         <ack id='xxx' to='xxx@s.whatsapp.net' class='receipt'/>
  //接收的包
        <call from='xxx@s.whatsapp.net' id='xxx' t='xxx'><preaccept call-id='xxx' call-creator='xxx@s.whatsapp.net'><audio rate='16000' enc='opus'/><encopt keygen='2'/><capability ver='1'>xxx</capability></preaccept></call>

        //需要回复ack
        <ack id='xxx' to='xxx.0:0@s.whatsapp.net' class='call' type='preaccept'/>
 //接收的包
    <call from='xxx@s.whatsapp.net' id='xxx' t='xxx'>
        <relaylatency call-id='xxx' call-creator='xxx@s.whatsapp.net'>
            <te latency='xxx'>xxx</te>
        </relaylatency>
    </call>
    //需要回复ack
    <ack id='xxx' to='xxx.0:0@s.whatsapp.net' class='call' type='relaylatency'/>
  1. 中转服务器会将一些需要发给WA服务器的包发过来,这些包需要转成xmpp 格式的数据发给WA 服务器
 <call to="xxx@s.whatsapp.net" id="xxx">
        <relaylatency call-creator="xxx.0:0@s.whatsapp.net" call-id="xxx">
            <te latency="xxx">xxx</te>
        </relaylatency>
    </call>

总结一下步骤:

1. 和中转服务器建立websocket 连接

2. 从中转服务器获取 加密秘钥

3. XMPP 发送call 请求,并且接收服务器返回的ack, 特别需要注意期间会收到很多包,都需要回ack,上面也列出了一些需要回ack的包

4. 将WA 服务器的ack包转成xml 格式发给中转服务器, 特别需要注意xml格式节点值需要base64 编码

5. 中转服务器会主动发送一些xml数据, 客户端需要将这些xml数据转成xmpp包发给服务器。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1599926.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VMware 虚拟机中的 Ubuntu 16.04 设置 USB 连接

VMware 虚拟机中的 Ubuntu 16.04 设置 USB 连接 1. VMware USB Arbitration Service2. 可移动设备 USB 口连接主机3. 虚拟机 -> 可移动设备 -> 连接 (断开与主机的连接)4. 状态栏 -> 断开连接 (连接主机)References 1. VMware USB Arbitration Service 计算机 -> …

探索分布式系统监控zabbix------------自动发现与自动注册

目录 一、部署 zabbix 服务端 二、部署 zabbix 客户端 2.1环境准备 2.2服务端和客户端都配置时间同步 &#xff08;ntp&#xff09; 2.2.1服务端zbx-server 2.2.2服务端zabbix-agent01客户端 2.3客户端配置时区&#xff0c;与服务器保持一致 2.4设置 zabbix 的下载源&…

[Linux]--关于进程控制

进程创建,fork/vfork 在linux中fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 #include <unistd.h> pid_t fork(void); 返回值&#xff1a;自进程中返回0&#xff0c;父进程返回子进程id&#x…

服务器Linux搭建NPM私有仓库

服务器Linux搭建NPM私有仓库 环境搭建 安装 nodejs nodejs官网&#xff1a;https://nodejs.org/en/download/package-manager 可以去官网自行下载nodejs的Linux版本&#xff0c;但是出于别的原因考虑&#xff0c;可以使用nvm去下载nodejs这样会切换nodejs也方便。 nvm 这…

苹果电脑启动磁盘是什么意思 苹果电脑磁盘清理软件 mac找不到启动磁盘 启动磁盘没有足够的空间来进行分区

当你一早打开苹果电脑&#xff0c;结果系统突然提示&#xff1a; “启动磁盘已满&#xff0c;需要删除部分文件”。你会怎么办&#xff1f;如果你认为单纯靠清理废纸篓或者删除大型文件就能释放你的启动磁盘上的空间&#xff0c;那就大错特错了。其实苹果启动磁盘的清理技巧有很…

Java复习第十九天学习笔记(Cookie、Session登录),附有道云笔记链接

【有道云笔记】十九 4.7 Cookie、Session登录 https://note.youdao.com/s/VwpxfEim 一、会话技术简介 生活中会话 我&#xff1a; 小张&#xff0c;你会跳小苹果码&#xff1f; 小张&#xff1a; 会&#xff0c;怎么了&#xff1f; 我&#xff1a; 公司年会上要表演节目&a…

张大哥笔记:一个很笨但是能赚钱的方法

曾经有一个学生向南怀瑾先生提问&#xff1a;什么样的人才能轻松赚到钱&#xff1f;他说方法很简单&#xff0c;假设你现在是一个天天卖烧饼油条的小老板&#xff0c;一天两天没有人买&#xff0c;卖不掉就自己吃&#xff0c;但仍然是一心一意做烧饼油条&#xff0c;三四天后就…

数据结构——双向循环链表

目录 前言 一、链表的分类 二、双向循环链表 2.1 开辟新的节点 2.2 链表初始化 2.3 打印链表 2.4 链表的尾插 2.5 链表的头插 2.6 链表的尾删 2.7 链表的头删 2.8 查找链表 2.9 在pos位置之后插入数据 2.10 删除pos位置的数据 三、完整代码实现 四、顺序表和双向…

Redis中的订阅发布和事务(一)

订阅发布 PUBSUB NUMSUB PUBSUB NUMSUB [channel-1 channel-2… channel-n]子命令接受任意多个频道作为输入参数&#xff0c;并返回这些频道的订阅者数量。 这个子命令是通过pubsub_channels字典中找到频道对应的订阅者链表&#xff0c;然后返回订阅者链表的长度来实现的(订阅…

bdf文件导入事件错误情况

先打开脑电再导入事件 数据情况

500元左右的运动耳机怎么选?五大质量超群品牌分享

在运动中&#xff0c;一款合适的耳机不仅可以提升运动的乐趣&#xff0c;更能激励我们坚持锻炼&#xff0c;在市场上的运动耳机种类繁多&#xff0c;价格不一&#xff0c;如何选择一款既适合自己又物有所值的运动耳机呢&#xff1f;特别是在500元左右的预算范围内&#xff0c;我…

PostgreSQL入门到实战-第二十七弹

PostgreSQL入门到实战 PostgreSQL中数据分组操作(二)官网地址PostgreSQL概述PostgreSQL中HAVING命令理论PostgreSQL中HAVING命令实战更新计划 PostgreSQL中数据分组操作(二) 使用PostgreSQL HAVING子句来指定组或聚合的搜索条件 官网地址 声明: 由于操作系统, 版本更新等原因…

卷积神经网络的结构组成与解释(详细介绍)

文章目录 前言 1、卷积层 2、激活层 3、BN层 4、池化层 5、FC层&#xff08;全连接层&#xff09; 6、损失层 7、Dropout层 8、优化器 9、学习率 10、卷积神经网络的常见结构 前言 卷积神经网络是以卷积层为主的深层网络结构&#xff0c;网络结构包括有卷积层、激活层、BN层、…

Visual Studio 2019 社区版下载

一、网址 https://learn.microsoft.com/zh-cn/visualstudio/releases/2019/release-notes#start-window 二、选择这个即可

【每日刷题】Day16

【每日刷题】Day16 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 24. 两两交换链表中的节点 - 力扣&#xff08;LeetCode&#xff09; 2. 160. 相交链表 - 力扣&…

【编程Tool】DevC++的安装配置及使用保姆级教程

目录 前言&#xff1a;软件介绍 1.软件下载及安装 1.1. 双击可执行文件进行安装 2.软件配置 2.1.选择语言 2.2 同意相关协议 2.3.组件保持默认并点击Next 2.4. 修改安装路径 2.5. 等待安装 2.6. 点击Finish&#xff0c;完成安装 2.7 选择语言 2.8.个性化设置 2.9. 点击OK&…

MySQL——创建和插入

一、插入数据 INSERT 使用建议; 在任何情况下建议列出列名&#xff0c;在 VALUES 中插入值时&#xff0c;注意值和列的意义对应关系 values 指定的值顺序非常重要&#xff0c;决定了值是否被保存到正确的列中 在指定了列名的情况下&#xff0c;你可以仅对需要插入的列给到…

MongoDB的go SDK使用集锦

在上一章解读MongoDB官方文档获取mongo7.0版本的安装步骤与基本使用介绍了如何使用mongo shell操作mongo数据库&#xff0c;接下来介绍如何使用sdk来操作数据库&#xff0c;这里以go语言为例&#xff0c;其他语言请查看源文档mongo docs Quick Start 内置数据结构 MongoDB是存…

24 静动态库

首先创建两个函数的头文件和源文件 最后的main函数 #include <stdio.h> #include "print.h" #include "sum.h"int main() {Print("时间");printf("%d\n", sum(3, 5));return 0; }将函数编译成.o文件 make生成文件 make he…

ENVI实战—一文学会使用传感器自带信息配准工具进行几何校正

实验1&#xff1a;学会使用传感器自带信息配准工具 目的&#xff1a;利用ENVI的传感器自带信息配准工具&#xff0c;掌握几何校正的一般方法。 过程&#xff1a; 1.对MODIS影像进行校正&#xff1a; ①读取影像&#xff1a;打开文件&#xff0c;点击“打开为”&#xff0c;…