Android WebRTC 1v1通话样例
一、概述
WebRTC是一套音视频实时通讯的解决方案,利用它我们可以很方便的只做出1v1 1v多 多v多的通话应用。如:音视频聊天、视频会议、直播等。而且这玩意完全开源、免费做应用不用担心版权,想要深入学习,直接撸源码。非常nice。今天主要介绍一下单对单视频通话场景,并在最后给出核心样例代码。
二、通话流程
1.在此我默认所有的看客(包括我自己)都已经理解了。sdp、ice、stun、turn。
2.大流程上主要分为三块
1.媒体协商
2.网络协商
3.开始通话
下面用A和B来表示客户端A和客户端B两个人通话。
1.A创建Offer,设置本地媒体描述setLocalDescription,并通过信令将本地媒体描述发送给B
2.B收到A发送的本地媒体描述并将A的本地媒体描述设置为setRemoteDescription。
3.B创建一个Answer,并设置自己的setLocalDescription,并通过信令服务将B的媒体描述发送给A
4.A收到B的本地媒体描述后设置setRemoteDescription。到此A和B已经交换了个字的媒体信息。
5.媒体协商完成A和B都会回调PeerConnection.Observer的onIceCandidate,并通过信令服务器将个字的candidate发送给对方,各自收到后会设置相应的candidate
6.A和B打开各自的媒体流(音频/视频),通过PeerConnection.Observer的回调函addStream接收远程媒体流,并显示。
三、核心源码实现
1.初始化部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | this .context = context; peerConnectionMap = new HashMap<>(); iceServers = new ArrayList<>(); eglBaseContext = EglBase.create().getEglBaseContext(); surfaceTextureHelper = SurfaceTextureHelper.create( "CaptureThread" , eglBaseContext); //初始化PeerConnectionFactory PeerConnectionFactory.initialize(PeerConnectionFactory.InitializationOptions.builder(context).createInitializationOptions()); //创建一个默认的PeerConnectionFactory.Options PeerConnectionFactory.Options options = new PeerConnectionFactory.Options(); //创建默认视频编码器 DefaultVideoEncoderFactory defaultVideoEncoderFactory = new DefaultVideoEncoderFactory(eglBaseContext, true , true ); //创建默认视频解码器 DefaultVideoDecoderFactory defaultVideoDecoderFactory = new DefaultVideoDecoderFactory(eglBaseContext); //构建PeerConnectionFactory peerConnectionFactory = PeerConnectionFactory.builder().setOptions(options) .setVideoEncoderFactory(defaultVideoEncoderFactory) .setVideoDecoderFactory(defaultVideoDecoderFactory) .setAudioDeviceModule(JavaAudioDeviceModule.builder(context).createAudioDeviceModule()) //设置音频设备 .createPeerConnectionFactory(); setIceServers(Config.IP.STUN); mediaStream = peerConnectionFactory.createLocalMediaStream( "ARDAMS" ); //初始化信令服务 SignalingClient.get().init( this ); |
2.开启本地视频预览和开启音频并将本地的音视频加入媒体流
1 2 | rtcClient.startVideoPreview( this , true , surfaceViewRenderer); rtcClient.startLocalAudio(); |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /** * 预览视频 * * @param context 上下文环境 * @param isFront true 前置摄像头 false后置摄像头 * @param localViewRender 用来渲染视频的View */ public void startVideoPreview(Context context, boolean isFront, SurfaceViewRenderer localViewRender) { Log.e(TAG, "startVideoPreview----->start" ); //添加视频预览 if (videoCapturer == null ) { videoCapturer = createVideoCapture(isFront, context); } if (videoSource == null ) { Log.e(TAG, "startVideoPreview----->createVideoSource" ); videoSource = createVideoSource(videoCapturer); } Log.e(TAG, "startVideoPreview----->startCapture" ); videoCapturer.initialize(surfaceTextureHelper, context, videoSource.getCapturerObserver()); videoCapturer.startCapture( 480 , 640 , 30 ); if (videoTrack == null ) { Log.e(TAG, "startVideoPreview----->createVideoTrack" ); videoTrack = createVideoTrack(videoSource); } addLocalVideoSink(localViewRender); //将视频加入媒体流 Log.e(TAG, "startVideoPreview----->createMediaStream" ); mediaStream = createMediaStream(); mediaStream.addTrack(videoTrack); Log.e(TAG, "startVideoPreview----->end" ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | /** * 将音频加入媒体流 */ public void startLocalAudio() { if (audioSource == null ) { Log.e(TAG, "startAudio----->createAudioSource" ); audioSource = createAudioSource(); } if (audioTrack == null ) { Log.e(TAG, "startAudio----->createAudioTrack" ); audioTrack = createAudioTrack(); } //将音频加入媒体流 Log.e(TAG, "startAudio----->addTrack(audioTrack)" ); mediaStream = createMediaStream(); mediaStream.addTrack(audioTrack); } |
3.展示远程音视频
1 2 3 4 5 6 7 8 9 10 | @Override public void onUserVideoAvailable(String userId, boolean available, VideoTrack videoTrack) { if (available) { remoteVideoTrack = videoTrack; if (remoteVideoTrack != null ) { videoTrack.addSink(remoteSurfaceViewRender); } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | @Override public void onAddStream(MediaStream mediaStream) { super .onAddStream(mediaStream); Log.e(TAG, "PeerConnectionAdapter----->onAddStream" ); if (listener != null ) { //远程视频 List<VideoTrack> remoteVideoTracks = mediaStream.videoTracks; if (remoteVideoTracks != null && remoteVideoTracks.size() > 0 ) { listener.onUserVideoAvailable(key, true , remoteVideoTracks.get( 0 )); } List<AudioTrack> remoteAudioTracks = mediaStream.audioTracks; if (remoteAudioTracks != null && remoteAudioTracks.size() > 0 ) { listener.onUserAudioAvailable(key, true , remoteAudioTracks.get( 0 )); } } } |
ps:由于这并不是一个简单的demo,都是经过封装的,所以一块都贴出来代码量太大,等后面会把响应的源代码上传的github上,有需要的可以直接下载
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
2013-08-12 Android Des加密解密
2013-08-12 Android MD5算法