Android Video学习笔记

最近一直在弄安卓视频通信这一块,因为自己以前完全没有接触过,好多东西都没概念,故在此记录下自己学习的内容方便日后复习。

  • YUV

YUV主要用于优化彩色视频信号的传输,与RGB 视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是 色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白 灰度图像

YUV格式有两大类:planar和packed。
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
对于packed的YUV格式,每个像素点的Y,U,V是连续交*存储的。


YV12和I420的区别
一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Bit,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Bit。
在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下:
YV12 : 亮度(行×列) + V(行×列/4) + U(行×列/4)
I420 : 亮度(行×列) + U(行×列/4) + V(行×列/4)
可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。yuv420p就是I420格式

YUV420 数据在内存中的长度是 width * hight * 3 / 2


旋转90度的算法:

public static void rotateYUV240SP(byte[] src,byte[] des,int width,int height)
 {
   
  int wh = width * height;
  //旋转Y
  int k = 0;
  for(int i=0;i<width;i++) {
   for(int j=0;j<height;j++)
   {
               des[k] = src[width*j + i];   
         k++;
   }
  }
  
  for(int i=0;i<width;i+=2) {
   for(int j=0;j<height/2;j++)
   { 
               des[k] = src[wh+ width*j + i]; 
               des[k+1]=src[wh + width*j + i+1];
         k+=2;
   }
  }
  
  
 }

 

YV12和I420的区别        一般来说,直接采集到的视频数据是RGB24的格式,RGB24一帧的大小size=width×heigth×3 Bit,RGB32的size=width×heigth×4,如果是I420(即YUV标准格式4:2:0)的数据量是 size=width×heigth×1.5 Bit。       在采集到RGB24数据后,需要对这个格式的数据进行第一次压缩。即将图像的颜色空间由RGB2YUV。因为,X264在进行编码的时候需要标准的YUV(4:2:0)。但是这里需要注意的是,虽然YV12也是(4:2:0),但是YV12和I420的却是不同的,在存储空间上面有些区别。如下: YV12 : 亮度(行×列) + U(行×列/4) + V(行×列/4)

I420 : 亮度(行×列) + V(行×列/4) + U(行×列/4)

可以看出,YV12和I420基本上是一样的,就是UV的顺序不同。

经过第一次数据压缩后RGB24->YUV(I420)。这样,数据量将减少一半,为什么呢?呵呵,这个就太基础了,我就不多写了。同样,如果是RGB24->YUV(YV12),也是减少一半。但是,虽然都是一半,如果是YV12的话效果就有很大损失。然后,经过X264编码后,数据量将大大减少。将编码后的数据打包,通过RTP实时传送。到达目的地后,将数据取出,进行解码。完成解码后,数据仍然是YUV格式的,所以,还需要一次转换,这样windows的驱动才可以处理,就是YUV2RGB2

yuv420p 和 YUV420的区别 在存储格式上有区别
yuv420p:yyyyyyyy uuuuuuuu vvvvv yuv420: yuv yuv yuv

YUV420P,Y,U,V三个分量都是平面格式,分为I420和YV12。I420格式和YV12格式的不同处在U平面和V平面的位置不同。在I420格式中,U平面紧跟在Y平面之后,然后才是V平面(即:YUV);但YV12则是相反(即:YVU)。
YUV420SP, Y分量平面格式,UV打包格式, 即NV12。 NV12与NV21类似,U 和 V 交错排列,不同在于UV顺序。
I420: YYYYYYYY UU VV    =>YUV420P
YV12: YYYYYYYY VV UU    =>YUV420P
NV12: YYYYYYYY UVUV     =>YUV420SP
NV21: YYYYYYYY VUVU     =>YUV420SP


====================================================================================================================================上述内容参考自《http://www.cnblogs.com/azraelly/archive/2013/01/01/2841269.html》

====================================================================================================================================

Android强制设置横屏或竖屏《http://2960629.blog.51cto.com/2950629/701227》

横屏

按照下面代码示例修改Activity的onResume方法
@Override
protected void onResume() {
 /**
  * 设置为横屏
  */
 if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
  setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
 }
 super.onResume();
}
或者在配置文件中对Activity节点添加android:screenOrientation属性(landscape是横向,portrait是纵向)
android:launchMode="singleTask" android:screenOrientation="portrait">
要设置成竖屏设置成 SCREEN_ORIENTATION_PORTRAIT

====================================================================================================================================

  • Android SurfaceView 与 View 的区别:

SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。
  那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
  当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中 thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。

  • SurfaceTexture 与 SurfaceView 的区别:

SurfaceTexture是从Android3.0(API 11)加入的一个新类。这个类跟SurfaceView很像,可以从camera preview或者video decode里面获取图像流(image stream)。但是,和SurfaceView不同的是,SurfaceTexture在接收图像流之后,不需要显示出来。有做过Android camera开发的人都知道,比较头疼的一个问题就是,从camera读取到的预览(preview)图像流一定要输出到一个可见的(Visible)SurfaceView上,然后通过Camera.PreviewCallback的public void onPreviewFrame(byte[] data, Camera camera)函数来获得图像帧数据的拷贝。这就存在一个问题,比如我希望隐藏摄像头的预览图像或者对每一帧进行一些处理再显示到手机显示屏上,那么在Android3.0之前是没有办法做到的,或者说你需要用一些小技巧,比如用其他控件把SurfaceView给挡住,注意这个显示原始camera图像流的SurfaceView其实是依然存在的,也就是说被挡住的SurfaceView依然在接收从camera传过来的图像,而且一直按照一定帧率去刷新,这是消耗cpu的,而且如果一些参数设置的不恰当,后面隐藏的SurfaceView有可能会露出来,因此这些小技巧并不是好办法。但是,有了SurfaceTexture之后,就好办多了,因为SurfaceTexture不需要显示到屏幕上,因此我们可以用SurfaceTexture接收来自camera的图像流,然后从SurfaceTexture中取得图像帧的拷贝进行处理,处理完毕后再送给另一个SurfaceView用于显示即可。

SurfaceTexture 比 SurfaceView 更耗电


====================================================================================================================================

  • Android摄像头采集的视频数据流如何通过Socket实时发送到目标服务端

    分两块:

    1.取得摄像头采集的视频流

    2.发送到服务器端

        protected MediaRecorder mMediaRecorder;
        private LocalServerSocket mLss = null;
        private LocalSocket mReceiver, mSender = null;
        mLss = new LocalServerSocket("myVideoStream");
        
        mReceiver = new LocalSocket();
        //连接mLss,即接受方主动发起连接
        mReceiver.connect( new LocalSocketAddress("myVideoStream"));
        mReceiver.setReceiveBufferSize(100*1024);
        mSender = mLss.accept();
        mSender.setSendBufferSize(100*1024);
        mMediaRecorder.start();
        
        //mReceiver已经和mSender建立连接,也就可以认为,Camera采集的视频流会持续
        //发给mReceiver,通过mReceiver.getInputStream()就可以获取到mSerder输入的视频流
        mSocketHelper.setInputStream(mReceiver.getInputStream());
        //mSocketHelper是你自己对Socket的封装,根据mMediaRecorder的不同的编码格式,实现不同的发送方式
        mSocketHelper.start();

====================================================================================================================================

关于android的Camera的竖屏拍摄 (http://blog.chinaunix.net/uid-26410105-id-4933880.html)

Android Camera视频流格式转换(YUV420SP–RGB)  (http://www.ztyhome.com/android-camera-yuv420/ )




    /**
     * Converts Android YV12 format to YUV420 planar.
     * @param input input YV12 image bytes.
     *
     * @param output output buffer.
     * @param width image width.
     * @param height image height.
     */
    static void YV12toYUV420Planar(final byte[] input, final byte[] output,
                                     final int width, final int height)
    {
         
        if(width % 16 != 0)
            throw new IllegalArgumentException("Unsupported width: "+width);

        int yStride   = (int) Math.ceil( width / 16.0 ) * 16;
        int uvStride  = (int) Math.ceil( (yStride / 2) / 16.0) * 16;
        int ySize     = yStride * height;
        int uvSize    = uvStride * height / 2;

        int I420uvStride = (int)(((yStride / 2) / 16.0) * 16);
        int I420uvSize = width*height/4;
        int uvStridePadding = uvStride - I420uvStride;

        System.arraycopy(input, 0, output, 0, ySize); // Y

        // If padding is 0 then just swap U and V planes
        if(uvStridePadding == 0)
        {
            System.arraycopy(input, ySize,
                             output, ySize + uvSize, uvSize); // Cr (V)
            System.arraycopy(input, ySize + uvSize,
                             output, ySize, uvSize); // Cb (U)
        }
        else
        {
            logger.warn("Not recommended resolution: " + width + "x" + height);
            int src = ySize;
            int dst = ySize;
            //Copy without padding
            for(int y=0; y < height/2; y++)
            {
                System.arraycopy(input, src, output,
                                 I420uvSize + dst, I420uvStride); // Cr (V)
                System.arraycopy(input, uvSize + src,
                                 output, dst, I420uvStride); // Cb (U)
                src += uvStride;
                dst += I420uvStride;
            }
           
        }
        
    }

    /**
     * Calculates YV12 image data size in bytes.
     * @param width image width.
     * @param height image height.
     * @return YV12 image data size in bytes.
     */
    public static int calcYV12Size(int width, int height)
    {
        float yStride   = (int) Math.ceil( width / 16.0 ) * 16;
        float uvStride  = (int) Math.ceil( (yStride / 2) / 16.0) * 16;
        float ySize     = yStride * height;
        float uvSize    = uvStride * height / 2;
        //float yRowIndex = yStride * y;
        //float uRowIndex = ySize + uvSize + uvStride * c;
        //float vRowIndex = ySize + uvStride * c;
        return (int) (ySize + uvSize * 2);
    }
    
    

        public static void YUV420pRotate90(byte[] des, byte[] src, int width,
                int height) {
            int n = 0;
            int hw = width / 2;
            int hh = height / 2;
            // copy y
            for (int j = 0; j < width; j++) {
                for (int i = height - 1; i >= 0; i--) {
                    des[n++] = src[width * i + j];
                }
            }

            // copy u
            int uPos = width * height;
            for (int j = 0; j < hw; j++) {
                for (int i = hh - 1; i >= 0; i--) {
                    des[n++] = src[uPos + hw * i + j];
                }
            }

            // copy v
            int vPos = uPos + width * height / 4;
            for (int j = 0; j < hw; j++) {
                for (int i = hh - 1; i >= 0; i--) {
                    des[n++] = src[vPos + hw * i + j];
                }
            }
        }
        
        public static void YUV420pRotate180(byte[] des, byte[] src, int width,
                int height) {
            int n = 0;
            int hw = width / 2;
            int hh = height / 2;
            // copy y
            for (int j = height - 1; j >= 0; j--) {
                for (int i = width; i > 0; i--) {
                    des[n++] = src[width * j + i];
                }
            }

            // copy u
            int uPos = width * height;
            for (int j = hh - 1; j >= 0; j--) {
                for (int i = hw; i > 0; i--) {
                    des[n++] = src[uPos + hw * i + j];
                }
            }

            // copy v
            int vPos = uPos + width * height / 4;
            for (int j = hh - 1; j >= 0; j--) {
                for (int i = hw; i > 0; i--) {
                    des[n++] = src[vPos + hw * i + j];
                }
            }
        }

        public static void YUV420pRotate270(byte[] des, byte[] src, int width,
                int height) {
            int n = 0;
            int hw = width / 2;
            int hh = height / 2;
            // copy y
            for (int j = width - 1; j >= 0; j--) {
                for (int i = 0; i < height; i++) {
                    des[n++] = src[width * i + j];
                }
            }

            // copy u
            int uPos = width * height;
            for (int j = hw - 1; j >= 0; j--) {
                for (int i = 0; i < hh; i++) {
                    des[n++] = src[uPos + hw * i + j];
                }
            }

            // copy v
            int vPos = uPos + width * height / 4;
            for (int j = hw - 1; j >= 0; j--) {
                for (int i = 0; i < hh; i++) {
                    des[n++] = src[vPos + hw * i + j];
                }
            }
        }

        public static void YUV420pMirrorY(byte[] des, byte[] src, int width,
                int height) {
            int n = 0;
            int hw = width / 2;
            int hh = height / 2;
            // copy y
            for (int j = 0; j < height; j++) {
                for (int i = width - 1; i >= 0; i--) {
                    des[n++] = src[width * j + i];
                }
            }

            // copy u
            int uPos = width * height;
            for (int j = 0; j < hh; j++) {
                for (int i = hw - 1; i >= 0; i--) {
                    des[n++] = src[uPos + hw * j + i];
                }
            }

            // copy v
            int vPos = uPos + width * height / 4;
            for (int j = 0; j < hh; j++) {
                for (int i = hw - 1; i >= 0; i--) {
                    des[n++] = src[vPos + hw * j + i];
                }
            }
        }

        public static void YUV420pMirrorX(byte[] des, byte[] src, int width,
                int height) {
            int n = 0;
            int hw = width / 2;
            int hh = height / 2;

            int nPos = width * height;
            for (int j = 0; j < height; j++) {
                nPos -= width;
                for (int i = 0; i < width; i++) {
                    des[n++] = src[nPos + i];
                }
            }

            nPos = width * height + width * height / 4;
            for (int j = 0; j < hh; j++) {
                nPos -= hw;
                for (int i = 0; i < hw; i++) {
                    des[n++] = src[nPos + i];
                }
            }

            nPos = width * height + width * height / 2;
            for (int j = 0; j < hh; j++) {
                nPos -= hw;
                for (int i = 0; i < hw; i++) {
                    des[n++] = src[nPos + i];
                }
            }
        }

====================================================================================================================================关于android的Camera的竖屏拍摄 (http://blog.chinaunix.net/uid-26410105-id-4933880.html)


====================================================================================================================================

camera帧数据的输出接口是

 public void onPreviewFrame(byte[] data, Camera camera) 
这个byte[] data就是图像帧数据了...注意这个数据是YUV420格式的...

首先转成RGB格式

public static int[] YV12ToRGB(byte[] src, int width, int height){  
    int numOfPixel = width * height;  
    int positionOfV = numOfPixel;  
    int positionOfU = numOfPixel/4 + numOfPixel;  
    int[] rgb = new int[numOfPixel*3];  

    for(int i=0; i<height; i++){  
        int startY = i*width;  
        int step = (i/2)*(width/2);  
        int startV = positionOfV + step;  
        int startU = positionOfU + step;  
        for(int j = 0; j < width; j++){  
            int Y = startY + j;  
            int V = startV + j/2;  
            int U = startU + j/2;  
            int index = Y*3;  

            //rgb[index+R] = (int)((src[Y]&0xff) + 1.4075 * ((src[V]&0xff)-128));  
            //rgb[index+G] = (int)((src[Y]&0xff) - 0.3455 * ((src[U]&0xff)-128) - 0.7169*((src[V]&0xff)-128));  
            //rgb[index+B] = (int)((src[Y]&0xff) + 1.779 * ((src[U]&0xff)-128));  
            RGB tmp = yuvTorgb(src[Y], src[U], src[V]);  
            rgb[index+R] = tmp.r;  
            rgb[index+G] = tmp.g;  
            rgb[index+B] = tmp.b;  
        }  
    }  
    return rgb;  
}  

然后就是图像处理了;jhlabs.....很古老的java库....图像处理部分是直接针对RGB数组的..所以可以很方便的移植到Android上

http://www.jhlabs.com/ip/filters/index.html

全靠CPU运算...效率一般... 不过你需求的变色/加阴影复杂度不高 应该还能接受

你想要效率高可能还要用 RenderScript...
或者OPENGL


====================================================================================================================================


yv12转nv12:

void swapYV12toNV12(byte[] yv12bytes, byte[] nv12bytes, int width,int height) {

int nLenY = width * height;
int nLenU = nLenY / 4;


System.arraycopy(yv12bytes, 0, nv12bytes, 0, width * height);
for (int i = 0; i < nLenU; i++) {
nv12bytes[nLenY + 2 * i] = yv12bytes[nLenY + i];
nv12bytes[nLenY + 2 * i + 1] = yv12bytes[nLenY + nLenU + i];
}
}



nv12转I420:

void swapNV12toI420(byte[] nv12bytes, byte[] i420bytes, int width,int height) {

int nLenY = width * height;
int nLenU = nLenY / 4;


System.arraycopy(nv12bytes, 0, i420bytes, 0, width * height);
for (int i = 0; i < nLenU; i++) {
i420bytes[nLenY + i] = nv12bytes[nLenY + 2 * i + 1];
i420bytes[nLenY + nLenU + i] = nv12bytes[nLenY + 2 * i];
}
}


转换后如果发现颜色不对,u、v顺序换一下。


private static void YUV420SP2YUV420( byte [] yuv420sp, byte [] yuv420, int width, int height)
{
if (yuv420sp == null ||yuv420 == null )
return ;
int framesize = width*height;
int i = 0 , j = 0 ;
//copy y
for (i = 0 ; i < framesize; i++)
{
yuv420[i] = yuv420sp[i];
}
i = 0 ;
for (j = 0 ; j < framesize/ 2 ; j+= 2 )
{
yuv420[i + framesize* 5 / 4 ] = yuv420sp[j+framesize];
i++;
}
i = 0 ;
for (j = 1 ; j < framesize/ 2 ;j+= 2 )
{
yuv420[i+framesize] = yuv420sp[j+framesize];
i++;
}
}

====================================================================================================================================

  @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
         if (data == null)
         {
             logger.error("Null data received on callback, " +
                                  " invalid buffer size ?");
             return;
         }

          int w = format.getSize().width;
        int h = format.getSize().height;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] rawImage = null;

        // Decode image from the retrieved buffer to JPEG
        YuvImage yuv = new YuvImage(data, ImageFormat.NV21, w, h, null);
        yuv.compressToJpeg(new Rect(0, 0, w, h),80,baos);
        rawImage = baos.toByteArray();

        // This is the same image as the preview but in JPEG and not rotated
        Bitmap bitmap = BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length);
        ByteArrayOutputStream rotatedStream = new ByteArrayOutputStream();

        // Rotate the Bitmap
        Matrix matrix = new Matrix();
        Camera.CameraInfo info = new Camera.CameraInfo();
        if(info.facing ==CameraInfo.CAMERA_FACING_FRONT)
        {   
            matrix.setRotate(270F);
        } else{
                matrix.setRotate(90F);
        }

        // We rotate the same Bitmap
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, w, h, matrix, false);

        // We dump the rotated Bitmap to the stream
        bitmap.compress(CompressFormat.JPEG, 80, rotatedStream);

        rawImage = rotatedStream.toByteArray();

        // Do something we this byte array
        // Calculate statistics
        calcStats();
        // Convert image format
        synchronized (bufferQueue)
        {
            bufferQueue.addFirst(rawImage);
        }
        transferHandler.transferData(this);
    }


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值