最近一直在弄安卓视频通信这一块,因为自己以前完全没有接触过,好多东西都没概念,故在此记录下自己学习的内容方便日后复习。
- YUV
YUV格式有两大类:planar和packed。
对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
对于packed的YUV格式,每个像素点的Y,U,V是连续交*存储的。
在采集到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的驱动才可以处理,就是YUV2RGB2YUV420P,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》
横屏
@Override protected void onResume() { /** * 设置为横屏 */ if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); } super.onResume(); }
android:launchMode="singleTask" android:screenOrientation="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);
}