2025最强Android媒体开发指南:从零基础到精通音视频全场景应用
你是否还在为Android音视频开发中的兼容性问题头疼?是否想掌握MediaCodec、MediaRecorder等核心API却不知从何下手?本文将通过9个实战项目带你系统掌握Android媒体开发全流程,从基础播放到高级投屏,从音频合成到屏幕录制,一站式解决你的开发痛点。读完本文,你将获得:
- 10+核心媒体API的实战应用经验
- 7个完整项目的源码解析与最佳实践
- 5大媒体场景的解决方案与性能优化技巧
- 一套可直接复用的媒体开发框架
📋 项目概述:Android媒体示例仓库
Android媒体示例项目(media-samples)是Google官方提供的媒体开发学习宝库,包含多个独立的Android Studio项目,展示了音频、视频等媒体API的最佳实践。该仓库采用模块化设计,每个项目专注于特定媒体功能,从基础播放到高级媒体路由,覆盖了现代Android应用开发中常见的媒体场景。
项目结构解析
仓库采用清晰的目录结构,每个子项目都是独立的Android应用,可单独编译运行:
| 项目名称 | 核心功能 | 最低API | 技术亮点 |
|---|---|---|---|
| BasicMediaDecoder | 视频解码与渲染 | 16 | MediaCodec + TimeAnimator同步 |
| BasicMediaRouter | 媒体路由控制 | 14 | 多设备内容投射 |
| MediaRecorder | 音视频录制 | 14 | Camera + MediaRecorder整合 |
| MediaRouter | 高级媒体路由 | 14 | 自定义路由提供者 |
| MidiScope | MIDI信号分析 | 23 | MIDI设备检测与信号解析 |
| MidiSynth | 音频合成器 | 23 | 波形生成与音频输出 |
| PictureInPicture | 画中画功能 | 26 | Android O新特性 |
| ScreenCapture | 屏幕录制 | 21 | MediaProjection API |
| VideoPlayer | 高级播放器 | 26 | ExoPlayer + MediaSession |
🚀 环境准备与项目构建
开发环境要求
- Android Studio 4.0+
- Android SDK 28+
- Android Build Tools v28.0.3+
- Gradle 4.10.1+
- JDK 8+
快速开始
- 克隆项目
git clone https://gitcode.com/gh_mirrors/me/media-samples.git
cd media-samples
- 构建项目
# 构建单个项目(以MediaRecorder为例)
cd MediaRecorder
./gradlew assembleDebug
# 或使用Android Studio打开项目
- 运行应用
# 安装调试版APK
./gradlew installDebug
# 启动应用
adb shell am start -n com.example.android.mediarecorder/.MainActivity
🎥 视频处理实战
1. BasicMediaDecoder:深入理解视频解码流程
BasicMediaDecoder项目展示了如何使用MediaCodec API进行视频解码,并通过TimeAnimator实现与系统显示帧的同步渲染。这是理解Android底层视频处理的绝佳起点。
核心技术栈
- MediaCodec:Android低级媒体编解码API
- MediaExtractor:媒体文件提取器
- TextureView:硬件加速的纹理渲染视图
- TimeAnimator:时间同步动画器
解码流程解析
视频解码主要分为以下步骤,每个步骤都有其关键实现要点:
关键代码实现
1. 初始化媒体提取器
MediaExtractor extractor = new MediaExtractor();
try {
extractor.setDataSource(this, videoUri, null);
} catch (IOException e) {
Log.e(TAG, "Failed to set data source", e);
return;
}
// 查找视频轨道
int trackCount = extractor.getTrackCount();
for (int i = 0; i < trackCount; i++) {
MediaFormat format = extractor.getTrackFormat(i);
String mime = format.getString(MediaFormat.KEY_MIME);
if (mime.startsWith("video/")) {
extractor.selectTrack(i);
videoFormat = format;
break;
}
}
2. 配置MediaCodec解码器
String mimeType = videoFormat.getString(MediaFormat.KEY_MIME);
MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
codec.configure(videoFormat, textureView.getSurface(), null, 0);
codec.start();
// 获取输入输出缓冲区
MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
ByteBuffer[] inputBuffers = codec.getInputBuffers();
ByteBuffer[] outputBuffers = codec.getOutputBuffers();
3. TimeAnimator同步渲染
TimeAnimator timeAnimator = new TimeAnimator();
timeAnimator.setTimeListener((animation, totalTime, deltaTime) -> {
boolean isEOS = false;
// 处理输入缓冲区
int inputBufferIndex = codec.dequeueInputBuffer(10000);
if (inputBufferIndex >= 0) {
ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
int sampleSize = extractor.readSampleData(inputBuffer, 0);
if (sampleSize < 0) {
codec.queueInputBuffer(inputBufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
isEOS = true;
} else {
long presentationTimeUs = extractor.getSampleTime();
codec.queueInputBuffer(inputBufferIndex, 0, sampleSize, presentationTimeUs, 0);
extractor.advance();
}
}
// 处理输出缓冲区
int outputBufferIndex = codec.dequeueOutputBuffer(info, 10000);
while (outputBufferIndex >= 0) {
codec.releaseOutputBuffer(outputBufferIndex, true);
outputBufferIndex = codec.dequeueOutputBuffer(info, 0);
if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
isEOS = true;
}
}
// 结束处理
if (isEOS) {
timeAnimator.end();
codec.stop();
codec.release();
extractor.release();
}
});
timeAnimator.start();
4. 纹理视图准备
textureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
// 纹理可用时初始化解码
startPlayback();
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height, int format) {
// 处理视图大小变化
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface, int width, int height) {
// 释放资源
stopPlayback();
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface, int width, int height) {
// 纹理更新回调
}
});
性能优化要点
- 缓冲区管理:合理设置缓冲区大小,避免频繁内存分配
- 同步策略:使用TimeAnimator而非Handler.postDelayed,确保与显示帧同步
- 错误处理:添加完整的异常捕获和资源释放机制
- 格式检测:提前检查视频格式支持情况,提供友好的错误提示
2. VideoPlayer:构建企业级视频播放应用
VideoPlayer项目展示了如何构建功能完善的视频播放器,支持本地和远程视频播放、播放列表管理、MediaSession集成以及画中画功能。该项目采用了ExoPlayer作为核心播放引擎,是构建现代Android视频应用的理想参考。
核心功能模块
关键实现:MediaSession集成
MediaSession允许应用与系统媒体控制中心集成,支持耳机按键、车载系统等外部设备控制:
private fun initMediaSession() {
mediaSession = MediaSessionCompat(context, TAG).apply {
setCallback(mediaSessionCallback)
setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
// 设置媒体会话元数据
val metadata = MediaMetadataCompat.Builder()
.putString(MediaMetadataCompat.METADATA_KEY_TITLE, videoTitle)
.putString(MediaMetadataCompat.METADATA_KEY_ARTIST, "Sample Artist")
.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, videoDuration)
.build()
setMetadata(metadata)
// 设置播放状态
val state = PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, currentPosition, 1.0f)
.setActions(PlaybackStateCompat.ACTION_PLAY or
PlaybackStateCompat.ACTION_PAUSE or
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
.build()
setPlaybackState(state)
isActive = true
}
}
画中画功能实现
Android O引入的画中画功能可让视频在小窗口中继续播放,即使应用处于后台:
private fun enterPictureInPictureMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.setActions(
listOf(
RemoteAction(
Icon.createWithResource(context, R.drawable.ic_play),
"Play", "Play",
PendingIntent.getBroadcast(
context, 0, Intent(ACTION_PLAY),
PendingIntent.FLAG_UPDATE_CURRENT
)
),
RemoteAction(
Icon.createWithResource(context, R.drawable.ic_pause),
"Pause", "Pause",
PendingIntent.getBroadcast(
context, 0, Intent(ACTION_PAUSE),
PendingIntent.FLAG_UPDATE_CURRENT
)
)
)
)
.build()
setPictureInPictureParams(params)
enterPictureInPictureMode(params)
}
}
// 处理画中画模式变化
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
if (isInPictureInPictureMode) {
// 进入画中画模式,隐藏控件
hideSystemUi()
} else {
// 退出画中画模式,显示控件
showSystemUi()
}
}
3. PictureInPicture:多任务时代的视频体验
PictureInPicture项目专注于展示Android O及以上版本的画中画功能实现,让应用能够在小窗口中继续播放视频,同时允许用户使用其他应用。这在视频通话、视频播放类应用中尤为重要。
实现画中画的核心步骤
- 清单配置
<activity
android:name=".MovieActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:resizeableActivity="true">
</activity>
- 实现活动逻辑
public class MovieActivity extends AppCompatActivity {
private MovieView movieView;
private boolean isInPictureInPictureMode;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_movie);
movieView = findViewById(R.id.movie);
movieView.setMovieListener(new MovieListener() {
@Override
public void onMovieStarted() {
// 视频开始播放时更新MediaSession状态
updateMediaSessionState(PlaybackStateCompat.STATE_PLAYING);
}
@Override
public void onMovieStopped() {
// 视频停止时更新MediaSession状态
updateMediaSessionState(PlaybackStateCompat.STATE_PAUSED);
}
@Override
public void onMovieMinimized() {
// 最小化时进入画中画模式
enterPictureInPictureMode();
}
});
}
@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
Configuration newConfig) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
this.isInPictureInPictureMode = isInPictureInPictureMode;
if (isInPictureInPictureMode) {
// 进入画中画模式,隐藏控件
getSupportActionBar().hide();
} else {
// 退出画中画模式,显示控件
getSupportActionBar().show();
}
}
@Override
public void onBackPressed() {
// 如果视频正在播放,按返回键进入画中画模式而非退出
if (movieView.isPlaying()) {
enterPictureInPictureMode();
} else {
super.onBackPressed();
}
}
}
🎧 音频处理实战
1. MidiScope:探索音乐设备通信
MidiScope项目展示了如何使用Android MIDI API检测和分析MIDI设备发送的信号。这对于开发音乐应用、乐器模拟器或任何需要与外部音乐设备通信的应用非常有价值。
MIDI通信基础
MIDI(Musical Instrument Digital Interface)是一种电子乐器之间的通信标准,Android 6.0(API 23)引入了对MIDI的原生支持。MidiScope通过以下步骤实现MIDI信号监听:
核心代码实现
1. 初始化MIDI管理器
private void initializeMidi() {
midiManager = (MidiManager) getSystemService(Context.MIDI_SERVICE);
// 检查MIDI支持
if (midiManager == null) {
Log.e(TAG, "MIDI is not supported on this device");
return;
}
// 枚举已连接的MIDI设备
midiManager.registerDeviceCallback(new MidiManager.DeviceCallback() {
@Override
public void onDeviceAdded(MidiDeviceInfo deviceInfo) {
// 设备添加时更新设备列表
addMidiDevice(deviceInfo);
}
@Override
public void onDeviceRemoved(MidiDeviceInfo deviceInfo) {
// 设备移除时更新设备列表
removeMidiDevice(deviceInfo);
}
}, handler);
// 获取已连接的设备列表
MidiDeviceInfo[] devices = midiManager.getDevices();
for (MidiDeviceInfo device : devices) {
addMidiDevice(device);
}
}
2. 连接MIDI设备
private void connectToDevice(MidiDeviceInfo deviceInfo) {
// 打开MIDI设备
midiManager.openDevice(deviceInfo, new MidiManager.OnDeviceOpenedListener() {
@Override
public void onDeviceOpened(MidiDevice device) {
if (device == null) {
Log.e(TAG, "Failed to open MIDI device");
return;
}
// 获取输入端口
for (int portIndex = 0; portIndex < deviceInfo.getInputPortCount(); portIndex++) {
MidiInputPort inputPort = device.openInputPort(portIndex);
if (inputPort != null) {
// 设置MIDI接收者
inputPort.connect(new MyReceiver());
Log.d(TAG, "Connected to MIDI input port " + portIndex);
}
}
}
}, handler);
}
3. 实现MIDI接收者
private class MyReceiver extends MidiReceiver {
@Override
public void onSend(byte[] data, int offset, int count, long timestamp) throws IOException {
// 处理接收到的MIDI消息
String message = MidiPrinter.formatMessage(data, offset);
logMidiMessage(message);
// 在UI线程更新显示
runOnUiThread(() -> {
TextView textView = findViewById(R.id.midi_data);
textView.append(message + "\n");
// 滚动到底部
ScrollView scrollView = findViewById(R.id.scroll_view);
scrollView.fullScroll(View.FOCUS_DOWN);
});
}
}
2. MidiSynth:从MIDI信号到美妙音乐
MidiSynth项目展示了如何将MIDI信号转换为音频输出,实现一个简单的软件合成器。该项目包含了基础的波形生成、 envelopes(包络)控制和音频输出功能,是理解音频合成原理的绝佳案例。
音频合成核心组件
振荡器实现
振荡器是合成器的核心,负责生成基本波形:
public class SawOscillatorDPW {
private float frequency;
private float sampleRate;
private float phase;
private float dpwZ1;
public void setFrequency(float freq) {
this.frequency = freq;
}
public void setSampleRate(float sampleRate) {
this.sampleRate = sampleRate;
phase = 0;
dpwZ1 = 0;
}
public float getSample() {
// 计算相位增量
float phaseIncrement = frequency / sampleRate;
phase += phaseIncrement;
if (phase >= 1.0f) {
phase -= 1.0f;
}
// DPW(Differentiated Pulse Width)算法生成锯齿波
float square = (phase < 0.5f) ? 1.0f : -1.0f;
float dpw = square - dpwZ1;
dpwZ1 = square;
return dpw * 2.0f; // 调整幅度
}
}
包络控制(ADSR)
ADSR(Attack-Decay-Sustain-Release)包络控制着音符的音量变化:
public class EnvelopeADSR {
private float attackTime = 0.05f; // 50ms
private float decayTime = 0.1f; // 100ms
private float sustainLevel = 0.7f; // 70%
private float releaseTime = 0.5f; // 500ms
private enum State {
IDLE, ATTACK, DECAY, SUSTAIN, RELEASE
}
private State state = State.IDLE;
private float currentLevel = 0.0f;
private float sampleRate;
private long noteOnTime;
private long noteOffTime;
public void setSampleRate(float sampleRate) {
this.sampleRate = sampleRate;
}
public void noteOn() {
state = State.ATTACK;
noteOnTime = System.nanoTime();
}
public void noteOff() {
if (state != State.IDLE) {
state = State.RELEASE;
noteOffTime = System.nanoTime();
}
}
public float getAmplitude() {
switch (state) {
case ATTACK: {
float elapsed = (System.nanoTime() - noteOnTime) / 1e9f;
float progress = elapsed / attackTime;
currentLevel = progress;
if (progress >= 1.0f) {
currentLevel = 1.0f;
state = State.DECAY;
}
break;
}
case DECAY: {
float elapsed = (System.nanoTime() - noteOnTime - attackTime * 1e9f) / 1e9f;
float progress = elapsed / decayTime;
currentLevel = 1.0f - (1.0f - sustainLevel) * progress;
if (progress >= 1.0f) {
currentLevel = sustainLevel;
state = State.SUSTAIN;
}
break;
}
case SUSTAIN:
currentLevel = sustainLevel;
break;
case RELEASE: {
float elapsed = (System.nanoTime() - noteOffTime) / 1e9f;
float progress = elapsed / releaseTime;
currentLevel = sustainLevel * (1.0f - progress);
if (progress >= 1.0f) {
currentLevel = 0.0f;
state = State.IDLE;
}
break;
}
case IDLE:
currentLevel = 0.0f;
break;
}
return currentLevel;
}
public boolean isActive() {
return state != State.IDLE;
}
}
📡 媒体路由与投射
1. BasicMediaRouter:多设备媒体分发
BasicMediaRouter项目展示了如何使用MediaRouter API将媒体内容投射到其他设备,如智能电视、 Chromecast等。这是实现多屏互动的基础技术。
媒体路由工作原理
核心实现代码
1. 初始化媒体路由
private void initializeMediaRouter() {
// 获取MediaRouter实例
mMediaRouter = MediaRouter.getInstance(this);
// 创建媒体路由选择器
mMediaRouteSelector = new MediaRouteSelector.Builder()
.addControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO)
.build();
// 创建媒体路由回调
mMediaRouterCallback = new MediaRouter.Callback() {
@Override
public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
super.onRouteSelected(router, route);
Log.d(TAG, "Route selected: " + route.getName());
// 连接到所选路由
connectToRoute(route);
}
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
super.onRouteUnselected(router, route);
Log.d(TAG, "Route unselected: " + route.getName());
// 断开与路由的连接
disconnectFromRoute();
}
@Override
public void onRoutePresentationDisplayChanged(MediaRouter router, MediaRouter.RouteInfo route) {
super.onRoutePresentationDisplayChanged(router, route);
// 显示发生变化时更新
if (route.isSelected()) {
updatePresentation(route.getPresentationDisplay());
}
}
};
}
2. 创建演示窗口
private void updatePresentation(MediaRouter.RouteInfo route) {
Display display = route.getPresentationDisplay();
// 关闭现有演示
if (mPresentation != null) {
mPresentation.dismiss();
mPresentation = null;
}
// 如果有有效的显示设备,创建新演示
if (display != null) {
mPresentation = new SamplePresentation(this, display);
mPresentation.setOnDismissListener(this);
try {
mPresentation.show();
} catch (WindowManager.InvalidDisplayException ex) {
Log.e(TAG, "Invalid display: " + ex.getMessage());
mPresentation = null;
}
}
}
// 自定义演示类
private class SamplePresentation extends Presentation {
private View mContentView;
public SamplePresentation(Context context, Display display) {
super(context, display);
setContentView(R.layout.presentation_content);
mContentView = findViewById(R.id.content_view);
}
public void setColor(int color) {
mContentView.setBackgroundColor(color);
}
}
3. 添加媒体路由按钮
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_media_route"
android:title="@string/media_route_menu_title"
app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider"
app:showAsAction="always" />
</menu>
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
// 获取媒体路由菜单项
MenuItem mediaRouteMenuItem = menu.findItem(R.id.menu_media_route);
// 获取MediaRouteActionProvider
mMediaRouteActionProvider = (MediaRouteActionProvider) MenuItemCompat.getActionProvider(mediaRouteMenuItem);
// 设置路由选择器
mMediaRouteActionProvider.setRouteSelector(mMediaRouteSelector);
return true;
}
2. MediaRouter:高级媒体路由控制
MediaRouter项目提供了更高级的媒体路由功能,包括自定义路由提供者、会话管理和媒体控制。这对于构建复杂的媒体分发系统非常有用。
🎥 内容捕获与录制
1. MediaRecorder:音视频捕获基础
MediaRecorder项目展示了如何使用Android的MediaRecorder API捕获音视频,实现类似相机应用的录制功能。该项目使用Camera作为输入源,并通过TextureView显示预览。
录制流程解析
核心实现代码
1. 初始化Camera和预览
private void initCamera() {
// 打开相机
mCamera = CameraHelper.getDefaultCameraInstance();
// 设置预览
try {
mCamera.setPreviewTexture(mTextureView.getSurfaceTexture());
mCamera.startPreview();
} catch (IOException e) {
Log.e(TAG, "Failed to set preview texture", e);
}
}
2. 配置MediaRecorder
private boolean prepareMediaRecorder() {
mMediaRecorder = new MediaRecorder();
// 解锁相机并将其设置为MediaRecorder的输入
mCamera.unlock();
mMediaRecorder.setCamera(mCamera);
// 设置音频和视频源
mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
// 设置输出格式
mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
// 设置输出文件
mOutputFile = CameraHelper.getOutputMediaFile(CameraHelper.MEDIA_TYPE_VIDEO);
if (mOutputFile == null) {
Log.e(TAG, "Failed to create output file");
return false;
}
mMediaRecorder.setOutputFile(mOutputFile.getAbsolutePath());
// 设置编码器
mMediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
// 设置视频尺寸和帧率
mMediaRecorder.setVideoSize(1280, 720);
mMediaRecorder.setVideoFrameRate(30);
mMediaRecorder.setVideoEncodingBitRate(10000000); // 10Mbps
// 设置预览方向
mMediaRecorder.setOrientationHint(90);
try {
mMediaRecorder.prepare();
} catch (IOException e) {
Log.e(TAG, "prepare() failed", e);
releaseMediaRecorder();
return false;
}
return true;
}
3. 录制控制
private void startRecording() {
if (!prepareMediaRecorder()) {
releaseMediaRecorder();
return;
}
// 开始录制
try {
mMediaRecorder.start();
mIsRecording = true;
updateControls();
} catch (IllegalStateException e) {
Log.e(TAG, "start() failed", e);
releaseMediaRecorder();
}
}
private void stopRecording() {
if (mIsRecording) {
try {
mMediaRecorder.stop();
} catch (IllegalStateException e) {
Log.e(TAG, "stop() failed", e);
}
releaseMediaRecorder();
mCamera.lock();
mIsRecording = false;
updateControls();
// 保存录制完成的视频
addVideoToGallery();
}
}
private void releaseMediaRecorder() {
if (mMediaRecorder != null) {
mMediaRecorder.reset(); // 重置以准备下一次录制
mMediaRecorder.release(); // 释放资源
mMediaRecorder = null;
mCamera.lock(); // 确保相机被锁定
}
}
2. ScreenCapture:捕捉设备屏幕内容
ScreenCapture项目展示了如何使用Android的MediaProjection API捕获设备屏幕内容,实现类似屏幕录制工具的功能。这在游戏录制、教程制作等场景中非常有用。
屏幕捕获实现步骤
- 请求捕获权限
private static final int REQUEST_MEDIA_PROJECTION = 1;
private void startScreenCapture() {
MediaProjectionManager mProjectionManager =
(MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(
mProjectionManager.createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_MEDIA_PROJECTION) {
if (resultCode == RESULT_OK) {
// 用户授权,开始捕获
mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
startCapture();
}
}
}
- 开始屏幕捕获
private void startCapture() {
// 创建虚拟显示
mVirtualDisplay = mMediaProjection.createVirtualDisplay(
"ScreenCapture",
mWidth, mHeight, mDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mSurface, null, null);
// 开始录制
mMediaRecorder.start();
}
- 停止屏幕捕获
private void stopCapture() {
if (mMediaProjection != null) {
mMediaProjection.stop();
mMediaProjection = null;
}
if (mVirtualDisplay != null) {
mVirtualDisplay.release();
mVirtualDisplay = null;
}
if (mMediaRecorder != null) {
mMediaRecorder.stop();
mMediaRecorder.reset();
}
}
📝 项目实战与最佳实践
1. 媒体应用架构设计
构建健壮的媒体应用需要合理的架构设计,以下是推荐的架构模式:
2. 错误处理与兼容性
媒体开发中,错误处理和兼容性至关重要。以下是一些关键策略:
常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 设备兼容性 | 使用Build.VERSION.SDK_INT检查API级别,提供替代实现 |
| 权限问题 | 实现运行时权限请求,处理权限被拒情况 |
| 资源竞争 | 使用synchronized或锁机制确保线程安全 |
| 格式不支持 | 使用MediaCodecList检查编解码器支持情况 |
| 性能问题 | 使用硬件加速,避免在UI线程处理媒体操作 |
兼容性处理示例
// 检查API级别并提供替代实现
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
// 使用MediaCodec.createByCodecName()
MediaCodec codec = MediaCodec.createByCodecName(codecName);
} else {
// 使用旧版API
MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
}
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 请求权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CAMERA_PERMISSION);
} else {
// 权限已授予,初始化相机
initCamera();
}
3. 性能优化指南
媒体应用对性能要求较高,以下是一些关键优化技巧:
- 使用硬件加速:优先使用硬件编解码器,避免软件编解码的性能开销
- 异步处理:所有媒体操作放在后台线程,避免阻塞UI
- 资源复用:复用MediaCodec、MediaExtractor等对象,减少创建销毁开销
- 缓冲区管理:合理设置缓冲区大小,避免频繁内存分配
- 电量优化:闲置时释放相机、麦克风等耗电资源
- 网络优化:实现自适应码率,根据网络状况调整视频质量
🔚 总结与展望
Android媒体开发是一个复杂但充满机遇的领域。通过本文介绍的media-samples项目,你已经了解了Android媒体API的核心功能和最佳实践。从基础的视频解码到高级的媒体路由,从简单的音频合成到复杂的屏幕录制,这些项目覆盖了现代Android应用开发中常见的媒体场景。
随着5G技术的普及和AR/VR应用的兴起,Android媒体开发将迎来更多创新机遇。未来,我们可以期待更高效的编解码技术、更低延迟的媒体传输和更丰富的交互方式。掌握本文介绍的媒体开发基础,将为你在这些新兴领域的探索打下坚实基础。
最后,建议你深入研究media-samples项目的源码,尝试修改和扩展功能,将这些知识应用到自己的项目中。媒体开发充满挑战,但也乐趣无穷,祝你在Android媒体开发的道路上越走越远!
📚 扩展学习资源
- Android官方媒体开发文档:https://developer.android.com/guide/topics/media
- ExoPlayer官方文档:https://exoplayer.dev/
- Android媒体架构指南:https://developer.android.com/topic/architecture
- Android性能优化模式:https://developer.android.com/topic/performance
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



