本文介绍C++ 集成实时音视频Linux ARTC SDK。
一、准备工作
解压Linux SDK压缩包,打开解压后的文件夹里面的Cpp文件夹,交付产物包括如下
├── Release
│ ├── include ##此目录包含需要引入的头文件
│ │ ├── AliRTCEngineCentralInterface.h
│ │ └── AliRTCSdkDefineCentral.h
│ └── lib ##此目录包含需要链接的SDK动态库
│ ├── AliRtcCoreService
│ ├── libAliRtcLinuxEngine.so
│ └── libPluginOpus.so
└── Demo ##简易例程
├── CMakeLists.txt
├── fake_linux_event_listener.cc ##包含音、视频拉流的回调逻辑
├── fake_linux_event_listener.h
└── simple_main.cc ##demo主体,包含初始化、入会、推流等操作
Release目录包含需要引入的头文件(include)、需要链接的SDK动态库(lib)。API与数据结构均在include目录中的各头文件中声明,要求配置正确的动态库链接地址,如:export LD_LIBRARY_PATH=./lib
Demo目录提供简易例程,其中simple_main.cc是demo主体,包含初始化、入会、推流等操作,fake_linux_event_listener包含音、视频拉流的回调逻辑。
执行Demo请确保开发环境g++版本在4.8以上。
二、核心API
1.初始化SDK
实现EventHandler类,该类型用于拉流回调,收到数据将触发其中的回调函数
创建EventHandler实例
调用AliRTCEngineInterface.h中的CreateAliRTCEngine函数创建AliRTCEngine实例,过程中会将EventHandler实例注册为engine的回调对象。后面只需要调用AliRTCEngine实例的各方法完成推拉流设置
如果AliRTCEngine实例创建失败,需要销毁EventHandler实例
每创建一个AliRTCEngine,将会启动一个进程对应一个虚拟用户。
示例如下:
//实现EventHandle类
...
//初始化SDK
AliRTCSdk::Central::FakeLinuxEngineEventHandler *engineEventHandler = new AliRTCSdk::Central::FakeLinuxEngineEventHandler();
std::string extra = "";
AliRTCSdk::Central::AliRTCEngineInterface *linuxEngine = AliRTCSdk::Central::CreateAliRTCEngine(engineEventHandler, 42000, 45000, logPath.c_str(), nullptr, false, extra.c_str());
if (linuxEngine == nullptr) {
delete engineEventHandler;
engineEventHandler = nullptr;
return;
}
CreateAliRTCEngine函数的参数如下:
EngineEventHandlerInterface * eventHandler
:回调对象,负责处理回调逻辑int lowPort
:端口号下限,lowPort~highPort之间的端口将被随机选择用于进程间通信int highPort
:端口号上限const char * logPath
:SDK运行过程中,日志文件的保存路径const char * coreServicePath
:AliRtcCoreService的实际路径bool h5mode
:h5兼容模式,一般false即可const char * extra
:对SDK进行额外配置时传入的JSON格式字符串
2.入会
请先了解鉴权功能,详见Token鉴权。
入会存在两个版本的接口,其中单参数入会是在多参数入会的基础上做的语法糖,剥离了业务方传入nonce、timestamp以入会的必要性,在直接获取token的场景下更易于使用。
2.1 多参数入会接口
获取authInfo,以使用token完成鉴权
配置JoinChannelConfig,目前先使用默认配置
调用JoinChannel
// 获取authInfo
AliRTCSdk::Central::AuthInfo authInfo;
authInfo.appid = "";
authInfo.channel = "";
authInfo.userid = "";
authInfo.username = "";
authInfo.nonce = "";
authInfo.token = "";
authInfo.timestamp = 1583582330; //sample
int gslbCount = /* */;
authInfo.gslb_count = gslbCount;
const char *gslbArray[gslbCount];
if (gslbCount > 0)
{
for(int i = 0; i < gslbCount; i++)
{
gslbArray[i] = "";
}
authInfo.gslb = gslbArray;
}
int agentCount = /* */;
authInfo.agent_count = agentCount;
const char *agentArray[agentCount];
if (agentCount > 0)
{
for (int i = 0; i < agentCount; i++)
{
agentArray[i] = "";
}
authInfo.agent = agentArray;
}
//配置JoinChannelConfig
AliRTCSdk::Central::JoinChannelConfig config;
//打开自动推流功能(如果业务需要也可以采用手动推流模式,先入会,再开启推流)
config.publishMode = AliRTCSdk::Central::PublishAutomatically;
//关闭自动录制功能(如无需录制)
config.subscribeMode = AliRTCSdk::Central::SubscribeManually;
//入会自动订阅远端视频,详见AliRTCSdk::Central::VideoFormat
config.subscribeVideoFormat = 1;
//入会自动订阅远端音频,详见AliRTCSdk::Central::AudioFormat
config.subscribeAudioFormat = 2;
//若订阅所有远端用户的混合音频流,设置 config.subAudioMode = 1
//设置channelprofile,通常需要手动设定为互动模式
config.channelProfile = AliRTCSdk::Central::ChannelProfileInteractiveLive;
//设置是否推camera流,置为false则不推camera视频流
linuxEngine->PublishLocalVideoStream(true);
//设置是否推音频流,置为false则不推音频流
linuxEngine->PublishLocalAudioSrtream(true);
//设置视频相机流的编码配置
AliRTCSdk::Central::AliEngineVideoEncoderConfiguration videoConfig;
linuxEngine->SetVideoEncoderConfiguration(videoConfig);
//开启Yuv输入,使用camera流进行推送
linuxEngine->SetExternalVideoSource(true, AliRTCSdk::Central::VideoSourceCamera, AliRTCSdk::Central::RenderModeAuto);
//开启Pcm输入
//第二个参数为pcm的采样率,请根据实际情况设置
//第三个参数为pcm的channel数,请根据实际情况设置
linuxEngine->SetExternalAudioSource(true, 16000, 2);
//设置用户模式:主播或观众
// 观众AliEngineClientRoleLive:只拉流不推流,且上线时远端用户无感知
// 主播AliEngineClientRoleInteractive:具有推流的能力,无论是否推流,远端用户都会有感知
linuxEngine->SetClientRole(AliRTCSdk::Central::AliEngineClientRoleInteractive);
//入会
linuxEngine->JoinChannel(authInfo, config);
2.2 单参数入会接口
获取token,和用户名、房间号等基本信息
配置JoinChannelConfig
调用重载的JoinChannel
// 获取入会所需基本信息
const char* token = ""; // 业务方传入
const char* channel = ""; // 房间号
const char* userid = ""; // 用户ID
const char* username = ""; // 用户名
//配置JoinChannelConfig
AliRTCSdk::Central::JoinChannelConfig config;
// ..... 配置选项同上
linuxEngine->JoinChannel(token, channel, userid, username, config);
请勿重复调用JoinChannel
Demo中演示了用GenerateToken模拟appserver生成入会参数的过程,该接口是专为简化开发、测试而提供的。
生产环境中,请通过与app server的交互获取token,避免appkey泄漏。
3.手动推流
以下API设置是否推送音视频流。
/**
* @brief 是否推送本地视频(摄像头)流
* @param enabled 是否开启/关闭本地视频流推送
- true: 开启视频流推送
- false: 关闭视频流推送
* @return
- 0: 设置成功
- <0: 设置失败,返回错误码
* @note SDK默认设置推送视频流,在加入频道前也可以调用此接口修改默认值,并在加入频道成功时生效
*/
virtual int PublishLocalVideoStream(bool enabled) = 0;
/**
* @brief 是否推送本地音频流
* @param enabled 是否开启/关闭本地音频流推送
- true: 开启音频流推送
- false: 关闭音频流推送
* @return
- 0: 设置成功
- <0: 设置失败,返回错误码
* @note SDK默认设置推送音频流,在加入频道前也可以调用此接口修改默认值,并在加入频道成功时生效
*/
virtual int PublishLocalAudioStream(bool enabled) = 0;
4.推YUV视频流
/**
* @brief 启用外部视频输入源
* @param enable true 开启, false 关闭
* @param useTexture 是否使用texture 模式,目前只支持false
* @param type 流类型
* @note 启用后使用PushExternalVideoFrame接口输入视频数据
*/
virtual int SetExternalVideoSource(bool enable, bool useTexture, AliRTCSdk::Central::VideoSource sourceType) = 0;
/**
* @brief 输入外部输视频
* @param frame 帧数据
* @param type 流类型
* @param 支持的输入视频类型请见VideoDataFormat
*/
virtual int PushExternalVideoFrame(AliRTCSdk::Central::VideoDataSample *frame, AliRTCSdk::Central::VideoSource sourceType) = 0;
5.推PCM音频流
/**
* @brief 设置是否启用外部音频输入推流
* @param enable true 开启,false 关闭
* @param sampleRate 采样率 16k 48k...
* @param channelsPerFrame 采样率 16k 48k...
* @return >=0表示成功, <0表示失败
* @note 可通过SetExternalAudioPublishVolume设置输入音频推流音量
*/
virtual int SetExternalAudioSource(bool enable, unsigned int sampleRate, unsigned int channelsPerFrame) = 0;
/**
* @brief 输入外部音频数据推流
* @param audioSamples 音频数据
* @param sampleLength 音频数据长度
* @param timestamp 时间戳
* @return <0表示失败,返回值为ERR_AUDIO_BUFFER_FULL时,需要在间隔投递数据时间长度后再次重试投递
*/
virtual int PushExternalAudioFrameRawData(const void* audioSamples, unsigned int sampleLength, long long timestamp) = 0;
/**
* @brief 设置外部输入音频推流混音音量
* @param vol 音量 0-100
*/
virtual int SetExternalAudioPublishVolume(int volume) = 0;
/**
* @brief 获取外部输入音频推流混音音量
* @return vol 音量
*/
virtual int GetExternalAudioPublishVolume() = 0;
6.手动订阅
在自动订阅的模式下,无需调用这部分接口拉流
/**
* @brief 停止/恢复订阅特定远端用户的音频流, 用于会中调用, 会前调用无效
* @param uid 用户ID,从App server分配的唯一标示符
* @param sub 是否订阅远端用户的音频流
* - true:订阅指定用户的音频流
* - false:停止订阅指定用户的音频流
* @return
* - 0: 成功
* - 非0: 失败
*/
virtual int SubscribeRemoteAudioStream(const char* uid, bool sub) = 0;
/**
* @brief 停止/恢复订阅远端用户的视频流, 用于会中调用, 会前调用无效
* @param uid 用户ID,从App server分配的唯一标示符
* @param track 视频流类型
* - AliEngineVideoTrackNo: 无效参数,设置不会有任何效果
* - AliEngineVideoTrackCamera: 相机流
* - AliEngineVideoTrackScreen: 屏幕共享流
* - AliEngineVideoTrackBoth: 相机流和屏幕共享流
* @param sub 是否订阅远端用户的视频流
* - true:订阅指定用户的视频流
* - false:停止订阅指定用户的视频流
* @return
* - 0:设置成功
* - <0:设置失败
* @note
*/
virtual int SubscribeRemoteVideoStream(const char* uid, AliRTCSdk::Central::VideoTrack videoTrack, bool sub) = 0;
7.数据回调
7.1 音频
audioFormat选择AudioFormatPcmBeforMixing模式后,收到音频帧将触发EventHandler实例的回调函数OnSubscribeAudioFrame
。
uid: 表示此时收到的音频帧来自哪个远端用户,借此区分订阅的各路音频流
frame: 收到的音频帧,pcm格式
void OnSubscribeAudioFrame(const std::string& uid, const AliRTCSdk::Central::AudioFrame * frame)
选择AudioFormatMixedPcm模式,收到音频帧将触发EventHandler实例的回调函数OnSubscribeMixAudioFrame
。
void OnSubscribeMixAudioFrame(const AliRTCSdk::Central::AudioFrame * frame)
7.2 视频
订阅视频帧不存在混合流,videoFormat置为VideoFormatH264后,收到视频帧将触发EventHandler实例的回调函数OnRemoteVideoSample
。
uid: 表示此时收到的视频帧来自哪个远端用户,借此区分订阅的各路视频流
frame: 收到的视频帧,yuv格式
void OnRemoteVideoSample(const char * uid, const AliRTCSdk::Central::VideoFrame * frame)
8.离会
/* 结束推流 */
linuxEngine->PublishLocalVideoStream(false);
linuxEngine->PublishLocalAudioStream(false);
若要在停止推流后,仍在会中,请手动调用此接口;直接离会也具有停止推流的效果
linuxEngine->LeaveChannel();
9.销毁SDK
linuxEngine->Release();
linuxEngine = nullptr;
delete linuxEventHandler;
linuxEventHandler = nullptr;
三、Demo使用方式
在Demo目录执行下述命令
mkdir build
cd build
cmake ..
make
将在build目录得到可执行文件linux_sdk_simple_demo,直接执行./linux_sdk_simple_demo
若终端有以下输出,表示入会、推拉流成功
# 入会成功
[JoinChannelStateChanged] state: success
# 推流成功
[AudioPublishStateChanged] oldState:0, newState:2, elapseSinceLastState:0, channelId:9090
[VideoPublishStateChanged] oldState:0, newState:2, elapseSinceLastState:0, channelId:9090
[DualStreamPublishStateChanged] oldState:0, newState:2, elapseSinceLastState:0, channelId:9090
[AudioPublishStateChanged] oldState:2, newState:3, elapseSinceLastState:292, channelId:9090
[VideoPublishStateChanged] oldState:2, newState:3, elapseSinceLastState:293, channelId:9090
[DualStreamPublishStateChanged] oldState:2, newState:3, elapseSinceLastState:293, channelId:9090
# 远端用户abcd已上线
OnRemoteUserOnLineNotify userid: abcd
# 收到来自远端用户abcd的音视频流
[AudioSubscribeStateChanged] uid:abcd, oldState:3, newState:1, elapseSinceLastState:38865, channelId:9090
[VideoSubscribeStateChanged] uid:abcd, oldState:3, newState:1, elapseSinceLastState:38865, channelId:9090
音频订阅选择AudioFormatMixedPcm模式时,无论是否有远端用户在会上,入会时就会有音频回调。选择AudioFormatPcmBeforMixing时,只有会上有其他用户,且其他用户在推音频流时,才会触发音频回调。
想要退出离会时,在终端输入exit
,等待其自行退出即可。
四、消息通讯
除音视频互动外,RTC SDK还支持实时消息收发,以用于有消息互动需求的场景。您可以通过Data Channel和SEI(Supplemental Enhancement Information)两种通道进行消息收发,前者独立于音视频传输通道,后者依赖视频数据收发,一般场景下建议使用Data Channel。
1. Data Channel
通过SendDataChannelMessage
发送消息:
/**
* @note 发送消息前需通过linuxEngine->SetParameter("{\"data\":{\"enablePubDataChannel\":true,\"enableSubDataChannel\":true}}");,打开DataChannel
*/
std::string message = "This is data channel message";
AliRTCSdk::Central::AliEngineDataChannelMsg dataChannelMsg;
dataChannelMsg.data = (void*)message.c_str();
dataChannelMsg.dataLen = message.length();
dataChannelMsg.networkTime = t0; // 网络时间字段,也可以自定义取值,不影响消息收发
dataChannelMsg.progress = 0; // 保留字段
dataChannelMsg.type = AliRTCSdk::Central::AliEngineDataChannelProgress;
linuxEngine->SendDataChannelMessage(dataChannelMsg);
当接收到Data Channel消息后,会触发回调:
/**
* @brief 获得dataChannel远端数据
* @param uid 发出消息的远端用户id
* @param msg 远端传来的消息
*/
virtual void OnDataChannelMsg(const char* uid, AliRTCSdk::Central::AliEngineDataChannelMsg& msg) {}
2. SEI
/**
* @brief 发送 媒体扩展信息(SEI), 最大长度为4*1024字节,用于业务的少量数据传输
* @param message 扩展信息内容,可以传递4K Bytes数据
* @param length 扩展信息长度,单位:字节
* @param repeatCount 重复次数,用于防止网络丢包导致的消息丢失
* @param delay 延迟多久发出去,单位:毫秒
* @param isKeyFrame 是否只在关键帧上增加SEI
* @return <0: 成功,-1: SDK内部错误>
*/
int SendMediaExtensionMsg(const char* message,
size_t length,
int repeatCount,
uint32_t delay,
bool isKeyFrame);
通过SendMediaExtensionMsg
发送消息:
std::string seiMsg = "This is SEI message";
linuxEngine->SendMediaExtensionMsg(seiMsg.c_str(), seiMsg.length(), 3, 0 ,false);
由于关键帧间隔较长,若需要高频收发SEI消息,建议isKeyFrame设置为false。同时为保证消息有效送达,建议重复次数大于1。
当接收到SEI消息后,会触发回调:
/**
* @brief 收到媒体扩展信息回调
* @param uid 发送用户userId
* @param message 扩展信息内容
* @param size 扩展信息长度
* @note 当一端通过 {@link SendMediaExtensionMsg} 发送信息后,其他端通过该回调接收数据
*/
virtual void OnMediaExtensionMsgReceived(const char* userid, const char* message, size_t size) {}
五、其他功能
创建引擎时的extra字段,可用于开闭部分额外功能。extra字段为JSON格式,各键对应不同配置项,修改示例如下。
Json::Value extraJson;
extraJson["key"] = value; // 填充字段示例
std::string extra = extraJson.toStyledString();
1. 关闭Audio Ranking
Audio Ranking是多人语聊房中,仅订阅音量较大几路音频流的功能,使听众听到的声音更为清晰,默认为开启状态。若场景需要订阅全部音频流,需要通过如下字段关闭Audio Ranking功能:
extraJson["user_specified_disable_audio_ranking"] = "true";
2. 开启订阅音频时的转码AAC功能
对于有拉流存档需求的场景,SDK提供将订阅到的远端音频转码至AAC编码的能力,并通过回调函数OnSubscribeAudioAac()返回。您可通过AudioTranscodingCodec中的各项选择以何种格式获取远端音频(PCM、AAC或二者皆要),若开启AAC转码功能,可以选择是否进行重采样以降低CPU和存储占用。当user_specified_audio_observer_resample_rate设置为0表示不进行重采样。
extraJson["user_specified_audio_observer_codec"] =
(int)AliRTCSdk::Central::AudioTranscodingCodecBothPcmAndAac;
extraJson["user_specified_audio_observer_codec_format"] = 0; // 目前只支持adts格式
extraJson["user_specified_audio_observer_resample_rate"] = 16000; // 重采样率
extraJson["user_specified_audio_observer_codec_bitrate"] = 64000; // 转码的目标码率
3. 开启订阅视频时直接获取H264码流功能
对于有拉流存档需求的场景,SDK提供直接获取远端视频H264码流的能力。您可通过VideoTranscodingCodec的各项,选择以何种格式获取远端视频(YUV、H264码流或二者皆要)。
extraJson["user_specified_video_observer_codec"] =
(int)AliRTCSdk::Central::VideoTranscodingCodecBothYuvAndH264;
extraJson["user_specified_video_observer_codec_format"] = 0; // 目前只支持annexb格式