1. 前言
因?yàn)楣ぷ髦幸褂?code>Android Camera2 API,但因?yàn)?code>Camera2比較復(fù)雜,網(wǎng)上資料也比較亂,有一定入門門檻,所以花了幾天時(shí)間系統(tǒng)研究了下,并在CSDN
上記錄了下,希望能幫助到更多的小伙伴。
上兩篇文章使用Camera2
實(shí)現(xiàn)了相機(jī)預(yù)覽和拍照的功能,這篇文章我們接著上文,來實(shí)現(xiàn)Camera2
視頻錄制的功能。
2. 前置操作
2.1 聲明相機(jī)參數(shù)和成員變量
首先還是聲明相機(jī)參數(shù)和成員變量,比起前文增加了這些
private var mediaRecorder: MediaRecorder? = null
private var isRecordingVideo: Boolean = false
private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 270)
append(Surface.ROTATION_90, 180)
append(Surface.ROTATION_180, 90)
append(Surface.ROTATION_270, 0)
}
完整的需要聲明的相機(jī)參數(shù)和成員變量如下
//后攝 : 0 ,前攝 : 1
private val cameraId = "0"
private val TAG = CameraActivity3::class.java.simpleName
private lateinit var cameraDevice: CameraDevice
private val cameraThread = HandlerThread("CameraThread").apply { start() }
private val cameraHandler = Handler(cameraThread.looper)
private val cameraManager: CameraManager by lazy {
getSystemService(Context.CAMERA_SERVICE) as CameraManager
}
private val characteristics: CameraCharacteristics by lazy {
cameraManager.getCameraCharacteristics(cameraId)
}
private lateinit var session: CameraCaptureSession
private lateinit var imageReader: ImageReader
//JPEG格式,所有相機(jī)必須支持JPEG輸出,因此不需要檢查
private val pixelFormat = ImageFormat.JPEG
//imageReader最大的圖片緩存數(shù)
private val IMAGE_BUFFER_SIZE: Int = 3
//線程池
private val threadPool = Executors.newCachedThreadPool()
private val imageReaderThread = HandlerThread("imageReaderThread").apply { start() }
private val imageReaderHandler = Handler(imageReaderThread.looper)
/** Live data listener for changes in the device orientation relative to the camera */
private lateinit var relativeOrientation: OrientationLiveData
private var mediaRecorder: MediaRecorder? = null
private var isRecordingVideo: Boolean = false
private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 90)
append(Surface.ROTATION_90, 0)
append(Surface.ROTATION_180, 270)
append(Surface.ROTATION_270, 180)
}
private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
append(Surface.ROTATION_0, 270)
append(Surface.ROTATION_90, 180)
append(Surface.ROTATION_180, 90)
append(Surface.ROTATION_270, 0)
}
2.2 添加布局
首先我們需要在XML
中添加兩個(gè)按鈕,分別是錄制按鈕和停止錄制按鈕
<Button
android:id="@+id/btn_capture_video"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center"
android:layout_marginRight="16dp"
android:text="錄屏"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
<Button
android:id="@+id/btn_stop_capture"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|left"
android:text="停止錄屏"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" />
2.3 初始化MediaPlayer
我們需要在打開相機(jī)的時(shí)候,去初始化mediaRecorder
mediaRecorder = MediaRecorder()
完整代碼如下
@SuppressLint("MissingPermission")
private fun openCamera(cameraId: String) {
cameraManager.openCamera(cameraId, object : CameraDevice.StateCallback() {
override fun onOpened(camera: CameraDevice) {
cameraDevice = camera
mediaRecorder = MediaRecorder()
startPreview()
}
override fun onDisconnected(camera: CameraDevice) {
this@CameraActivity3.finish()
}
override fun onError(camera: CameraDevice, error: Int) {
Toast.makeText(application, "openCamera Failed:$error", Toast.LENGTH_SHORT).show()
}
}, cameraHandler)
}
3. 實(shí)現(xiàn)視頻錄制功能
3.1 關(guān)閉原本的Session
因?yàn)榕恼蘸弯浿埔曨l功能不好一起使用,所以需要先調(diào)用closePreviewSession
,來關(guān)閉原來的session
private fun closePreviewSession() {
session?.close()
}
3.2 給MediaRecorder設(shè)置參數(shù)
接著,需要調(diào)用setUpMediaRecorder()
來初始化MediaRecorder
setUpMediaRecorder
中,會(huì)給mediaRecorder
設(shè)置很多預(yù)置參數(shù)
首先獲取目標(biāo)路徑
val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)
fun getVideoFilePath(context: Context?): String {
val filename = "VIDEO_${System.currentTimeMillis()}.mp4"
val dir = context?.getExternalFilesDir("video")
return if (dir == null) {
filename
} else {
"${dir.absolutePath}/$filename"
}
}
然后設(shè)置mediaRecorder
方向
val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
val rotation = cameraActivity.windowManager.defaultDisplay.rotation
when (sensorOrientation) {
SENSOR_ORIENTATION_DEFAULT_DEGREES ->
mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
SENSOR_ORIENTATION_INVERSE_DEGREES ->
mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
}
最后給meidaRecorder
設(shè)置若干參數(shù)項(xiàng),這里我們默認(rèn)給視頻尺寸設(shè)置成了1920*1080
,如果你的設(shè)備相機(jī)不支持這個(gè)分辨率,需要修改一下。
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(nextVideoAbsolutePath)
setVideoEncodingBitRate(10000000)
setVideoFrameRate(30)
setVideoSize(1920,1080)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
再來看下完整的代碼
private fun setUpMediaRecorder() {
val cameraActivity = this
val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)
val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
val rotation = cameraActivity.windowManager.defaultDisplay.rotation
when (sensorOrientation) {
SENSOR_ORIENTATION_DEFAULT_DEGREES ->
mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
SENSOR_ORIENTATION_INVERSE_DEGREES ->
mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
}
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(nextVideoAbsolutePath)
setVideoEncodingBitRate(10000000)
setVideoFrameRate(30)
setVideoSize(1920,1080) //FIXME 如果你的設(shè)備相機(jī)不支持這個(gè)分辨率,需要修改一下
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
}
fun getVideoFilePath(context: Context?): String {
val filename = "VIDEO_${System.currentTimeMillis()}.mp4"
val dir = context?.getExternalFilesDir("video")
return if (dir == null) {
filename
} else {
"${dir.absolutePath}/$filename"
}
}
3.3 重新創(chuàng)建Session
接著就將binding.surfaceView
和recorderSurface
添加到surfaces
val recorderSurface = mediaRecorder!!.surface
val surfaces = ArrayList<Surface>().apply {
add(binding.surfaceView.holder.surface)
add(recorderSurface)
}
重新調(diào)用cameraDevice?.createCaptureSession
,將surfaces
傳入
cameraDevice?.createCaptureSession(surfaces,
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
//待實(shí)現(xiàn)
}
override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
Toast.makeText(application, "onConfigureFailed", Toast.LENGTH_SHORT).show()
}
}, cameraHandler)
3.4 開始錄制
當(dāng)onConfigured
調(diào)用后,我們執(zhí)行下面這些代碼,主要執(zhí)行了這些操作
- 將
cameraCaptureSession
賦值給session
-
session?.setRepeatingRequest
,這將不斷地實(shí)時(shí)發(fā)送視頻流,直到會(huì)話斷開或調(diào)用session.stoprepeat()
- 調(diào)用
mediaRecorder?.start
錄制視頻
session = cameraCaptureSession
val previewRequestBuilder = cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply {
addTarget(binding.surfaceView.holder.surface)
addTarget(recorderSurface)
}
session?.setRepeatingRequest(previewRequestBuilder!!.build(), null, cameraHandler)
isRecordingVideo = true
mediaRecorder?.start()
3.5 錄制視頻完整代碼
binding.btnCaptureVideo.setOnClickListener {
startRecordingVideo()
}
private fun startRecordingVideo() {
closePreviewSession()
setUpMediaRecorder()
val recorderSurface = mediaRecorder!!.surface
val surfaces = ArrayList<Surface>().apply {
add(binding.surfaceView.holder.surface)
add(recorderSurface)
}
cameraDevice?.createCaptureSession(
surfaces,
object : CameraCaptureSession.StateCallback() {
override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
session = cameraCaptureSession
val previewRequestBuilder =
cameraDevice!!.createCaptureRequest(TEMPLATE_RECORD).apply {
addTarget(binding.surfaceView.holder.surface)
addTarget(recorderSurface)
}
session?.setRepeatingRequest(
previewRequestBuilder!!.build(),
null,
cameraHandler
)
isRecordingVideo = true
mediaRecorder?.start()
}
override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
Toast.makeText(application, "onConfigureFailed", Toast.LENGTH_SHORT).show()
}
}, cameraHandler
)
}
private fun closePreviewSession() {
session?.close()
}
private fun setUpMediaRecorder() {
val cameraActivity = this
val nextVideoAbsolutePath = getVideoFilePath(cameraActivity)
val sensorOrientation = characteristics?.get(SENSOR_ORIENTATION)
val rotation = cameraActivity.windowManager.defaultDisplay.rotation
when (sensorOrientation) {
SENSOR_ORIENTATION_DEFAULT_DEGREES ->
mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
SENSOR_ORIENTATION_INVERSE_DEGREES ->
mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
}
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(nextVideoAbsolutePath)
setVideoEncodingBitRate(10000000)
setVideoFrameRate(30)
setVideoSize(1920, 1080) //FIXME 如果你的設(shè)備相機(jī)不支持這個(gè)分辨率,需要修改一下
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
}
我們運(yùn)行程序,點(diǎn)擊錄制視頻,過幾秒點(diǎn)擊停止錄制,然后打開文件管理器,在/sdcard/Android/data/包名/files/video
文件夾下,可以看到這個(gè)視頻了
4. 停止錄制視頻
停止錄制視頻比較簡單,只需要釋放mediaRecorder
然后再調(diào)用startPreview
重新開始預(yù)覽就可以了
private fun stopRecordingVideo() {
isRecordingVideo = false
mediaRecorder?.apply {
stop()
reset()
}
//重新開始預(yù)覽
startPreview()
}
5. 實(shí)現(xiàn)動(dòng)態(tài)設(shè)置分辨率
之前我們這是錄制分辨率是寫死的1920*1080
,這樣是不夠動(dòng)態(tài)靈活的,接下來我們來實(shí)現(xiàn)下動(dòng)態(tài)設(shè)置分辨率
首先通過characteristics
獲取到可用的分辨率列表
val characteristics = manager.getCameraCharacteristics(cameraId)
val map = characteristics.get(SCALER_STREAM_CONFIGURATION_MAP) ?:
throw RuntimeException("Cannot get available preview/video sizes")
然后通過這個(gè)map
來選擇出最適合的分辨率,這里的選擇規(guī)則是返回寬高比3:4
的分辨率中最高的分辨率
videoSize = chooseVideoSize(map.getOutputSizes(MediaRecorder::class.java))
fun chooseVideoSize(choices: Array<Size>) = choices.firstOrNull {
it.width == it.height * 4 / 3 } ?: choices[choices.size - 1]
最后,將該分辨率設(shè)置到mediaRecorder
中就行了
mediaRecorder?.apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setVideoSource(MediaRecorder.VideoSource.SURFACE)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setOutputFile(nextVideoAbsolutePath)
setVideoEncodingBitRate(10000000)
setVideoFrameRate(30)
//setVideoSize(1920, 1080)
setVideoSize(videoSize.width,videoSize.height)
setVideoEncoder(MediaRecorder.VideoEncoder.H264)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
prepare()
}
6. 其他
6.1 本文源碼下載
下載地址 : Android Camera2 Demo - 實(shí)現(xiàn)相機(jī)預(yù)覽、拍照、錄制視頻功能
6.2 Android Camera2 系列
更多Camera2相關(guān)文章,請(qǐng)看
十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)預(yù)覽_氦客的博客-CSDN博客
十分鐘實(shí)現(xiàn) Android Camera2 相機(jī)拍照_氦客的博客-CSDN博客
十分鐘實(shí)現(xiàn) Android Camera2 視頻錄制_氦客的博客-CSDN博客文章來源:http://www.zghlxwxcb.cn/news/detail-509882.html
6.3 Android 相機(jī)相關(guān)文章
Android 使用CameraX實(shí)現(xiàn)預(yù)覽/拍照/錄制視頻/圖片分析/對(duì)焦/縮放/切換攝像頭等操作_氦客的博客-CSDN博客
Android 從零開發(fā)一個(gè)簡易的相機(jī)App_android開發(fā)簡易app_氦客的博客-CSDN博客
Android 使用Camera1實(shí)現(xiàn)相機(jī)預(yù)覽、拍照、錄像_android 相機(jī)預(yù)覽_氦客的博客-CSDN博客文章來源地址http://www.zghlxwxcb.cn/news/detail-509882.html
到了這里,關(guān)于十分鐘實(shí)現(xiàn) Android Camera2 視頻錄制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!