package com.yixia.camera;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.List;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.graphics.ImageFormat;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Area;
import android.hardware.Camera.AutoFocusCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.Size;
import android.os.AsyncTask;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import com.yixia.camera.model.MediaObject;
import com.yixia.camera.model.MediaObject.MediaPart;
import com.yixia.camera.util.DeviceUtils;
import com.yixia.camera.util.FileUtils;
import com.yixia.camera.util.StringUtils;
import com.yixia.videoeditor.adapter.UtilityAdapter;
/**
* 视频录制抽象类
*
* @author yixia.com
*
*/
public abstract class MediaRecorderBase implements Callback, PreviewCallback, IMediaRecorder {
/** 未知错误 */
public static final int MEDIA_ERROR_UNKNOWN = 1;
/** 预览画布设置错误 */
public static final int MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY = 101;
/** 预览错误 */
public static final int MEDIA_ERROR_CAMERA_PREVIEW = 102;
/** 自动对焦错误 */
public static final int MEDIA_ERROR_CAMERA_AUTO_FOCUS = 103;
public static final int AUDIO_RECORD_ERROR_UNKNOWN = 0;
/** 采样率设置不支持 */
public static final int AUDIO_RECORD_ERROR_SAMPLERATE_NOT_SUPPORT = 1;
/** 最小缓存获取失败 */
public static final int AUDIO_RECORD_ERROR_GET_MIN_BUFFER_SIZE_NOT_SUPPORT = 2;
/** 创建AudioRecord失败 */
public static final int AUDIO_RECORD_ERROR_CREATE_FAILED = 3;
/** 视频码率 1M */
public static final int VIDEO_BITRATE_NORMAL = 1024;
/** 视频码率 1.5M(默认) */
public static final int VIDEO_BITRATE_MEDIUM = 1536;
/** 视频码率 2M */
public static final int VIDEO_BITRATE_HIGH = 2048;
/** 开始转码 */
protected static final int MESSAGE_ENCODE_START = 0;
/** 转码进度 */
protected static final int MESSAGE_ENCODE_PROGRESS = 1;
/** 转码完成 */
protected static final int MESSAGE_ENCODE_COMPLETE = 2;
/** 转码失败 */
protected static final int MESSAGE_ENCODE_ERROR = 3;
/** 最大帧率 */
public static final int MAX_FRAME_RATE = 25;
/** 最小帧率 */
public static final int MIN_FRAME_RATE = 15;
/** 摄像头对象 */
protected Camera camera;
/** 摄像头参数 */
protected Camera.Parameters mParameters = null;
/** 摄像头支持的预览尺寸集合 */
protected List<Size> mSupportedPreviewSizes;
/** 画布 */
protected SurfaceHolder mSurfaceHolder;
/** 声音录制 */
protected AudioRecorder mAudioRecorder;
/** 转码Handler */
protected EncodeHandler mEncodeHanlder;
/** 拍摄存储对象 */
protected MediaObject mMediaObject;
/** 转码监听器 */
protected OnEncodeListener mOnEncodeListener;
/** 录制错误监听 */
protected OnErrorListener mOnErrorListener;
/** 录制已经准备就绪的监听 */
protected OnPreparedListener mOnPreparedListener;
/** 帧率 */
protected int mFrameRate = MIN_FRAME_RATE;
/** 摄像头类型(前置/后置),默认后置 */
protected int mCameraId = Camera.CameraInfo.CAMERA_FACING_BACK;
/** 视频码率 */
protected int mVideoBitrate = 2048;
/** 状态标记 */
protected boolean mPrepared, mStartPreview, mSurfaceCreated;
/** 是否正在录制 */
protected volatile boolean mRecording;
/** PreviewFrame调用次数,测试用 */
protected volatile long mPreviewFrameCallCount = 0;
public MediaRecorderBase() {
}
/**
* 设置预览输出SurfaceHolder
* @param sh
*/
@SuppressWarnings("deprecation")
public void setSurfaceHolder(SurfaceHolder sh) {
if (sh != null) {
sh.addCallback(this);
if (!DeviceUtils.hasHoneycomb()) {
sh.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
}
}
/** 设置转码监听 */
public void setOnEncodeListener(OnEncodeListener l) {
this.mOnEncodeListener = l;
mEncodeHanlder = new EncodeHandler(this);
}
/** 设置预处理监听 */
public void setOnPreparedListener(OnPreparedListener l) {
mOnPreparedListener = l;
}
/** 设置错误监听 */
public void setOnErrorListener(OnErrorListener l) {
mOnErrorListener = l;
}
/** 是否前置摄像头 */
public boolean isFrontCamera() {
return mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT;
}
/** 是否支持前置摄像头 */
@SuppressLint("NewApi")
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public static boolean isSupportFrontCamera() {
if (!DeviceUtils.hasGingerbread()) {
return false;
}
int numberOfCameras = android.hardware.Camera.getNumberOfCameras();
if (2 == numberOfCameras) {
return true;
}
return false;
}
/** 切换前置/后置摄像头 */
public void switchCamera(int cameraFacingFront) {
switch (cameraFacingFront) {
case Camera.CameraInfo.CAMERA_FACING_FRONT:
case Camera.CameraInfo.CAMERA_FACING_BACK:
mCameraId = cameraFacingFront;
stopPreview();
startPreview();
break;
}
}
/** 切换前置/后置摄像头 */
public void switchCamera() {
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK) {
switchCamera(Camera.CameraInfo.CAMERA_FACING_FRONT);
} else {
switchCamera(Camera.CameraInfo.CAMERA_FACING_BACK);
}
}
/**
* 自动对焦
*
* @param cb
* @return
*/
public boolean autoFocus(AutoFocusCallback cb) {
if (camera != null) {
try {
camera.cancelAutoFocus();
if (mParameters != null) {
String mode = getAutoFocusMode();
if (StringUtils.isNotEmpty(mode)) {
mParameters.setFocusMode(mode);
camera.setParameters(mParameters);
}
}
camera.autoFocus(cb);
return true;
} catch (Exception e) {
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_AUTO_FOCUS, 0);
}
if (e != null)
Log.e("Yixia", "autoFocus", e);
}
}
return false;
}
/** 连续自动对焦 */
private String getAutoFocusMode() {
if (mParameters != null) {
//持续对焦是指当场景发生变化时,相机会主动去调节焦距来达到被拍摄的物体始终是清晰的状态。
List<String> focusModes = mParameters.getSupportedFocusModes();
if ((Build.MODEL.startsWith("GT-I950") || Build.MODEL.endsWith("SCH-I959") || Build.MODEL.endsWith("MEIZU MX3")) && isSupported(focusModes, "continuous-picture")) {
return "continuous-picture";
} else if (isSupported(focusModes, "continuous-video")) {
return "continuous-video";
} else if (isSupported(focusModes, "auto")) {
return "auto";
}
}
return null;
}
/**
* 手动对焦
*
* @param focusAreas 对焦区域
* @return
*/
@SuppressLint("NewApi")
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public boolean manualFocus(AutoFocusCallback cb, List<Area> focusAreas) {
if (camera != null && focusAreas != null && mParameters != null && DeviceUtils.hasICS()) {
try {
camera.cancelAutoFocus();
// getMaxNumFocusAreas检测设备是否支持
if (mParameters.getMaxNumFocusAreas() > 0) {
// mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);//
// Macro(close-up) focus mode
mParameters.setFocusAreas(focusAreas);
}
if (mParameters.getMaxNumMeteringAreas() > 0)
mParameters.setMeteringAreas(focusAreas);
mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_MACRO);
camera.setParameters(mParameters);
camera.autoFocus(cb);
return true;
} catch (Exception e) {
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_AUTO_FOCUS, 0);
}
if (e != null)
Log.e("Yixia", "autoFocus", e);
}
}
return false;
}
/**
* 切换闪关灯,默认关闭
*/
public boolean toggleFlashMode() {
if (mParameters != null) {
try {
final String mode = mParameters.getFlashMode();
if (TextUtils.isEmpty(mode) || Camera.Parameters.FLASH_MODE_OFF.equals(mode))
setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
else
setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
return true;
} catch (Exception e) {
Log.e("Yixia", "toggleFlashMode", e);
}
}
return false;
}
/**
* 设置闪光灯
*
* @param value
* @see Camera.Parameters.FLASH_MODE_TORCH
* @see Camera.Parameters.FLASH_MODE_OFF
*/
private boolean setFlashMode(String value) {
if (mParameters != null && camera != null) {
try {
if (Camera.Parameters.FLASH_MODE_TORCH.equals(value) || Camera.Parameters.FLASH_MODE_OFF.equals(value)) {
mParameters.setFlashMode(value);
camera.setParameters(mParameters);
}
return true;
} catch (Exception e) {
Log.e("Yixia", "setFlashMode", e);
}
}
return false;
}
/** 设置码率 */
public void setVideoBitRate(int bitRate) {
if (bitRate > 0)
mVideoBitrate = bitRate;
}
/**
* 开始预览
*/
public void prepare() {
mPrepared = true;
if (mSurfaceCreated)
startPreview();
}
/**
* 设置视频临时存储文件夹
*
* @param key 视频输出的名称,同目录下唯一,一般取系统当前时间
* @param path 文件夹路径
* @return 录制信息对象
*/
public MediaObject setOutputDirectory(String key, String path) {
if (StringUtils.isNotEmpty(path)) {
File f = new File(path);
if (f != null) {
if (f.exists()) {
//已经存在,删除
if (f.isDirectory())
FileUtils.deleteDir(f);
else
FileUtils.deleteFile(f);
}
if (f.mkdirs()) {
mMediaObject = new MediaObject(key, path, mVideoBitrate);
}
}
}
return mMediaObject;
}
/** 设置视频信息 */
public void setMediaObject(MediaObject mediaObject) {
this.mMediaObject = mediaObject;
}
public void stopRecord() {
mRecording = false;
// 判断数据是否处理完,处理完了关闭输出流
if (mMediaObject != null) {
MediaPart part = mMediaObject.getCurrentPart();
if (part != null && part.recording) {
part.recording = false;
part.endTime = System.currentTimeMillis();
part.duration = (int) (part.endTime - part.startTime);
part.cutStartTime = 0;
part.cutEndTime = part.duration;
// 检测视频大小是否大于0,否则丢弃(注意有音频没视频的情况下音频也会丢弃)
// File videoFile = new File(part.mediaPath);
// if (videoFile != null && videoFile.length() < 1) {
// mMediaObject.removePart(part, true);
// }
}
}
}
/** 停止所有块的写入 */
private void stopAllRecord() {
mRecording = false;
if (mMediaObject != null && mMediaObject.getMedaParts() != null) {
for (MediaPart part : mMediaObject.getMedaParts()) {
if (part != null && part.recording) {
part.recording = false;
part.endTime = System.currentTimeMillis();
part.duration = (int) (part.endTime - part.startTime);
part.cutStartTime = 0;
part.cutEndTime = part.duration;
// 检测视频大小是否大于0,否则丢弃(注意有音频没视频的情况下音频也会丢弃)
File videoFile = new File(part.mediaPath);
if (videoFile != null && videoFile.length() < 1) {
mMediaObject.removePart(part, true);
}
}
}
}
}
/** 检测是否支持指定特性 */
private boolean isSupported(List<String> list, String key) {
return list != null && list.contains(key);
}
/**
* 预处理一些拍摄参数
* 注意:自动对焦参数cam_mode和cam-mode可能有些设备不支持,导致视频画面变形,需要判断一下,已知有"GT-N7100", "GT-I9308"会存在这个问题
*
*/
@SuppressWarnings("deprecation")
protected void prepareCameraParaments() {
if (mParameters == null)
return;
List<Integer> rates = mParameters.getSupportedPreviewFrameRates();
if (rates != null) {
if (rates.contains(MAX_FRAME_RATE)) {
mFrameRate = MAX_FRAME_RATE;
} else {
Collections.sort(rates);
for (int i = rates.size() - 1; i >= 0; i--) {
if (rates.get(i) <= MAX_FRAME_RATE) {
mFrameRate = rates.get(i);
break;
}
}
}
}
mParameters.setPreviewFrameRate(mFrameRate);
// mParameters.setPreviewFpsRange(15 * 1000, 20 * 1000);
mParameters.setPreviewSize(640, 480);// 3:2
// 设置输出视频流尺寸,采样率
mParameters.setPreviewFormat(ImageFormat.NV21);
//设置自动连续对焦
String mode = getAutoFocusMode();
if (StringUtils.isNotEmpty(mode)) {
mParameters.setFocusMode(mode);
}
//设置人像模式,用来拍摄人物相片,如证件照。数码相机会把光圈调到最大,做出浅景深的效果。而有些相机还会使用能够表现更强肤色效果的色调、对比度或柔化效果进行拍摄,以突出人像主体。
// if (mCameraId == Camera.CameraInfo.CAMERA_FACING_FRONT && isSupported(mParameters.getSupportedSceneModes(), Camera.Parameters.SCENE_MODE_PORTRAIT))
// mParameters.setSceneMode(Camera.Parameters.SCENE_MODE_PORTRAIT);
if (isSupported(mParameters.getSupportedWhiteBalance(), "auto"))
mParameters.setWhiteBalance("auto");
//是否支持视频防抖
if ("true".equals(mParameters.get("video-stabilization-supported")))
mParameters.set("video-stabilization", "true");
// mParameters.set("recording-hint", "false");
//
// mParameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
if (!DeviceUtils.isDevice("GT-N7100", "GT-I9308", "GT-I9300")) {
mParameters.set("cam_mode", 1);
mParameters.set("cam-mode", 1);
}
}
/** 开始预览 */
public void startPreview() {
if (mStartPreview || mSurfaceHolder == null || !mPrepared)
return;
else
mStartPreview = true;
try {
if (mCameraId == Camera.CameraInfo.CAMERA_FACING_BACK)
camera = Camera.open();
else
camera = Camera.open(mCameraId);
camera.setDisplayOrientation(90);
try {
camera.setPreviewDisplay(mSurfaceHolder);
} catch (IOException e) {
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_SET_PREVIEW_DISPLAY, 0);
}
Log.e("Yixia", "setPreviewDisplay fail " + e.getMessage());
}
//设置摄像头参数
mParameters = camera.getParameters();
mSupportedPreviewSizes = mParameters.getSupportedPreviewSizes();// 获取支持的尺寸
prepareCameraParaments();
camera.setParameters(mParameters);
setPreviewCallback();
camera.startPreview();
onStartPreviewSuccess();
if (mOnPreparedListener != null)
mOnPreparedListener.onPrepared();
} catch (Exception e) {
e.printStackTrace();
if (mOnErrorListener != null) {
mOnErrorListener.onVideoError(MEDIA_ERROR_CAMERA_PREVIEW, 0);
}
Log.e("Yixia", "startPreview fail :" + e.getMessage());
}
}
/** 预览调用成功,子类可以做一些操作 */
protected void onStartPreviewSuccess() {
}
/** 设置回调 */
protected void setPreviewCallback() {
Camera.Size size = mParameters.getPreviewSize();
if (size != null) {
PixelFormat pf = new PixelFormat();
PixelFormat.getPixelFormatInfo(mParameters.getPreviewFormat(), pf);
int buffSize = size.width * size.height * pf.bitsPerPixel / 8;
try {
camera.addCallbackBuffer(new byte[buffSize]);
camera.addCallbackBuffer(new byte[buffSize]);
camera.addCallbackBuffer(new byte[buffSize]);
camera.setPreviewCallbackWithBuffer(this);
} catch (OutOfMemoryError e) {
Log.e("Yixia", "startPreview...setPreviewCallback...", e);
}
Log.e("Yixia", "startPreview...setPreviewCallbackWithBuffer...width:" + size.width + " height:" + size.height);
} else {
camera.setPreviewCallback(this);
}
}
/** 停止预览 */
public void stopPreview() {
if (camera != null) {
try {
camera.stopPreview();
camera.setPreviewCallback(null);
// camera.lock();
camera.release();
} catch (Exception e) {
Log.e("Yixia", "stopPreview...");
}
camera = null;
}
mStartPreview = false;
}
/** 释放资源 */
public void release() {
stopAllRecord();
// 停止视频预览
stopPreview();
// 停止音频录制
if (mAudioRecorder != null) {
mAudioRecorder.interrupt();
mAudioRecorder = null;
}
mSurfaceHolder = null;
mPrepared = false;
mSurfaceCreated = false;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
this.mSurfaceHolder = holder;
this.mSurfaceCreated = true;
if (mPrepared && !mStartPreview)
startPreview();
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
this.mSurfaceHolder = holder;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mSurfaceHolder = null;
mSurfaceCreated = false;
}
@Override
public void onAudioError(int what, String message) {
if (mOnErrorListener != null)
mOnErrorListener.onAudioError(what, message);
}
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
mPreviewFrameCallCount++;
camera.addCallbackBuffer(data);
}
/**
* 测试PreviewFrame回调次数,时间1分钟
*
*/
public void testPreviewFrameCallCount() {
new CountDownTimer(1 * 60 * 1000, 1000) {
@Override
public void onTick(long millisUntilFinished) {
android.util.Log.e("[Vitamio Recorder]", "testFrameRate..." + mPreviewFrameCallCount);
mPreviewFrameCallCount = 0;
}
@Override
public void onFinish() {
}
}.start();
}
/** 接收音频数据 */
@Override
public void receiveAudioData(byte[] sampleBuffer, int len) {
}
/**
* 预处理监听
*
*/
public interface OnPreparedListener {
/**
* 预处理完毕,可以开始录制了
*/
void onPrepared();
}
/**
* 错误监听
*
*/
public interface OnErrorListener {
/**
* 视频录制错误
*
* @param what
* @param extra
*/
void onVideoError(int what, int extra);
/**
* 音频录制错误
*
* @param what
* @param message
*/
void onAudioError(int what, String message);
}
/** 转码接口 */
public interface OnEncodeListener {
/** 开始转码 */
void onEncodeStart();
/** 转码进度 */
void onEncodeProgress(int progress);
/** 转码完成 */
void onEncodeComplete();
/** 转码失败 */
void onEncodeError();
}
/** 开始转码,主要是合并视频片段 */
public void startEncoding() {
if (mMediaObject == null || mEncodeHanlder == null)
return;
mEncodeHanlder.removeMessages(MESSAGE_ENCODE_PROGRESS);
mEncodeHanlder.removeMessages(MESSAGE_ENCODE_COMPLETE);
mEncodeHanlder.removeMessages(MESSAGE_ENCODE_START);
mEncodeHanlder.removeMessages(MESSAGE_ENCODE_ERROR);
mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_START);
}
/** 合并视频片段 */
protected void concatVideoParts() {
new AsyncTask<Void, Void, Boolean>() {
@Override
protected Boolean doInBackground(Void... params) {
//合并ts流
String cmd = String.format("ffmpeg %s -i \"%s\" -vcodec copy -acodec copy -absf aac_adtstoasc -f mp4 -movflags faststart \"%s\"", FFMpegUtils.getLogCommand(), mMediaObject.getConcatYUV(), mMediaObject.getOutputTempVideoPath());
return UtilityAdapter.FFmpegRun("", cmd) == 0;
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_COMPLETE);
} else {
mEncodeHanlder.sendEmptyMessage(MESSAGE_ENCODE_ERROR);
}
}
}.execute();
}
/** 转码队列 */
public static class EncodeHandler extends Handler {
private WeakReference<MediaRecorderBase> mMediaRecorderBase;
public EncodeHandler(MediaRecorderBase l) {
mMediaRecorderBase = new WeakReference<MediaRecorderBase>(l);
}
@Override
public void handleMessage(Message msg) {
MediaRecorderBase mrb = mMediaRecorderBase.get();
if (mrb == null || mrb.mOnEncodeListener == null)
return;
OnEncodeListener listener = mrb.mOnEncodeListener;
switch (msg.what) {
case MESSAGE_ENCODE_START:
listener.onEncodeStart();
sendEmptyMessage(MESSAGE_ENCODE_PROGRESS);
break;
case MESSAGE_ENCODE_PROGRESS:
//查询片段是否都已经转码完成
final int progress = UtilityAdapter.FilterParserAction("", UtilityAdapter.PARSERACTION_PROGRESS);
if (progress == 100) {
listener.onEncodeProgress(progress);
mrb.concatVideoParts();//合并视频
} else if (progress == -1) {
sendEmptyMessage(MESSAGE_ENCODE_ERROR);
} else {
listener.onEncodeProgress(progress);
sendEmptyMessageDelayed(MESSAGE_ENCODE_PROGRESS, 200);
}
break;
case MESSAGE_ENCODE_COMPLETE:
listener.onEncodeComplete();
break;
case MESSAGE_ENCODE_ERROR:
listener.onEncodeError();
break;
}
}
};
}