声网RTC介绍
声网实时互动(Real-Time Communication,RTC)是提供音视频实时互动服务的产品,可帮助你将实时音视频功能集成到
App 中,实现互动直播、纯语音通话、高清视频通话等功能。在大规模的实时互动场景下,你可以用它实现更好的实时互动效果。 优势介绍
沉浸式音频体验 3A 处理:配备回声消除 (AEC)、降噪 (ANS) 和增益控制 (AGC)
技术,可强力消除百种突发噪声,实现复杂场景非线性回声抑制和近端人声保真,带来纯净沟通体验。
虚拟声卡:声网自研虚拟声卡算法,通过专业算法对用户声音进行优化,让用户的声音美妙动听、更有魅力。
空间音效:用户可以通过音频实时感知到其他用户距离、方位、朝向的变化,增强声音传播的空间感,打造沉浸式音频体验。 至臻画质体验
全平台极致高清:移动端支持 1080P 60 fps,PC端支持 4K 60 fps,配合声网自研 AI 画质算法,实现画质跨级提升。 高效视频编解码:SDK 自适应贴合场景的编解码方式,提供优秀的画质体验和更低的带宽消耗。 自适应高清策略:声网推出自适应高清视频策略,根据设备性能和网络质量自动调整视频属性,实现高清且流畅的视频效果。
声网 RTC 使用流程
控制台:声网提供给开发者管理声网各项服务的工具。控制台提供直观的界面,方便开发者在使用声网各项服务时进行充值、查询、管理等操作。
App ID:声网为开发项目生成的字符串,是项目的唯一标识。
App 证书:又名 App Certificate,它是声网控制台为开发项目生成的随机字符串,用于开启 Token 鉴权,并作为生成 Token 的参数之一。
Token:也称为动态密钥,是在加入频道时用于校验用户权限的一组字符串。
在控制台获取 App ID、App 证书和 Token 后,就可以开始尝试实现音视频通信的基本流程了。
《网络摄像机实战》系列文章索引
《网络摄像机实战①:Flutter 集成声网 Agora 打造实时音视频对讲》
《网络摄像机实战②:Flutter集成声网Agora图片抓拍与录像功能》
《网络摄像机实战③:设备端集成声网Agora打造实时音视频对讲功能》
《网络摄像机实战④:设备端集成声网Agora 打造远程控制设备或者异常报警推送功能》
《网络摄像机实战⑤:手机端 flutter集成声网Agora RTM打造远程控制设备功能》
《网络摄像机实战⑥:手机端 flutter集成亚马逊 AWS Kinesis Video Streams 音视频界面和控制》
《网络摄像机实战⑦:嵌入式端AWS KVS IoT SDK 库编译与测试全流程-君正/瑞芯微/全志(超详细指南)》
《网络摄像机实战⑧:嵌入式端接入 AWS KVS Producer 的设计要点与注意事项》
声网rtc 的flutter库安装
flutter pub add agora_rtc_engine
声网rtc核心业务集成
- 定义变量appId userId token channelName
注意: 必须要的变量是 appid 和channelName,其他的参数可以不用定义,使用空,默认值
static const String appId = "xxx-xxx-xxx"; // 替换 声网控制台获取的 appid
static const int userId = 0; // 替换成用户的 id
static const String token = ''; // 替换成控制台获取的 token,或者为空
var channelName = "xxx-xxx-xxx".obs; // 替换设备的 uuid
var localUid = RxnInt(0); // 远端用户ID
var remoteUid = RxnInt(0); // 远端用户ID
RxList<int> remoteUidList = <int>[].obs;//远程用户 ID 列表
- 定义声网rtcEngine类
var rtcEngine = Rxn<RtcEngine>();//rtc 引擎-核心
- 初始化声网引擎rtcEngine
rtcEngine.value = createAgoraRtcEngine();
await rtcEngine.value?.initialize(const RtcEngineContext(appId: appId));
- 注册事件处理回调函数registerEventHandler
注意: onSnapshotTaken是抓拍的回调函数 onJoinChannelSuccess加入 rtc 通道成功的回调函数 onUserJoined有其他的用户或者设备进入通道的回调函数 onUserOffline是其他的用户退出通道的回调函数 onRemoteVideoStateChanged是远程视频状态改变的回调函数
// 监听事件
rtcEngine.value?.registerEventHandler(
RtcEngineEventHandler(
onSnapshotTaken: (RtcConnection connection, int uid, String filePath,
int width, int height, int errCode) async {
Get.log('[onSnapshotTaken] connection: ${connection.toJson()}, uid: $uid, filePath: $filePath, width: $width, height: $height, errCode: $errCode');
// if (_snapshotPath.isNotEmpty) {
// final preSnapshotFile = File(_snapshotPath);
// if (await preSnapshotFile.exists()) {
// await preSnapshotFile.delete();
// }
// }
thumbImageData.value = filePath;
Get.log("thumbImageData.value: ${thumbImageData.value}");
},
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
isJoined.value = true;
localUid.value = connection.localUid;
Get.log("local user join success: ${connection.localUid}");
},
onUserJoined: (RtcConnection connection, int uid, int elapsed) {
if (!remoteUidList.contains(uid)) {
remoteUidList.add(uid);
remoteUid.value = remoteUidList.first;
}
// 如果当前没有选中远端用户,则默认选第一个加入的
if (remoteUid.value == 0) {
remoteUid.value = remoteUidList.first;
}
Get.log("remote user joined: $uid remoteUid.value: ${remoteUid.value}" );
},
onUserOffline: (RtcConnection connection, int uid, UserOfflineReasonType reason) {
remoteUidList.remove(uid);
// 只有列表还有用户才更新 remoteUid
if (remoteUidList.isNotEmpty) {
remoteUid.value = remoteUidList.first;
} else {
remoteUid.value = 0; // 可选,也可以保持原来的值
}
Get.log("remote uid leave: $uid remoteUid.value:${remoteUid.value}");
},
onRemoteVideoStateChanged: (connection, uid, state, reason, elapsed) {
// 判断当前远程用户是否可用
final available = state == RemoteVideoState.remoteVideoStateDecoding;
if (!available) return; // 只在解码成功时处理
// final available = state == RemoteVideoState.remoteVideoStateStopped;
Get.log("remote video state changed: $uid, state=$state available= $available");
// 在 remoteVideoStreamList 中查找是否已经有该 uid
final index = remoteVideoStreamList.indexWhere((e) => e.uid == uid);
if (index != -1) {
// 已经存在,更新 remoteVideoAvailable
remoteVideoStreamList[index] = remoteVideoStreamList[index].copyWith(remoteVideoAvailable: available);
} else {
// 不存在,新增
remoteVideoStreamList.add(RemoteVideoStream(uid: uid, remoteVideoAvailable: available));
}
}
),
);
- UI 界面处理显示远程的音视频
controller.isRemoteVideoAvailable(controller.remoteUid.value!)?
Stack(
children: [
AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: controller.rtcEngine.value!,
canvas: VideoCanvas(uid: controller.remoteUid.value),
// canvas: VideoCanvas(uid: controller.selectedRemoteUid.value),
// canvas: VideoCanvas(uid: null),
connection: RtcConnection(channelId: controller.channelName.value),
),
),
Positioned(
top:0 ,
right:10,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.black54,
borderRadius: BorderRadius.circular(6),
),
child: Text(
"UID: ${controller.remoteUid.value}",
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
),
),
)
),
],
) :Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// CircularProgressIndicator(),
SpinKitThreeInOut(
color: Get.theme.primaryColor,
size: 30.0,
),
SizedBox(height: 20),
Text('Loading'.tr,style: TextStyle(color: Get.theme.primaryColor)),
],
),
)
- UI 界面显示本地的音视频
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: controller.muteLocalCameraFlag.value?AgoraVideoView(
// child: controller.rtcEngine.value != null?AgoraVideoView(
controller: VideoViewController(
rtcEngine: controller.rtcEngine.value!,
canvas: const VideoCanvas(uid: 0,renderMode: RenderModeType.renderModeHidden),
),
):Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// CircularProgressIndicator(),
SpinKitThreeInOut(
color: Get.theme.primaryColor,
size: 30.0,
),
SizedBox(height: 20),
Text('Loading'.tr,style: TextStyle(color: Get.theme.primaryColor)),
],
),
),
/// 底部中间的控制栏
Positioned.fill(
child: Align(
alignment: Alignment.bottomCenter, // 底部居中
child: Row(
mainAxisSize: MainAxisSize.min, // Row 宽度自适应内容
children: [
IconButton(
onPressed: () => controller.switchPublishMicrophoneTrack(),
icon: controller.isPublishMicrophoneTrack.value
? Icon(Icons.mic,
size: 26,
color: Get.theme.primaryColor.withOpacity(1))
: Icon(Icons.mic_off, size: 26, color: Colors.red),
),
IconButton(
onPressed: () => controller.switchPublishCameraTrack(),
icon: controller.isPublishCameraTrack.value
? Icon(Icons.videocam_outlined,
size: 26,
color: Get.theme.primaryColor.withOpacity(1))
: Icon(Icons.videocam_off_outlined,
size: 26,
color: Colors.red),
),
],
),
),
)
],
)
标题APP 截图显示
友情提示🔔
🙏 感谢你的阅读! 如果这篇文章对你有所启发,欢迎关注我 ⭐,欢迎点击 “打赏支持作者” 支持一下我,你的支持是我持续创作的最大动力! 我会持续分享更多关于 智能摄像头 📷、机器人项目、 🤖音视频 RTC 🎧、App 开发 📱、嵌入式开发 🔧 等方向的实战经验,让你更快落地、更少踩坑。 欢迎浏览我其他文章 📚,或许能解决你当前的难题。 如果你正好在做相关项目产品,也欢迎随时私信我,一起技术交流、一起搞事情! 🤝💬📞 联系微信/电话:13826173658