whatsapp 语音通话接入

语音推流(一)

适用有自己whatsapp 协议,可以使用协议登录账号
语音通话涉及到很多方面,打洞协商,编解码,加解密等等,所以很多协议并没有实现真正的语音通话,最多是发个xmpp协议,响铃,然后挂断。接下来如何接入 语音通话服务

语音数据的采集和播放

由于涉及到麦克风采集,扬声器播放,以及语音编解码,有一定的难度,所以高度封装了jni接口。使用起来极其简单

//这个是封装的类
package org.voice;

import java.util.HashMap;

public class RecordAndPlayer {
    static HashMap<Long, RecordAndPlayer> instanceMap = new HashMap<>();
    public interface Delegate {
        void OnRecord(byte[] data);
    }


    public RecordAndPlayer(Delegate delegate) {
        this.delegate = delegate;
        instance = CreatePlayer();
        instanceMap.put(instance, this);
    }

    public void PutFrame(byte[] data, short serial, int timestamp) {
        PutFrame(instance, data, serial, timestamp);
    }

    public void Stop() {
        DestroyPlayer(instance);
        //从表中移除
        instanceMap.remove(instance);
    }


    private void OnRecord(byte[] data) {
        delegate.OnRecord(data);
    }

    private long instance;
    Delegate delegate;

    public static native long CreatePlayer();
    public static native void DestroyPlayer(long instance);
    public static native void PutFrame(long instance, byte[] data, short serial, int timestamp);
    public static void PullFrame(long instance, byte[] data) {
        RecordAndPlayer player = instanceMap.get(instance);
        if (player != null)
        {
            player.OnRecord(data);
        }
    }
}

//调用 封装接口采集和播放
 recordAndPlayer = new RecordAndPlayer(new RecordAndPlayer.Delegate() {
      @Override
       public void OnRecord(byte[] data) {
           //麦克风录制到声音回调
       }
   });

 //当接收到解密之后的语音数据
 recordAndPlayer.PutFrame(root.getBytes("data"), root.getShort("packetSerial"), root.getInteger("timeStamp"));

和服务器建议websocket 连接

websocket 服务器可以提供加解密 语音数据的功能,所以当麦克风回调数据的时候需要通过websocket 发给服务器,服务器会加密,然后再发回来,这样客户端就可以通过udp将 加密之后的数据发给whatsapp。 当udp接收到数据的时候同样需要 通过websocket 发给服务器解密, 解密之后又会发回来,然后就可以直接丢给扬声器播放。

麦克风数据1
websocket
UDP发送3
客户端
Websocket服务器
加密数据2
whatsapp
UDP接收
websocket
解密数据3
客户端
Websocket服务器
解密数据2
扬声器

主动打电话

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

1) 获取协商秘钥

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"
        }
    
    

2) 发起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 之后再发过来, 如果是 16进制的字符串需要设置 result.put("format", 1)
    <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");
    //如果节点内容是16进制字符串编码则需要 设置format
    //result.put("format", 1)
    // 用于测试的音频文件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>

3) 发送/接收语音数据

   @Override
    public void OnRecord(byte[] data) {
        //录音消息
        //发送音频数据
        JSONObject result = new JSONObject();
        result.put("command", "SendAudio");
        result.put("data", data);
        SendCommand(result);
    }

   private void HandleRecvAudioData(JSONObject root) {
        if (root.getBoolean("isRtp")) {
            if (recordAndPlayer != null) {
                recordAndPlayer.PutFrame(root.getBytes("data"));
            }
        }
    }

总结一下步骤:

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

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

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

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

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

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1. 在前端增加右下角的询盘表单弹窗 可以使用以下步骤实现: 1. 在WordPress的主题文件中打开 functions.php 文件,添加以下代码: ``` function add_popup_form() { ?> <div id="popup-form-container"> <form id="popup-form" method="post" action=""> <label for="name">Name:</label> <input type="text" id="name" name="name" required> <label for="email">Email:</label> <input type="email" id="email" name="email" required> <label for="message">Message:</label> <textarea id="message" name="message"></textarea> <input type="submit" value="Submit"> </form> </div> <?php } add_action('wp_footer', 'add_popup_form'); ``` 2. 将上面代码中的表单内容进行自定义修改,比如修改表单项的名称,添加更多的表单项等。 3. 在 WordPress 的主题样式文件(style.css)中添加以下 CSS 代码,以控制弹窗的显示和样式: ``` #popup-form-container { position: fixed; bottom: 0; right: 0; width: 300px; height: 400px; background-color: #fff; border: 1px solid #ccc; z-index: 9999; display: none; } #popup-form-container form { padding: 20px; } #popup-form-container label { display: block; margin-bottom: 10px; } #popup-form-container input, #popup-form-container textarea { display: block; width: 100%; margin-bottom: 20px; } #popup-form-container input[type="submit"] { background-color: #333; color: #fff; border: none; padding: 10px 20px; cursor: pointer; } ``` 4. 最后,使用 JavaScript 代码监听点击事件,显示和隐藏弹窗: ``` jQuery(document).ready(function($) { $('#popup-form-container').fadeIn(); }); $('#close-button').click(function(e) { e.preventDefault(); $('#popup-form-container').fadeOut(); }); ``` 2. 在前端的右侧增加在线悬浮窗口 可以使用以下步骤实现: 1. 在 WordPress 的主题文件中打开 functions.php 文件,添加以下代码: ``` function add_whatsapp_button() { ?> <div id="whatsapp-button"> <a href="https://wa.me/8518588629881" target="_blank"><img src="path/to/whatsapp-icon.png" alt="WhatsApp"></a> </div> <?php } add_action('wp_footer', 'add_whatsapp_button'); ``` 2. 修改代码中的 WhatsApp 号码,以及 WhatsApp 图标的路径。 3. 在 WordPress 的主题样式文件(style.css)中添加以下 CSS 代码,以控制按钮的显示和样式: ``` #whatsapp-button { position: fixed; right: 20px; bottom: 20px; z-index: 9999; } #whatsapp-button img { width: 50px; height: 50px; } ``` 3. 实现后台的排版 可以使用以下步

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值