发送和接收媒体流
本文介绍如何集成服务端 Go SDK,通过少量代码从 0 开始实现发送和接收媒体流。
前提条件
在实现功能以前,请按照以下要求准备开发环境:
-
已下载 Visual Studio Code
-
Go 1.21 或以上版本
-
服务器接入公网,有公网 IP。服务器允许访问
.agora.io
以及.agoralab.co
域名。 -
操作系统与硬件环境需满足下列要求:
开发平台 操作系统版本 CPU 架构 设备性能 Linux - Ubuntu 18.04 LTS 或以上
- CentOS 7.0 或以上
x86-64 - CPU:8 核,1.8 GHz 主频
- 内存:2 GB 或以上,推荐 4 GB 或以上
-
一个有效的声网账号以及声网项目。请参考开通服务从声网控制台获得以下信息:
- App ID:声网随机生成的字符串,用于识别你的项目。
- 临时 Token:Token 也称为动态密钥,在客户端加入频道时对用户鉴权。临时 Token 的有效期为 24 小时。
集成 SDK
按照以下步骤集成 SDK:
-
在终端中,运行以下命令下载 SDK。
Bashgit clone https://github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK.git go_rtc_sdk
-
依次运行以下命令编译 SDK。
信息如果在运行
make deps
命令下载 Go Module 时发生超时,可以尝试先运行go env -w GOPROXY=https://goproxy.cn,direct
命令设置代理,再运行make deps
命令重新下载。Bash# 进入 SDK 目录
cd go_rtc_sdk
# 下载 SDK 动态库,该命令会把 SDK 动态库下载到 agora_sdk 目录下
# 在后续项目调试时,你需要把 agora_sdk 的路径添加进环境变量
make deps
# 执行编译
make build -
在项目的
go.mod
文件里面,添加下列行,增加 SDK 所需依赖。Bash# 将 /path/to/go_rtc_sdk 修改为你在步骤 1 中下载的 SDK 的实际路径
replace github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK/v2 => /path/to/go_rtc_sdk
# 将 version_number 修改为 SDK 实际版本号,你可以在发版说明中获取版本号,例如 v2.1.0
require github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK/v2 version_number
实现步骤
本小节介绍如何实现服务端发送和接收媒体流,你可以按照实现步骤了解核心 API 调用。下图展示了基本的实现流程:
初始化 SDK
在调用其他声网 API 前,需要先导入 agoraservice
包并调用 initialize
初始化 agoraservice
包:
package main
import (
agoraservice "github.com/AgoraIO-Extensions/Agora-Golang-Server-SDK/v2/go_sdk/agoraservice"
)
func main() {
svcCfg := agoraservice.NewAgoraServiceConfig()
// 如需收发视频,设置 EnableVideo 为 true
svcCfg.EnableVideo = true
svcCfg.AppId = appid
// 全局只初始化一次
agoraservice.Initialize(svcCfg)
}
与频道建立连接
初始化后,按照以下步骤与声网 RTC 频道建立连接:
-
调用
NewRtcConnection
方法创建RtcConnection
对象,用于与声网服务器建立连接。信息你可以在创建连接时设置
AutoSubscribeAudio: true
和AutoSubscribeVideo: true
自动订阅音视频数据,也可以在创建连接后,随时调用SubscribeAudio
和SubscribeVideo
方法开始订阅音视频数据。本文以设置AutoSubscribeAudio: true
和AutoSubscribeVideo: true
为例。GoconCfg := agoraservice.RtcConnectionConfig{
AutoSubscribeAudio: true,
AutoSubscribeVideo: true,
ClientRole: agoraservice.ClientRoleBroadcaster,
ChannelProfile: agoraservice.ChannelProfileLiveBroadcasting,
}
con := agoraservice.NewRtcConnection(&conCfg) -
调用
RegisterObserver
方法注册连接事件观测器,并监听相关回调。信息示例代码中仅处理了连接成功事件,实际开发中,你还需要监听其他事件,并处理连接异常的情况。
GoconSignal := make(chan struct{})
conHandler := &agoraservice.RtcConnectionObserver{
OnConnected: func(con *agoraservice.RtcConnection, info *agoraservice.RtcConnectionInfo, reason int) {
fmt.Printf("Connected, reason %d\n", reason)
conSignal <- struct{}{}
},
OnDisconnected: func(con *agoraservice.RtcConnection, info *agoraservice.RtcConnectionInfo, reason int){
fmt.Printf("Disconnected, reason %d\n", reason)
},
OnConnecting: func(con *agoraservice.RtcConnection, conInfo *agoraservice.RtcConnectionInfo, reason int) {
fmt.Printf("Connecting, reason %d\n", reason)
},
OnReconnecting: func(con *agoraservice.RtcConnection, conInfo *agoraservice.RtcConnectionInfo, reason int) {
fmt.Printf("Reconnecting, reason %d\n", reason)
},
OnReconnected: func(con *agoraservice.RtcConnection, conInfo *agoraservice.RtcConnectionInfo, reason int) {
fmt.Printf("Reconnected, reason %d\n", reason)
},
OnConnectionLost: func(con *agoraservice.RtcConnection, conInfo *agoraservice.RtcConnectionInfo) {
fmt.Printf("Connection lost\n")
},
OnConnectionFailure: func(con *agoraservice.RtcConnection, conInfo *agoraservice.RtcConnectionInfo, errCode int) {
fmt.Printf("Connection failure, error code %d\n", errCode)
},
OnUserJoined: func(con *agoraservice.RtcConnection, uid string) {
fmt.Println("user joined, " + uid)
},
OnUserLeft: func(con *agoraservice.RtcConnection, uid string, reason int) {
fmt.Println("user left, " + uid)
},
}
con.RegisterObserver(conHandler) -
调用
Connect
与声网 RTC 频道建立连接,你需要传入以下参数:token
: RTC Token。如果你的项目开启了 App 证书,你需要传入 RTC Token,详见获取临时 Token;如果你的项目未开启 App 证书,则只需传入空字符串。channelName
: 你自行定义的频道名。userId
: 你自行定义的本地用户 ID。
Gocon.Connect(token, channelName, userId)
// 等待连接成功
<-conSignal
发送媒体流
成功建立连接后,参考以下步骤向客户端发送媒体流:
-
调用
NewMediaNodeFactory
方法创建MediaNodeFactory
媒体节点工厂对象。信息全局只需创建一个
MediaNodeFactory
实例。GomediaNodeFactory := agoraservice.NewMediaNodeFactory()
-
在
MediaNodeFactory
对象中,根据实际需求创建音视频数据发送器。支持以下发送器:AudioPcmDataSender
:发送 PCM 格式的音频数据。VideoFrameSender
:发送 YUV 格式的视频数据。AudioEncodedFrameSender
:发送已编码的音频数据。VideoEncodedImageSender
:发送已编码的视频数据。
本节以发送 PCM 音频数据和 YUV 视频数据为例,示例代码如下:
Go// 创建可发送 PCM 格式的音频数据的发送器
audioSender := mediaNodeFactory.NewAudioPcmDataSender()
defer audioSender.Release()
// 创建可发送 YUV 格式的视频数据的发送器
videoSender := mediaNodeFactory.NewVideoFrameSender()
defer videoSender.Release() -
使用步骤 2 中创建好的音视频数据发送器,创建
LocalAudioTrack
对象和LocalVideoTrack
对象,分别对应本地音频轨道和本地视频轨道。Go// 创建自定义音频轨道(使用可发送 PCM 格式的音频数据的发送器)
audioTrack := agoraservice.NewCustomAudioTrackPcm(audioSender)
// 如果不需要使用音频轨道,则调用 Release 方法释放资源
defer audioTrack.Release()
// 创建自定义视频轨道(可发送 YUV 格式的视频数据的发送器)
videoTrack := agoraservice.NewCustomVideoTrackFrame(videoSender)
// 如果不需要使用视频轨道,则调用 Release 方法释放资源
defer videoTrack.Release() -
调用
SetEnabled
方法分别开启本地音频轨道和视频轨道。Go// 开启本地音频轨道
audioTrack.SetEnabled(true)
// 开启本地视频轨道
videoTrack.SetEnabled(true) -
调用
PublishAudio
和PublishVideo
方法在频道中分别发布本地音频轨道和视频轨道。Go// 发布本地音频轨道
localUser.PublishAudio(audioTrack)
// 发布本地视频轨道
localUser.PublishVideo(videoTrack) -
发送音视频数据。
- 调用
AudioPcmDataSender
对象的SendAudioPcmData
方法发送 PCM 音频数据。
Goframe := agoraservice.AudioFrame{
Type: agoraservice.AudioFrameTypePCM16,
SamplesPerChannel: 160,
BytesPerSample: 2,
Channels: 1,
SamplesPerSec: 16000,
Buffer: make([]byte, 320),
RenderTimeMs: 0,
}
file, err := os.Open("./test.pcm")
if err != nil {
fmt.Println("Error opening file:", err)
return
}
defer file.Close()
firstSendTime := time.Now()
for !(*bStop) {
shouldSendCount := int(time.Since(firstSendTime).Milliseconds()/10) - (sendCount - 18)
for i := 0; i < shouldSendCount; i++ {
dataLen, err := file.Read(frame.Buffer)
if err != nil || dataLen < 320 {
fmt.Println("Finished reading file:", err)
file.Seek(0, 0)
i--
continue
}
sendCount++
ret := sender.SendAudioPcmData(&frame)
fmt.Printf("SendAudioPcmData %d ret: %d\n", sendCount, ret)
}
fmt.Printf("Sent %d frames this time\n", shouldSendCount)
time.Sleep(50 * time.Millisecond)
}- 调用
SetVideoEncoderConfiguration
方法设置视频编码参数,并调用SendVideoFrame
方法发送 YUV 视频数据。
Go// 设置视频编码分辨率,帧率,码率等参数
videoTrack.SetVideoEncoderConfiguration(&agoraservice.VideoEncoderConfiguration{
CodecType: agoraservice.VideoCodecTypeH264,
Width: 320,
Height: 240,
Framerate: 30,
Bitrate: 500,
MinBitrate: 100,
OrientationMode: agoraservice.OrientationModeAdaptive,
DegradePreference: 0,
})
w := SendYuvWidth
h := SendYuvHeight
dataSize := w * h * 3 / 2
data := make([]byte, dataSize)
// 打开 YUV 文件
file, err := os.Open(SendYuvPath)
if err != nil {
fmt.Printf("task %d Error opening file: %s\n", taskCtx.id, err.Error())
return
}
defer file.Close()
// 循环读取 YUV 文件并发送
ticker := time.NewTicker(33 * time.Millisecond)
for {
select {
case <-ticker.C:
dataLen, err := file.Read(data)
if err != nil || dataLen < dataSize {
file.Seek(0, 0)
continue
}
yuvSender.SendVideoFrame(&agoraservice.ExternalVideoFrame{
Type: agoraservice.VideoBufferRawData,
Format: agoraservice.VideoPixelI420,
Buffer: data,
Stride: w,
Height: h,
Timestamp: 0,
})
case <-ctx.Done():
fmt.Printf("task %d video sender finished\n", taskCtx.id)
return
}
} - 调用
接收媒体流
成功建立连接后,根据实际需求,调用 RegisterAudioFrameObserver
和 RegisterVideoFrameObserver
方法注册以下音视频数据观测器,并监听相关回调。
AudioFrameObserver
:音频数据观测器。VideoFrameObserver
:原始视频数据观测器。VideoEncodedFrameObserver
:已编码视频数据观测器。
本节以接收 PCM 音频数据和 YUV 视频数据为例,示例代码如下:
// 当客户端开始发送音频数据时,通过 OnPlaybackAudioFrameBeforeMixing 回调接收 PCM 音频数据
audioObserver := &agoraservice.AudioFrameObserver{
OnPlaybackAudioFrameBeforeMixing: func(localUser *agoraservice.LocalUser, channelId string, userId string, frame *agoraservice.AudioFrame) bool {
// 添加处理逻辑
fmt.Printf("Playback audio frame before mixing, from userId %s\n", userId)
return true
},
}
localUser := con.GetLocalUser()
// 在 RegisterAudioFrameObserver 方法之前调用 SetPlaybackAudioFrameBeforeMixingParameters 方法,设置音频数据的采样率
localUser.SetPlaybackAudioFrameBeforeMixingParameters(1, 16000)
// 注册音频数据观测器
localUser.RegisterAudioFrameObserver(audioObserver)
// 当客户端开始发送视频数据时,通过 OnFrame 回调接收 YUV 视频数据
videoObserver := &agoraservice.VideoFrameObserver{
OnFrame: func(channelId string, userId string, frame *agoraservice.VideoFrame) bool {
// 添加处理逻辑
fmt.Printf("recv video frame, from channel %s, user %s\n", channelId, userId)
return true
},
}
localUser := con.GetLocalUser()
// 注册视频数据观测器
localUser.RegisterVideoFrameObserver(videoObserver)
停止发送和接收媒体流
如果你不再需要发送和接收媒体流,你可以按照以下步骤停止发送和接收媒体流。
停止发送媒体流
- 先调用
UnpublishAudio
和UnpublishVideo
方法停止发布音视频轨道GolocalUser.UnpublishAudio(audioTrack)
localUser.UnpublishVideo(videoTrack) - 调用
SetEnabled
方法分别关闭本地音频轨道和视频轨道。GoaudioTrack.SetEnabled(false)
videoTrack.SetEnabled(false) - 依次调用
Release
方法释放音视频轨道、音视频发送器、媒体节点工厂的资源。Go// 释放音频轨道
defer audioTrack.Release()
// 释放音频发送器
defer audioSender.Release()
// 释放视频轨道
defer videoTrack.Release()
// 释放视频发送器
defer videoSender.Release()
// 释放媒体节点工厂
defer mediaNodeFactory.Release()
-
停止接收媒体流:调用
UnregisterAudioFrameObserver
和UnregisterVideoFrameObserver
方法注销音视频数据观测器。GolocalUser.UnregisterAudioFrameObserver(audioObserver)
localUser.UnregisterVideoFrameObserver(videoObserver)
与频道断开连接
如果你不再需要对频道进行任何操作,或者不再需要接收任何频道的事件,按照以下步骤与频道断开连接:
你必须严格按照步骤释放相关资源。
-
调用
Disconnect
方法断开与声网服务器的连接。Gocon.Disconnect()
-
调用
UnregisterObserver
方法注销连接事件观测器。Gocon.UnregisterObserver()
-
调用
Release
方法释放RTCConnection
对象。Gocon.Release()
释放 SDK 资源
当你退出服务端程序时,为避免资源浪费,调用 Release
方法释放 agoraservice
包,以释放 SDK 的资源。
全局只需调用一次 agoraservice.Release
释放 SDK 资源。资源释放后,你将无法调用 agoraserivce
包中的任何方法。
agoraservice.Release()
调试项目
按照以下步骤来测试你的项目:
-
在终端中依次运行下列命令行,编译你的 Go 项目:
Bash# 进入你的项目目录
cd /path/to/your/project
# 清理并更新项目的依赖项
go mod tidy
# 编译项目
go build
# 设置环境变量,指向 SDK 动态库所在目录
export LD_LIBRARY_PATH=/path/to/go_rtc_sdk/agora_sdk
# 运行项目
./your_project -
打开声网实时互动 Web Demo,完成初始化设置,在浏览器中模拟一个客户端。App ID 需和你在步骤 2 中填入的 App ID 一致。
-
进入基础视频通话页面,在 Channel 填入与步骤 2 相同的频道名,依次点击 Create Client、Join Channel。
-
成功加入频道后,你可以开始发流。当频道中有发流用户时,Web Demo 会在 Step4 Subscribe & Play 下方的输入框中自动填充发流用户的用户 ID,点击 Subscribe & Play 按钮订阅发流用户。
由于 Web Demo 的限制,体验服务端发流时,你需要先在 Web 端完成加入频道,然后再在服务端运行项目发流;体验服务端收流时,你需要先在服务端运行项目连接频道,然后再在 Web 端加入频道、发流。
示例项目
声网提供了开源的服务端示例项目供你参考,你可以前往下载或查看其中的源代码。