webrtc android 视频帧 VideoFrame 的理解 ----- 第一篇

为什么要这这里说一下VideoFrame呢?

在上一章节,摄像头预览数据显示全过程

我们分析了程序框架中,获取摄像头预览的java层类之间的从属关系.

但是如果我们无法把其中的细节整理明白的话,那么其实我们压根就不会明白,作者程序为何这么设计?这些代码这么写是为了解决什么问题?

所以我们要来分析下VideoFrame。

在SurfaceTextureHelper中,我们需要对获取到了纹理更新通知后,封装一个VideoFrame.

final VideoFrame.Buffer buffer = createTextureBuffer(textureWidth, textureHeight,
          RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix));

      final VideoFrame frame = new VideoFrame(buffer, frameRotation, timestampNs);
      ((VideoSink) listener).onFrame(frame);

在封装之前,有2个概念我们需要整理明白:

问题一:

camera出来的数据其实是是旋转的,比如我们的手机屏幕是720*1280,但是预览的数据其实是1280*720 ,也就是经过了旋转的数据.

float[] transformMatrix = new float [16];
            surfaceTexture.getTransformMatrix(transformMatrix);

所以我们在用opengl的shader去取纹理坐标的时候,需要乘以这个transformMatrix.

但是大家发现问题没?

opengl的纹理坐标是 vec2,但是getTransformMatrix获取到的是一个 4*4的矩阵,所以中间设计到很多转换.

RendererCommon.convertMatrixToAndroidGraphicsMatrix(transformMatrix)

有一篇文章,我个人认为写的是非常的不错:
https://blog.csdn.net/gb702250823/article/details/53526149

大家可以参考下.

问题二 :
时间的统一.

//相对时间

            //单位为纳秒:
            // 一秒的10亿分之一
            long timestamp = surfaceTexture.getTimestamp();
            if( timestamp != 0 && firstTime )
            {
                ts = tn = timestamp;
                firstTime = false;
            }
            tn = timestamp;
            //转为毫秒
            Log.e(TAG,"timestamp:"+ (tn - ts) / 1000000.0f );
            ts = tn;

surfaceTexture.getTimestamp采用的是相对时间统计方法,最开始的时间是怎么计算,不是我们研究的重点,但是因为涉及到p2p的通信,所以时间上必须进行统一,这点后面我们会进行说明.

时间统一后,就设计到一些丢帧和策略的选择,比如是保证稳定性还是画面质量什么的.

理解了这些,后面才能对作者的代码进行更深层次的剖析.

我自己单独写了一个demo,可以很方便的让大家看到上面的情况,方便大家分析.

https://download.csdn.net/download/zhangkai19890929/10642192

(注意代码就是一个玩具,仅供参考!)

请大家参考下官方对文档说明:
http://developer.android.com/reference/android/hardware/Camera.html#setDisplayOrientation(int)

如果我们对显示方式

        //we set preview data to
        try {
            SurfaceHolder holder =  this.surfaceView.getHolder();
            holder.addCallback(this);
            camera.setPreviewDisplay(holder);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //get camera config
        android.hardware.Camera.CameraInfo info  =
                new android.hardware.Camera.CameraInfo();

        android.hardware.Camera.getCameraInfo(android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT,info);

        //屏幕的旋转方向:竖屏 0度 竖屏有顺时针90度, 180270度等
        int rotation = this.getWindowManager().getDefaultDisplay().getRotation();

        //我们着重分析竖屏90度方向下度视频
        int degrees = 0;
        switch(rotation)
        {
            case Surface.ROTATION_0: degrees = 0 ;break;
            case Surface.ROTATION_90: degrees = 90; break;
            case Surface.ROTATION_180: degrees = 180; break;
            case Surface.ROTATION_270: degrees = 270; break;
        }

        int result;
        if (info.facing == android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT) {
            result = (info.orientation + degrees) % 360;
            result = (360 - result) % 360;  // compensate the mirror
        } else {  // back-facing
            result = (info.orientation - degrees + 360) % 360;
        }
        camera.setDisplayOrientation(result);

如果我们按照上面对显示方式,那么setDisplayOrientation会直接会显示到surfaceholder上对图像产生直接影响.

但是camera.setDisplayOrientation(result)对于绑定对纹理数据是没有影响的.

下面我们看下相关对类图:

VideoFrame类结构图

这里运用了object-c中对引用计数器,主要是用在多线程对问题中。

比如摄像头对数据,我们除了要运用在屏幕显示上,还需要运用到rtp发送线程中,因此,什么时候对内存进行释放就是一个考验对地方了,采用引用计数器的方案可以完美解决,引用计数器本质上是通过原子操作来完成的.

版权声明:本文为zhangkai19890929原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhangkai19890929/article/details/82344751