一、概述
视频录制,在一般开发中很少遇到,大部分开发工作都是写写页面,请求接口,展示数据等等。真要遇到,可能采用第三方库实现,一来实现快速,二来可能觉得别人实现的比较好。特别是在开发周期很紧的情况下,一般都不会自己花时间实现。
其实最好是使用手机系统的录制视频,功能完善,稳定。实现起来最简单,简简单单几句代码:
//跳转系统的录制视频页面
val intent = Intent(MediaStore.ACTION_VIDEO_CAPTURE)
intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY,1)
intent.putExtra(MediaStore.EXTRA_DURATION_LIMIT,30)//录制时长
startActivityForResult(intent, 666)
//打开手机的选择视频页
// val intent = Intent()
// intent.action = Intent.ACTION_PICK
// intent.data = MediaStore.Video.Media.EXTERNAL_CONTENT_URI
// startActivityForResult(intent,666)
然后在onActiityResult方法中接收录制好的视频路径处理即可。
但如果需求是像微信那样app内的录制视频,就不能使用系统自带录制功能,需要自己实现。
下面将我自己实现的,记录下来。这里也只是实现了一个简单的录制功能,甚至还有问题:前置摄像头录制视频是镜像的。
另外下面的实现不支持在Android6.0以下的手机上使用,因为使用到了API23的方法:MediaCodec.createPersistentInputSurface(),主要是为了能支持横屏录制的视频方向为横屏。
先看看演示效果:
二、实现方案和细节
使用的Camera2 和 MediaRecorder。
如果使用Camera1的话,可能会更简单一些,Camera2用起来确实相对麻烦一点。不过Camera1毕竟已经被弃用了,且使用Camera1打开相机比Camera2要耗时一些。
Camera2使用
- 用CameraManager获取相机Id列表cameraIdList,然后openCamera指定的相机id,打开相机
- 打开成功后,使用 CameraDevice.createCaptureSession 创建CameraCaptureSession
- 创建成功后,使用CameraCaptureSession.setRepeatingRequest 发起预览请求,它需要传入CaptureRequest,通过CameraDevice.captureRequest创建,CaptureRequest可以设置一些参数,对焦、曝光、闪光灯等等
第2步 createCaptureSession 时需要传入Surface列表。
这里传入了两个Surface,一个是预览使用,由SurfaceView提供。
另一个是录制使用,通过MediaCodec.createPersistentInputSurface() 创建,设置给MediaRecorder。
如果预览时不创建MediaRecorder,只传入预览Surface,等到点击录制时,才创建MediaRecorder,需要重新创建createCaptureSession,传入新的Surface,这样虽然可以,但是点击录制时会很慢,预览画面会断一下。
第2步传入的Surface列表,还需要在第3步中使用CaptureRequest.addTarget 添加,两个地方必须对应,不然发起预览请求时会报错。
MediaRecorder配置
因为使用的是Camera2,所以不能使用MediaRecorder.setCamera()。
替代方法是使用MediaRecorder.surface,前提是需要设置数据源为Surface
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
且需要在mediaRecorder.prepare之后,mediaRecorder.surface 才可用。
后面因为要支持横屏录制,没有采用 mediaRecorder.surface 。
如果进入页面开启相机预览时手机竖屏,点击录制时手机横屏,因为在预览时就创建了mediaRecorder,并且setOrientationHint确定了视频方向,无法再改变(只能prepare之前设置),这时录制的视频方向肯定就不对。
要改变视频方向,只能重新创建 mediaRecorder ,但是重新创建mediaRecorder,同时也重新创建了一个新的Sueface,需要重新createCaptureSession传入新的Sueface。(改成点击录制时,创建mediaRecorder,然后重新createCaptureSession,测试中也发现画面会断一下,效果不好)。
正因如此,最终改为使用 MediaCodec.createPersistentInputSurface() 创建 Surface,然后 setInputSurface 给 mediaRecorder。MediaCodec.createPersistentInputSurface()创建的Surface只有在mediaRecorder.prepare之后才可用。 在点击录制时,重新配置mediaRecorder,设置新的方向。这样虽然mediaRecorder重新配置了,但是Surface还是同一个。
var mediaRecorder = MediaRecorder()
recordSurface = MediaCodec.createPersistentInputSurface()
var recordSurface = mRecordSurface
if (recordSurface == null) {
recordSurface = MediaCodec.createPersistentInputSurface()
mRecordSurface = recordSurface
}
mediaRecorder.setInputSurface(recordSurface)
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
//数据源来之surface
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
//设置QUALITY_HIGH,可以提高视频的录制质量(文件也会变大),但是不能设置编码格式和帧率等参数,否则报错
val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P)
mediaRecorder.setProfile(profile)
mediaRecorder.setMaxFileSize(0)
mediaRecorder.setVideoSize(width, height)
//视频方向
mediaRecorder.setOrientationHint(mRecordVideoOrientation)
val parentFile = externalCacheDir ?: cacheDir
val fileName = "${System.currentTimeMillis()}.mp4"
//不设置setOutputFile prepare时会报错
mediaRecorder.setOutputFile(parentFile.absolutePath + File.separator + fileName)
//prepare之后recordSurface才能用
mediaRecorder.prepare()
三、全部代码
/**
* 录制视频
* 支持后置、前置摄像头切换(但前置摄像头录制视频是镜像的,需要翻转) TODO
* Android 6.0以下不支持
* 支持横屏录制
* 2023/03/17
*/
@RequiresApi(Build.VERSION_CODES.M)
class VideoRecord23Activity : AppCompatActivity(), SurfaceHolder.Callback,
View.OnClickListener {
companion object {
const val DURATION = "duration"
const val FILE_NAME = "name"
const val FILE_PATH = "path"
}
private val requestPermissionCode = 52
private val requestActivityCode = 10
/**
* mCountDownMsg 录制进度倒计时msg,一秒钟发送一次
* mStartRecordMsg 开始录制
* mCameraOpenFailMsg 相机打开失败
* mCameraPreviewFailMsg 相机预览失败
* mRecordErrorMsg 录制出现错误
* 在 mCountDownHandler(主线程的Handler)中处理
*/
private val mCountDownMsg = 19
private val mStartRecordMsg = 20
private val mCameraOpenFailMsg = 21
private val mCameraPreviewFailMsg = 22
private val mRecordErrorMsg = 23
private lateinit var mSurfaceView :SurfaceView
private lateinit var mRecordProgressBar: ProgressBar
private lateinit var mRecordStateIv: ImageView
private lateinit var mFlashlightIv: ImageView
private lateinit var mSwitchCameraIv: ImageView
/**
* 录制视频文件路径
*/
@Volatile
private var mFilePath: String? = null
/**
* 录制的视频文件名
*/
@Volatile
private var mFileName: String? = null
/**
* 预览画面尺寸,和视频录制尺寸
*/
@Volatile
private var mRecordSize: Size? = null
/**
* 相机方向
*/
private var mCameraOrientation: Int = 0
/**
* 录制视频的方向,随着手机方向的改变而改变
*/
@Volatile
private var mRecordVideoOrientation: Int = 0
/**
* 默认打开后置相机 LENS_FACING_BACK
* 可以切换为前置相机 LENS_FACING_FRONT
*/
private var mFensFacing = CameraCharacteristics.LENS_FACING_BACK
/**
* 预览Surface
*/
@Volatile
private var mPreviewSurface: Surface? = null
/**
* 录制Surface
*/
@Volatile
private var mRecordSurface: Surface? = null
@Volatile
private var mCameraDevice: CameraDevice? = null
@Volatile
private var mCameraCaptureSession: CameraCaptureSession? = null
@Volatile
private var mCaptureRequest: CaptureRequest.Builder? = null
private var mOrientationEventListener: OrientationEventListener? = null
@Volatile
private var mMediaRecorder: MediaRecorder? = null
/**
* 是否是录制中的状态
* true:录制中
*/
@Volatile
private var mRecordingState = false
/**
* 是否录制完成。从手动点击开始录制到手动点击停止录制(或者录制时长倒计时到了),为录制完成,值为true。其他情况为false
*/
private var mRecordComplete = false
/**
* 闪光灯状态
* true 开启
* false 关闭
*/
private var mFlashlightState = false
/**
* 是否可以录制
* 录制完成,跳转播放页面后,返回时点击的完成,不能录制
* 其他情况都为可以录制
*/
private var mRecordable = true
/**
* 录制最大时长,时间到了之后录制完成
* 单位:秒
*/
private var mMaxRecordDuration = 30
/**
* 已录制的时长
*/
private var mCurrentRecordDuration = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_video_record)
mSurfaceView = findViewById(R.id.surfaceView)
mSurfaceView.holder.addCallback(this)
mRecordStateIv = findViewById(R.id.recordStateIv)
mRecordProgressBar = findViewById(R.id.recordProgressBar)
mFlashlightIv = findViewById(R.id.flashlightIv)
mSwitchCameraIv = findViewById(R.id.switchIv)
mRecordStateIv.setOnClickListener(this)
mFlashlightIv.setOnClickListener(this)
mSwitchCameraIv.setOnClickListener(this)
initOrientationEventListener()
mMaxRecordDuration = intent.getIntExtra(DURATION, 30)
mSurfaceView.scaleX = -1f
}
override fun surfaceCreated(holder: SurfaceHolder?) {
if (mRecordable) {
mRecordComplete = false
mFlashlightState = false
mFlashlightIv.setImageResource(R.drawable.flashlight_off)
checkPermissionAndOpenCamera()
mOrientationEventListener?.enable()
}
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
@SuppressLint("MissingPermission")
override fun onClick(v: View) {
when (v.id) {
R.id.recordStateIv -> {
if (mRecordingState) {
//停止录制
stopRecord()
mRecordComplete = true
//跳转预览页面
openPlayActivity()
} else {
startRecord()
mOrientationEventListener?.disable()
}
}
R.id.flashlightIv -> {
val captureRequest = mCaptureRequest ?: return
val cameraCaptureSession = mCameraCaptureSession ?: return
if (mFlashlightState) {
//闪光灯开启,点击关闭
captureRequest[CaptureRequest.FLASH_MODE] = CaptureRequest.FLASH_MODE_OFF
mFlashlightIv.setImageResource(R.drawable.flashlight_off)
} else {
//闪关灯关闭,点击开启
captureRequest[CaptureRequest.FLASH_MODE] = CaptureRequest.FLASH_MODE_TORCH
mFlashlightIv.setImageResource(R.drawable.flashlight_on)
}
mFlashlightState = !mFlashlightState
cameraCaptureSession.stopRepeating()
mCameraCaptureSession?.setRepeatingRequest(captureRequest.build(), mCameraCaptureSessionCaptureCallback, mRecordThreadHandler)
}
R.id.switchIv -> {
if (mRecordingState) {
//正在录制中
Toast.makeText(this, "正在录制", Toast.LENGTH_SHORT).show()
return
}
if (mFensFacing == CameraCharacteristics.LENS_FACING_BACK) {
//当前打开的是后置摄像头,切换到前置摄像头
mFensFacing = CameraCharacteristics.LENS_FACING_FRONT
close()
openCamera()
} else if (mFensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
//当前打开的是前置摄像头,切换到后置摄像头
mFensFacing = CameraCharacteristics.LENS_FACING_BACK
close()
openCamera()
}
}
}
}
private fun startRecord() {
mRecordThreadHandler.sendEmptyMessage(mStartRecordMsg)
mRecordingState = true
mRecordStateIv.setImageResource(R.drawable.record_start_state_bg)
mCurrentRecordDuration = 0
//开始录制倒计时
mUiHandler.sendEmptyMessageDelayed(mCountDownMsg, 1000)
}
private fun stopRecord() {
//视图变为 停止录制状态
mRecordingState = false
mRecordStateIv.setImageResource(R.drawable.record_stop_state_bg)
mRecordProgressBar.progress = 0
mUiHandler.removeMessages(mCountDownMsg)
val mediaRecorder = mMediaRecorder ?: return
try {
mediaRecorder.stop()
} catch (t: Throwable) {
t.printStackTrace()
}
}
/**
* 检查相机和录音与权限,并打开相机
*/
private fun checkPermissionAndOpenCamera(){
//录制音频权限ok
val audioOk = ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED
//相机权限ok
val cameraOk = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)== PackageManager.PERMISSION_GRANTED
if (audioOk && cameraOk) {
openCamera()
} else if (!audioOk && !cameraOk) {
val array = arrayOf(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)
ActivityCompat.requestPermissions(this, array, requestPermissionCode)
} else if (!audioOk && cameraOk) {
val array = arrayOf(Manifest.permission.RECORD_AUDIO)
ActivityCompat.requestPermissions(this, array, requestPermissionCode)
} else if (audioOk && !cameraOk) {
val array = arrayOf(Manifest.permission.CAMERA)
ActivityCompat.requestPermissions(this, array, requestPermissionCode)
}
}
@SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == requestPermissionCode) {
for (i in grantResults) {
if (i != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "请开启相机和录音权限", Toast.LENGTH_SHORT).show()
finish()
return
}
}
openCamera()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == requestActivityCode) {
when (resultCode) {
//确认
Activity.RESULT_OK -> {
val fileName = mFileName ?: return
val filePath = mFilePath ?: return
//不能再录制
mRecordable = false
val intent = Intent()
//把录制的视频文件名、文件路径传给外面调用的页面
intent.putExtra(FILE_NAME, fileName)
intent.putExtra(FILE_PATH, filePath)
setResult(Activity.RESULT_OK, intent)
finish()
}
//重新录制
Activity.RESULT_CANCELED -> {
//删除文件,重新录制
deleteRecordFile()
}
}
}
}
/**
* 页面暂停时,关闭相机,停止录制
*/
override fun onStop() {
super.onStop()
close()
mOrientationEventListener?.disable()
mRecordThreadHandler.removeMessages(mStartRecordMsg)
}
override fun onDestroy() {
super.onDestroy()
mRecordThreadHandler.looper.quit()
mPreviewSurface?.release()
mRecordSurface?.release()
mMediaRecorder?.release()
mMediaRecorder = null
}
/**
* 准备录制相关处理
*/
@RequiresPermission(Manifest.permission.CAMERA)
fun openCamera() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//使用context可能会出现内存泄漏(红米手机上),CameraManager会一直持有context
val cameraManager = applicationContext.getSystemService(CAMERA_SERVICE) as? CameraManager
val cameraIdList = cameraManager?.cameraIdList
if (cameraManager == null || cameraIdList == null || cameraIdList.isEmpty()) {
Toast.makeText(this, "无法使用设备相机", Toast.LENGTH_SHORT).show()
finish()
return
}
for (id in cameraIdList) {
var cameraCharacteristics: CameraCharacteristics? = null
try {
cameraCharacteristics = cameraManager.getCameraCharacteristics(id)
} catch (t: Throwable) {
t.printStackTrace()
}
if (cameraCharacteristics == null) {
continue
}
val fensFacing = cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
if (fensFacing != mFensFacing) {
continue
}
val level = cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
val capabilities = cameraCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES)
mCameraOrientation = cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
val map = cameraCharacteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) ?: continue
// //获取预览支持的分辨率
// val outputSizes = map.getOutputSizes(SurfaceHolder::class.java)
// Log.i("TAG", "prepareRecord: outputSizes ${Arrays.toString(outputSizes)}")
//获取录制支持的分辨率
val recorderSizes = map.getOutputSizes(MediaRecorder::class.java)
// Log.i("TAG", "prepareRecord: recorderSizes ${Arrays.toString(recorderSizes)}")
val recordSize = getRecordSize(recorderSizes)
mRecordSize = recordSize
resizeSurfaceSize(recordSize.width, recordSize.height)
mSurfaceView.holder.setFixedSize(recordSize.width, recordSize.height)
try {
cameraManager.openCamera(id, mCameraDeviceStateCallback, mRecordThreadHandler)
} catch (t: Throwable) {
t.printStackTrace()
Toast.makeText(this, "相机打开失败,请关闭重试", Toast.LENGTH_SHORT).show()
}
break
}
} else {
//6.0以下
Toast.makeText(this, "Android系统版本太低不支持", Toast.LENGTH_SHORT).show()
finish()
}
}
private val mCameraDeviceStateCallback: CameraDevice.StateCallback by lazy(LazyThreadSafetyMode.NONE) {
object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
mCameraDevice = camera
val recordSize = mRecordSize ?: return
//预览surface
val previewSurface = mSurfaceView.holder.surface
mPreviewSurface = previewSurface
setupMediaRecorder(recordSize.width, recordSize.height, false)
val recordSurface = mRecordSurface
mRecordSurface = recordSurface
val surfaceList = listOf(previewSurface, recordSurface)
camera.createCaptureSession(surfaceList, mCameraCaptureSessionStateCallback, mRecordThreadHandler)
}
override fun onDisconnected(camera: CameraDevice) {
//相机连接断开
if (mCameraDevice != null) {
close()
} else {
camera.close()
}
}
override fun onError(camera: CameraDevice, error: Int) {
camera.close()
mUiHandler.sendEmptyMessage(mCameraOpenFailMsg)
}
}
}
private val mCameraCaptureSessionStateCallback: CameraCaptureSession.StateCallback by lazy {
object : CameraCaptureSession.StateCallback(){
override fun onConfigured(session: CameraCaptureSession) {
mCameraCaptureSession = session
val camera = mCameraDevice ?: return
val previewSurface = mPreviewSurface ?: return
val recordSurface = mRecordSurface ?: return
val captureRequest = camera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)
mCaptureRequest = captureRequest
captureRequest.addTarget(previewSurface)
captureRequest.addTarget(recordSurface)
//对焦
captureRequest.set(CaptureRequest.CONTROL_AF_MODE,CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO)
//自动曝光
captureRequest.set(CaptureRequest.CONTROL_AE_MODE,CaptureRequest.CONTROL_AE_MODE_ON)
//进行重复请求录制预览
session.setRepeatingRequest(captureRequest.build(), mCameraCaptureSessionCaptureCallback, mRecordThreadHandler)
}
override fun onConfigureFailed(session: CameraCaptureSession) {
session.close()
mUiHandler.sendEmptyMessage(mCameraPreviewFailMsg)
}
}
}
private val mCameraCaptureSessionCaptureCallback: CameraCaptureSession.CaptureCallback by lazy(LazyThreadSafetyMode.NONE){
object :CameraCaptureSession.CaptureCallback(){
override fun onCaptureCompleted(session: CameraCaptureSession, request: CaptureRequest, result: TotalCaptureResult) {
super.onCaptureCompleted(session, request, result)
//这个方法在预览过长中,会一直被回调
// Log.i("TAG", "onCaptureCompleted thread ${Thread.currentThread().name}")
}
override fun onCaptureFailed(session: CameraCaptureSession, request: CaptureRequest, failure: CaptureFailure) {
super.onCaptureFailed(session, request, failure)
}
}
}
/**
* 录制线程的Handler
*/
private val mRecordThreadHandler: Handler by lazy(LazyThreadSafetyMode.NONE) {
val recordThread = HandlerThread("RecordVideoThread")
recordThread.start()
object : Handler(recordThread.looper) {
override fun handleMessage(msg: Message) {
if (isFinishing || isDestroyed) return
when (msg.what) {
mStartRecordMsg -> {//开始录制
if (!mRecordingState) {//不是开始录制状态,return
return
}
val recordSize = mRecordSize ?: return
try {
//重新配置MediaRecorder,因为用户刚打开页面时的手机方向,和点击录制时的手机方向可能不一样,所以重新配置。注意是为了支持横屏录制的视频为横屏视频,不然都是竖屏视频
setupMediaRecorder(recordSize.width, recordSize.height, true)
//视图变为录制状态
mMediaRecorder?.start()
} catch (t: Throwable) {
t.printStackTrace()
//录制出现错误
mUiHandler.sendEmptyMessage(mRecordErrorMsg)
}
}
}
}
}
}
/**
* ui线程Handler 处理录制倒计时,相机打开失败相关消息
*/
private val mUiHandler: Handler by lazy(LazyThreadSafetyMode.NONE) {
object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message?) {
if (isFinishing || isDestroyed) {
return
}
when(msg?.what){
mCountDownMsg -> {
mCurrentRecordDuration += 1
val progress = (mCurrentRecordDuration * 1f / mMaxRecordDuration * 100 + 0.5f).toInt()
mRecordProgressBar.progress = progress
if (mCurrentRecordDuration >= mMaxRecordDuration) {
//录制时间到了,停止录制
stopRecord()
mRecordComplete = true
//跳转预览页面
openPlayActivity()
} else {
sendEmptyMessageDelayed(mCountDownMsg, 1000)
}
}
mCameraOpenFailMsg -> {
Toast.makeText(this@VideoRecord23Activity, "相机打开失败,请关闭重试", Toast.LENGTH_SHORT).show()
}
mCameraPreviewFailMsg -> {
Toast.makeText(this@VideoRecord23Activity, "相机预览失败,请关闭重试", Toast.LENGTH_SHORT).show()
}
}
}
}
}
/**
* 创建并配置 MediaRecorder
* @param width 视频宽度
* @param height 视频高度
* @param outputFileCreated 输出文件是否已经创建;第一次prepare时,文件已经创建了,开始录制时,不用再次创建文件
*/
private fun setupMediaRecorder(width: Int, height: Int, outputFileCreated: Boolean): MediaRecorder {
var mediaRecorder = mMediaRecorder
if (mediaRecorder == null) {
mediaRecorder = MediaRecorder()
mMediaRecorder = mediaRecorder
} else {
mediaRecorder.reset()
}
var recordSurface = mRecordSurface
if (recordSurface == null) {
recordSurface = MediaCodec.createPersistentInputSurface()
mRecordSurface = recordSurface
}
mediaRecorder.setInputSurface(recordSurface)
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC)
//数据源来之surface
mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE)
//设置QUALITY_HIGH,可以提高视频的录制质量(文件也会变大),但是不能设置编码格式和帧率等参数,否则报错
val profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P)
mediaRecorder.setProfile(profile)
mediaRecorder.setMaxFileSize(0)
mediaRecorder.setVideoSize(width, height)
//视频方向
mediaRecorder.setOrientationHint(mRecordVideoOrientation)
//录制文件没有创建,创建文件
if (!outputFileCreated) {
val parentFile = externalCacheDir ?: cacheDir
val fileName = "${System.currentTimeMillis()}.mp4"
mFileName = fileName
mFilePath = parentFile.absolutePath + File.separator + fileName
}
//不设置setOutputFile prepare时会报错
mediaRecorder.setOutputFile(mFilePath)
//prepare之后recordSurface才能用
mediaRecorder.prepare()
return mediaRecorder
}
/**
* 页面关闭或不在前台时,停止录制、释放相机
*/
private fun close() {
if (mRecordingState) {
//停止录制
stopRecord()
}
if (!mRecordComplete) {
//没有录制完成,或者没有开始录制过(MediaRecorder prepare时会创建文件),删除录制的文件
deleteRecordFile()
}
//释放相机
val previewSurface = mPreviewSurface
if (previewSurface != null) {
mCaptureRequest?.removeTarget(previewSurface)
}
val recordSurface = mRecordSurface
if (recordSurface != null) {
mCaptureRequest?.removeTarget(recordSurface)
}
mCameraCaptureSession?.close()
mCameraDevice?.close()
mCaptureRequest = null
mCameraCaptureSession = null
mCameraDevice = null
}
/**
* 删除录制的文件
*/
private fun deleteRecordFile() {
val filePath = mFilePath ?: return
try {
val file = File(filePath)
if (file.exists()) {
file.delete()
}
mFilePath = null
} catch (t: Throwable) {
t.printStackTrace()
}
}
/**
* 获取录制的视频尺寸
* @param sizes 支持的尺寸列表
*/
private fun getRecordSize(sizes: Array<Size>): Size {
//参考尺寸 1280*720
val compareWidth = 1280
val compareHeight = 720
var resultSize = sizes[0]
var minDiffW = Int.MAX_VALUE
var minDiffH = Int.MAX_VALUE
for (size in sizes) {
if (size.width == compareWidth && size.height == compareHeight) {
resultSize = size
break
}
//找到最接近 1280*720的size
val diffW = abs(size.width - compareWidth)
val diffH = abs(size.height - compareHeight)
if (diffW < minDiffW && diffH < minDiffH) {
minDiffW = diffW
minDiffH = diffH
resultSize = size
}
}
return resultSize
}
/**
* 根据视频宽高,修改surfaceView的宽高,来适应预览尺寸
*
* @param width 预览宽度
* @param height 预览高度
*/
private fun resizeSurfaceSize(height: Int, width: Int) {
val displayW: Int = mSurfaceView.width
val displayH: Int = mSurfaceView.height
if (displayW == 0 || displayH == 0) return
var ratioW = 1f
var ratioH = 1f
if (width != displayW) {
ratioW = width * 1f / displayW
}
if (height != displayH) {
ratioH = height * 1f / displayH
}
var finalH = displayH
var finalW = displayW
if (ratioW >= ratioH) {
finalH = (height / ratioW).toInt()
} else {
finalW = (width / ratioH).toInt()
}
val layoutParams = mSurfaceView.layoutParams
if (layoutParams.width == finalW && layoutParams.height == finalH) {
return
}
layoutParams.width = finalW
layoutParams.height = finalH
mSurfaceView.layoutParams = layoutParams
}
/**
* 监听手机方向改变,计算录制时的视频方向。横屏录制时,视频横屏。竖屏录制时,视频竖屏
*/
private fun initOrientationEventListener() {
val orientationEventListener = object : OrientationEventListener(this) {
override fun onOrientationChanged(orientation: Int) {
if (orientation == ORIENTATION_UNKNOWN) return
val rotation = (orientation + 45) / 90 * 90
if (mFensFacing == CameraCharacteristics.LENS_FACING_BACK) {
//后置摄像头
mRecordVideoOrientation = (mCameraOrientation + rotation) % 360
} else if (mFensFacing == CameraCharacteristics.LENS_FACING_FRONT) {
//前置摄像头
mRecordVideoOrientation = mCameraOrientation - rotation
}
}
}
mOrientationEventListener = orientationEventListener
}
/**
* 跳转录制视频预览页面
*/
private fun openPlayActivity() {
//val intent = Intent(this, VideoPlayActivity::class.java)
//intent.putExtra(VideoPlayActivity.FILE_PATH, mFilePath)
//startActivity(intent)
}
}