NDK35_RTMP:NV21转换I420

NDK开发汇总

一 NV21转换I420

​ Android Camera对象通过setPreviewCallback 函数,在onPreviewFrame(byte[] data,Camera camera)中回调采集的数据就是NV21格式。而x264编码的输入数据却为I420格式。
因此,当我们采集到摄像头数据之后需要将NV21转为I420。

NV21和I420都是属于YUV420格式。而NV21是一种two-plane模式,即Y和UV分为两个Plane(平面),但是UV(CbCr)交错存储,2个平面,而不是分为三个。这种排列方式被称之为YUV420SP,而I420则称之为YUV420P。(Y:明亮度、灰度,UV:色度、饱和度)
下图是大小为4x4的NV21数据:Y1、Y2、Y5、Y6共用V1与U1,…

在这里插入图片描述

而I420则是
在这里插入图片描述

可以看出无论是哪种排列方式,YUV420的数据量都为:

w*h+w/2*h/2+w/2*h/2 即为w*h*3/2

将NV21转位I420则为:

Y数据按顺序完整复制,U数据则是从整个Y数据之后加一个字节再每隔一个字节取一次。

二 图像数据旋转

手机摄像头的图像数据来源于摄像头硬件的图像传感器,这个图像传感器被固定到手机上后会有一个默认的取景方向,这个取景方向坐标原点于手机横放时的左上角。当应用是横屏时候:图像传感器方向与屏幕自然方向原点一致。而当手机为竖屏时:

在这里插入图片描述

传感器与屏幕自然方向不一致,将图像传感器的坐标系逆时针旋转90度,才能显示到屏幕的坐标系上。所以看到的画面是逆时针旋转了90度的,因此我们需要将图像顺时针旋转90度才能看到正常的画面。而Camera对象提供一个setDisplayOrientation接口能够设置预览显示的角度:

在这里插入图片描述

根据文档,配置完Camera之后预览确实正常了,但是在onPreviewFrame中回调获得的数据依然是逆时针旋转了90度的。所以如果需要使用预览回调的数据,还需要对onPreviewFrame回调的byte[] 进行旋转。
即对NV21数据顺时针旋转90度。

旋转前:
在这里插入图片描述

旋转后:
在这里插入图片描述

三 代码实现

private void rotation90(byte[] data) {
            int index = 0;
            int ySize = mWidth * mHeight;
            //u和v
            int uvHeight = mHeight / 2;
        //后置摄像头顺时针旋转90度
        if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
            //将y的数据旋转之后 放入新的byte数组
            for (int i = 0; i < mWidth; i++) {
                for (int j = mHeight - 1; j >= 0; j--) {
                    bytes[index++] = data[mWidth * j + i];
                }
            }

            //每次处理两个数据
            for (int i = 0; i < mWidth; i += 2) {
                for (int j = uvHeight - 1; j >= 0; j--) {
                    // v
                    bytes[index++] = data[ySize + mWidth * j + i];
                    // u
                    bytes[index++] = data[ySize + mWidth * j + i + 1];
                }
            }
        } else {
            //逆时针旋转90度
            for (int i = 0; i < mWidth; i++) {
                int nPos = mWidth - 1;
                for (int j = 0; j < mHeight; j++) {
                    bytes[index++] = data[nPos - i];
                    nPos += mWidth;
                }
            }
            //u v
            for (int i = 0; i < mWidth; i += 2) {
                int nPos = ySize + mWidth - 1;
                for (int j = 0; j < uvHeight; j++) {
                    bytes[index++] = data[nPos - i - 1];
                    bytes[index++] = data[nPos - i];
                    nPos += mWidth;
                }
            }
        }
    }

四 解决bug

1 中断联网,死机

断网时,RTMP_SendPacket依然被调用
->发送失败走trmp.c:WriteN
->写出失败走RTMP_Close®
->SendFCUnpublish®;
-> RTMP_SendPacket 进入死循环

解决: 我们自己RTMP_Close,注释掉TRM_Close的调用

2 设置超时时间无用

trmp->Link.timeout = 5

原因:

RTMP_Connect(RTMP *r, RTMPPacket *cp)
->RTMP_Connect0

  {
  int on = 1;
  r->m_sb.sb_timedout = FALSE;
  r->m_pausing = 0;
  r->m_fDuration = 0.0;

  r->m_sb.sb_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if (r->m_sb.sb_socket != -1)
    {
      if (connect(r->m_sb.sb_socket, service, sizeof(struct sockaddr)) < 0)
	{
	  int err = GetSockError();
	  RTMP_Log(RTMP_LOGERROR, "%s, failed to connect socket. %d (%s)",
	      __FUNCTION__, err, strerror(err));
	  RTMP_Close(r);
	  return FALSE;
	}

      if (r->Link.socksport)
	{
	  RTMP_Log(RTMP_LOGDEBUG, "%s ... SOCKS negotiation", __FUNCTION__);
	  if (!SocksNegotiate(r))
	    {
	      RTMP_Log(RTMP_LOGERROR, "%s, SOCKS negotiation failed.", __FUNCTION__);
	      RTMP_Close(r);
	      return FALSE;
	    }
	}
    }
  else
    {
      RTMP_Log(RTMP_LOGERROR, "%s, failed to create socket. Error: %d", __FUNCTION__,
	  GetSockError());
      return FALSE;
    }

  /* set timeout */
  {
    SET_RCVTIMEO(tv, r->Link.timeout);
    if (setsockopt
        (r->m_sb.sb_socket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)))
      {
        RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
	    __FUNCTION__, r->Link.timeout);
      }
  }

  setsockopt(r->m_sb.sb_socket, IPPROTO_TCP, TCP_NODELAY, (char *) &on, sizeof(on));

  return TRUE;
}


connect(r->m_sb.sb_socket, 没用用到超时时间,超时时间在后面才用到

解决: connect之前

if (setsockopt
        (r->m_sb.sb_socket, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(tv)))
      {
        RTMP_Log(RTMP_LOGERROR, "%s, Setting socket timeout to %ds failed!",
	    __FUNCTION__, r->Link.timeout);
  }
	  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值