Android 前置摄像头预览与编码

Android Camera前置摄像头采集。基于android.hardware.Camera,已经提示过时

目标:在前置摄像头预览过程中,采集预览数据并编码到本地。

1. 设置摄像头的预览

  1. 获取摄像头        可以通过CameraInfo常量直接获取或者根据遍历摄像头获取。
    int number = Camera.getNumberOfCameras();
    for (int cameraId = 0; cameraId < number; cameraId++) {
        Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
        Camera.getCameraInfo(cameraId, cameraInfo);
        if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
            mCamera = Camera.open(cameraId);
        }
    }
  2. 开启预览           在设置SurfaceHolder或者SurfaceTexture后,调用startPreview预览。摄像头将视频数据渲染至Holder或者Texture纹理上。
    mCamera.setPreviewTexture(surfaceTexture);
    mCamera.startPreview();

    需要注意的是本文中使用TextureView作为渲染的容器,在onCreate阶段注册了SurfaceViewTextureListener,Camera的获取和预览都是在onSurfaceTextureAvailable回调函数中调用的。使用SurfaceView也一样需要在Holder的Callback中启动,替换对应的参数即可。

       startPreview之后显示应该是有问题的,需要在预览之前设置参数。不同机型支持的参数不同,因此在设参前调用相关的support接口从内置的参数中选取合适的参数。此处主要调整预览的尺寸和方向。另外摄像头预览画面回调中画面格式默认是NV21的。

companion object {
    /**
     * 计算最完美的Size
     * @param sizes
     * @param expectWidth
     * @param expectHeight
     * @return
     */
    fun calculatePerfectSize(
        sizes: List<Camera.Size>, expectWidth: Int,
        expectHeight: Int, ratio: Float
    ): Camera.Size {
        var result = sizes[0]
        var widthOrHeight = false // 判断存在宽或高相等的Size
        // 辗转计算宽高最接近的值
        for (size in sizes) { // 如果宽高相等,则直接返回
            if (size.width == expectWidth && size.height == expectHeight) {
                result = size
                break
            }
            val w = (expectWidth * ratio).toInt()
            // 仅仅是宽度相等,计算高度最接近的size
            if (size.width == w) {
                widthOrHeight = true
                if (abs(result.height - expectHeight) > abs(size.height - expectHeight)) {
                    result = size
                }
            } else if (size.height == expectHeight) {
                widthOrHeight = true
                if (abs(result.width - w) > abs(size.width - w)) {
                    result = size
                }
            } else if (!widthOrHeight) {
                if (abs(result.width - w) > abs(size.width - w)
                    && abs(result.height - expectHeight) > abs(size.height - expectHeight)
                ) {
                    result = size
                }
            }
        }
        return result
    }

    /**
     * 得到摄像头默认旋转角度后,旋转回来  注意是顺时针旋转
     *
     */
    fun setCameraDisplayOrientation(
        context: Context,
        camera: Camera?,
        cameraID: Int
    ) {
        val info = getCameraInfo(camera, cameraID)
        val rotation =
            (context as Activity).windowManager.defaultDisplay.rotation
        var degrees = 0
        when (rotation) {
            Surface.ROTATION_0 -> degrees = 0
            Surface.ROTATION_90 -> degrees = 90
            Surface.ROTATION_180 -> degrees = 180
            Surface.ROTATION_270 -> degrees = 270
        }
        var result: Int =
            if (info.front == Camera.CameraInfo.CAMERA_FACING_FRONT) {
                val temp = (info.orientation + degrees) % 360
                (360 - temp) % 360 // compensate the mirror
            } else { // back-facing
                (info.orientation - degrees + 360) % 360
            }
        camera?.setDisplayOrientation(result)
    }
}

至此,已经可以屏幕上看到尺寸和方向都正常的预览画面了。

2.摄像头画面采集

Camera类

public final void addCallbackBuffer(byte[] callbackBuffer)
public final void setPreviewCallbackWithBuffer(PreviewCallback cb)

addCallbackBuffer: 创建预览内存缓冲区,每当有帧画面显示时填入此内存区域。除了YV12格式以外,内存区域的大小=长 * 宽 * 像素占用字节,像素占用字节数可以通过ImageFormat.getBitsPerPixel方法算出。如果使用YV12格式,可以通过Camera.Parameters#setPreviewFormat内所罗列的方程计算得出。这个方法只有在setPreviewCallabackWithBuffe调用时才有必要设置。而且摄像头预览画面默认是NV21或者YV12的,官方强烈推荐这两种格式,因为它们时所有设想设备都支持的格式。当callbackBuffer的内存太小无法装载预览帧数据时,会被移除队列从而不再回调。

setPreviewCallbackWithBuffer: 每当摄像头预览画面更新都会回调,参数data就是addCallbackBuffer设置的缓冲区。

本文基于FFmpegFrameRecorder进行画面采集:

1. 创建FFmpegFrameRecorder

public FFmpegFrameRecorder(File file, int imageWidth, int imageHeight, int audioChannels)

file: 视频编码文件输出路径

imageWidth/imageHeight: 输出视频的宽高。如果使用Camera.Size内的宽高需要注意横竖屏的问题,以OnePlus7T为例,前摄默认角度为270度,w=2176,h=1080。与看上去屏幕宽高正好相反(可能是我理解有偏差)。

audioChannels: 声道数,可以写死为1即单声道。

设置参数

format: 输出文件的格式,可以为mp4

frameRate: 帧率,30就可以了

videoCodec: 编码格式,可设为 AV_CODEC_ID_H264即h264编码

crf: ffmpeg参数,控制转码,0~51,0为无损,数值越大画质越差文件越小,18~28合适,18几乎就是视觉无损的了,28可以作为通用选择。

preset: ffmpeg参数, 指定编码速度。ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo,按顺序从快到慢

tune: ffmpeg参数,视觉优化参数。film:  电影、真人类型; animation:  动画; grain:      需要保留大量的grain时用; stillimage:  静态图像编码时使用; psnr:      为提高psnr做了优化的参数; ssim:      为提高ssim做了优化的参数; fastdecode: 可以快速解码的参数; zerolatency:零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码。

2. FFmpeg 滤镜参数 FFmpeg Filters Documentation

transpose: 旋转翻转效果  transpose=[cclock_flip | ......]

crop: 裁剪  crop=w:h:x:y  输出视频的宽高

scale: 缩放  scale=w:h.    输出视频的宽高

hflip: 水平翻转

vflip: 垂直翻转

FFmpegFrameFilter滤镜类,以 【,】分割传入上述参数构建滤镜对象。

public FFmpegFrameFilter(String filters, int imageWidth, int imageHeight)

构造函数中的imageWidth/imageHeight需要设置为Camera.Size中对应的宽高,即预览视频的宽高。        

后通过FFmpegFrameFilter的push/pull完成视频帧的滤镜处理。

Frame

public Frame(int width, int height, int depth, int channels)

width/height: 预览视频的宽/高

depth: 像素位数。默认8

channel: 图像通道。默认为2,没明白参数含义。

以上对象在构建时需要注意宽高参数设置,否则会造成ffmpeg编码错误导致异常退出或者无法编码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值