最下面是GIT 地址
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3i0EaImv-1637722081187)(https://liudao01.github.io/picture/img/视频录制.gif)]
https://liudao01.github.io/picture/img/视频录制.gif
效果图
activity代码
package com.sinochem.www.station.activity
import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.app.AlertDialog
import android.app.ProgressDialog
import android.content.pm.PackageManager
import android.graphics.Point
import android.net.Uri
import android.os.Bundle
import android.os.Environment
import android.os.SystemClock
import android.text.TextUtils
import android.util.Log
import android.util.Size
import android.view.View
import android.widget.*
import androidx.annotation.NonNull
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.google.common.util.concurrent.ListenableFuture
import com.sinochem.www.station.R
import com.sinochem.www.station.base.BaseActivity
import com.sinochem.www.station.utils.*
import com.sinochem.www.station.utils.camerax.CameraXCustomPreviewView
import com.sinochem.www.station.utils.camerax.FocusImageView
import com.sinochem.www.station.view.LoadingFragment
import java.io.File
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
// Typealias (本文下称 "类型别名")。类型别名可以使您在不增加新类型的情况下,为现有类或函数类型提供替代名称。
typealias LumaListener = (luma: Double) -> Unit
class CameraXActivity : BaseActivity(), View.OnClickListener {
private val title: RelativeLayout by lazy { findViewById<RelativeLayout>(R.id.title) }
private val viewFinder: CameraXCustomPreviewView by lazy {
findViewById<CameraXCustomPreviewView>(
R.id.viewFinder
)
}
private val back: ImageView by lazy { findViewById<ImageView>(R.id.back) }
private val switchBtn: Button by lazy { findViewById<Button>(R.id.switch_btn) }
private val chronometer: Chronometer by lazy { findViewById<Chronometer>(R.id.chronometer) }
private val recorderStart: ImageView by lazy { findViewById<ImageView>(R.id.recorder_start) }
private val recorderStop: ImageView by lazy { findViewById<ImageView>(R.id.recorder_stop) }
private val focusView: FocusImageView by lazy { findViewById<FocusImageView>(R.id.focusView) }
private lateinit var cameraExecutor: ExecutorService
var cameraProvider: ProcessCameraProvider? = null//相机信息
var preview: Preview? = null//预览对象
var cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA//当前相机
var camera: Camera? = null//相机对象
private var imageCapture: ImageCapture? = null//拍照用例
var videoCapture: VideoCapture? = null//录像用例
var maxMinuie = 180 //最大时间
val minMinuie = 5 //最小时间
private var progressDialog: ProgressDialog? = null
var localPath: String? = null;
var isTimeOver: Boolean = false //是否时间结束
var alertDialogBuilder: AlertDialog.Builder? = null
override fun setLayoutId(): Int {
return R.layout.activity_camerax_record;
}
override fun initVariables() {
if (intent != null) {
maxMinuie = intent.getIntExtra("maxMinuie", 180)
}
LogUtil.d("最大录制时长 : $maxMinuie")
// 设置视频文件输出的路径
// 设置视频文件输出的路径
localPath = (PathUtil.getInstance().videoPath
+ System.currentTimeMillis() + ".mp4")
}
override fun initViews(savedInstanceState: Bundle?) {
}
override fun doBusiness() {
recorderStop.setOnClickListener(this)
recorderStart.setOnClickListener(this)
back.setOnClickListener(this)
chronometer.onChronometerTickListener =
Chronometer.OnChronometerTickListener { chronometer ->
val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
val contentDescription = chronometer.contentDescription
val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
LogUtil.d("打印回调计时器 = " + second)
if (maxMinuie <= second) {
isTimeOver = true
recorderStopAction();
}
}
}
@SuppressLint("ClickableViewAccessibility")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView()
if (allPermissionsGranted()) {
startCamera()
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
}
// camera_capture_button.setOnClickListener { takePhoto() }
// recorderStart.setOnClickListener {
btnStartVideo.text = "Stop Video"
// takeVideo()
// }
switchBtn.setOnClickListener {
if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
} else {
cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
}
startCamera()
}
}
/**
* 初始化手势动作
*/
private fun initCameraListener() {
val zoomState = camera!!.cameraInfo.zoomState
viewFinder.setCustomTouchListener(object : CameraXCustomPreviewView.CustomTouchListener {
override fun zoom(delta: Float) {
//双指缩放
// zoomState.value?.let {
// val currentZoomRatio = it.zoomRatio
// camera!!.cameraControl.setZoomRatio(currentZoomRatio * delta)
// }
}
override fun click(x: Float, y: Float) {
//点击对焦
if (cameraSelector == CameraSelector.DEFAULT_BACK_CAMERA) {
val factory = viewFinder.createMeteringPointFactory(cameraSelector)
val point = factory.createPoint(x, y)
val action = FocusMeteringAction.Builder(point, FocusMeteringAction.FLAG_AF)
.setAutoCancelDuration(3, TimeUnit.SECONDS)
.build()
focusView.startFocus(Point(x.toInt(), y.toInt()))
val future: ListenableFuture<*> =
camera!!.cameraControl.startFocusAndMetering(action)
future.addListener(Runnable {
try {
val result = future.get() as FocusMeteringResult
if (result.isFocusSuccessful) {
focusView.onFocusSuccess()
} else {
focusView.onFocusFailed()
}
} catch (e: Exception) {
Log.e("", "", e)
}
}, cameraExecutor)
}
}
override fun doubleClick(x: Float, y: Float) {
//双击放大缩小
// zoomState.value?.let {
// val currentZoomRatio = it.zoomRatio
// if (currentZoomRatio > it.minZoomRatio) {
// camera!!.cameraControl.setLinearZoom(0f)
// } else {
// camera!!.cameraControl.setLinearZoom(0.5f)
// }
// }
}
override fun longClick(x: Float, y: Float) {}
})
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "Permissions not granted by the user.", Toast.LENGTH_SHORT)
.show()
// finish()
sendVideo()
}
}
}
@SuppressLint("RestrictedApi")
private fun startCamera() {
// if (cameraExecutor != null) {
cameraExecutor = Executors.newSingleThreadExecutor()
// }
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener(Runnable {
cameraProvider = cameraProviderFuture.get()//获取相机信息
//预览配置
preview = Preview.Builder()
.build()
.also {
it.setSurfaceProvider(viewFinder.createSurfaceProvider())
}
imageCapture = ImageCapture.Builder().build()//拍照用例配置
val imageAnalyzer = ImageAnalysis.Builder()
.build()
.also {
it.setAnalyzer(cameraExecutor, LuminosityAnalyzer { luma ->
Log.d(TAG, "Average luminosity: $luma")
})
}
//比特率
videoCapture = VideoCapture.Builder()//录像用例配置
// .setBitRate(3 * 1024 * 1024)较为清晰,且文件大小
.setBitRate(900 * 1024)较为清晰,且文件大小为3.26M(30秒)
.setVideoFrameRate(20)//帧率 视频帧率 越高视频体积越大
// .setAudioBitRate(1024)//设置音频的码率
.setTargetResolution(Size(720, 1080))//setTargetResolution设置生成的视频的分辨率
// .setTargetResolution(Size(720,1080))//setTargetResolution设置生成的视频的分辨率
// .setTargetAspectRatio(AspectRatio.RATIO_16_9) //设置高宽比
// .setTargetRotation(viewFinder.display.rotation)//设置旋转角度
// .setAudioRecordSource(AudioSource.MIC)//设置音频源麦克风
.build()
try {
cameraProvider?.unbindAll()//先解绑所有用例
camera = cameraProvider?.bindToLifecycle(
this,
cameraSelector,
preview,
imageCapture,
videoCapture
)//绑定用例
} catch (exc: Exception) {
Log.e(TAG, "Use case binding failed", exc)
}
initCameraListener()
}, ContextCompat.getMainExecutor(this))
}
//拍照
private fun takePhoto() {
val imageCapture = imageCapture ?: return
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).path +
"/CameraX" + SimpleDateFormat(
FILENAME_FORMAT,
Locale.CHINA
).format(System.currentTimeMillis()) + ".jpg"
)
val outputOptions = ImageCapture.OutputFileOptions.Builder(file).build()
imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this),
object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = Uri.fromFile(file)
val msg = "Photo capture succeeded: $savedUri"
Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
Log.d(TAG, msg)
}
})
}
//录制视频
@SuppressLint("RestrictedApi", "ClickableViewAccessibility")
private fun takeVideo() {
recorderStop.visibility = View.VISIBLE
recorderStart.visibility = View.GONE
//视频保存路径
val file = File(localPath)
//开始录像
videoCapture?.startRecording(
file,
Executors.newSingleThreadExecutor(),
object : VideoCapture.OnVideoSavedCallback {
override fun onVideoSaved(@NonNull file: File) {
//保存视频成功回调,会在停止录制时被调用
LogUtil.d("onVideoSaved: file.absolutePath = " + file.absolutePath)
// finish()
// recorderStopAction(false)
// showSelectDialog(isTimeOver)
runOnUiThread {
LoadingFragment.dismiss()
showSelectDialog(isTimeOver)
}
// Toast.makeText(this@MainActivity, file.absolutePath, Toast.LENGTH_SHORT).show()
}
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
//保存失败的回调,可能在开始或结束录制时被调用
Log.e("", "onError: $message")
runOnUiThread {
LoadingFragment.dismiss()
ToastUtils.showCenter("视频处理失败")
finish()
}
}
})
}
@SuppressLint("RestrictedApi")
private fun stopRecording() {
videoCapture?.stopRecording()//停止录制
preview?.clear()//清除预览
recorderStop.visibility = View.GONE
recorderStart.visibility = View.VISIBLE
chronometer.stop()
chronometer.base = SystemClock.elapsedRealtime()
}
private fun recorderStopAction() {
LogUtil.d("视频停止看看是否重复调用")
// videoCapture.
// recorderStop.setEnabled(false)
// 停止拍摄
var weakReference: WeakReference<Activity> = WeakReference(this)
LoadingFragment.showLodingDialog(weakReference.get())
stopRecording()
// btn_switch.setVisibility(View.VISIBLE);
}
private fun showSelectDialog(isTime: Boolean) {
var msg = "是否发送视频"
if (isTime) {
msg = "录制时间已到" + maxMinuie / 60 + "分钟,是否发送视频"
}
if (alertDialogBuilder == null) {
alertDialogBuilder = AlertDialog.Builder(this)
.setMessage(msg)
.setPositiveButton("确定") { dialog, which ->
dialog.dismiss()
sendVideo()
}
.setNegativeButton("取消") { dialog, which ->
if (localPath != null) {
val file: File = File(localPath)
if (file.exists()) file.delete()
}
finish()
}.setCancelable(false)
}
alertDialogBuilder?.show()
}
fun sendVideo() {
if (TextUtils.isEmpty(localPath)) {
LogUtil.e( "recorder fail please try again!")
return
}
setResult(Activity.RESULT_OK, intent.putExtra("uri", localPath))
finish()
}
private fun stopOrSave() {
val recordingTime = SystemClock.elapsedRealtime() - chronometer.base // 保存这次记录了的时间
val second = Math.round(recordingTime.toFloat() / 1000).toFloat()
LogUtil.d("second $second")
try {
if (second < minMinuie) {
ToastUtils.showCenter("视频录制时间最少5秒")
return
}
recorderStop.isEnabled = false
recorderStart.isEnabled = false
recorderStopAction()
} catch (e: Exception) {
ToastUtils.showCenter("操作异常,请返回后重试")
e.printStackTrace()
CrashReportUtil.getInstance().postException(e)
}
}
override fun onPause() {
super.onPause()
if (progressDialog != null && progressDialog?.isShowing == true) {
progressDialog?.dismiss()
}
}
private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
@SuppressLint("RestrictedApi")
override fun onDestroy() {
super.onDestroy()
alertDialogBuilder = null
cameraExecutor.shutdown()
}
private class LuminosityAnalyzer(private val listener: LumaListener) : ImageAnalysis.Analyzer {
private fun ByteBuffer.toByteArray(): ByteArray {
rewind()
val data = ByteArray(remaining())
get(data)
return data
}
override fun analyze(image: ImageProxy) {
val buffer = image.planes[0].buffer
val data = buffer.toByteArray()
val pixels = data.map { it.toInt() and 0xFF }
val luma = pixels.average()
listener(luma)
image.close()
}
}
companion object {
private const val TAG = "CameraXBasic"
private const val FILENAME_FORMAT = "yyyy-MM-dd-HH-mm-ss-SSS"
private const val REQUEST_CODE_PERMISSIONS = 10
private val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO
)
}
override fun onBackPressed() {
// super.onBackPressed()
stopOrSave()
}
override fun onClick(v: View?) {
when (v?.id) {
R.id.recorder_start -> {
// 重置其他
chronometer.base = SystemClock.elapsedRealtime()
chronometer.start()
takeVideo()
}
R.id.back,
R.id.recorder_stop -> {
isTimeOver = false;
stopOrSave()
}
}
}
}
项目地址.
gitee地址
https://gitee.com/liudao/camera-xvideo/tree/master
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)