Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

2023-11-13

一对一音视频通话使用场景

一对一音视频通话都需要稳定、清晰和流畅,以确保良好的用户体验,常用的使用场景如下:

  1. 社交应用:社交应用是一种常见的使用场景,用户可以通过音视频通话进行面对面的交流;
  2. 在线教育:老师和学生可以通过音视频通话功能进行实时互动,提高教学效率;
  3. 远程协助:在某些工作场景下,比如应急指挥项目,需要通过音视频通话功能进行远程协助,进行技术支持、维修服务等;
  4. 视频会议:一对一的音视频通话是视频会议非常重要的一部分,用于两个参会者之间的沟通,当然也可以合流输出;
  5. 语音通话:使用语音通话,如在行车过程中,此时语音通话就是一个很好的选择。

一对一音视频通话技术方案

WebRTC方案

在Android平台上实现一对一音视频通话,你可以使用WebRTC,WebRTC提供了实时音视频通话的功能。以下是一个简单的步骤说明如何实现:

  1. 设置环境:首先,你需要在你的开发环境中安装Android Studio,并且配置好必要的SDK;
  2. 添加依赖:在你的项目中,你需要添加WebRTC的库。在你的build.gradle文件中添加如下依赖;
  3. 实现音视频捕获:你需要实现音视频的捕获。在Java中,你可以使用AudioRecord和VideoCapturer类来实现;
  4. 创建PeerConnection:创建PeerConnection对象,这个对象会用于音视频的编解码和网络传输;
  5. 显示本地音视频流:使用MediaStream.VideoTrack和MediaStream.AudioTrack将捕获的音视频流添加到PeerConnection中,然后通过VideoRenderer和AudioRenderer显示出来;
  6. 创建并发送offer:创建并发送一个offer,这个offer包含了你的音视频通道信息以及你愿意接受的连接参数;
  7. 接收并解析offer:在另一端,接收到offer后,解析出音视频通道信息以及连接参数,然后创建并返回一个answer;
  8. 接收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,具体根据实际场景选择即可。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP 的相关文章

随机推荐

  • Spring aop报错:com.sun.proxy.$Proxyxxx cannot be cast to yyy

    在使用Spring AOP时 遇到如下的错误 Exception in thread main java lang ClassCastException com sun proxy Proxy0 cannot be cast to com
  • Linux内核学习:I2C_SLAVE_FORCE

    在Linux内核源代码include linux i2c dev h文件内 有如下定义 define I2C SLAVE 0x0703 Use this slave address define I2C SLAVE FORCE 0x0706
  • 立体电影

    立体电影 百科名片 1953年5月24日立体电影首次出现 为了把观众从电视夺回来 好莱坞推出了一种新玩艺儿 立体电影 戴着特殊眼镜的观众像在观看 布瓦那魔鬼 及 蜡屋 这类惊险片那样 发现自己躲在逃跑的火车及魔鬼的后面 从而为我们带入了立体
  • weex-26-dom模块

    D0BE7A90 F94A 4C9A 98D6 1EE3D44C1E5E png 本节学习目标 dom 滚动到指定组件 dom 获取组件的布局信息 我们经常会看到上图所示的需求 当我们将列表向下滑动一段时间后 想要立刻回到顶部 这个时候就要
  • qt中bug总结:遇到C1071:在注释中遇到意外的文件结束

    qt中编译时突然出现这个问题 然后就是编译不过去 百度了好多之后 最后就是在报错的cpp文件中进行查找 找到这个 的注释格式 发现问题就是它导致的 去掉这种注释之后 则发现没问题了
  • 第十三届蓝桥杯省赛JavaA组-试题D-GCD

    题目 代码 import java util Scanner public class lqb13 A组04GCD public static void main String args 希望gcd a k b k 尽可能的大 而k尽可能的
  • 那些从黑马毕业的学生,都去哪工作了?

    我们对这个世界的认知 常常受到身边人的影响 你是否听到过这样的话 房子越来越买不起了 结婚彩礼真的是越来越贵了 找一份不错的工作真是越来越难了 这些看法 让你对生活 对工作产生畏惧 但是 你身边一定也有这样的人 30出头已经买房买车了 刚毕
  • 汽车加油问题【贪心算法】

    1 原版 Problem Description 一辆汽车加满油后可行驶n公里 旅途中有若干个加油站 设计一个有效算法 指出应在哪些加油站停靠加油 使沿途加油次数最少 并证明算法能产生一个最优解 对于给定的n和k个加油站位置 计算最少加油次
  • 计算机房房间要求,数据中心机房的标准规范

    数据中心机房的标准规范 工程中的数据中心机房建设是保证计算机网络设备和各级劳动保障系统正常运转的关键 现在的计算机设备对运行环境要求较高 因此 必须按照一定的标准规范 科学地设计机房 一 机房建设需要执行的标准 主机房建设工程必须遵循国家机
  • Echarts+Python让你的数据可视化(文末有完整源码)

    用一个小例子 一个小程序带你了解pyecharts的基础用法 什么是Echarts Echarts介绍 代码演示 支持自定义主题 使用Echarts 安装pyecharts库 使用pyecharts制作柱形图 Echarts Python制
  • Java static 代码块测试

    Java 中代码执行顺序 类加载 gt 执行引擎 类加载 执行静态代码块 今天成员初始化 执行引擎 执行成员变量初始化 然后构造函数 package basic class Test2 Person2 person new Person2
  • 全局处理Long类型字段返回前端精度丢失

    项目场景 后端返回一个列表数据到前端 其中有部分字段类型后端定义的是Long类型并且是用雪花算法生成的 从响应数据中发现返回的值与数据库的不一致 丢失了精度 问题描述 后端使用MybatisPlus时 主键字段的主键策略用是 TableId
  • ABAP DOI技术中I_OI_SPREADSHEET接口的使用

    前言部分 大家可以关注我的公众号 公众号里的排版更好 阅读更舒适 正文部分 在DOI技术中 I OI SPREADSHEET接口有很多对excel的操作方法 举个例子 CELL FORMAT方法 这个方法里面就有参数ALIGN 可以去覆盖e
  • antd pro(ProLayout) mix混合菜单不生效

    一 问题描述 antd pro的混合菜单模式 算是一种比较新的导航菜单模式 可以让顶部全局导航 侧边导航混合模式同时出现 满足一些特别的需求 ProLayout高级布局组件的API里有一个layout参数 可以设置layout的菜单模式 我
  • nodejs-处理http请求

    文章目录 前言 node 处理 get 请求 node 处理 post 请求 总结 前言 使用nodejs搭建后端代理服务 处理http请求 理解nodejs是如何处理get post请求的 node 处理 get 请求 使用 http 模
  • 时序预测

    时序预测 MATLAB实现SO CNN BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 目录 时序预测 MATLAB实现SO CNN BiLSTM蛇群算法优化卷积双向长短期记忆神经网络时间序列预测 预测效果 基本介绍 程序设
  • uniapp实现逆解析地址(经纬度换具体地址)

    调用高德地图的sdk ifndef H5 this qqmapsdk reverseGeocoder get poi 1 poi options address format short policy 1 radius 3000 page
  • 验证网站列表,持续更新中...

    verificationacademy com verificationguide com chipverify com https www runoob com w3cnote verilog2 sdf html https www th
  • Altium Designer常用快捷键

    1 shift s 切换单层显示 2 q 英寸和毫米 尺寸切换 3 D R 进入布线规则设置 其中 Clearance 是设置最小安全线间距 覆铜时候间距 比较常用 4 CTRL 鼠标单击某个线 整个线的NET 网络 呈现高亮状态 5 小键
  • Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

    一对一音视频通话使用场景 一对一音视频通话都需要稳定 清晰和流畅 以确保良好的用户体验 常用的使用场景如下 社交应用 社交应用是一种常见的使用场景 用户可以通过音视频通话进行面对面的交流 在线教育 老师和学生可以通过音视频通话功能进行实时互