15、多媒体开发:Camera与CameraX拍照、MediaPlayer与ExoPlayer播放音视频、音频录制与播放、图片选择与裁剪
多媒体开发,说白了就是让手机能「看」能「听」能「说」。我这些年做项目,几乎每个App都绕不开拍照、播放视频、录音这些功能。今天咱们就把这几个核心模块拆开揉碎了讲清楚。
15.1 Camera与CameraX:拍照这件事
Android的相机API经历过一次大换代。早期是Camera API(已废弃),后来是Camera2,再后来Google推出了CameraX——一个基于Camera2的Jetpack库。
我个人强烈建议新项目直接用CameraX。为什么?因为Camera2的坑实在太多了。我记得有一次做扫码功能,用Camera2处理不同厂商的预览尺寸,华为、小米、OPPO各有一套逻辑,调试到崩溃。换成CameraX后,一行配置就搞定了。
15.1.1 CameraX的基本使用
先加依赖:
implementation "androidx.camera:camera-core:1.3.0"
implementation "androidx.camera:camera-camera2:1.3.0"
implementation "androidx.camera:camera-lifecycle:1.3.0"
implementation "androidx.camera:camera-view:1.3.0"
核心流程就三步:绑定生命周期、配置预览、拍照。
// 1. 获取ProcessCameraProvider
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
// 2. 配置预览
val preview = Preview.Builder().build()
preview.setSurfaceProvider(previewView.surfaceProvider)
// 3. 配置拍照
val imageCapture = ImageCapture.Builder()
.setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
.build()
// 4. 绑定到生命周期
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
cameraProvider.bindToLifecycle(
lifecycleOwner, cameraSelector, preview, imageCapture
)
}, ContextCompat.getMainExecutor(context))
拍照时调用:
imageCapture.takePicture(
ContextCompat.getMainExecutor(context),
object : ImageCapture.OnImageCapturedCallback() {
override fun onCaptureSuccess(image: ImageProxy) {
// 处理图片
}
override fun onError(exception: ImageCaptureException) {
// 处理错误
}
}
)
15.1.2 踩过的坑:预览方向与屏幕旋转
我曾经被预览方向问题折磨了两天。手机竖屏拿着,预览画面却是横的。原因很简单——CameraX默认的预览方向是传感器方向,不是屏幕方向。
解决办法:
val preview = Preview.Builder()
.setTargetRotation(Surface.ROTATION_0) // 根据屏幕方向设置
.build()
嗯,这里要注意:setTargetRotation要在bindToLifecycle之前调用,否则不生效。
15.2 MediaPlayer与ExoPlayer:播放音视频
播放音视频,Android原生提供了MediaPlayer,但说实话,它太「重」了。你想想看,一个MediaPlayer实例只能播放一个文件,想切换还得reset再setDataSource。而且它对HLS、DASH这些流媒体协议支持很差。
我个人更推荐ExoPlayer。它是Google官方的开源播放器,灵活、可扩展,现在YouTube都在用。
15.2.1 MediaPlayer快速上手
虽然不推荐,但简单场景还是能用用的:
val mediaPlayer = MediaPlayer.create(context, R.raw.audio)
mediaPlayer.start()
// 监听播放完成
mediaPlayer.setOnCompletionListener {
// 播放结束
}
// 释放资源
mediaPlayer.release()
15.2.2 ExoPlayer实战
加依赖:
implementation "androidx.media3:media3-exoplayer:1.2.0"
implementation "androidx.media3:media3-ui:1.2.0"
基本用法:
// 创建播放器
val player = ExoPlayer.Builder(context).build()
// 绑定到PlayerView
playerView.player = player
// 准备媒体
val mediaItem = MediaItem.fromUri(videoUrl)
player.setMediaItem(mediaItem)
player.prepare()
player.playWhenReady = true
// 释放
player.release()
ExoPlayer最强大的地方在于它的自定义能力。比如你想加个缓存策略:
val cache = SimpleCache(
cacheDir,
LeastRecentlyUsedCacheEvictor(maxCacheSize),
DatabaseProvider(context)
)
val cacheDataSourceFactory = CacheDataSource.Factory()
.setCache(cache)
.setUpstreamDataSourceFactory(DefaultHttpDataSource.Factory())
val player = ExoPlayer.Builder(context)
.setMediaSourceFactory(DefaultMediaSourceFactory(cacheDataSourceFactory))
.build()
这样用户第二次播放同一个视频时,直接从本地缓存加载,体验好很多。
15.3 音频录制与播放
音频这块,我做过语音聊天、录音笔记、甚至K歌App。核心就两个类:MediaRecorder(录制)和AudioTrack(播放)。
15.3.1 使用MediaRecorder录制
val recorder = MediaRecorder().apply {
setAudioSource(MediaRecorder.AudioSource.MIC)
setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
setAudioSamplingRate(44100)
setAudioBitRate(128000)
setOutputFile(filePath)
prepare()
start()
}
// 停止录制
recorder.stop()
recorder.release()
15.3.2 播放音频的两种方式
简单播放用MediaPlayer,低延迟播放用AudioTrack。举个例子,做语音实时对讲时,AudioTrack更合适:
val audioTrack = AudioTrack(
AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
.build(),
AudioFormat.Builder()
.setEncoding(AudioFormat.ENCODING_PCM_16BIT)
.setSampleRate(44100)
.setChannelMask(AudioFormat.CHANNEL_OUT_MONO)
.build(),
bufferSize,
AudioTrack.MODE_STREAM,
AudioTrack.AUDIO_SESSION_ID_GENERATE
)
audioTrack.play()
audioTrack.write(audioData, 0, audioData.size)
15.4 图片选择与裁剪
图片选择,以前大家都用第三方库,比如PhotoPicker、Matisse。但Android 13开始,Google推出了官方的图片选择器——ActivityResultContracts.PickVisualMedia。
15.4.1 使用官方图片选择器
val pickMedia = registerForActivityResult(
ActivityResultContracts.PickVisualMedia()
) { uri ->
// 处理选中的图片URI
if (uri != null) {
imageView.setImageURI(uri)
}
}
pickMedia.launch(PickVisualMediaRequest(
ActivityResultContracts.PickVisualMedia.ImageOnly
))
这个API的好处是不需要申请READ_MEDIA_IMAGES权限,系统帮你处理了。
15.4.2 图片裁剪
裁剪我一般用系统自带的裁剪Intent,或者用uCrop库。系统裁剪简单但功能有限:
val cropIntent = Intent("com.android.camera.action.CROP").apply {
setDataAndType(uri, "image/*")
putExtra("crop", "true")
putExtra("aspectX", 1)
putExtra("aspectY", 1)
putExtra("outputX", 300)
putExtra("outputY", 300)
putExtra("return-data", true)
}
startActivityForResult(cropIntent, CROP_REQUEST_CODE)
15.5 本章知识体系
下面这张图帮你理清多媒体开发的核心脉络:
多媒体开发的核心就是「采集→处理→展示」这条链路。CameraX帮你简化了相机采集,ExoPlayer帮你搞定了播放展示,中间的处理环节(裁剪、编码、缓存)则需要你根据业务场景灵活组合。
好了,这一章的内容就到这里。记住一句话:能用官方库就别自己造轮子,能少写代码就少写代码。毕竟,我们的目标是高效交付,不是炫技。