用Camera2 API打造短视频拍摄功能从零实现抖音式交互体验在移动互联网时代短视频应用已经成为人们日常生活中不可或缺的娱乐方式。作为Android开发者掌握如何构建一个高效、流畅的短视频拍摄功能至关重要。本文将带你深入探索如何利用Camera2 API和MediaRecorder从零开始实现一个类似抖音的短视频拍摄模块涵盖实时预览、录制控制、视频保存等核心功能。1. Camera2 API基础与项目准备Camera2 API是Android 5.0API 21引入的全新相机框架相比传统的Camera API它提供了更精细的控制和更高的性能。在开始实现短视频拍摄功能前我们需要做好以下准备工作开发环境要求Android Studio最新稳定版目标API级别设置为21或更高测试设备运行Android 5.0及以上系统关键依赖与权限配置在AndroidManifest.xml中添加必要的权限声明uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.RECORD_AUDIO / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE /项目结构规划app/ ├── java/ │ ├── com.example.videorecorder/ │ │ ├── CameraController.kt // 相机控制核心逻辑 │ │ ├── VideoRecorder.kt // 视频录制处理 │ │ ├── PreviewView.kt // 自定义预览视图 │ │ └── MainActivity.kt // 主界面 └── res/ ├── layout/ │ └── activity_main.xml // 主界面布局Camera2 API的核心组件包括CameraManager管理系统中的所有摄像头设备CameraDevice代表单个摄像头设备CameraCharacteristics描述摄像头设备的特性CaptureRequest定义捕获图像的参数CameraCaptureSession管理摄像头捕获会话2. 实现相机预览功能相机预览是短视频拍摄的基础良好的预览体验直接影响用户的使用感受。下面我们一步步实现高质量的预览功能。2.1 初始化相机设备首先创建一个CameraController类来管理相机相关操作class CameraController(context: Context, textureView: TextureView) { private val cameraManager context.getSystemService(Context.CAMERA_SERVICE) as CameraManager private val cameraId: String private var cameraDevice: CameraDevice? null private var captureSession: CameraCaptureSession? null private lateinit var previewRequestBuilder: CaptureRequest.Builder init { // 获取后置摄像头ID cameraId cameraManager.cameraIdList.first { id - cameraManager.getCameraCharacteristics(id) .get(CameraCharacteristics.LENS_FACING) CameraCharacteristics.LENS_FACING_BACK } } private val stateCallback object : CameraDevice.StateCallback() { override fun onOpened(camera: CameraDevice) { cameraDevice camera startPreview() } override fun onDisconnected(camera: CameraDevice) { camera.close() cameraDevice null } override fun onError(camera: CameraDevice, error: Int) { camera.close() cameraDevice null } } fun openCamera() { if (ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED) { cameraManager.openCamera(cameraId, backgroundHandler, stateCallback) } } // 其他方法将在后续实现 }2.2 配置预览会话创建预览会话需要以下几个步骤创建SurfaceTexture并设置到TextureView配置预览尺寸和输出Surface创建CaptureRequest.Builder建立CameraCaptureSessionprivate fun startPreview() { val texture textureView.surfaceTexture ?: return // 设置默认缓冲区大小 val characteristics cameraManager.getCameraCharacteristics(cameraId) val map characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP) val previewSize map?.getOutputSizes(SurfaceTexture::class.java)?.maxByOrNull { it.width * it.height } texture.setDefaultBufferSize(previewSize?.width ?: 1080, previewSize?.height ?: 1920) val surface Surface(texture) previewRequestBuilder cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW) previewRequestBuilder.addTarget(surface) cameraDevice?.createCaptureSession( listOf(surface), object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { captureSession session previewRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) session.setRepeatingRequest(previewRequestBuilder.build(), null, backgroundHandler) } override fun onConfigureFailed(session: CameraCaptureSession) { Log.e(TAG, Failed to configure capture session) } }, backgroundHandler ) }2.3 处理生命周期和权限在Activity中正确处理生命周期和权限请求class MainActivity : AppCompatActivity() { private lateinit var cameraController: CameraController private lateinit var textureView: TextureView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) textureView findViewById(R.id.texture_view) cameraController CameraController(this, textureView) textureView.surfaceTextureListener object : TextureView.SurfaceTextureListener { override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) { if (checkPermissions()) { cameraController.openCamera() } else { requestPermissions() } } // 其他回调方法... } } private fun checkPermissions(): Boolean { return ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) PackageManager.PERMISSION_GRANTED ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) PackageManager.PERMISSION_GRANTED } private fun requestPermissions() { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO), REQUEST_CODE_PERMISSIONS ) } override fun onRequestPermissionsResult( requestCode: Int, permissions: Arrayout String, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode REQUEST_CODE_PERMISSIONS grantResults.all { it PackageManager.PERMISSION_GRANTED }) { cameraController.openCamera() } } companion object { private const val REQUEST_CODE_PERMISSIONS 1001 } }3. 实现视频录制功能视频录制是短视频应用的核心功能我们需要整合Camera2 API和MediaRecorder来实现高质量的录制体验。3.1 配置MediaRecorder创建一个VideoRecorder类来封装视频录制逻辑class VideoRecorder(private val context: Context) { private var mediaRecorder: MediaRecorder? null private var videoFile: File? null fun prepare(outputFile: File, width: Int, height: Int, surface: Surface): Surface { mediaRecorder MediaRecorder().apply { setAudioSource(MediaRecorder.AudioSource.MIC) setVideoSource(MediaRecorder.VideoSource.SURFACE) setOutputFormat(MediaRecorder.OutputFormat.MPEG_4) setAudioEncoder(MediaRecorder.AudioEncoder.AAC) setVideoEncoder(MediaRecorder.VideoEncoder.H264) setVideoEncodingBitRate(5 * 1024 * 1024) // 5Mbps setVideoFrameRate(30) setVideoSize(width, height) setOrientationHint(90) // 竖屏录制 setOutputFile(outputFile.absolutePath) setPreviewDisplay(surface) prepare() } videoFile outputFile return mediaRecorder!!.surface } fun start() { mediaRecorder?.start() } fun stop() { mediaRecorder?.apply { stop() reset() release() } mediaRecorder null } fun getOutputFile(): File? videoFile }3.2 集成录制功能到CameraController在CameraController中添加录制相关逻辑class CameraController(context: Context, textureView: TextureView) { private val videoRecorder VideoRecorder(context) private var isRecording false fun startRecording() { if (isRecording) return val outputFile File(context.getExternalFilesDir(null), video_${System.currentTimeMillis()}.mp4) val recorderSurface videoRecorder.prepare( outputFile, previewSize.width, previewSize.height, Surface(textureView.surfaceTexture) ) captureSession?.stopRepeating() captureSession?.abortCaptures() val previewRequestBuilder cameraDevice!!.createCaptureRequest( CameraDevice.TEMPLATE_RECORD ).apply { addTarget(Surface(textureView.surfaceTexture)) addTarget(recorderSurface) set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO) } cameraDevice?.createCaptureSession( listOf(Surface(textureView.surfaceTexture), recorderSurface), object : CameraCaptureSession.StateCallback() { override fun onConfigured(session: CameraCaptureSession) { captureSession session session.setRepeatingRequest(previewRequestBuilder.build(), null, null) videoRecorder.start() isRecording true } override fun onConfigureFailed(session: CameraCaptureSession) { Log.e(TAG, Failed to configure recording session) } }, null ) } fun stopRecording() { if (!isRecording) return videoRecorder.stop() isRecording false // 重启预览 startPreview() // 保存视频到媒体库 saveVideoToGallery(videoRecorder.getOutputFile()) } private fun saveVideoToGallery(file: File?) { file ?: return val values ContentValues().apply { put(MediaStore.Video.Media.TITLE, file.name) put(MediaStore.Video.Media.DISPLAY_NAME, file.name) put(MediaStore.Video.Media.MIME_TYPE, video/mp4) put(MediaStore.Video.Media.DATE_ADDED, System.currentTimeMillis() / 1000) put(MediaStore.Video.Media.DATA, file.absolutePath) } context.contentResolver.insert( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values ) } }3.3 添加录制控制UI在布局文件中添加录制控制按钮androidx.constraintlayout.widget.ConstraintLayout xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightmatch_parent TextureView android:idid/texture_view android:layout_widthmatch_parent android:layout_heightmatch_parent app:layout_constraintBottom_toBottomOfparent app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent / com.google.android.material.floatingactionbutton.FloatingActionButton android:idid/record_button android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginBottom32dp android:srcdrawable/ic_record app:layout_constraintBottom_toBottomOfparent app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toStartOfparent / /androidx.constraintlayout.widget.ConstraintLayout在Activity中处理录制按钮点击事件record_button.setOnClickListener { if (cameraController.isRecording()) { cameraController.stopRecording() record_button.setImageResource(R.drawable.ic_record) } else { cameraController.startRecording() record_button.setImageResource(R.drawable.ic_stop) } }4. 优化用户体验与性能一个优秀的短视频拍摄功能不仅需要实现基本功能还需要考虑用户体验和性能优化。4.1 添加录制计时器在录制过程中显示已录制时间可以提升用户体验private var recordingStartTime 0L private val timerHandler Handler(Looper.getMainLooper()) private lateinit var timerTextView: TextView private val timerRunnable object : Runnable { override fun run() { val elapsedMillis System.currentTimeMillis() - recordingStartTime timerTextView.text SimpleDateFormat(mm:ss, Locale.getDefault()) .format(Date(elapsedMillis)) timerHandler.postDelayed(this, 1000) } } fun startRecording() { recordingStartTime System.currentTimeMillis() timerHandler.post(timerRunnable) // 其他录制逻辑... } fun stopRecording() { timerHandler.removeCallbacks(timerRunnable) timerTextView.text 00:00 // 其他停止录制逻辑... }4.2 实现闪光灯控制添加闪光灯控制功能可以增强拍摄灵活性fun toggleFlash(): Boolean { val characteristics cameraManager.getCameraCharacteristics(cameraId) val hasFlash characteristics.get(CameraCharacteristics.FLASH_INFO_AVAILABLE) ?: false if (!hasFlash) return false isFlashOn !isFlashOn previewRequestBuilder.set( CaptureRequest.FLASH_MODE, if (isFlashOn) CaptureRequest.FLASH_MODE_TORCH else CaptureRequest.FLASH_MODE_OFF ) captureSession?.setRepeatingRequest(previewRequestBuilder.build(), null, null) return isFlashOn }4.3 优化录制性能为了确保录制过程流畅需要注意以下几点选择合适的视频参数// 在VideoRecorder.prepare()中设置 setVideoEncodingBitRate(5 * 1024 * 1024) // 5Mbps适合1080p视频 setVideoFrameRate(30) // 30fps是流畅视频的标准帧率处理设备旋转override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) // 重新配置相机方向 cameraController.updateOrientation() }内存管理override fun onPause() { super.onPause() if (isRecording) { stopRecording() } cameraController.closeCamera() }4.4 添加录制进度提示在UI中添加录制进度条可以提升用户体验ProgressBar android:idid/recording_progress stylestyle/Widget.AppCompat.ProgressBar.Horizontal android:layout_widthmatch_parent android:layout_height4dp android:layout_marginHorizontal16dp android:max15000 !-- 15秒最大录制时长 -- android:progressDrawabledrawable/progress_bar_recording app:layout_constraintTop_toTopOfparent /在录制过程中更新进度private val progressRunnable object : Runnable { override fun run() { val progress (System.currentTimeMillis() - recordingStartTime).toInt() recordingProgress.progress progress if (progress recordingProgress.max) { progressHandler.postDelayed(this, 50) } else { // 自动停止录制 stopRecording() } } }