关于 Android 的音视频,也能够叫作多媒体,分红图像、声音和视频。咱们先从最基本的图像入手,图像分红 2D 和 3D,Android 自身也提供了不少 API 来实现图像的功能。对于 Android 的图像内存优化,能够看我以前的这篇文章:Android应用篇 - 最全图片相关的优化。android
YUV 是一种颜色编码方法,常使用在各个视频处理组件中。 YUV 在对照片或视频编码时,考虑到人类的感知能力,容许下降色度的带宽。git
YUV 是编译 true-color 颜色空间 (colorspace) 的种类,Y'UV, YUV, YCbCr, YPbPr 等专有名词均可以称为 YUV,彼此有重叠。"Y" 表示明亮度 (Luminance、Luma),"U" 和 "V" 则是色度、浓度 (Chrominance、Chroma)。github
彩色图像记录的格式,常见的有 RGB、YUV、CMYK 等。彩色电视最先的构想是使用 RGB 三原色来同时传输,这种设计方式是原来黑白带宽的 3 倍,在当时并非很好的设计。RGB 诉求于人眼对色彩的感应,YUV 则着重于视觉对于亮度的敏感程度,Y 表明的是亮度,UV 表明的是彩度 (所以黑白电影可省略 UV,相近于 RGB),分别用 Cr 和 Cb 来表示,所以 YUV 的记录一般以Y:UV 的格式呈现。算法
为节省带宽起见,大多数 YUV 格式平均使用的每像素位数都少于 24 位。主要的抽样 (subsample) 格式有 YCbCr4:2:0、YCbCr4:2:二、YCbCr4:1:1 和 YCbCr4:4:4。YUV 的表示法称为 A:B:C 表示法:编程
最经常使用 Y:UV 记录的比重一般 1:1 或 2:1,DVD-Video 是以 YUV4:2:0 的方式记录,也就是咱们俗称的 I420,YUV4:2:0 并非说只有 U (即 Cb) , V(即 Cr) 必定为 0,而是指 U:V 互相援引,时见时隐,也就是说对于每个行,只有一个 U 或者 V 份量,若是一行是 4:2:0 的话,下一行就是 4:0:2,再下一行是 4:2:0...以此类推。bash
至于其余常见的 YUV 格式有 YUY二、YUYV、YVYU、UYVY、AYUV、Y41P、Y4十一、Y2十一、IF0九、IYUV、YV十二、YVU九、YUV4十一、YUV420 等。架构
好比在作直播或者美颜相机的时候,由于须要添加美白,滤镜,AR 贴图等效果。因此不能简单的使用 SufaceView 加 Camera 的方式进行数据的采集,而是须要对 Camera 采集到的 YUV 数据进行相关的处理以后而后再进行推流的操做,YUV 数据的返回接口。框架
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
}
复制代码
Android 摄像头采集的数据都是有必定的旋转的。通常前置摄像头有 270 度的旋转,后置摄像头有 90 的旋转。因此要对 YUV 数据进行必定旋转操做,同时对于前置摄像头的数据还要进行镜像翻转的操做。网上通常比较多的算法是关于旋转的:编程语言
private byte[] rotateYUVDegree90(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate the Y luma
int i = 0;
for (int x = 0; x < imageWidth; x++) {
for (int y = imageHeight - 1; y >= 0; y--) {
yuv[i] = data[y * imageWidth + x];
i++;
}
}
// Rotate the U and V color components
i = imageWidth * imageHeight * 3 / 2 - 1;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i--;
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i--;
}
}
return yuv;
}
private byte[] rotateYUVDegree270(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate the Y luma
int i = 0;
for (int x = imageWidth - 1; x >= 0; x--) {
for (int y = 0; y < imageHeight; y++) {
yuv[i] = data[y * imageWidth + x];
i++;
}
}// Rotate the U and V color components
i = imageWidth * imageHeight;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x - 1)];
i++;
yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
i++;
}
}
return yuv;
}
复制代码
上述两个算法分别用于 90 度旋转 (后置摄像头) 和 270 度旋转 (前置摄像头),可是对于前置摄像头的 YUV 数据是须要镜像的,参照上面的算法,实现了前置摄像头的镜像算法:ide
private byte[] rotateYUVDegree270AndMirror(byte[] data, int imageWidth, int imageHeight) {
byte[] yuv = new byte[imageWidth * imageHeight * 3 / 2];
// Rotate and mirror the Y luma
int i = 0;
int maxY = 0;
for (int x = imageWidth - 1; x >= 0; x--) {
maxY = imageWidth * (imageHeight - 1) + x * 2;
for (int y = 0; y < imageHeight; y++) {
yuv[i] = data[maxY - (y * imageWidth + x)];
i++;
}
}
// Rotate and mirror the U and V color components
int uvSize = imageWidth * imageHeight;
i = uvSize;
int maxUV = 0;
for (int x = imageWidth - 1; x > 0; x = x - 2) {
maxUV = imageWidth * (imageHeight / 2 - 1) + x * 2 + uvSize;
for (int y = 0; y < imageHeight / 2; y++) {
yuv[i] = data[maxUV - 2 - (y * imageWidth + x - 1)];
i++;
yuv[i] = data[maxUV - (y * imageWidth + x)];
i++;
}
}
return yuv;
}
复制代码
其实对于 YUV 数据的处理,Google 已经开源了一个叫作 libyuv 的库专门用于 YUV 数据的处理。libyuv 并不能直接为 Android 开发直接进行使用,须要对它进行编译的操做。
libyuv 能够对 YUV 数据进行缩放,旋转,镜像,裁剪、转化成 RGBA 等操做。在 libyuv 的实际使用过程当中,更多的是用于直播推流前对 Camera采集到的 YUV 数据进行处理的操做。对现在,Camera 的预览通常采用的是 1080p,而且摄像头采集到的数据是旋转以后的,通常来讲后置摄像头旋转了 90 度,前置摄像头旋转了 270 度而且水平镜像。github 上有一个 demo: github.com/hzl123456/L…
固然关于 YUV 转换其余格式,能够本身手动实现,也可使用其余框架的现成方法。
JNI:
bool YV12ToBGR24_Native(unsigned char* pYUV,unsigned char* pBGR24,int width,int height)
{
if (width < 1 || height < 1 || pYUV == NULL || pBGR24 == NULL)
return false;
const long len = width * height;
unsigned char* yData = pYUV;
unsigned char* vData = &yData[len];
unsigned char* uData = &vData[len >> 2];
int bgr[3];
int yIdx,uIdx,vIdx,idx;
for (int i = 0;i < height;i++){
for (int j = 0;j < width;j++){
yIdx = i * width + j;
vIdx = (i/2) * (width/2) + (j/2);
uIdx = vIdx;
bgr[0] = (int)(yData[yIdx] + 1.732446 * (uData[vIdx] - 128)); // b份量
bgr[1] = (int)(yData[yIdx] - 0.698001 * (uData[uIdx] - 128) - 0.703125 * (vData[vIdx] - 128)); // g分
量
bgr[2] = (int)(yData[yIdx] + 1.370705 * (vData[uIdx] - 128)); // r份量
for (int k = 0;k < 3;k++){
idx = (i * width + j) * 3 + k;
if(bgr[k] >= 0 && bgr[k] <= 255)
pBGR24[idx] = bgr[k];
else
pBGR24[idx] = (bgr[k] < 0)?0:255;
}
}
}
return true;
}
复制代码
OpenCV:
bool YV12ToBGR24_OpenCV(unsigned char* pYUV,unsigned char* pBGR24,int width,int height)
{
if (width < 1 || height < 1 || pYUV == NULL || pBGR24 == NULL)
return false;
Mat dst(height,width,CV_8UC3,pBGR24);
Mat src(height + height/2,width,CV_8UC1,pYUV);
cvtColor(src,dst,CV_YUV2BGR_YV12);
return true;
}
复制代码
FFmpeg:
bool YV12ToBGR24_FFmpeg(unsigned char* pYUV,unsigned char* pBGR24,int width,int height)
{
if (width < 1 || height < 1 || pYUV == NULL || pBGR24 == NULL)
return false;
//int srcNumBytes,dstNumBytes;
//uint8_t *pSrc,*pDst;
AVPicture pFrameYUV,pFrameBGR;
//pFrameYUV = avpicture_alloc();
//srcNumBytes = avpicture_get_size(PIX_FMT_YUV420P,width,height);
//pSrc = (uint8_t *)malloc(sizeof(uint8_t) * srcNumBytes);
avpicture_fill(&pFrameYUV,pYUV,PIX_FMT_YUV420P,width,height);
//U,V互换
uint8_t * ptmp=pFrameYUV.data[1];
pFrameYUV.data[1]=pFrameYUV.data[2];
pFrameYUV.data [2]=ptmp;
//pFrameBGR = avcodec_alloc_frame();
//dstNumBytes = avpicture_get_size(PIX_FMT_BGR24,width,height);
//pDst = (uint8_t *)malloc(sizeof(uint8_t) * dstNumBytes);
avpicture_fill(&pFrameBGR,pBGR24,PIX_FMT_BGR24,width,height);
struct SwsContext* imgCtx = NULL;
imgCtx =
sws_getContext(width,height,PIX_FMT_YUV420P,width,height,PIX_FMT_BGR24,SWS_BILINEAR,0,0,0);
if (imgCtx != NULL){
sws_scale(imgCtx,pFrameYUV.data,pFrameYUV.linesize,0,height,pFrameBGR.data,pFrameBGR.linesize);
if(imgCtx){
sws_freeContext(imgCtx);
imgCtx = NULL;
}
return true;
}
else{
sws_freeContext(imgCtx);
imgCtx = NULL;
return false;
}
}
复制代码
Android YuvImage 包含四元组的 YUV 数据,contains YUV data and provides a method that compresses a region of the YUV data to a Jpeg,提供了一个向 jpeg 格式压缩的方法。
public static @Nullable byte[] convertNv21ToJpeg(byte[] nv21, int w, int h, Rect rect){
if(nv21 == null) return null;
ByteArrayOutputStream outputSteam = new ByteArrayOutputStream();
YuvImage image = new YuvImage(nv21, ImageFormat.NV21, w, h, null);
image.compressToJpeg(rect, 70, outputSteam);
return outputSteam.toByteArray();
}
复制代码
可是 YuvImage.compressToJpeg 存在 native 级别的内存泄漏:blog.csdn.net/q979713444/…
在 Google 推出 Android 5.0 的时候,Android Camera API 版本升级到了 API2 (android.hardware.camera2),以前使用的API1 (android.hardware.camera) 就被标为 Deprecated 了。Camera API2 相较于 API1 有很大不一样, 而且 API2 是为了配合 HAL3 进行使用的,API2 有不少 API1 不支持的特性,好比:
Camera2 API 相比原来 android.hardware.Camera API 在架构上有了很大的改变,虽然让手机拍照功能更增强大,但同时也增长了开发复杂度。感兴趣的能够看看这篇文章,分析了 Camera2 的架构与使用:www.jianshu.com/p/d83161e77…
SurfaceView 继承自 View,并提供了一个独立的绘图层,你能够彻底控制这个绘图层,好比说设定它的大小,因此 SurfaceView能够嵌入到 View 结构树中,须要注意的是,因为 SurfaceView 直接将绘图表层绘制到屏幕上,因此和普通的 View 不一样的地方就在与它不能执行 Transition,Rotation,Scale 等转换,也不能进行 Alpha 透明度运算。
SurfaceView 的 Surface 排在 Window 的 Surface (也就是 View 树所在的绘图层) 的下面,SurfaceView 嵌入到 Window 的 View结构树中就好像在 Window 的 Surface 上强行打了个洞让本身显示到屏幕上,并且 SurfaceView 另起一个线程对本身的 Surface进行刷新。须要注意的是 SurfaceHolder.Callback 的全部回调方法都是在主线程中回调的。
SurfaceView、SurfaceHolder、Surface 的关系能够归纳为如下几点:
- SurfaceView 是拥有独立绘图层的特殊 View。
- Surface 就是指 SurfaceView 所拥有的那个绘图层,其实它就是内存中的一段绘图缓冲区。
- SurfaceView 中具备两个 Surface,也就是咱们所说的双缓冲机制。
- SurfaceHolder 顾名思义就是 Surface 的持有者,SurfaceView 就是经过 SurfaceHolder 来对 Surface 进行管理控制的。而且 SurfaceView.getHolder() 方法能够获取 SurfaceView 相应的 SurfaceHolder。
- Surface 是在 SurfaceView 所在的 Window 可见的时候建立的。咱们可使用 SurfaceHolder.addCallback() 方法来监听 Surface 的建立与销毁的事件。
Surface 的渲染能够放到单独线程去作,渲染时能够有本身的 GL context。这对于一些游戏、视频等性能相关的应用很是有益,由于它不会影响主线程对事件的响应。但它也有缺点,由于这个 Surface 不在 View hierachy 中,它的显示也不受 View 的属性控制,因此不能进行平移,缩放等变换,也不能放在其它 ViewGroup 中,一些 View 中的特性也没法使用。
TextureView 专门用来渲染像视频或 OpenGL 场景之类的数据的,并且 TextureView 只能用在具备硬件加速的 Window 中,若是使用的是软件渲染,TextureView 将什么也不显示。也就是说对于没有 GPU 的设备,TextureView 彻底不可用。
TextureView 有两个相关类 SurfaceTexture、Surface,下面说明一下几者相关的特色:
- Surface 就是 SurfaceView 中使用的 Surface,就是内存中的一段绘图缓冲区。
- SurfaceTexture 用来捕获视频流中的图像帧的,视频流能够是相机预览或者视频解码数据。SurfaceTexture 能够做为android.hardware.camera2, MediaCodec, MediaPlayer, 和 Allocation 这些类的目标视频数据输出对象。能够调用updateTexImage() 方法从视频流数据中更新当前帧,这就使得视频流中的某些帧能够跳过。
- TextureView 能够经过 getSurfaceTexture() 方法来获取 TextureView 相应的 SurfaceTexture。可是最好的方式仍是使用TextureView.SurfaceTextureListener 监听器来对 SurfaceTexture 的建立销和毁进行监听,由于 getSurfaceTexture() 可能获取的是空对象。
GLSurfaceView 做为 SurfaceView 的补充,能够看做是 SurfaceView 的一种典型使用模式。在 SurfaceView 的基础上,它加入了EGL 的管理,并自带了渲染线程。另外它定义了用户须要实现的 Render 接口,提供了用 Strategy pattern 更改具体 Render 行为的灵活性。做为 GLSurfaceView 的 Client,只须要将实现了渲染函数的 Renderer 的实现类设置给 GLSurfaceView 便可。
SurfaceTexture 和 SurfaceView 不一样的是,它对图像流的处理并不直接显示,而是转为 GL 外部纹理,所以可用于图像流数据的二次处理 (如 Camera 滤镜,桌面特效等)。好比 Camera 的预览数据,变成纹理后能够交给 GLSurfaceView 直接显示,也能够经过 SurfaceTexture 交给 TextureView 做为 View heirachy 中的一个硬件加速层来显示。首先,SurfaceTexture 从图像流 (来自Camera 预览,视频解码,GL 绘制场景等) 中得到帧数据,当调用 updateTexImage() 时,根据内容流中最近的图像更新SurfaceTexture 对应的 GL 纹理对象,接下来,就能够像操做普通 GL 纹理同样操做它了。
SurfaceView
继承自 View,拥有 View 的大部分属性,可是因为 holder 的存在,不能设置透明度。
GlSurfaceView 继承自 SurfaceView 类,专门用来显示 OpenGL 渲染的,简单理解能够显示视频,图像及 3D 场景。
SurfaceTexture
和 SurfaceView 功能相似,区别是,SurfaceTexure 能够不显示在界面中。使用 OpenGL 对图片流进行美化,添加水印,滤镜这些操做的时候咱们都是经过 SurfaceTexure 去处理,处理完以后再经过 GlSurfaceView 显示。缺点,可能会致使个别帧的延迟。自己管理着 BufferQueue,因此内存消耗会多一点。
TextureView
TextureView 一样继承自 View,必须在开启硬件加速的设备中使用 (保守估计目前 90% 的 Android 设备都开启了),TextureView 经过 setSurfaceTextureListener 的回调在子线程中进行更新 UI。
OpenGL (全写 Open Graphics Library) 是指定义了一个跨编程语言、跨平台的编程接口规格的专业的图形程序接口。它用于三维图像 (二维的亦可),是一个功能强大,调用方便的底层图形库。
OpenGL 在不一样的平台上有不一样的实现,可是它定义好了专业的程序接口,不一样的平台都是遵守该接口来进行实现的,思想彻底相同,方法名也是一致的,因此使用时也基本一致,只须要根据不一样的语言环境稍有不一样而已。OpenGL 这套 3D 图形 API 从1992 年发布的 1.0 版本到目前最新 2014 年发布的 4.5 版本,在众多平台上多有着普遍的使用。
OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL 三维图形 API 的子集,针对手机、PDA 和游戏主机等嵌入式设备而设计。
OpenGL ES 相对于 OpenGL 来讲,减小了许多不是必须的方法和数据类型,去掉了没必要须的功能,对代价大的功能作了限制,比 OpenGL 更为轻量。在 OpenGL ES 的世界里,没有四边形、多边形,不管多复杂的图形都是由点、线和三角形组成的,也去除了 glBegin/glEnd 等方法。
OpenGL ES 是手机、PDA 和游戏主机等嵌入式设备三维 (二维也包括) 图形处理的 API,固然是用来在嵌入式设备上的图形处理了,OpenGL ES 强大的渲染能力使其成为咱们在嵌入式设备上进行图形处理的优良选择。咱们常用的场景有:
OpenGL ES 当前主要版本有 1.0/1.1/2.0/3.0/3.1。这些版本的主要状况以下:
GPUImage 是 iOS 下一个开源的基于 GPU 的图像处理库,提供各类各样的图像处理滤镜,而且支持照相机和摄像机的实时滤镜。GPUImage for Android 是它在 Android 下的实现,一样也是开源的。其中提供了几十多种常见的图片滤镜 API,且其机制是基于 GPU 渲染,处理速度相应也比较快,是一个不错的图片实时处理框架。
github 地址:github.com/CyberAgent/…