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

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

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

  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,具体根据实际场景选择即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/63454.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

OLAP ModelKit Crack,ADO.NET和IList

OLAP ModelKit Crack,ADO.NET和IList OLAP ModelKit是一个多功能的.NET OLAP组件,用C#编写,只包含100%托管代码。它具有XP主题的外观,并能够使用任何.NET数据源(ADO.NET和IList)。借助任何第三方组件(尤其是图表组件)呈现数据的能力扩展了产品…

Pytorch深度学习-----损失函数(L1Loss、MSELoss、CrossEntropyLoss)

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用(ToTensor,Normalize,Resize ,Co…

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应,按如下方式构造一个HystrixCommand即可&a…

24届近5年江南大学自动化考研院校分析

今天给大家带来的是江南大学控制考研分析 满满干货~还不快快点赞收藏 一、江南大学 学校简介 江南大学(Jiangnan University)是国家“双一流”建设高校,“211工程”、“985工程优势学科创新平台”重点建设高校,入选…

【linux源码学习】【实验篇】使用bochs运行linux0.11系统(搭建一个自己的工作站)

目录 背景资源获取bochs环境搭建windowsbochs环境搭建linux声明 背景 最近看赵炯老师的《linux内核完全注释》,然后在最后一个习题里面看到使用bochs跑一下0.11的内核代码,本来觉得很难,但是如果做过一遍就会发现其实很简单,这个…

在centos7上使用非编译方式安装ffmpeg

很多在centos7上安装ffmpeg的教程都需要使用编译方式的安装;编译时间较长而且需要配置; 后来搜索到可以通过加载rpm 源的方式实现快速便捷操作 第一种方式: 首先需要安装yum源: yum install epel-release yum install -y https://mirrors.…

内网安全-隧道技术SSH实现通信DNS上线与通信CS上线Linux主机

内网安全-隧道技术&SSH实现通信&DNS上线与通信&CS上线Linux主机 一、DNS隧道技术 DNS简介:DNS协议为应用层协议,区域传输时用tcp协议,域名解析时用udp协议 ###通过DNS隧道绕过防火墙,实现CS上线 实验背景&#xff…

在收到满意的大厂offer之前,面试也是至关重要的,那么该如何做好IT类的面试呢?

方向一:分享你面试IT公司的小技巧 沉着冷静应对刁难:应试场上,考官往往会针对求职者的薄弱点提出一些带有挑战性的问题。面对这样的考题,你一定要心平气和,较为委婉地加以反驳和申诉,绝不可情绪激动&#x…

ELK、ELFK日志分析系统

菜单一、ELK简介1.1 ELK组件说明1.1.1 ElasticSearch1.1.2 Kiabana1.1.3 Logstash 1.2 可以添加的其它组件1.2.1 Filebeat1.2.2 缓存/消息队列(redis、kafka、RabbitMQ等)1.2.3 Fluentd 1.3 为什么要用ELK1.4 完整日志系统的基本特征1.5 ELK 的工作原理 …

python+requests+json 接口测试思路示例

实际项目中用python脚本实现接口测试的步骤: 1 发送请求,获取响应 》》2 提取响应里的数据,对数据进行必要的处理 》》3 断言响应数据是否与预期一致 以豆瓣接口为例,做一个简单的接口测试吧。使用到的知识涉及requests库&…

Glass指纹识别工具,多线程Web指纹识别工具-Chunsou

Glass指纹识别工具,多线程Web指纹识别工具-Chunsou。 Glass指纹识别工具 Glass一款针对资产列表的快速指纹识别工具,通过调用Fofa/ZoomEye/Shodan/360等api接口快速查询资产信息并识别重点资产的指纹,也可针对IP/IP段或资产列表进行快速的指…

C语言有关文件的操作

打开文件与关闭文件 在编写代码时,我有一个习惯是“保证一一对应”。 写下代码fopen()之后,还没有写对文件进行增删查改等操作的代码,先立刻写上fclose(),避免忘记关闭FILE* fd的情况。 不关闭fd,在fopen()次数较少的…

怎么在树莓派环境上搭建web网站,并发布到外网可访问,今天教给大家

怎么在树莓派上搭建web网站,并发布到外网可访问? 文章目录 怎么在树莓派上搭建web网站,并发布到外网可访问?概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpo…

【Redis】——AOF持久化

什么是AOF日志 AOF日志是redis为数据的持久化提供了的一个技术,日志里面记录着执行redis写命令。每当redis执行一条写命令的时候,就会将该命令记录 到AOF日志当中。当redis启动的时候,可以加载AOF日志中的所有指令,并执行这些指令恢复所有的…

[国产MCU]-BL602开发实例-定时器

定时器 文章目录 定时器1、BL602定时器介绍2、定时器驱动API介绍3、定时器使用实例3.1 单次计时3.2 持续计时通用定时器,用于定时,当时间到达我们所设置的定时时间会产生定时中断,可以用来完成定时任务。本文将详细介绍如何使用BL602的定时器功能。 1、BL602定时器介绍 BL6…

1、sparkStreaming概述

1、sparkStreaming概述 1.1 SparkStreaming是什么 它是一个可扩展,高吞吐具有容错性的流式计算框架 吞吐量:单位时间内成功传输数据的数量 之前我们接触的spark-core和spark-sql都是处理属于离线批处理任务,数据一般都是在固定位置上&…

解决vite+vue3项目npm装包失败

报错如下: Failed to remove some directories [ npm WARN cleanup [ npm WARN cleanup D:\\V3Work\\v3project\\node_modules\\vue, npm WARN cleanup [Error: EPERM: operation not permitted, rmdir D:\V3Work\v3project\node_modules\vue\reactivity\…

【腾讯云Cloud Studio实战训练营】使用React快速构建点餐H5

文章目录 前言一、Cloud Studio是什么二、Cloud Studio特点三、Cloud Studio使用1.访问官网2.账号注册3.模板选择4.模板初始化5.H5开发安装 antd-mobile安装 Less安装 normalize上传项目需要的素材替换App.js主文件项目启动、展示 6.发布仓库 总结 前言 随着云计算产业的发展&…

数据结构 | 搜索和排序——排序

目录 一、冒泡排序 二、选择排序 三、插入排序 四、希尔排序 五、归并排序 六、快速排序 排序是指将集合中的元素按照某种顺序排序的过程。 一、冒泡排序 冒泡排序多次遍历列表。它比较相邻的元素,将不合顺序的交换。每一轮遍历都将下一个最大值放到正确的位…

合规管理,企业生存之本!这4大方法,助你规避风险

当下,合规管理已成为企业必修的一门学问。无论是上市公司还是民营企业,都面临着日益严苛的监管合规要求。然而,许多企业在在应对频繁更新的合规要求时,仍然手忙脚乱,合规工作参差不齐。 专家分析认为,企业合规困境的主要症结在于,业务运转过程中产生了大量证明文件,但企业对其…