Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP
一对一音视频通话使用场景
一对一音视频通话都需要稳定、清晰和流畅,以确保良好的用户体验,常用的使用场景如下:
- 社交应用:社交应用是一种常见的使用场景,用户可以通过音视频通话进行面对面的交流;
- 在线教育:老师和学生可以通过音视频通话功能进行实时互动,提高教学效率;
- 远程协助:在某些工作场景下,比如应急指挥项目,需要通过音视频通话功能进行远程协助,进行技术支持、维修服务等;
- 视频会议:一对一的音视频通话是视频会议非常重要的一部分,用于两个参会者之间的沟通,当然也可以合流输出;
- 语音通话:使用语音通话,如在行车过程中,此时语音通话就是一个很好的选择。
一对一音视频通话技术方案
WebRTC方案
在Android平台上实现一对一音视频通话,你可以使用WebRTC,WebRTC提供了实时音视频通话的功能。以下是一个简单的步骤说明如何实现:
- 设置环境:首先,你需要在你的开发环境中安装Android Studio,并且配置好必要的SDK;
- 添加依赖:在你的项目中,你需要添加WebRTC的库。在你的build.gradle文件中添加如下依赖;
- 实现音视频捕获:你需要实现音视频的捕获。在Java中,你可以使用AudioRecord和VideoCapturer类来实现;
- 创建PeerConnection:创建PeerConnection对象,这个对象会用于音视频的编解码和网络传输;
- 显示本地音视频流:使用MediaStream.VideoTrack和MediaStream.AudioTrack将捕获的音视频流添加到PeerConnection中,然后通过VideoRenderer和AudioRenderer显示出来;
- 创建并发送offer:创建并发送一个offer,这个offer包含了你的音视频通道信息以及你愿意接受的连接参数;
- 接收并解析offer:在另一端,接收到offer后,解析出音视频通道信息以及连接参数,然后创建并返回一个answer;
- 接收answer:在本地,接收到answer后,解析出音视频通道信息以及连接参数,然后创建并启动对应的通道。
RTMP方案
RTMP是一种基于TCP的流媒体协议,主要用于视频直播。它提供了实时传输音频和视频的功能,可以用于一对一或一对多的场景,RTMP可用于内网或公网环境下,缺点是需要单独部署RTMP Server,数据通过RTMP Server中转,配合低延迟的RTMP Player,互动可以很轻松的在毫秒级。
以大牛直播SDK的demo为例,RTMP推送的代码如下:
class ButtonPushStartListener implements OnClickListener{public void onClick(View v){ if (isPushingRtmp){stopPush();btnPushStartStop.setText("推送RTMP");isPushingRtmp = false;return;}Log.i(PUSH_TAG, "onClick start push rtmp..");if (libPublisher == null)return;InitPusherAndSetConfig();Log.i(PUSH_TAG, "videoWidth: "+ pushVideoWidth + " videoHeight: " + pushVideoHeight + " pushType:" + pushType);if ( libPublisher.SmartPublisherSetURL(publisherHandle, publishURL) != 0 ){Log.e(PUSH_TAG, "Failed to set rtmp pusher URL..");}int startRet = libPublisher.SmartPublisherStartPublisher(publisherHandle);if (startRet != 0) {isPushingRtmp = false;Log.e(TAG, "Failed to start push stream..");return;}CheckInitAudioRecorder();btnPushStartStop.setText("停止推送 ");isPushingRtmp = true;};
停止RTMP推送:
//停止rtmp推送
private void stopPush() {if(!isPushingRtmp){return;}if ( !isRTSPPublisherRunning) {if (audioRecord_ != null) {Log.i(TAG, "stopPush, call audioRecord_.StopRecording..");audioRecord_.Stop();if (audioRecordCallback_ != null) {audioRecord_.RemoveCallback(audioRecordCallback_);audioRecordCallback_ = null;}audioRecord_ = null;}}if (libPublisher != null) {libPublisher.SmartPublisherStopPublisher(publisherHandle);}if (!isRTSPPublisherRunning) {if (publisherHandle != 0) {if (libPublisher != null) {libPublisher.SmartPublisherClose(publisherHandle);publisherHandle = 0;}}}
}
RTMP播放:
btnPlaybackStartStopPlayback.setOnClickListener(new Button.OnClickListener() { // @Override public void onClick(View v) { if(isPlaybackViewStarted){btnPlaybackStartStopPlayback.setText("开始播放 ");if ( playerHandle != 0 ){libPlayer.SmartPlayerStopPlay(playerHandle);libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;}isPlaybackViewStarted = false;}else{Log.i(PLAY_TAG, "Start playback stream++");playerHandle = libPlayer.SmartPlayerOpen(curContext);if(playerHandle == 0){Log.e(PLAY_TAG, "sur faceHandle with nil..");return;}libPlayer.SetSmartPlayerEventCallbackV2(playerHandle,new EventHandePlayerV2());libPlayer.SmartPlayerSetSur face(playerHandle, playerSur faceView); //if set the second param with null, it means it will playback audio only..libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);libPlayer.SmartPlayerSetExternalAudioOutput(playerHandle, new PlayerExternalPcmOutput());libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);libPlayer.SmartPlayerSetBuffer(playerHandle, playbackBuffer);libPlayer.SmartPlayerSetFastStartup(playerHandle, isPlaybackFastStartup?1:0);if ( isPlaybackMute ){libPlayer.SmartPlayerSetMute(playerHandle, isPlaybackMute?1:0);}if (isPlaybackHardwareDecoder) {int isSupportHevcHwDecoder = libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle,1);int isSupportH264HwDecoder = libPlayer.SetSmartPlayerVideoHWDecoder(playerHandle,1);Log.i(TAG, "isSupportH264HwDecoder: " + isSupportH264HwDecoder + ", isSupportHevcHwDecoder: " + isSupportHevcHwDecoder);}libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);int iPlaybackRet = libPlayer.SmartPlayerStartPlay(playerHandle);if( iPlaybackRet != 0 ){libPlayer.SmartPlayerClose(playerHandle);playerHandle = 0;Log.e(PLAY_TAG, "StartPlayback strem failed.."); return;}btnPlaybackStartStopPlayback.setText("停止播放 ");btnPlaybackPopInputUrl.setEnabled(false);btnPlaybackHardwareDecoder.setEnabled(false);btnPlaybackSetPlayBuffer.setEnabled(false);btnPlaybackFastStartup.setEnabled(false);isPlaybackViewStarted = true;Log.i(PLAY_TAG, "Start playback stream--");}}});
轻量级RTSP服务+RTSP播放方案
纯内网环境下,两个终端可同时开启轻量级RTSP服务,然后相互拉取对方回调上来的RTSP URL,通过回音消除等,实现智能化场景的一对一音视频互动,不然智能门禁等场景,均可使用,实测延迟毫秒级,不影响互动体验,效果非常好:
对应的代码如下:
//Author: daniusdk.com //启动/停止RTSP服务class ButtonRtspServiceListener implements OnClickListener {public void onClick(View v) {if (isRTSPServiceRunning) {stopRtspService();btnRtspService.setText("启动RTSP服务");btnRtspPublisher.setEnabled(false);isRTSPServiceRunning = false;return;}Log.i(TAG, "onClick start rtsp service..");rtsp_handle_ = libPublisher.OpenRtspServer(0);if (rtsp_handle_ == 0) {Log.e(TAG, "创建rtsp server实例失败! 请检查SDK有效性");} else {int port = 8554;if (libPublisher.SetRtspServerPort(rtsp_handle_, port) != 0) {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "创建rtsp server端口失败! 请检查端口是否重复或者端口不在范围内!");}//String user_name = "admin";//String password = "12345";//libPublisher.SetRtspServerUserNamePassword(rtsp_handle_, user_name, password);if (libPublisher.StartRtspServer(rtsp_handle_, 0) == 0) {Log.i(TAG, "启动rtsp server 成功!");} else {libPublisher.CloseRtspServer(rtsp_handle_);rtsp_handle_ = 0;Log.e(TAG, "启动rtsp server失败! 请检查设置的端口是否被占用!");}btnRtspService.setText("停止RTSP服务");btnRtspPublisher.setEnabled(true);isRTSPServiceRunning = true;}}}
发布RTSP流:
//发布/停止RTSP流class ButtonRtspPublisherListener implements OnClickListener {public void onClick(View v) {if (isRTSPPublisherRunning) {stopRtspPublisher();if (!isPushingRtmp) {ConfigControlEnable(true);}btnRtspPublisher.setText("发布RTSP流");btnGetRtspSessionNumbers.setEnabled(false);btnRtspService.setEnabled(true);isRTSPPublisherRunning = false;return;}Log.i(TAG, "onClick start rtsp publisher..");if (!isPushingRtmp) {InitPusherAndSetConfig();}if (publisherHandle == 0) {Log.e(TAG, "Start rtsp publisher, publisherHandle is null..");return;}String rtsp_stream_name = "stream1";libPublisher.SetRtspStreamName(publisherHandle, rtsp_stream_name);libPublisher.ClearRtspStreamServer(publisherHandle);libPublisher.AddRtspStreamServer(publisherHandle, rtsp_handle_, 0);if (libPublisher.StartRtspStream(publisherHandle, 0) != 0) {Log.e(TAG, "调用发布rtsp流接口失败!");return;}if (!isPushingRtmp) {if (pushType == 0 || pushType == 1) {CheckInitAudioRecorder(); //enable pure video publisher..}ConfigControlEnable(false);}startLayerPostThread();btnRtspPublisher.setText("停止RTSP流");btnGetRtspSessionNumbers.setEnabled(true);btnRtspService.setEnabled(false);isRTSPPublisherRunning = true;}}
获取RTSP流会话链接数:
//当前RTSP会话数弹出框private void PopRtspSessionNumberDialog(int session_numbers) {final EditText inputUrlTxt = new EditText(this);inputUrlTxt.setFocusable(true);inputUrlTxt.setEnabled(false);String session_numbers_tag = "RTSP服务当前客户会话数: " + session_numbers;inputUrlTxt.setText(session_numbers_tag);AlertDialog.Builder builderUrl = new AlertDialog.Builder(this);builderUrl.setTitle("内置RTSP服务").setView(inputUrlTxt).setNegativeButton("确定", null);builderUrl.show();}//获取RTSP会话数class ButtonGetRtspSessionNumbersListener implements OnClickListener {public void onClick(View v) {if (libPublisher != null && rtsp_handle_ != 0) {int session_numbers = libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);Log.i(TAG, "GetRtspSessionNumbers: " + session_numbers);PopRtspSessionNumberDialog(session_numbers);}}}
播放RTSP不再赘述,和播放RTMP一样,只是URL类型不一样,需要注意的是,不管走RTMP还是RTSP,都需要开启回音消除。
技术总结
Android平台一对一互动,纯内网环境下,不部署单独的流媒体服务器,走轻量级RTSP服务真的非常方便,如果需要扩展到公网业务,建议可以考虑RTMP,如果有很好的开发能力,也可以考虑WebRTC,具体根据实际场景选择即可。
相关文章:

Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP
一对一音视频通话使用场景 一对一音视频通话都需要稳定、清晰和流畅,以确保良好的用户体验,常用的使用场景如下: 社交应用:社交应用是一种常见的使用场景,用户可以通过音视频通话进行面对面的交流;在线教…...
--binlog-row-event-max-size
--binlog-row-event-max-size MySQL中用于控制rows格式的Binlog,binlog以chunk的方式存储,每个chunk的大小由binlog-row-event-max-size 进行控制; 如果event比较大的时候可以调大这个值;;改值必须是256的倍数&#…...

Jmeter命令行运行实例讲解
1. 简介 使用非 GUI 模式,即命令行模式运行 JMeter 测试脚本能够大大缩减所需要的系统资 本文介绍windows下以命令行模式运行的方法。 1.1. 命令介绍 jmeter -n -t <testplan filename> -l <listener filename> 示例: jmeter -n -t test…...
pl/sql函数如何返回多行数据?在线等......
编辑csm8109022010-01-27 09:59:18 这个问题我以前问过类似的,但一直没得到如意的答案!在oracle 里soctt的用户下的emp表,比如写一个函数,传入的参数为部门编号,然后返回所有该部门人员信息的函数。要用到游标&…...
Ubuntu Find命令详解
一、Find命令简介 Ubuntu的Find命令是一种常用的终端指令,用于在文件系统中查找符合条件的文件和目录。该命令的语法格式如下: find [PATH] [OPTION] [EXPRESSION]其中,PATH表示待查找的目录,OPTION为选项参数,EXPRES…...
ADS Momentum学习笔记
ADS Momentum的简介 ADS Layout界面仿真采用的方法主要是Momentum(矩量法)。 Momentum的特点 Momentum是高级设计系统(ADS)的重要组成部分,它提供了设计现代通信系统的电磁仿真。它可以用来计算一般平面电路的S参数…...

解决Vue3 使用Element-Plus导航刷新active高亮消失
解决Vue3 使用Element-Plus导航刷新后active高亮消失的问题 启用路由模式会在激活导航时以 index 作为 path 进行路由跳转 使用 default-active 来设置加载时的激活项。 接下来打印一下选中项index和index路径, 刷新也是没有任何问题的,active不会消失…...

K8S系列文章之 一键部署K8S环境
部署的原理是基于自动化部署工具 Ansible 实现的,需要提前安装Ansible 并配置下主机节点环境 1. 安装 Ansible 首先ansible基于python2.X 环境,默认centos都已经安装好了python2环境 // 最好更新下库 // yum update yum install -y epel-release yum i…...

Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本
Spring Boot、Spring Cloud、Spring Alibaba 版本对照关系及稳定兼容版本 引言 在 Java 生态系统中,Spring Boot、Spring Cloud 和 Spring Alibaba 是非常流行的框架,它们提供了丰富的功能和优雅的解决方案。然而,随着不断的发展和更新&…...

虫情监测仪介绍—技术原理、功能优势是什么?
KH-CQPest虫情监测仪是做好虫情监测的重要设备,利用虫情监测仪能够对农业大田、智慧温室、林业等场景的害虫分布情况及害虫种类进行监测,协助人们制定合理的防治措施。 1.技术原理: KH-CQPest虫情监测仪采用光学诱虫原理,配合传感…...

HTML5 Canvas和Svg:哪个简单且好用?
HTML5 Canvas 和 SVG 都是基于标准的 HTML5 技术,可用于创建令人惊叹的图形和视觉体验。 首先,让我们花几句话介绍HTML5 Canvas和SVG。 什么是Canvas? Canvas(通过 标签使用)是一个 HTML 元素,用于在用户计算机屏幕…...
ChatGPT在社交媒体聊天和评论分析中的应用如何?
ChatGPT在社交媒体聊天和评论分析中具有广泛的应用前景,可以帮助企业、个人和社会从多个角度更好地理解用户观点、趋势和情感。以下是详细的讨论: **1. 舆情分析与趋势预测:** ChatGPT可以用于分析社交媒体上的评论、帖子和消息,…...

DoIP学习笔记系列:(四)用CAPL脚本读取DID的关键点
文章目录 1. 如何在CAPL中读取DID?1.1 避坑如何新建CAPL工程,在此不再赘述,本章主要分享一下如何在CAPL中调用DoIP接口、diag接口进行DoIP和诊断的测试。 1. 如何在CAPL中读取DID? 通常在实际项目中,会有很多DID,各种版本号、各种观测量,如果手动点,显然很麻烦,如果要…...
chrome插件开发实例06-定制自己的Chrome DevTools调试工具
目录 Chrome DevTools 调试工具 演示 编辑 源码 devtools.html devtools.js panel.html panel.js...

安卓读取,添加,更新,删除联系人,读取短信
目录 读取联系人 添加联系人 更新联系人 删除联系人 读取短信 读取联系人 安卓可以通过contentResolver来读取联系人表,联系人表的Uri信息是:content://com.android.contacts/data/phones 从而输出联系人信息, 需要相关权限:…...
Practices6|69. x 的平方根、(哈希表)205. 同构字符串、(哈希表)1002. 查找共用字符
69. x 的平方根 1.题目: 给你一个非负整数 x ,计算并返回 x 的 算术平方根 。 由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。 注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.…...

Qt扫盲-Model/View入门
Model/View 编程入门 一、概述二、介绍1. 标准部件2. Model/View 控件3. Model/View控件概述4. 在表格单和 model 之间使用适配器 Adapters 三、 简单的 model / view 应用程序示例1. 一个只读表2. 使用role扩展只读示例3. 表格单元中的时钟4. 为列和行设置标题5. 最小编辑示例…...

关于win11 debian wsl 子系统安装启动docker一直starting,无法启动
首先我先说明,我的步骤都是按照官网步骤来的 通过官网的操作步骤 通过测试命令 sudo docker run hello-world得到下面的命令,我们通过启动命令 sudo service docker start 执行结果如下图 也就是说无法启动,一直显示在启动中 遇到这种情况…...

Nginx反向代理配置+负载均衡集群部署
文章目录 负载均衡反向代理基础环境部署:什么是代理实验环境图流量过程 环境部署准备两台Web服务器安装Nginx准备页面内容添加主机名 代理服务器配置 修改windos hosts文件测试:终端浏览器 负载均衡反向代理基础环境部署: 什么是代理 正向代…...

设计模式行为型——迭代器模式
什么是迭代器模式 迭代器模式(Iterator Pattern)属于行为型模式,其提供一种方法顺序访问一个聚合对象中的各种元素,而又不暴露该对象的内部表示,即不需要知道集合对象的底层表示。编程环境中非常常用的设计模式。 迭代…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...

基于PHP的连锁酒店管理系统
有需要请加文章底部Q哦 可远程调试 基于PHP的连锁酒店管理系统 一 介绍 连锁酒店管理系统基于原生PHP开发,数据库mysql,前端bootstrap。系统角色分为用户和管理员。 技术栈 phpmysqlbootstrapphpstudyvscode 二 功能 用户 1 注册/登录/注销 2 个人中…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)
一、OpenBCI_GUI 项目概述 (一)项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台,其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言,首次接触 OpenBCI 设备时,往…...

手机平板能效生态设计指令EU 2023/1670标准解读
手机平板能效生态设计指令EU 2023/1670标准解读 以下是针对欧盟《手机和平板电脑生态设计法规》(EU) 2023/1670 的核心解读,综合法规核心要求、最新修正及企业合规要点: 一、法规背景与目标 生效与强制时间 发布于2023年8月31日(OJ公报&…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
ThreadLocal 源码
ThreadLocal 源码 此类提供线程局部变量。这些变量不同于它们的普通对应物,因为每个访问一个线程局部变量的线程(通过其 get 或 set 方法)都有自己独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,这些类希望将…...