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

简介: 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,互动可以很轻松的在毫秒级。

smartechocancellation.jpg

以大牛直播SDK的demo为例,RTMP推送的代码如下:

classButtonPushStartListenerimplementsOnClickListener    {
publicvoidonClick(Viewv)
        {    
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..");
            }
intstartRet=libPublisher.SmartPublisherStartPublisher(publisherHandle);
if (startRet!=0) {
isPushingRtmp=false;
Log.e(TAG, "Failed to start push stream..");
return;
            }
CheckInitAudioRecorder();
btnPushStartStop.setText("停止推送 ");
isPushingRtmp=true;
    };

停止RTMP推送:

//停止rtmp推送privatevoidstopPush() {
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(newButton.OnClickListener() 
        {  
//  @Override  publicvoidonClick(Viewv) {  
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,
newEventHandePlayerV2());
libPlayer.SmartPlayerSetSurface(playerHandle, playerSurfaceView);   //if set the second param with null, it means it will playback audio only..libPlayer.SmartPlayerSetRenderScaleMode(playerHandle, 1);
libPlayer.SmartPlayerSetExternalAudioOutput(playerHandle, newPlayerExternalPcmOutput());
libPlayer.SmartPlayerSetAudioOutputType(playerHandle, 1);
libPlayer.SmartPlayerSetBuffer(playerHandle, playbackBuffer);
libPlayer.SmartPlayerSetFastStartup(playerHandle, isPlaybackFastStartup?1:0);
if ( isPlaybackMute )
                      {
libPlayer.SmartPlayerSetMute(playerHandle, isPlaybackMute?1:0);
                      }
if (isPlaybackHardwareDecoder) {
intisSupportHevcHwDecoder=libPlayer.SetSmartPlayerVideoHevcHWDecoder(playerHandle,1);
intisSupportH264HwDecoder=libPlayer                        .SetSmartPlayerVideoHWDecoder(playerHandle,1);
Log.i(TAG, "isSupportH264HwDecoder: "+isSupportH264HwDecoder+", isSupportHevcHwDecoder: "+isSupportHevcHwDecoder);
                  }
libPlayer.SmartPlayerSetAudioVolume(playerHandle, curAudioVolume);
libPlayer.SmartPlayerSetUrl(playerHandle, playbackUrl);
intiPlaybackRet=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服务classButtonRtspServiceListenerimplementsOnClickListener {
publicvoidonClick(Viewv) {
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 {
intport=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流classButtonRtspPublisherListenerimplementsOnClickListener {
publicvoidonClick(Viewv) {
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;
            }
Stringrtsp_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会话数弹出框privatevoidPopRtspSessionNumberDialog(intsession_numbers) {
finalEditTextinputUrlTxt=newEditText(this);
inputUrlTxt.setFocusable(true);
inputUrlTxt.setEnabled(false);
Stringsession_numbers_tag="RTSP服务当前客户会话数: "+session_numbers;
inputUrlTxt.setText(session_numbers_tag);
AlertDialog.BuilderbuilderUrl=newAlertDialog.Builder(this);
builderUrl                .setTitle("内置RTSP服务")
                .setView(inputUrlTxt).setNegativeButton("确定", null);
builderUrl.show();
    }
//获取RTSP会话数classButtonGetRtspSessionNumbersListenerimplementsOnClickListener {
publicvoidonClick(Viewv) {
if (libPublisher!=null&&rtsp_handle_!=0) {
intsession_numbers=libPublisher.GetRtspServerClientSessionNumbers(rtsp_handle_);
Log.i(TAG, "GetRtspSessionNumbers: "+session_numbers);
PopRtspSessionNumberDialog(session_numbers);
            }
        }
    }

播放RTSP不再赘述,和播放RTMP一样,只是URL类型不一样,需要注意的是,不管走RTMP还是RTSP,都需要开启回音消除。

技术总结

Android平台一对一互动,纯内网环境下,不部署单独的流媒体服务器,走轻量级RTSP服务真的非常方便,如果需要扩展到公网业务,建议可以考虑RTMP,如果有很好的开发能力,也可以考虑WebRTC,具体根据实际场景选择即可。

相关文章
|
2月前
|
Java Android开发 Swift
安卓与iOS开发对比:平台选择对项目成功的影响
【10月更文挑战第4天】在移动应用开发的世界中,选择合适的平台是至关重要的。本文将深入探讨安卓和iOS两大主流平台的开发环境、用户基础、市场份额和开发成本等方面的差异,并分析这些差异如何影响项目的最终成果。通过比较这两个平台的优势与挑战,开发者可以更好地决定哪个平台更适合他们的项目需求。
118 1
|
3月前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
2月前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
97 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
Web App开发 Java Android开发
单独编译使用WebRTC的音频处理模块 - android
VAD的使用和NS区别不大,唯一需要注意的是VAD仅仅只是检测,返回结果1表示VAD检测此帧为活动帧,0表示此帧为静音帧,至于判断为静音后该进行何种处理,就和你自己的项目相关了。
6028 0
|
Web App开发 算法 API
【单独编译使用WebRTC的音频处理模块 - android】
更新 【2015年2月15日】     Bill 这段时间没有再关注 WebRTC 以及音频处理的相关信息,且我个人早已不再推荐单独编译 WebRTC 中的各个模块出来使用。实际上本文的参考价值已经很小了,甚至可能会产生误导。
2459 0
|
10天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台策略
在移动应用开发的战场上,安卓和iOS两大阵营各据一方。随着技术的演进,跨平台开发框架成为开发者的新宠,旨在实现一次编码、多平台部署的梦想。本文将探讨跨平台开发的优势与挑战,并分享实用的开发技巧,帮助开发者在安卓和iOS的世界中游刃有余。
|
15天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
1天前
|
Java 调度 Android开发
安卓与iOS开发中的线程管理差异解析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自拥有独特的魅力。如同东西方文化的差异,它们在处理多线程任务时也展现出不同的哲学。本文将带你穿梭于这两个平台之间,比较它们在线程管理上的核心理念、实现方式及性能考量,助你成为跨平台的编程高手。
|
17天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
19天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。