实现音视频互动
本文介绍如何集成声网实时互动 SDK,通过少量代码从 0 开始实现一个简单的实时互动 App,适用于互动直播和视频通话场景。
首先,你需要了解以下有关音视频实时互动的基础概念:
- 声网实时互动 SDK:由声网开发的、帮助开发者在 App 中实现实时音视频互动的 SDK。
- 频道:用于传输数据的通道,在同一个频道内的用户可以进行实时互动。
- 主播:可以在频道内发布音视频,同时也可以订阅其他主播发布的音视频。
- 观众:可以在频道内订阅音视频,不具备发布音视频权限。
更多概念详见关键概念。
下图展示在 App 中实现音视频互动的基本工作流程:
- 所有用户调用
joinChannel
方法加入频道,并根据需要设置用户角色:- 互动直播:如果用户需要在频道中发流,则设为主播;如果用户只需要收流,则设为观众。
- 视频通话:将所有的用户角色都为主播。
- 加入频道后,不同角色的用户具备不同的行为:
- 所有用户默认都可以接收频道中的音视频流。
- 主播可以在频道内发布音视频流。
- 观众如果需要发流,可在频道内调用
setClientRole
方法修改用户角色,使其具备发流权限。
前提条件
在实现功能以前,请按照以下要求准备开发环境:
- Xcode 12.0 或以上版本。
- Apple 开发者账号。
- 如需使用 Cocoapods 集成 SDK,则确保已安装 Cocoapods,否则请参考 Getting Started with CocoaPods 进行安装。
- 两台 macOS 10.10 或以上版本的设备。
- 可以访问互联网的计算机。如果你的网络环境部署了防火墙,参考应对防火墙限制以正常使用声网服务。
- 一个有效的声网账号以及声网项目。请参考开通服务从声网控制台获得 和临时 。
临时 Token 的有效期是 24 小时。Token 过期会导致加入频道失败。
创建项目
按照以下步骤,在 Xcode 中创建一个项目:
-
参考 Create a project 创建一个新的项目。Application 选择 App,Interface 选择 Storyboard,Language 选择 Swift。
信息如果你没有添加过开发团队信息,会看到 Add account… 按钮。点击该按钮并按照屏幕提示登入 Apple ID,点击 Next,完成后即可选择你的 Apple 账户作为开发团队。
-
为你的项目设置自动签名。
-
设置部署你的 App 的目标设备。
-
添加项目的设备权限。
在项目导航栏中打开
info.plist
文件,编辑属性列表,添加以下属性:注意- 下表中列出的权限均为可选权限。但如果不添加所列权限,你将无法使用麦克风和摄像头进行实时音视频互动。
- 如果你的项目中需要添加第三方插件或库(例如第三方摄像头),且该插件或库的签名与项目的签名不一致,你还需勾选 Hardened Runtime > Runtime Exceptions 中的 Disable Library Validation。
- 更多注意事项,可以参考 Preparing Your App for Distribution。
Key Type Value Privacy - Microphone Usage Description String 使用麦克风的目的,例如:for a call or live interactive streaming。 Privacy - Camera Usage Description String 使用摄像头的目的,例如:for a call or live interactive streaming。 进入 TARGETS > Project Name > Signing & Capabilities,在 App Sandbox 和 Hardened Runtime 中添加如下权限:
Capability Category Permission App Sandbox Network - Incoming Connections (Server)
- Outgoing Connections (Client)
App Sandbox Hardware - Camera
- Audio Input
Hardened Runtime Resource Access - Camera
- Audio Input
集成 SDK
根据实际情况,在以下集成方式中任选一种,在你的项目中集成 SDK。
- 通过 Cocoapods 集成
- 手动集成
- 通过 Swift Package Manager 集成
-
在终端里进入项目根目录,并运行
pod init
命令。项目文件夹下会生成一个Podfile
文本文件。 -
打开
Podfile
文件,修改文件为如下内容。注意将Your App
替换为你的 Target 名称。Rubyplatform :macos, '10.11'
target 'Your App' do
# x.y.z 请填写具体的 SDK 版本号,如 4.0.1。
# 可通过发版说明获取最新版本号。
pod 'AgoraRtcEngine_macOS', 'x.y.z'
end -
在终端内运行
pod install
命令安装声网 SDK。成功安装后,Terminal 中会显示Pod installation complete!
。 -
成功安装后,项目文件夹下会生成一个后缀为
.xcworkspace
的文件,通过 Xcode 打开该文件进行后续操作。
-
前往下载页面,获取最新版的 SDK,然后解压。
-
将 SDK 包内
libs
路径下的文件,拷贝到你的项目路径下。 -
打开 Xcode,添加对应动态库,确保添加的动态库 Embed 属性设置为 Embed & Sign。
信息声网 SDK 默认使用 libc++ (LLVM),如需使用 libstdc++ (GNU),请联系 sales@shengwang.cn。SDK 提供的库是 FAT Image,包含 32/64 位模拟器、32/64 位真机版本。
-
在 Xcode 中,进入 File > Swift Packages > Add Package Dependencies...,粘贴如下 URL:
HTTPhttps://github.com/AgoraIO/AgoraRtcEngine_macOS.git
-
在 Choose Package Options 中指定你想集成的 SDK 版本。你也可以参考 Apple 官方文档进行设置。
自 4.5.0 起,RTC SDK 和 RTM SDK (2.2.0 及以上版本) 都包含 aosl.xcframework
库。如果你通过 CDN 手动集成 RTC SDK 且同时集成了 RTM SDK,为避免冲突,请手动删除版本较低的 aosl.xcframework
库。4.5.0 RTC SDK aosl.xcframework
库版本为 1.2.13。你可以通过查看 aosl.xcframework
文件的属性来得知库的版本信息。
创建用户界面
根据使用场景,为你的项目创建一个用户界面,包括本地视频窗口和远端视频窗口。用以下代码替换 ViewController.swift
文件中的内容:
import Cocoa
import AgoraRtcKit
class ViewController: NSViewController {
// 本地视频视图
var localView: NSView!
// 远端视频视图
var remoteView: NSView!
// RTC 引擎
var agoraKit: AgoraRtcEngineKit!
override func viewDidLayout() {
super.viewDidLayout()
// 计算本地视图的位置和大小
let localViewWidth: CGFloat = 135
let localViewHeight: CGFloat = 240
let localViewX: CGFloat = 2
let localViewY: CGFloat = 2
// 设置本地视图的 frame
localView.frame = CGRect(x: localViewX, y: localViewY, width: localViewWidth, height: localViewHeight)
// 设置远端视图的 frame,让它充满整个 CustomView
remoteView.frame = view.bounds
}
}
实现步骤
下图展示了使用声网 RTC SDK 实现音视频互动的基本流程。
下面列出了一段实现实时互动基本流程的完整代码以供参考。复制以下代码到 ViewController.swift
文件中替换原有内容即可。
在 appId
、token
和 channelName
字段中传入你在控制台获取到的 App ID、临时 Token,以及生成临时 Token 时填入的频道名。
// ViewController.swift
import AgoraRtcKit
import Cocoa
class ViewController: NSViewController {
// 本地视频视图
var localView: NSView!
// 远端视频视图
var remoteView: NSView!
// RTC 引擎
var agoraKit: AgoraRtcEngineKit!
override func viewDidLayout() {
super.viewDidLayout()
// 计算本地视图的位置和大小
let localViewWidth: CGFloat = 135
let localViewHeight: CGFloat = 240
let localViewX: CGFloat = 2
let localViewY: CGFloat = 2
// 设置本地视图的 frame
localView.frame = CGRect(x: localViewX, y: localViewY, width: localViewWidth, height: localViewHeight)
// 设置远端视图的 frame,让它充满整个 CustomView
remoteView.frame = view.bounds
}
func initView() {
// 初始化远端视频窗口。
remoteView = NSView()
self.view.addSubview(remoteView)
// 初始化本地视频窗口。
localView = NSView()
self.view.addSubview(localView)
// 初始化 RTC 实例
agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: <#Your App ID#>, delegate: self)
}
deinit {
agoraKit.stopPreview()
agoraKit.leaveChannel(nil)
AgoraRtcEngineKit.destroy()
}
func startPreview() {
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.view = localView
videoCanvas.renderMode = .hidden
agoraKit.setupLocalVideo(videoCanvas)
agoraKit.startPreview()
}
func joinChannel() {
let options = AgoraRtcChannelMediaOptions()
// 设置频道场景为直播
options.channelProfile = .liveBroadcasting
// 设置用户角色为主播;如果要将用户角色设置为观众,保持默认值即可
options.clientRoleType = .broadcaster
// 发布麦克风采集的音频
options.publishMicrophoneTrack = true
// 发布摄像头采集的视频
options.publishCameraTrack = true
// 自动订阅所有音频流
options.autoSubscribeAudio = true
// 自动订阅所有视频流
options.autoSubscribeVideo = true
// 使用临时 Token 加入频道,在这里传入你的项目的 Token 和频道名
// uid 为 0 表示由引擎内部随机生成; 成功后会触发 didJoinChannel 回调
agoraKit.joinChannel(byToken: <#Your Token#>, channelId: <#Your Channel Name#>, uid: 0, mediaOptions: options)
}
override func viewDidLoad() {
super.viewDidLoad()
initView()
agoraKit.enableVideo()
startPreview()
joinChannel()
}
}
extension ViewController: AgoraRtcEngineDelegate {
// 成功加入频道回调
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
print("didJoinChannel: \(channel), uid: \(uid)")
}
// 远端用户或主播加入当前频道回调
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) {
// 打印调试信息
print("User \(uid) joined after \(elapsed) milliseconds")
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = remoteView
videoCanvas.renderMode = .hidden
agoraKit.setupRemoteVideo(videoCanvas)
}
// 远端用户或主播离开当前频道回调
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
// 打印调试信息
print("User \(uid) went offline due to \(reason)")
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = nil
agoraKit.setupRemoteVideo(videoCanvas)
}
}
导入声网组件
在 ViewController.swift
文件中引入 AgoraRtcKit
。
import AgoraRtcKit
初始化引擎
调用 sharedEngineWithConfig
方法,创建并初始化 AgoraRtcEngineKit
。
在初始化 SDK 前,需确保终端用户已经充分了解并同意相关的隐私政策。
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
启用视频模块
按照以下步骤启用视频模块。
- 调用
enableVideo
方法,启用视频模块。 - 调用
setupLocalVideo
方法初始化本地视图,同时设置本地的视频显示属性。 - 调用
startPreview
方法,开启本地视频预览。
// 启用视频模块
agoraKit.enableVideo()
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = 0
videoCanvas.renderMode = .hidden
videoCanvas.view = localView
// 设置本地视图
agoraKit.setupLocalVideo(videoCanvas)
// 开始本地视频预览
agoraKit.startPreview()
加入频道并发布音视频流
调用 joinChannelByToken
[2/4] 方法、填入你在控制台获取的临时 Token,以及获取 Token 时填入的频道名加入频道,并设置用户角色。
let options = AgoraRtcChannelMediaOptions()
// 设置频道场景为直播
options.channelProfile = .liveBroadcasting
// 设置用户角色为主播
// 如果要将用户角色设置为观众,则修改下一行代码为 options.clientRoleType = .audience
options.clientRoleType = .broadcaster
// 发布麦克风采集的音频
options.publishMicrophoneTrack = true
// 发布摄像头采集的视频
options.publishCameraTrack = true
// 自动订阅所有音频流
options.autoSubscribeAudio = true
// 自动订阅所有视频流
options.autoSubscribeVideo = true
// 使用临时 Token 加入频道,在这里传入你的项目的 Token 和频道名
// uid 为 0 表示由引擎内部随机生成; 成功后会触发 didJoinChannel 回调
agoraKit.joinChannel(byToken: <#Your Token#>, channelId: <#Your Channel Name#>, uid: 0, mediaOptions: options)
设置远端用户视图
调用 setupRemoteVideo
方法初始化远端用户视图,同时设置远端用户的视图在本地显示属性。你可以通过 didJoinedOfUid
回调获取远端用户的 uid。
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int){
// 当远端用户加入频道后,显示指定 uid 的远端视频流
print("User \(uid) joined after \(elapsed) milliseconds")
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = remoteView
videoCanvas.renderMode = .hidden
agoraKit.setupRemoteVideo(videoCanvas)
}
实现常用回调
根据使用场景,定义你所需的回调。以下示例代码展示了如何实现 didJoinChannel
和 didOfflineOfUid
回调。
// 成功加入频道回调
func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) {
print("didJoinChannel: \(channel), uid: \(uid)")
}
// 远端用户或主播离开当前频道回调
func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) {
// 打印调试信息
print("User \(uid) went offline due to \(reason)")
let videoCanvas = AgoraRtcVideoCanvas()
videoCanvas.uid = uid
videoCanvas.view = nil
agoraKit.setupRemoteVideo(videoCanvas)
}
开始音视频互动
当视图加载完成后,在 viewDidLoad
中调用一系列方法加入频道,开始音视频互动。
override func viewDidLoad() {
super.viewDidLoad()
// 当加载视图后,你可以进行其他其他设置
// 初始化视频窗口
initView()
// 当调用声网 API 时,以下函数会被调用
agoraKit.enableVideo()
startPreview()
joinChannel()
}
结束音视频互动
-
调用
leaveChannel
方法离开频道,与会话相关的资源也会被释放。SwiftagoraKit.stopPreview()
agoraKit.leaveChannel(nil) -
调用
destroy
销毁引擎,并释放声网 SDK 中使用的所有资源。SwiftAgoraRtcEngineKit.destroy()
警告调用该方法后,你将无法再使用 SDK 的所有方法和回调。如需再次使用实时音视频互动功能,你必须重新创建一个新的引擎。详见初始化引擎。
调试 App
参考以下步骤来测试你的 App:
- 将 macOS 设备连接至计算机。
- 点击 Build 来运行你的项目,需等待几秒至 App 安装完成。
- 允许 App 访问设备的麦克风和摄像头权限。
- 使用第二台 macOS 设备,重复以上步骤,在该设备上安装 App,并打开 App 加入频道,观察测试结果:
- 如果两台设备均作为主播加入频道,则可以看到对方并且听到对方的声音。
- 如果两台设备分别作为主播和观众加入,则主播可以在本地视频窗口看到自己;观众可以在远端视频窗口看到主播、并听到主播的声音。
后续步骤
- 本文的示例使用了临时 Token 加入频道。在测试或生产环境中,为确保通信安全,声网推荐使用 Token 服务器来生成 Token,详见使用 Token 鉴权。
- 如果你想要实现极速直播场景,可以在互动直播的基础上,通过修改观众端的延时级别为低延时 (
AgoraAudienceLatencyLevelLowLatency
)实现。详见实现极速直播。
参考信息
示例项目
声网提供了开源的实时互动示例项目供你参考,你可以前往下载或查看其中的源代码。
常见问题
- 直播场景下,如何监听远端观众角色用户加入/离开频道的事件?
- 如何处理视频黑屏问题?
- 如何处理 CocoaPods 常见问题?
- 为什么我无法打开摄像头?
- 如何处理频道相关常见问题?
- 编译 Xcode 项目时遇到 “无法打开 framework” 的弹窗警告怎么办?
- 如何设置日志文件?
- 为什么 SDK 中使用的是动态库而不是静态库?