声网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 的设计要点与注意事项》
定义声网处理类AgoraRtcService
class AgoraRtcService {
public:
AgoraRtcService() = default;
~AgoraRtcService();
void app_init_event_handler(agora_rtc_event_handler_t *event_handler);
int sendAgoraRtcAudioStream(short *audio_buffer, size_t audio_size);
int sendAgoraRtcVideoStream(int nalu_type, uint8_t *video_buffer, uint32_t video_size);
int stopAgoraRtcService();
int startAgoraRtcService();
private:
char m_rtc_token[512] = DEFAULT_TOKEN;
char m_rtc_app_id[64] = DEFAULT_APP_ID;
char m_rtc_channel_name[64] = DEFAULT_CHANNEL_NAME;
area_code_e m_rtc_arga_code = AREA_CODE_GLOB;
char m_rtc_log_path[64] = DEFAULT_RTC_LOG_PATH;
std::atomic<bool> m_rtc_log_disable = false;
std::atomic<bool> m_rtc_use_string_uid = RTC_USE_STRING_UID_ENABLE == 1?true:false;
rtc_log_level_e m_rtc_log_level = RTC_LOG_DEFAULT;
connection_id_t m_conn_id = 0;
// thread handle
std::thread m_rtc_thread ;
std::atomic<bool> m_rtc_running = true;
std::thread m_rtc_audio_thread ;
std::atomic<bool> m_rtc_audio_running = true;
snd_pcm_t *p_capture_handle;///< Flag to control thread execution
};
开始启动声网 rtc 服务
注意: 声网服务器数据,通过回调函数的方式处理 可以基于自己的业务,配置service_opt参数(区域,日志是否开启,日志的级别,是否使用 token 等)
int AgoraRtcService::startAgoraRtcService() {
agora_rtc_event_handler_t event_handler = {0};
app_init_event_handler(&event_handler);
rtc_service_option_t service_opt = {0};
service_opt.area_code = m_rtc_arga_code;
service_opt.log_cfg.log_path = m_rtc_log_path;
service_opt.log_cfg.log_disable = m_rtc_log_disable;
service_opt.log_cfg.log_level = m_rtc_log_level;
service_opt.use_string_uid = m_rtc_use_string_uid;
LOG(INFO) << "AgoraRtcService::startAgoraRtcService";
int rval = agora_rtc_init(m_rtc_app_id, &event_handler, &service_opt);
if (rval < 0) {
LOG(INFO) << "agora_rtc_init failed %d" << rval;
return -1;
}
rval = agora_rtc_create_connection(&m_conn_id);
if (rval < 0) {
LOG(INFO) << "agora_rtc_create_connection failed " << rval;
return -1;
} else
LOG(INFO) << "m_conn_id " << m_conn_id;
rval = agora_rtc_set_bwe_param(m_conn_id, DEFAULT_BANDWIDTH_ESTIMATE_MIN_BITRATE,
DEFAULT_BANDWIDTH_ESTIMATE_MAX_BITRATE,
DEFAULT_BANDWIDTH_ESTIMATE_START_BITRATE);
if (rval < 0) {
LOG(INFO) << "agora_rtc_set_bwe_param failed " << rval;
return -1;
}
rtc_channel_options_t channel_options = {0};
memset(&channel_options, 0, sizeof(channel_options));
channel_options.auto_subscribe_audio = true;
channel_options.auto_subscribe_video = true;
channel_options.enable_audio_mixer = false;
channel_options.enable_lan_accelerate = false;
channel_options.audio_codec_opt.pcm_duration = DEFAULT_SEND_AUDIO_FRAME_PERIOD_MS;
channel_options.audio_codec_opt.audio_codec_type = AUDIO_CODEC_TYPE_OPUS;
channel_options.audio_codec_opt.pcm_sample_rate = DEFAULT_PCM_SAMPLE_RATE;
channel_options.audio_codec_opt.pcm_channel_num = DEFAULT_PCM_CHANNEL_NUM;
if (m_rtc_use_string_uid) {
rval = agora_rtc_join_channel_with_user_account(m_conn_id, Device::getInstance().getDeviceGeneralInformation().getDeviceUuid(), m_rtc_channel_name,
m_rtc_token,
&channel_options);
} else
rval = agora_rtc_join_channel(m_conn_id, Device::getInstance().getDeviceGeneralInformation().getDeviceUuid(), 0, NULL, &channel_options);
// rval = agora_rtc_join_channel(m_conn_id, m_rtc_channel_name, DEFAULT_USER_ID, NULL, &channel_options);
return 0;
}
发送设备视频数据到通道接口agora_rtc_send_video_data
注意: 接口需要知道 H264 的类型: I/P 帧 视频的码流类型:高/低 一帧数据及其长度
int AgoraRtcService::sendAgoraRtcVideoStream(int nalu_type,uint8_t *video_buffer , uint32_t video_size) {
if (g_rtc_joined_user_count == 0 ) {
return 1;
}
video_frame_info_t info{};
info.frame_type = nalu_type==0? VIDEO_FRAME_KEY:VIDEO_FRAME_DELTA;
info.frame_rate = STREAM_VIDEO_FPS;
info.data_type = VIDEO_DATA_TYPE_H264; //h264
info.stream_type = VIDEO_STREAM_HIGH;
int rval = agora_rtc_send_video_data(m_conn_id, video_buffer, video_size, &info);
if (rval < 0) {
LOG(INFO) << "Failed to send video data, reason: " << agora_rtc_err_2_str(rval);
}
return 0;
}
手机端视频解码处理,通过回调函数on_video_data处理
event_handler->on_video_data = __on_video_data;
static void __on_video_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts,
const void *data, size_t len, const video_frame_info_t *info_ptr) {
// LOGD("[conn-%u] on_video_data: uid %u sent_ts %u data_type %d frame_type %d stream_type %d len %zu",
// conn_id, uid, sent_ts, info_ptr->data_type, info_ptr->frame_type, info_ptr->stream_type, len);
// write_file(g_app.video_file_writer, info_ptr->data_type, data, len);
}
判断房间是否有用户决定是否发送视频,通过回调函数on_user_joined和on_user_offline处理
注意: on_user_joined 表示有用户进入房间 on_user_offline表示有用户离开房间
static void __on_user_joined(connection_id_t conn_id, uint32_t uid, int elapsed_ms) {
g_rtc_joined_user_count++;
LOG(INFO) << "on_user_joined user->uid:" << uid << "elapsed_ms:" << elapsed_ms << "g_rtc_joined_user_count " << g_rtc_joined_user_count ;
}
static void __on_user_offline(connection_id_t conn_id, uint32_t uid, int reason) {
g_rtc_joined_user_count--;
LOG(INFO) << "on_user_offline user->uid:" << uid << "reason:" << reason << "g_rtc_joined_user_count: "<< g_rtc_joined_user_count;
}
错误码回调函数on_error,用于处理rtc 服务器返回的错误,调试非常有用
static void __on_error(connection_id_t conn_id, int code, const char *msg) {
LOG(INFO) << "code" << code << "msg" << msg;
if (code == ERR_VIDEO_SEND_OVER_BANDWIDTH_LIMIT) {
// LOGE("Not enough uplink bandwdith. Error msg \"%s\"", msg);
return;
}
if (code == ERR_INVALID_APP_ID) {
// LOGE("Invalid App ID. Please double check. Error msg \"%s\"", msg);
} else if (code == ERR_INVALID_CHANNEL_NAME) {
// LOGE("Invalid channel name for conn_id %u. Please double check. Error msg \"%s\"", conn_id,
// msg);
} else if (code == ERR_INVALID_TOKEN || code == ERR_TOKEN_EXPIRED) {
// LOGE("Invalid token. Please double check. Error msg \"%s\"", msg);
} else if (code == ERR_DYNAMIC_TOKEN_BUT_USE_STATIC_KEY) {
// LOGE("Dynamic token is enabled but is not provided. Error msg \"%s\"", msg);
} else {
// LOGW("Error %d is captured. Error msg \"%s\"", code, msg);
}
}
发送音频到 RTC
注意: 音频的参数(格式,采样率 ,单双通道)在进入通道的时候填充数据结构 channel_options.audio_codec_opt
int AgoraRtcService::sendAgoraRtcAudioStream(short *audio_buffer , size_t audio_size) {
if (g_rtc_joined_user_count == 0 ) {
return 1;
}
audio_frame_info_t audio_info = {AUDIO_DATA_TYPE_PCM};
audio_info.data_type= AUDIO_DATA_TYPE_PCM;
// LOG(INFO) << "audio data_len " << audio_size ;
int rval = agora_rtc_send_audio_data(m_conn_id, audio_buffer, audio_size, &audio_info);
if (rval < 0) {
fprintf(stderr, "Failed to send audio data: %s\n", agora_rtc_err_2_str(rval));
}
return 0;
}
处理手机端的音频,通过回调函数on_audio_data处理
注意: 我的设备使用的 alsa 的音频框架,这部分不同的芯片需要做不同的处理
void __on_audio_data(connection_id_t conn_id, const uint32_t uid, uint16_t sent_ts,
const void *data, size_t len, const audio_frame_info_t *info_ptr) {
RdkAudioService().getInstance().play_audio_data(data, len);
}
void RdkAudioService::play_audio_data(const void *data, size_t len) {
int bytes_per_frame = DEFAULT_PCM_CHANNEL_NUM * sizeof(int16_t);
int frames = len / bytes_per_frame;
// LOG(INFO) << "frames:" << frames;
if (frames > 0) {
if (init_playback_audio() != 0) {
LOG(INFO) << "init playback failed";
return;
}
int ret = snd_pcm_writei(p_playback_handle, data, frames);
if (ret < 0) {
if (ret == -EPIPE) {
// 缓冲区下溢,恢复
LOG(INFO) << "Playback buffer underrun, preparing...";
snd_pcm_prepare(p_playback_handle);
// 尝试重新写入同一帧数据
ret = snd_pcm_writei(p_playback_handle, data, frames);
if (ret < 0) {
LOG(INFO) << "snd_pcm_writei failed again after prepare: " << snd_strerror(ret);
}
} else {
LOG(INFO) << " snd_pcm_writei failed: " << snd_strerror(ret);
}
}
}
}
APP端参考网络摄像机实战①:Flutter 集成声网 Agora 打造实时音视频对讲
友情提示🔔
🙏 感谢你的阅读! 如果这篇文章对你有所启发,欢迎关注我 ⭐,欢迎点击 “打赏支持作者” 支持一下我,你的支持是我持续创作的最大动力! 我会持续分享更多关于 智能摄像头 📷、机器人项目、 🤖音视频 RTC 🎧、App 开发 📱、嵌入式开发 🔧 等方向的实战经验,让你更快落地、更少踩坑。 欢迎浏览我其他文章 📚,或许能解决你当前的难题。 如果你正好在做相关项目产品,也欢迎随时私信我,一起技术交流、一起搞事情! 🤝💬📞 联系微信/电话:13826173658