Android 音视频开发【视频篇】【二】视频采集 | 8月更文挑战

语言: CN / TW / HK

这是我参与8月更文挑战的第2天,活动详情查看:8月更文挑战

上一章介绍了RGBYUV格式,本章将介绍如何采集YUV格式的数据

一、视频相关概念

1.1 分辨率

用以横向纵向的像素点个数来衡量一副图像的大小,比如1080*960,其中1080表示横向的像素点个数为1080个,960表示纵向的像素点个数为960

1.2 帧率

1s内有多少个副图像,比如30fps,表示1s内有30副图像,即30帧,而60fps,表示1s内有60帧,更高的还有90fps120fps,帧率越高,相对来说,画面就会更流畅

根据人眼的视觉暂留特性,24fps时,人眼就会认为是流畅的,而更高的帧率,会给人一种身临其境的感觉

当然,也不是帧率越高越好,需要考虑显示器的刷新率,帧率过高可能反而浪费不必要的资源

1.3 码率/比特率

码率,也即比特率,单位是bps,表示视频的比特数,计算公式是:

$$ 码率 = 文件大小(kb) / 时长(s) $$

二、视频采集

采集视频,在手机中可以通过摄像头进行采集,那么,在Android也提供了很多对摄像头进行操作的类

  • Camera

    操作相对简单,提供对摄像头的很多的操作方法,比如打开、关闭摄像头、变焦、拍照等,不过谷歌已经不建议使用,而是建议使用CameraX,或者是Camera2

  • Camera2

    Camera2相对于Camera增加了很多特性,比如对Api的架构进行了很大的优化,比Camera更先进,还可以获取每帧的信息,等等,还有很多特性,不过,Camera2相对于Camera,使用上变得更加复杂

  • CameraX

    JetPack家族的一员,CameraX是在Camea2之后新出的一个相机框架,可能是谷歌觉得虽然Camera2功能强大,但是使用起来确实麻烦了很多,所有为了在保证功能的前提的下,操作变得更简洁,所有推出了CameraX

虽然谷歌建议我们使用CameraX或者是Camera2,但是视频系列的相关文章,还是以Camera,不为别的,就是为了简单,哈哈

2.1 Camera

关于相机的使用,可以放入子线程去使用

首先,第一步,当然是打开相机

打开相机

java camera = Camera.open(id);

打开相机,使用的是Camera类的静态方法open(),该方法需要传入一个int类型的id,有两个值可选

  • Camera.CameraInfo.CAMERA_FACING_BACK

    后摄

  • Camera.CameraInfo.CAMERA_FACING_FRONT

    前摄

在获取camera实例后,此时摄像头并没有正在的打开

接下来需要对camera做一些参数配置,比如预览尺寸

获取相机参数

java Camera.Parameters parameters = camera.getParameters();

camera进行参数设置,先得调用该方法,然后下面就会对parameters进行一些参数设置

获取全部预览尺寸

java List<Camera.Size> previewSizeList = parameters.getSupportedPreviewSizes();

通过调用parameters.getSupportedPreviewSizes()获取当前相机所支持的预览尺寸,因为我们传入相机的预览尺寸必须得是相机所支持的

一般来说,我们通常希望相机的尺寸能够和屏幕一样大,或者是和屏幕同等比例,这样,显示出来的效果也就比较好,所有下面通过一个算法,找到最佳的尺寸

java /** * 寻找最合适的尺寸 */ public static Camera.Size findTheBestSize(List<Camera.Size> sizeList, int screenW, int screenH) {    if (sizeList == null || sizeList.isEmpty()) {        throw new IllegalArgumentException();   }    Camera.Size bestSize = sizeList.get(0);    for (Camera.Size size : sizeList) {        int width = size.height;        int height = size.width;        float ratioW = (float) width / screenW;        float ratioH = (float) height / screenH;        if (ratioW == ratioH) {            bestSize = size;            break;       }   }    return bestSize; }

一个参数,直接传入相机所支持的预览尺寸,后面两个参数是屏幕的尺寸

该算法的目的就是遍历每一个预览尺寸,但预览尺寸的比例和屏幕的尺寸比例一致时,就可以返回,而如果当遍历完,还是没找到合适的尺寸,就返回第一个相机所支持的尺寸

通过此方法,就获取到了一个最佳的尺寸

设置预览尺寸

java parameters.setPreviewSize(width, height);

本章的目的是采样视频数据,所以我们得设置相机预览数据的回调格式

设置预览格式

java parameters.setPreviewFormat(ImageFormat.NV21);

相机设置参数

java camera.setParameters(parameters);

在对parameters做了一系列的设置后,我们就可以将其设置给camera

设置旋转角度

java camera.setDisplayOrientation(CameraUtils.getDisplayOrientation(activity, id));

设置旋转角度,使用的是CameraUtils.getDisplayOrientation(activity, id)方法

java /** * 获取摄像头旋转角度 */ public static int getDisplayOrientation(Activity activity, int facing) {    Camera.CameraInfo info = new Camera.CameraInfo();    Camera.getCameraInfo(facing, info);    int rotation = activity.getWindowManager().getDefaultDisplay()           .getRotation();    int degrees = 0;    switch (rotation) {        case Surface.ROTATION_0:            degrees = 0;            break;        case Surface.ROTATION_90:            degrees = 90;            break;        case Surface.ROTATION_180:            degrees = 180;            break;        case Surface.ROTATION_270:            degrees = 270;            break;   }    int result;    if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {        result = (info.orientation + degrees) % 360;        result = (360 - result) % 360;  // compensate the mirror   } else {  // back-facing        result = (info.orientation - degrees + 360) % 360;   }    return result; }

设置数据缓存和回调

之前设置了预览数据的回调格式为NV21,它是一种YUV420的格式

camera提供了addCallbackBuffersetPreviewCallbackWithBuffer的方法,用于接收预览数据和设置回调,它能够减少对象的创建

java camera.addCallbackBuffer(nv21Data); camera.setPreviewCallbackWithBuffer(this);

设置回调后,我们需要重写onPreviewFrame方法

java @Override public void onPreviewFrame(byte[] data, Camera camera) {    Log.d(TAG, "onPreviewFrame: " + nv21Data.length);    camera.addCallbackBuffer(nv21Data);    if (fos == null) {        return;   }    try {        fos.write(nv21Data);   } catch (IOException e) {        e.printStackTrace();   } }

在该方法中,每次回调预览数据,都会先调用addCallbackBuffer方法,该方法会复用对应的对象

接着后面将数据写入到文件中

设置SurfaceTexture,开启预览

当然,上面还没结束,还需要设置SurfaceTexture才能正常开启预览,也就才能正常获取预览回调数据

java try {    camera.setPreviewTexture(new SurfaceTexture(0)); } catch (IOException e) {    e.printStackTrace(); } camera.startPreview();

这里设置的是一个new出来的surfaceTexture,产生的效果是无预览采样视频

你也可以使用SurfaceView或者TextureView显示预览画面

停止预览,释放资源

当我们停止录制时,需要停止预览并释放资源

java private void release() {    if (camera != null) {        camera.stopPreview();        camera.release();        camera = null;   }    if (fos != null) {        try {            fos.close();            fos = null;       } catch (IOException e) {            e.printStackTrace();       }   } }

三、GitHub

YuvRecord

YuvActivity