第21章 多媒体开发:让APP“有声有色”

多媒体开发,说白了就是让你的APP能播放音乐、视频,能拍照录像。这几乎是每个商业APP的标配功能。我这些年做过的项目,从短视频工具到在线教育,几乎都离不开这些技术。今天我就把多媒体开发的几个核心模块——音频播放、视频播放、相机开发、录制、以及高级播放器——给你讲透。

本章核心知识点

  • MediaPlayer:音频播放的“老大哥”
  • VideoView:快速实现视频播放
  • CameraX:现代相机开发首选
  • MediaRecorder:音视频录制
  • ExoPlayer:高级播放器的王者
Android 多媒体开发知识体系 多媒体开发 MediaPlayer 音频播放 VideoView 视频播放 CameraX 相机开发 MediaRecorder 录制 ExoPlayer 高级播放器 从基础到进阶,覆盖APP多媒体开发全场景 建议学习顺序:MediaPlayer → VideoView → CameraX → MediaRecorder → ExoPlayer

21.1 MediaPlayer:音频播放的“老大哥”

MediaPlayer是Android官方提供的音频/视频播放类。它支持本地文件、网络流媒体、资源文件等多种来源。我个人习惯把它叫做“万能播放器”,因为它能处理大多数常见格式。

先看一个最简单的音频播放示例:

MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.music);
mediaPlayer.start(); // 开始播放

嗯,就这么简单。但实际项目中,你需要处理生命周期。我曾经在一个音乐APP里踩过坑——播放到一半Activity被回收了,MediaPlayer没释放,结果内存泄漏。所以一定要在onDestroy里释放:

@Override
protected void onDestroy() {
    super.onDestroy();
    if (mediaPlayer != null) {
        mediaPlayer.release();
        mediaPlayer = null;
    }
}

小技巧:MediaPlayer的状态机比较复杂。你想想看,它从Idle到Initialized到Prepared再到Started,每个状态都有对应的合法操作。我建议你画个状态图贴在工位上,避免在错误状态调用错误方法。

MediaPlayer支持设置监听器,比如播放完成、出错等:

mediaPlayer.setOnCompletionListener(mp -> {
    // 播放完成,可以播下一首
    Log.d("MediaPlayer", "播放完成");
});

mediaPlayer.setOnErrorListener((mp, what, extra) -> {
    Log.e("MediaPlayer", "播放出错: what=" + what + ", extra=" + extra);
    return true; // 返回true表示已处理错误
});

21.2 VideoView:快速实现视频播放

VideoView是MediaPlayer的“好搭档”。它把MediaPlayer和SurfaceView封装在一起,让你几行代码就能播放视频。说白了,它就是懒人神器。

VideoView videoView = findViewById(R.id.videoView);
videoView.setVideoPath("https://example.com/video.mp4");
videoView.start();

但要注意,VideoView有一些限制。我记得有一次做直播回放功能,VideoView对HLS流支持不太好,画面总是卡顿。后来我换成了ExoPlayer才解决。

避坑指南:VideoView默认不处理屏幕旋转。如果你需要全屏播放,得自己监听屏幕方向变化,动态调整VideoView的尺寸。我曾经因为没处理这个,被测试小姐姐提了3个bug……

VideoView的常用方法:

方法 说明
setVideoPath(String) 设置视频文件路径
setVideoURI(Uri) 设置视频URI
start() 开始播放
pause() 暂停播放
seekTo(int) 跳转到指定位置(毫秒)
getCurrentPosition() 获取当前播放位置
setOnPreparedListener() 准备完成监听

21.3 CameraX:现代相机开发首选

CameraX是Google推出的Jetpack相机库。它解决了Camera API的碎片化问题。说白了,你写一套代码,就能兼容从Android 5.0到最新版本的所有设备。

我强烈推荐用CameraX替代旧的Camera API。为什么?因为CameraX自动处理了生命周期、旋转、预览尺寸等麻烦事。你想想看,以前用Camera API时,要自己写SurfaceView、处理相机旋转、管理生命周期……那叫一个痛苦。

CameraX的核心组件:

  • Preview:相机预览
  • ImageCapture:拍照
  • ImageAnalysis:图像分析(比如扫码)
  • VideoCapture:视频录制(CameraX 1.1+)

看一个简单的拍照示例:

// 获取ProcessCameraProvider
ProcessCameraProvider cameraProvider = ProcessCameraProvider.getInstance(context).get();

// 创建预览用例
Preview preview = new Preview.Builder().build();
preview.setSurfaceProvider(previewView.getSurfaceProvider());

// 创建拍照用例
ImageCapture imageCapture = new ImageCapture.Builder()
    .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY)
    .build();

// 绑定到生命周期
Camera camera = cameraProvider.bindToLifecycle(
    lifecycleOwner, 
    CameraSelector.DEFAULT_BACK_CAMERA, 
    preview, 
    imageCapture
);

// 拍照
imageCapture.takePicture(
    outputFileOptions,
    ContextCompat.getMainExecutor(context),
    new ImageCapture.OnImageSavedCallback() {
        @Override
        public void onImageSaved(@NonNull ImageCapture.OutputFileResults output) {
            Log.d("CameraX", "照片保存成功");
        }
        @Override
        public void onError(@NonNull ImageCaptureException exception) {
            Log.e("CameraX", "拍照失败", exception);
        }
    }
);

个人经验:CameraX的ImageAnalysis用例非常强大。我做过一个扫码APP,用ImageAnalysis配合ML Kit,识别速度比传统ZXing快了一倍。而且CameraX自动处理了图像格式转换,省了不少事。

21.4 MediaRecorder:音视频录制

MediaRecorder是Android官方提供的录制类。它可以录制音频和视频,输出为MP4、3GP等格式。我建议你用它来做简单的录制功能,比如录音笔、短视频拍摄。

先看一个音频录制的例子:

MediaRecorder recorder = new MediaRecorder();
recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
recorder.setOutputFile(getExternalCacheDir() + "/record.mp4");

try {
    recorder.prepare();
    recorder.start();
    Log.d("MediaRecorder", "开始录制");
} catch (IOException e) {
    e.printStackTrace();
}

// 停止录制
recorder.stop();
recorder.release();

避坑指南:MediaRecorder的API调用顺序非常严格。你必须先setAudioSource,再setOutputFormat,再setAudioEncoder……顺序错了就会抛IllegalStateException。我曾经在重构代码时不小心调换了顺序,排查了半天才发现。

视频录制稍微复杂一点,需要配合Camera或CameraX:

// 使用CameraX的VideoCapture
VideoCapture videoCapture = new VideoCapture.Builder()
    .setBitRate(10_000_000) // 10Mbps
    .setVideoFrameRate(30)  // 30fps
    .build();

// 开始录制
videoCapture.startRecording(
    videoFile,
    ContextCompat.getMainExecutor(context),
    new VideoCapture.OnVideoSavedCallback() {
        @Override
        public void onVideoSaved(@NonNull VideoCapture.OutputFileResults output) {
            Log.d("CameraX", "视频录制完成");
        }
        @Override
        public void onError(int videoCaptureError, @NonNull String message, @Nullable Throwable cause) {
            Log.e("CameraX", "录制失败: " + message);
        }
    }
);

21.5 ExoPlayer:高级播放器的王者

ExoPlayer是Google官方推荐的播放器库。它比MediaPlayer强大得多,支持DASH、HLS、SmoothStreaming等流媒体协议,还支持自定义渲染器、DRM加密等高级功能。

我为什么说ExoPlayer是王者?因为它解决了MediaPlayer的很多痛点:

  • MediaPlayer不支持动态切换音轨/字幕
  • MediaPlayer对HLS支持不好
  • MediaPlayer在低端设备上解码性能差
  • MediaPlayer不能自定义渲染流程

ExoPlayer的基本用法:

// 创建播放器
ExoPlayer player = new ExoPlayer.Builder(context).build();

// 创建媒体项
MediaItem mediaItem = MediaItem.fromUri("https://example.com/video.mp4");

// 设置媒体项并准备播放
player.setMediaItem(mediaItem);
player.prepare();
player.play();

ExoPlayer支持丰富的配置:

// 自定义缓存
Cache cache = new SimpleCache(
    cacheDir, 
    new LeastRecentlyUsedCacheEvictor(50 * 1024 * 1024) // 50MB缓存
);
CacheDataSource.Factory cacheFactory = new CacheDataSource.Factory().setCache(cache);

// 创建播放器时传入自定义数据源工厂
ExoPlayer player = new ExoPlayer.Builder(context)
    .setMediaSourceFactory(new DefaultMediaSourceFactory(cacheFactory))
    .build();

核心优势:ExoPlayer支持模块化架构。你可以替换掉默认的音频渲染器、视频渲染器、甚至解码器。比如你想用FFmpeg解码,只需要实现一个自定义Renderer即可。这种灵活性是MediaPlayer完全不具备的。

ExoPlayer的监听器体系也很完善:

player.addListener(new Player.Listener() {
    @Override
    public void onPlaybackStateChanged(int playbackState) {
        switch (playbackState) {
            case Player.STATE_READY:
                Log.d("ExoPlayer", "准备就绪");
                break;
            case Player.STATE_BUFFERING:
                Log.d("ExoPlayer", "缓冲中...");
                break;
            case Player.STATE_ENDED:
                Log.d("ExoPlayer", "播放结束");
                break;
        }
    }
    
    @Override
    public void onPlayerError(PlaybackException error) {
        Log.e("ExoPlayer", "播放错误: " + error.getMessage());
    }
});

个人建议:如果你的APP需要播放网络视频,尤其是流媒体,直接上ExoPlayer。别在MediaPlayer上浪费时间。我做过一个在线教育APP,用ExoPlayer实现了倍速播放、后台播放、画中画等功能,开发效率非常高。

最后,总结一下这五个组件的选择建议:

场景 推荐组件 理由
简单音频播放 MediaPlayer 轻量、易用
简单视频播放 VideoView 快速集成
相机拍照/预览 CameraX 兼容性好、生命周期安全
音视频录制 MediaRecorder + CameraX 官方支持、稳定
流媒体/高级播放 ExoPlayer 功能强大、可定制

多媒体开发这块,说白了就是“选对工具,事半功倍”。你想想看,如果非要用MediaPlayer去播HLS流,那不是给自己找麻烦吗?我建议你根据实际场景,选择合适的组件。遇到复杂需求,优先考虑ExoPlayer和CameraX,它们代表了Android多媒体开发的未来方向。


公众号:蓝海资料掘金营,微信deep3321