日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

浅析WebRtc中视频数据的接收和渲染流程

發布時間:2024/4/15 编程问答 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 浅析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中视频数据的接收和渲染流程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。