浅析WebRtc中视频数据的接收和渲染流程
前言
本文基于PineAppRtc开源项目https://github.com/thfhongfeng/PineAppRtc
因为一个需求,我们需要将WebRtc发送过来的视频流中转出去,所以就研究一下WebRtc是如何接收视频数据并进行处理渲染的,于是有了这篇文章。
数据接收
在使用webrtc进行即时通话时,双方连接上后,会根据参数创建一个PeerConnection
连接对象,具体代码在PeerConnectionClient
类中,这个是需要自己来实现的。这个连接的作用来进行推拉流的。
我们在PeerConnectionClient
中可以找到PCObserver
,它实现了PeerConnection.Observer
这个接口。在它的onAddStream
回调中
if (stream.videoTracks.size() == 1) {mRemoteVideoTrack = stream.videoTracks.get(0);mRemoteVideoTrack.setEnabled(mRenderVideo);for (VideoRenderer.Callbacks remoteRender : mRemoteRenders) {mRemoteVideoTrack.addRenderer(new VideoRenderer(remoteRender));}
}
可以看到为remoteVideoTrack
添加了VideoRenderer
,这个VideoRenderer
就是处理接受到的视频数据的
VideoRenderer
的构造函数中传入的是VideoRenderer.Callbacks
,它是一个接口,我们以其中一个实现SurfaceViewRenderer
为例,它的回调函数renderFrame
代码如下
public void renderFrame(I420Frame frame) {this.updateFrameDimensionsAndReportEvents(frame);this.eglRenderer.renderFrame(frame);
}
这个I420Frame就是封装后的接收到的视频数据。
绘制
在renderFrame
中执行了eglRenderer.renderFrame
开始进行绘制
public void renderFrame(I420Frame frame) {...synchronized(this.handlerLock) {...synchronized(this.frameLock) {...this.pendingFrame = frame;this.renderThreadHandler.post(this.renderFrameRunnable);}}...
}
将frame
赋值给pendingFrame
,然后post一个runnable
,这个runnable
代码如下
private final Runnable renderFrameRunnable = new Runnable() {public void run() {EglRenderer.this.renderFrameOnRenderThread();}};
可以看到执行了renderFrameOnRenderThread
函数:
private void renderFrameOnRenderThread() {Object var2 = this.frameLock;I420Frame frame;synchronized(this.frameLock) {...frame = this.pendingFrame;this.pendingFrame = null;}if (this.eglBase != null && this.eglBase.hasSurface()) {...int[] yuvTextures = shouldUploadYuvTextures ? this.yuvUploader.uploadYuvData(frame.width, frame.height, frame.yuvStrides, frame.yuvPlanes) : null;if (shouldRenderFrame) {GLES20.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);GLES20.glClear(16384);if (frame.yuvFrame) {this.drawer.drawYuv(yuvTextures, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, this.eglBase.surfaceWidth(), this.eglBase.surfaceHeight());} else {this.drawer.drawOes(frame.textureId, drawMatrix, drawnFrameWidth, drawnFrameHeight, 0, 0, this.eglBase.surfaceWidth(), this.eglBase.surfaceHeight());}...}this.notifyCallbacks(frame, yuvTextures, texMatrix, shouldRenderFrame);VideoRenderer.renderFrameDone(frame);} else {this.logD("Dropping frame - No surface");VideoRenderer.renderFrameDone(frame);}}
将I420Frame加载成int[]
,然后通过drawer
的对应的drawXxx
函数进行绘制.
拦截处理
所以我们如果要自己处理接收的数据,就需要自行实现一个VideoRenderer.Callbacks
,将其封装到VideoRenderer
中并add到mRemoteVideoTrack
上。
那么还有一个问题,I420Frame如何转成原生数据呢?
我发现VideoRenderer.Callbacks
的另外一个实现VideoFileRenderer
。如果要写入文件,一定会以原生数据的形式写入的,它的部分代码
public void renderFrame(final I420Frame frame) {this.renderThreadHandler.post(new Runnable() {public void run() {VideoFileRenderer.this.renderFrameOnRenderThread(frame);}});
}private void renderFrameOnRenderThread(I420Frame frame) {float frameAspectRatio = (float)frame.rotatedWidth() / (float)frame.rotatedHeight();float[] rotatedSamplingMatrix = RendererCommon.rotateTextureMatrix(frame.samplingMatrix, (float)frame.rotationDegree);float[] layoutMatrix = RendererCommon.getLayoutMatrix(false, frameAspectRatio, (float)this.outputFileWidth / (float)this.outputFileHeight);float[] texMatrix = RendererCommon.multiplyMatrices(rotatedSamplingMatrix, layoutMatrix);try {ByteBuffer buffer = nativeCreateNativeByteBuffer(this.outputFrameSize);if (frame.yuvFrame) {nativeI420Scale(frame.yuvPlanes[0], frame.yuvStrides[0], frame.yuvPlanes[1], frame.yuvStrides[1], frame.yuvPlanes[2], frame.yuvStrides[2], frame.width, frame.height, this.outputFrameBuffer, this.outputFileWidth, this.outputFileHeight);buffer.put(this.outputFrameBuffer.array(), this.outputFrameBuffer.arrayOffset(), this.outputFrameSize);} else {this.yuvConverter.convert(this.outputFrameBuffer, this.outputFileWidth, this.outputFileHeight, this.outputFileWidth, frame.textureId, texMatrix);int stride = this.outputFileWidth;byte[] data = this.outputFrameBuffer.array();int offset = this.outputFrameBuffer.arrayOffset();buffer.put(data, offset, this.outputFileWidth * this.outputFileHeight);int r;for(r = this.outputFileHeight; r < this.outputFileHeight * 3 / 2; ++r) {buffer.put(data, offset + r * stride, stride / 2);}for(r = this.outputFileHeight; r < this.outputFileHeight * 3 / 2; ++r) {buffer.put(data, offset + r * stride + stride / 2, stride / 2);}}buffer.rewind();this.rawFrames.add(buffer);} finally {VideoRenderer.renderFrameDone(frame);}}
可以看到得到的是I420Frame
类,这个类里封装里视频数据,是i420格式的,且Y、U、V分别存储,可以看到yuvPlanes
是一个ByteBuffer[]
,yuvPlanes[0]
是Y,yuvPlanes[1]
是U,yuvPlanes[2]
是V
这些数据我们可能无法直接使用,所以需要进行转换,比如转成NV21格式。
我们知道NV21是YYYYVUVU这种格式,所以可以通过下面这个方法可以将其转成NV21格式的byte数组
public static byte[] convertLineByLine(org.webrtc.VideoRenderer.I420Frame src) {byte[] bytes = new byte[src.width*src.height*3/2];int i=0;for (int row=0; row<src.height; row++) {for (int col=0; col<src.width; col++) {bytes[i++] = src.yuvPlanes[0].get(col+row*src.yuvStrides[0]);}}for (int row=0; row<src.height/2; row++) {for (int col=0; col<src.width/2; col++) {bytes[i++] = src.yuvPlanes[2].get(col+row*src.yuvStrides[2]);bytes[i++] = src.yuvPlanes[1].get(col+row*src.yuvStrides[1]);}}return bytes;
}
总结
通过分析可以发现,在WebRtc中传输视频数据的时候用的是i420格式的,当然采集发送时候这个库在底层自动将原始数据转成i420格式;但是接收的数据则不同。如果我们要拿到这些数据进行处理,就需要我们自己进行转码,转到通用的格式后再处理。
关注公众号:BennuCTech,发送“电子书”获取经典学习资料。
浅析WebRtc中视频数据的接收和渲染流程相关推荐
- 浅析WebRtc中视频数据的收集和发送流程
前言 本文是基于PineAppRtc开源项目https://github.com/thfhongfeng/PineAppRtc 因为一个需求,我们需要将一个视频流通过WebRtc发送出去,所以就研究一 ...
- 网卡驱动和队列层中的数据包接收
一.从网卡说起 这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析.这里以Intel的e100驱动为例进行分析. 大多数网卡都是一个PCI设备,PCI设备都包含了一个标准 ...
- 使用Jrtplib实现RTP视频数据发送接收
在网上下载获取Jrtplib包的源码,解压缩配置编译安装以后就可以使用,下载的代码包里面有example代码示例: 示例都是基于Jrtplib的RTP/RTCP协议栈实现,在设置Server端的时候, ...
- C#中POST数据和接收的几种方式
POST方式提交数据,一种众所周知的方式: html页面中使用form表单提交,接收方式,使用Request.Form[""]或Request.QueryString[" ...
- linux如何使用鼠标数据的,浅析linux中鼠标数据读取
001 #include 002 #include 003 #include 004 #include 005 #include 006 // 以下代码源自 007 // [需要在文本控制台下运行,才 ...
- 浅析 Hadoop 中的数据倾斜
最近几次被问到关于数据倾斜的问题,这里找了些资料也结合一些自己的理解. 在并行计算中我们总希望分配的每一个task 都能以差不多的粒度来切分并且完成时间相差不大,但是集群中可能硬件不同,应用的类型不同 ...
- WebRTC视频数据流程分析
本文来自<WebRTC Native开发实战>书籍作者许建林在LiveVideoStack线上分享中的内容,详细分析总结 WebRTC 的视频数据流程,并对大型项目如何快速上手:分析方法, ...
- 多路视频数据实时采集系统设计与实现
多路视频数据实时采集系统设计与实现 常永亮 王霖萱 常馨蓉 摘要 面对越来越多的实时视频采集.播放的应用,如何能更加方便的操控视频采集,保证流畅的播放效果,成为近几年实时媒体流的一个重要研究方向 ...
- 在React中获取数据
React初学者经常从不需要获取数据的应用开始.他们经常面临一个计数器,任务列表获取井字棋游戏应用.这是很好的,因为在开始学习React的时候,数据获取在你的应用中添加了另一层复杂度. 然而,有些时候 ...
最新文章
- excel模糊匹配两列文字_高效便捷的Word、Excel操作技巧
- 数据段、数据报、数据包、帧的区别与联系
- 怎么用class引入svg_【蓝湖指北】走向设计巅峰,从蓝湖 Sketch 插件开始,用它!...
- “生骨肉”概念加速奔跑,宠物行业真要起飞了?
- 重磅!深入调研证券行业,神策数据发布《财富管理数字化转型现状与趋势洞察》报告...
- phpbreak跳出几层循环_PHP跳出循环之“break”
- P1197-星球大战【并查集,图论】
- BZOJ-2005能量采集-数论函数
- 微服务、Kubernetes和无服务器之后,即将发生的……
- 信息学奥赛一本通 2040:【例5.7】筛选法找质数 (普通筛 线性筛)
- 二分法解决力扣374.猜数字大小 C语言
- 出现ESXi系统无法连接FreeNAS的情况?90%以上的人都做错了!
- react 创建组件
- 阵列信号处理笔记-阵列信号处理基础
- 听音乐用什么蓝牙耳机好?音质好的tws蓝牙耳机推荐
- C语言--确定到底谁是凶手
- 最详细的SQL注入语句相关的命令整理
- 一个女孩子居然做了十年硬件。​。。
- js实现web网页版台球游戏
- css变成块级元素_设置标签的css样式代码为“display:block”,标签将变为块级元素。( )...
热门文章
- android 开发时遇到的环境问题3--eclipse整个项目工程报错
- Installshield获取安装包版本的系统变量是IFX_PRODUCT_VERSION
- 转载 cFos vs cFosSpeed
- vue项目实现按需加载的3种方式
- 二柱子2.0编程总结
- 【转】Oracle执行计划解释
- 用JavaScript实现100以内自然数求和
- Trie实现(C++)
- request,response,session,application,out对象的常用调用的函数
- WebGL three.js学习笔记 6种类型的纹理介绍及应用