🏆【声网Agora】「WebRTC-如何搭建语音认证服务」
前提准备
拥有一个【Agora账户】
选择 "项目管理 "下 "项目列表 "选项卡,点击蓝色的 "创建 "按钮,创建一个项目。(当提示使用 App ID+证书时,选择只使用 App ID。)记住你的 App ID,它将在开发 App 时用于授权你的请求。 完成步骤后,即可拥有一个账号。
文档中心
业务场景
伴奏混音
业务实现:将本地或在线的音频和用户声音,同时发送并播放给频道内其他用户。 适用场景:在线合唱,音乐互动课堂。
播放音频
业务实现:可以播放指定的音效文件,支持设置音效的音调和空间位置。 适用场景:棋牌游戏。
变声和混响
业务实现:提供多种预置的变声和混响效果,同时支持灵活调整用户声音的音调、均衡及混响效果。 适用场景:一起 KTV,语音聊天室变声。
听声辨位
业务实现:设置远端用户声音出现的位置,增加游戏角色的方位感,还原真实游戏场景。 适用场景:CS、CF、吃鸡游戏以及相关使命召唤等。
听声辨位
业务实现:设置远端用户声音出现的位置,增加游戏角色的方位感,还原真实游戏场景。 适用场景:CS、CF、吃鸡游戏以及相关使命召唤等。
双声道/高音质
业务实现:支持高音质、双声道的音频设置。 适用场景:音乐教学,FM 音频电台。
修改原始音频数据
业务实现:可支持变声,支持获取媒体引擎的原始语音,对原始数据进行处理
适用场景:语音聊天室变声
关键特性
SDK包体积:3.14 ~ 11.28 MB 音频属性 音频采样率:16 kHz - 48 kHz 支持单、双声道 音频抗丢包率:上下行抗丢包率 80%a
平台兼容
桌面端

移动端

在 Android 4.1+ 上,Web SDK 支持 Google Chrome 58 及以上版本 ,且 Agora 建议使用 VP8 编解码格式。这是由于 Android Chrome 对 H.264 的支持依赖硬件,而部分 Android 设备不支持 H.264 编解码格式。 在 iOS 11+ 上,Web SDK 支持 Safari 11 及以上版本 。但是由于 iOS Safari 存在较多 ,Agora 不推荐使用。你可以使用 在 iOS 上实现实时音视频功能。
服务端开发流程
使用Token鉴权
鉴权原理

Agora控制台创建项目时生成的 App ID 项目的App证书 频道名 用户ID 用户权限,如是否能发流或收流 Token过期的Unix时间戳
鉴权流程
获取App ID及 App 证书


部署Token服务器
Token生成相关的token信息(SDK)
package io.agora.sample;
import io.agora.media.RtcTokenBuilder;
import io.agora.media.RtcTokenBuilder.Role;
public class RtcTokenBuilderSample {
static String appId = "970CA35de60c44645bbae8a215061b33";
static String appCertificate = "5CFd2fd1755d40ecb72977518be15d3b";
static String channelName = "7d72365eb983485397e3e3f9d460bdda";
static String userAccount = "2082341273";
static int uid = 2082341273;
static int expirationTimeInSeconds = 3600;
public static void main(String[] args) throws Exception {
RtcTokenBuilder token = new RtcTokenBuilder();
int timestamp = (int)(System.currentTimeMillis() / 1000 + expirationTimeInSeconds);
String result = token.buildTokenWithUserAccount(appId, appCertificate,
channelName, userAccount, Role.Role_Publisher, timestamp);
System.out.println(result);
result = token.buildTokenWithUid(appId, appCertificate,
channelName, uid, Role.Role_Publisher, timestamp);
System.out.println(result);
result = token.buildTokenWithUid(appId, appCertificate, channelName, uid,
timestamp, timestamp, timestamp,
timestamp);
System.out.println(result);
result = token.buildTokenWithUserAccount(appId, appCertificate, channelName,
userAccount, timestamp, timestamp, timestamp,
timestamp);
System.out.println(result);
}
}
Token客户端介入(Http)
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
// 基于 Java 实现的 Token 认证示例,使用 RTM 的用户事件 RESTful API
public class Base64Encoding {
public static void main(String[] args) throws IOException, InterruptedException {
// RTM Token
String tokenHeader = "Your RTM token";
// 生成 RTM Token 时使用的 user ID
String uidHeader = "test_user";
HttpClient client = HttpClient.newHttpClient();
// 构建请求对象
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.agora.io/dev/v2/project//rtm/vendor/user_events"))
.GET()
// 在 header 中添加 x-agora-token 字段
.header("x-agora-token", tokenHeader )
// 在 header 中添加 x-agora-uid 字段
.header("x-agora-uid", uidHeader)
.header("Content-Type", "application/json")
.build();
// 发送请求
HttpResponse response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.Base64;
// 基于 Java 实现的 HTTP 基本认证示例,使用 RTC 的服务端 RESTful API
public class Base64Encoding {
public static void main(String[] args) throws IOException, InterruptedException {
// 客户 ID
final String customerKey = "Your customer key";
// 客户密钥
final String customerSecret = "Your customer secret";
// 拼接客户 ID 和客户密钥并使用 base64 编码
String plainCredentials = customerKey + ":" + customerSecret;
String base64Credentials = new String(Base64.getEncoder().encode(plainCredentials.getBytes()));
// 创建 authorization header
String authorizationHeader = "Basic " + base64Credentials;
HttpClient client = HttpClient.newHttpClient();
// 创建 HTTP 请求对象
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.agora.io/dev/v1/projects"))
.GET()
.header("Authorization", authorizationHeader)
.header("Content-Type", "application/json")
.build();
// 发送 HTTP 请求
HttpResponse response = client.send(request,
HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
}
}
语音通信服务端功能
频道管理 RESTful API :通过你的业务服务器向 Agora 服务器发送 HTTPS 请求,管理频道内的用户和查询频道信息。 频道事件回调(Webhook) :开通 Agora 消息通知服务后,当订阅的事件发生时,Agora 服务器会以 HTTPS 请求的形式向你的服务器发送通知,用于实时监控频道状态。
Agora 频道管理 RESTful API 提供以下功能:
封禁用户权限

查询在线频道信息

频道事件消息通知服务
开通消息通知服务
功能配置
如需配置 QPS (Query Per Second) 较高的事件,如实时通信中观众加入或离开直播频道,请确保你的服务器有足够的处理能力。
为提高安全性,Agora 消息通知服务不再支持 HTTP 服务器地址。 为降低消息投递的延时,Agora 建议你的服务器支持 HTTPS 连接重用,即 keep-alive 模式,并进行如下设置: :大于等于 100。 :大于等于 10 秒。 你的消息通知接收服务器所在的区域。Agora 会根据你提供的区域就近接入 Agora 节点服务器。目前,Agora 消息通知服务器部署在以下区域: :中国 :东南亚 :美国 :欧洲
获取密钥
配置测试
消息通知回调格式与内容
消息通知回调响应
Agora 消息服务器发送通知后的 10 秒内,如果没有收到你的服务器的响应或响应状态码不是 200,会认为消息通知失败。失败后,Agora 消息通知服务器会立即重新发送消息通知,投递间隔随着重试次数的上升逐渐增加,直到第一次投递的 30 分钟后停止投递。
频道回调机制
频道事件回调
请求的Header

请求的 Body
消息通知回调的请求包体包含以下字段:

消息通知回调的请求包体示例:
{
"noticeId": "2000001428:4330:107",
"productId": 1,
"eventType": 101,
"notifyMs": 1611566412672,
"payload": {...}
}
频道事件类型
Agora 消息通知服务可以通知实时通信业务中的以下频道事件类型:

验证签名
Java实现客户端解析数据案例
HMAC/SHA1
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class HmacSha {
// 将加密后的字节数组转换成字符串
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() < 2) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
//HMAC/SHA1 加密,返回加密后的字符串
public static String hmacSha1(String message, String secret) {
try {
SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(
"utf-8"), "HmacSHA1");
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("utf-8"));
return bytesToHex(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
//拿到消息通知的 raw request body 并对其计算签名,也就是说下面代码中的 request_body 是反序列化之前的 binary byte array,不是反序列化之后的 object
String request_body = "{\"eventMs\":1560408533119,\"eventType\":10,\"noticeId\":\"4eb720f0-8da7-11e9-a43e-53f411c2761f\",\"notifyMs\":1560408533119,\"payload\":{\"a\":\"1\",\"b\":2},\"productId\":1}";
String secret = "secret";
System.out.println(hmacSha1(request_body, secret)); //033c62f40f687675f17f0f41f91a40c71c0f134c
}
}
HMAC/SHA256
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class HmacSha {
// 将加密后的字节数组转换成字符串
public static String bytesToHex(byte[] bytes) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < bytes.length; i++) {
String hex = Integer.toHexString(bytes[i] & 0xFF);
if (hex.length() < 2) {
sb.append(0);
}
sb.append(hex);
}
return sb.toString();
}
//HMAC/SHA256 加密,返回加密后的字符串
public static String hmacSha256(String message, String secret) {
try {
SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(
"utf-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("utf-8"));
return bytesToHex(rawHmac);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
//拿到消息通知的 raw request body 并对其计算签名,也就是说下面代码中的 request_body 是反序列化之前的 binary byte array,不是反序列化之后的 object
String request_body = "{\"eventMs\":1560408533119,\"eventType\":10,\"noticeId\":\"4eb720f0-8da7-11e9-a43e-53f411c2761f\",\"notifyMs\":1560408533119,\"payload\":{\"a\":\"1\",\"b\":2},\"productId\":1}";
String secret = "secret";
System.out.println(hmacSha256(request_body, secret)); //033c62f40f687675f17f0f41f91a40c71c0f134c
}
}