Android Camera前置摄像头采集。基于android.hardware.Camera,已经提示过时。
目标:在前置摄像头预览过程中,采集预览数据并编码到本地。
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);
}
}
- 开启预览 在设置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编码错误导致异常退出或者无法编码。