Camera2采集摄像头原始数据,裁剪等操作

方今研讨了一下android摄像头开垦有关的本事,也看了Google提供的Camera2Basic调用示例,以至网络大器晚成部分代码,但都是在TextureView等预览幼功上贯彻,而小编想要做的是在不预览的状态下,能博获得录像头原始数据流,并由本身来决定是不是绘制呈现。经过风姿洒脱番折磨,开首完成了团结的目标—CamCap程序。

1.背景

  在Android做过自定义Camera的相恋的人应该都精通,大家得以因此public void
onPreviewFrame(byte[] data, Camera
camera卡塔尔(قطر‎回调中获取录制头采撷到的每风度翩翩帧的多寡,可是那么些byte[]
data的数码格式YUV的,并不可能一直给大家实行应用,那么该通过什么的艺术对那一个YUV数据开展拍卖吧?

须求深入分析

实际上首要正是在不预览的意况下得到到录制头原始数据,指标嘛,一是为着灵活性,方便随时打开关闭预览,二是为了现在可以一直对数码举行拍卖,三是为着其余程序开拓做一些计划。于是完毕一下多少个职能:

得到摄像头数量,并手动绘制图像 随即开启/关闭预览
任何时候保存当前摄像头图像,纵然在关门预览情形下 Android Camera2接口

查看了部分资料,Android
Java层由于从API21开头,已经裁撤原Camera接口,所以那边运用Camera2接口。比较Camera接口来讲,第二代录像头接口,调用复杂多了,可是灵活性也更加高了,通过Google的Camera2Basic例子能够很理解的问询到利用情势。这里把CamCap程序中的Camera2的调用顺序收拾如下:

图片 1

和其他程序同样,通过ImageReader来获取到CameraCaptureSession传递出去的数据,与谷歌(Google卡塔尔(英语:State of Qatar)例子分歧的是,笔者注销了把TextureView的传递,改为单独以ImageReader来获取图像流,并安装为YUV_420_888格式,以得到原有数据。

开荒摄像头

图片 2

录像头展开后,创制对话

图片 3

调用libyuv做冠道GB之间的多少调换

收获到YUV数据现在,就可以在UI分界面上张开绘图了,通过轻巧领悟,能够因而OpenGLES来绘制,也足以转为Bitmap直接在TextureView上绘制。这里为了简单,选用了后世。但是后来开采,android.graphics.Bitmap并不扶持直接将YUV数据存入,只好转为凯雷德GB数据格式,技巧存入Bitmap,进而在TextureView上制图。YUV调换EnclaveGB,在此之前在C++上行使过很频仍了,能够把现成代码更正一下放松权利java里运行,可是思忖到质量难点,决定只怕使用libyuv。libyuv是生龙活虎款以c/c++为功底的,专做YUV与KugaGB格式调换的开源项目,质量极高。

使用libyuv,供给通过NDK交叉编写翻译,并经过JNI来调用。libyuv编写翻译起来也异常粗略,首先下载libyuv源码,代码地址是:。然后确认保证NDK已经设置(那几个一向在AndroidStudio中就能够安装好),之后把NDK目录加多到景况变量。最后,步向libyuv目录,调用ndk-build即可。libyuv项目里早已写好了Android.mk,所以,直接编写翻译就能够了(我是在Windows上)。

图片 4

只顾!编写翻译的时候遇到JPEG库没有一点点名的标题,借使不想注重libjpeg,能够改革Android.mk,删除JPEG库相关编写翻译项就能够解决。

在AndroidStudio上树立c++文件,封装libyuv接口,然后依据JNI标准揭示接口,同期在Java层封装类来调用native方法。

图片 5

绘制图像

在绘制图像的时候,有个坑,那就是图像的转动,这几个是由于手提式有线电话机上的摄像头传感器的视线坐标,日常都以旋转了90度或270度的,所以,供给把录像头采撷到的镜头,实行旋转,本领还原出科学的视界画面。传感器旋转方向通过以下值得到,

CameraManager.getCameraCharacteristics(camid).get(CameraCharacteristics.SENSOR_ORIENTATION)

传闻这些值,营造Matrix将Bitmap进行旋转

图片 6

Matrix创设代码如下:

图片 7

与地点代码中接近,通过TextureView.lockCanvas(卡塔尔,获取到Canvas,调用drawBitmap(卡塔尔(قطر‎将图像写入,就可以产生绘制。

运行截图

拉开预览时的4:3画面和16:9镜头

图片 8

图片 9

闭馆预览,同有时间能够继续拍片

图片 10

2.YUV数据格式介绍

  首先大家来打听怎么是YUV数据,当然那上面的篇章有为数不菲,在那地笔者就不详细的介绍了,大家能够看下那篇文章:
图文精解YUV420数据格式
,在此边我们珍视采用的是YUV数据格式是NV21(yuv420sp卡塔尔(英语:State of Qatar)和I420(yuv420p卡塔尔(英语:State of Qatar),它们都是4:2:0的格式,唯生机勃勃的差异便是它们的YUV数据排列不相像,NV21的排列是YYYYYYYY
VUVU =>YUV420SP,而I420的排列是YYYYYYYY UU VV =>YUV420P。
  其实大家领悟的NV21和I420的数据格式和数码的排列,大家就能够依据排列形式对其展开一些操作,比方在头里的小说共享多少个Android摄像头收罗的YUV数据旋转与镜像翻转的议程介绍的团团转镜像的操作。可是它的作用实际不是异常高,假诺只是简单的操作单风姿浪漫的YUV数据,那么倒未有太大影响。不过假使要接收于直播推流的话,要保管推流录像的帧率,那么对YUV数据管理的耗费时间就一定的首要。

您只怕感兴趣的稿子:

  • Android完结Camera2预览和拍片功能
  • Android 用 camera2 API
    自定义相机
  • Android camera2
    剖断相机功用是或不是可控的实例

2.Libyuv库的介绍

  其实对于YUV数据的拍卖,Google已经开源了叁个誉为libyuv的库特地用来YUV数据的管理。

2.1 什么是libyuv

  libyuv是谷歌(Google卡塔尔(قطر‎开源的达成各个YUV与讴歌ZDXGB之间互相转变、旋转、缩放的库。它是跨平台的,可在Windows、Linux、Mac、Android等操作系统,x86、x64、arm布局上拓宽编写翻译运维,扶助SSE、AVX、NEON等SIMD指令加快。

2.2 Android上怎么行使Libyuv

  libyuv并不能够一贯为Android开采一贯开展利用,须求对它进行编写翻译的操作。在这里间介绍的是利用Android
Studio的Cmake的措施开展libyuv的编写翻译操作,首先从官方网址Libyuv上下载libyuv库,下载的目录构造如下

图片 11

libyuv.png

  假若不恐怕下载的话,也足以从小编小说最后的demo中去开展拷贝。新键Android项目,并且创办的时候勾选项include
C++ Support,也正是改android项目扶持C,C++的编写翻译,假使对于Android
Stuido怎么着帮忙C,C++编写翻译不清楚的,请自行百度Google,这里就非常的少细说。项目开创之后将下载的libyuv库直接拷贝到src/main/cpp目录下

图片 12

libyuv.png

  更改CMakeLists.txt文件,并在src/main/cpp下开创YuvJni.cpp文件,CMakeLists.txt校正如下

cmake_minimum_required(VERSION 3.4.1)
include_directories(src/main/cpp/libyuv/include)
add_subdirectory(src/main/cpp/libyuv ./build)
aux_source_directory(src/main/cpp SRC_FILE)
add_library(yuvutil SHARED ${SRC_FILE})
find_library(log-lib log)
target_link_libraries(yuvutil ${log-lib} yuv)

  创设文件YuvUtil.java,在那间小编增添了八个主意实行yuv数据的操作

public class YuvUtil {

    static {
        System.loadLibrary("yuvutil");
    }

    /**
     * YUV数据的基本的处理
     *
     * @param src        原始数据
     * @param width      原始的宽
     * @param height     原始的高
     * @param dst        输出数据
     * @param dst_width  输出的宽
     * @param dst_height 输出的高
     * @param mode       压缩模式。这里为0,1,2,3 速度由快到慢,质量由低到高,一般用0就好了,因为0的速度最快
     * @param degree     旋转的角度,90,180和270三种
     * @param isMirror   是否镜像,一般只有270的时候才需要镜像
     **/
    public static native void compressYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int mode, int degree, boolean isMirror);

    /**
     * yuv数据的裁剪操作
     *
     * @param src        原始数据
     * @param width      原始的宽
     * @param height     原始的高
     * @param dst        输出数据
     * @param dst_width  输出的宽
     * @param dst_height 输出的高
     * @param left       裁剪的x的开始位置,必须为偶数,否则显示会有问题
     * @param top        裁剪的y的开始位置,必须为偶数,否则显示会有问题
     **/
    public static native void cropYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int left, int top);

    /**
     * 将I420转化为NV21
     *
     * @param i420Src 原始I420数据
     * @param nv21Src 转化后的NV21数据
     * @param width   输出的宽
     * @param width   输出的高
     **/
    public static native void yuvI420ToNV21(byte[] i420Src, byte[] nv21Src, int width, int height);
}

  同期在前头创制的YuvJni.cpp文件中丰裕对应的办法

extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_compressYUV(JNIEnv *env, jclass type,
                                         jbyteArray src_, jint width,
                                         jint height, jbyteArray dst_,
                                         jint dst_width, jint dst_height,
                                         jint mode, jint degree,
                                         jboolean isMirror) {
}

extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_cropYUV(JNIEnv *env, jclass type, jbyteArray src_, jint width,
                                     jint height, jbyteArray dst_, jint dst_width, jint dst_height,
                                     jint left, jint top) {
}

extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_yuvI420ToNV21(JNIEnv *env, jclass type, jbyteArray i420Src,
                                           jbyteArray nv21Src,
                                           jint width, jint height) {

}

3.利用Libyuv库进行YUV数据的操作

  接下去正是要libyuv对yuv数据开展缩放,旋转,镜像,裁剪等操作。在libyuv的骨子里运用进程中,越多的是用以直播推流前对Camera搜集到的YUV数据实行拍卖的操作。对前日,Camera的预览常常选择的是1080p,并且摄像头搜集到的数额是旋转之后的,日常的话前置录像头旋转了90度,前置录像头旋转了270度还要水平镜像。在上边包车型大巴例子中,就对Camera再次回到的yuv数据开展连锁的拍卖操作。

3.1 NV21转化为I420

  对于怎样得到Camera再次回到的YUV数据,不是本篇文章的机要,不打听的请自行百度Google。因为Camera重返的YUV数据只好是NV21和YV12二种,而libyuv的缩放旋转镜像的操作需求的是I420的多少格式,那么首先步正是将NV21(例子中Camera重返数据格式设置的是NV21卡塔尔(قطر‎转化为I420了。方法如下:

#include "libyuv.h"
void nv21ToI420(jbyte *src_nv21_data, jint width, jint height, jbyte *src_i420_data) {
    jint src_y_size = width * height;
    jint src_u_size = (width >> 1) * (height >> 1);

    jbyte *src_nv21_y_data = src_nv21_data;
    jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;

    jbyte *src_i420_y_data = src_i420_data;
    jbyte *src_i420_u_data = src_i420_data + src_y_size;
    jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;


    libyuv::NV21ToI420((const uint8 *) src_nv21_y_data, width,
                       (const uint8 *) src_nv21_vu_data, width,
                       (uint8 *) src_i420_y_data, width,
                       (uint8 *) src_i420_u_data, width >> 1,
                       (uint8 *) src_i420_v_data, width >> 1,
                       width, height);
}

  首先大家必得需指点入libyuv(#include
“libyuv.h”卡塔尔,在这里地大家用到的是libyuv::NV21ToI420艺术,大家来看下它传参

// Convert NV21 to I420.  Same as NV12 but u and v pointers swapped.
LIBYUV_API
int NV21ToI420(const uint8* src_y,
               int src_stride_y,
               const uint8* src_vu,
               int src_stride_vu,
               uint8* dst_y,
               int dst_stride_y,
               uint8* dst_u,
               int dst_stride_u,
               uint8* dst_v,
               int dst_stride_v,
               int width,
               int height) {
  return X420ToI420(src_y, src_stride_y, src_stride_y, src_vu, src_stride_vu,
                    dst_y, dst_stride_y, dst_v, dst_stride_v, dst_u,
                    dst_stride_u, width, height);
}

  首先第三个参数src_y指的是NV21多少中的Y的数量,大家掌握NV21的数目格式是YYYYYYYY
VUVU,同时NV21的数码大小是widthheight3/2,可以预知Y的多寡大小是widthheight,而V和U均为widthheight/4。第一个参数src_stride_y表示的是Y的数组行间隔,在这里处超级轻巧精晓是width。由此及彼src_vu和src_stride_vu也能够相呼应的明亮了。对于背后的参数dst_y,dst_stride_y,dst_u,dst_stride_u,dst_v
,dst_stride_v表示分别表示的是出口的I420数据的YUV八个轻重的多寡,最终的width和height约等于大家设置的Camera的预览的width和height了。

3.2 I420多少的缩放和旋转

  经过地点的NV21转会为I420操作之后,我们就足以对I420数据开展持续的缩放和旋转的操作,它们的传参跟下边包车型客车NV21ToI420是临近的,这里就不具体的介绍了。缩放的主意

void scaleI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint dst_width,
               jint dst_height, jint mode) {

    jint src_i420_y_size = width * height;
    jint src_i420_u_size = (width >> 1) * (height >> 1);
    jbyte *src_i420_y_data = src_i420_data;
    jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
    jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;

    jint dst_i420_y_size = dst_width * dst_height;
    jint dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);
    jbyte *dst_i420_y_data = dst_i420_data;
    jbyte *dst_i420_u_data = dst_i420_data + dst_i420_y_size;
    jbyte *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;

    libyuv::I420Scale((const uint8 *) src_i420_y_data, width,
                      (const uint8 *) src_i420_u_data, width >> 1,
                      (const uint8 *) src_i420_v_data, width >> 1,
                      width, height,
                      (uint8 *) dst_i420_y_data, dst_width,
                      (uint8 *) dst_i420_u_data, dst_width >> 1,
                      (uint8 *) dst_i420_v_data, dst_width >> 1,
                      dst_width, dst_height,
                      (libyuv::FilterMode) mode);
}

  值得注意的是,那边有二个缩放的方式选拔(libyuv::FilterMode卡塔尔(قطر‎,它的值分别有0,1,2,3三种,代表分歧的缩放格局,在自个儿实际的利用进程中,0的缩放速度是最快的,且远远快与别的的3种,何况就缩放的职能来看,以自家的眼眸观察,看不出有何样界别,这里为了确定保证进程,平日用FilterMode.kFilterNone就好了

typedef enum FilterMode {
  kFilterNone = 0,      // Point sample; Fastest.
  kFilterLinear = 1,    // Filter horizontally only.
  kFilterBilinear = 2,  // Faster than box, but lower quality scaling down.
  kFilterBox = 3        // Highest quality.
} FilterModeEnum;

  旋转的办法如下,不过在这里地要小心的是,因为Camera输出的多少是急需开展90度仍是270的转动,那么要潜心的正是旋转之后width和height也就反而了

void rotateI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint degree) {
    jint src_i420_y_size = width * height;
    jint src_i420_u_size = (width >> 1) * (height >> 1);

    jbyte *src_i420_y_data = src_i420_data;
    jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
    jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;

    jbyte *dst_i420_y_data = dst_i420_data;
    jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;
    jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;

    if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {
        libyuv::I420Rotate((const uint8 *) src_i420_y_data, width,
                           (const uint8 *) src_i420_u_data, width >> 1,
                           (const uint8 *) src_i420_v_data, width >> 1,
                           (uint8 *) dst_i420_y_data, height,
                           (uint8 *) dst_i420_u_data, height >> 1,
                           (uint8 *) dst_i420_v_data, height >> 1,
                           width, height,
                           (libyuv::RotationMode) degree);
    }
}

3.3 libyuv别的的局地操作

  libyuv的操作不止是上边的这几个,它还应该有镜像,裁剪的某个操作,同有的时候候还应该有一点其余数据格式的转会和对于的操作。饱含rgba与yuv数据的转速等。在篇章中,镜像和剪裁的操作就不加以陈述了,在demo之中笔者早已加入了进来了。

4.最后

  近些日子做直播推流,小录制的摄像中才接触到的libyuv库的施用,网络也会有局部有关的小说。可是多数不是很详细,要么随笔中的方法运用进度中有丰富多采的难点,要么便是格局相当不够周详和实际。那篇文章也着重是做了部分总计。最终贴上demo的Github地址:https://github.com/hzl123456/LibyuvDemo

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注