2025最强Android媒体开发指南:从零基础到精通音视频全场景应用

2025最强Android媒体开发指南:从零基础到精通音视频全场景应用

【免费下载链接】media-samples Multiple samples showing the best practices in media APIs on Android (audio, video, etc.). 【免费下载链接】media-samples 项目地址: https://gitcode.com/gh_mirrors/me/media-samples

你是否还在为Android音视频开发中的兼容性问题头疼?是否想掌握MediaCodec、MediaRecorder等核心API却不知从何下手?本文将通过9个实战项目带你系统掌握Android媒体开发全流程,从基础播放到高级投屏,从音频合成到屏幕录制,一站式解决你的开发痛点。读完本文,你将获得:

  • 10+核心媒体API的实战应用经验
  • 7个完整项目的源码解析与最佳实践
  • 5大媒体场景的解决方案与性能优化技巧
  • 一套可直接复用的媒体开发框架

📋 项目概述:Android媒体示例仓库

Android媒体示例项目(media-samples)是Google官方提供的媒体开发学习宝库,包含多个独立的Android Studio项目,展示了音频、视频等媒体API的最佳实践。该仓库采用模块化设计,每个项目专注于特定媒体功能,从基础播放到高级媒体路由,覆盖了现代Android应用开发中常见的媒体场景。

mermaid

项目结构解析

仓库采用清晰的目录结构,每个子项目都是独立的Android应用,可单独编译运行:

项目名称核心功能最低API技术亮点
BasicMediaDecoder视频解码与渲染16MediaCodec + TimeAnimator同步
BasicMediaRouter媒体路由控制14多设备内容投射
MediaRecorder音视频录制14Camera + MediaRecorder整合
MediaRouter高级媒体路由14自定义路由提供者
MidiScopeMIDI信号分析23MIDI设备检测与信号解析
MidiSynth音频合成器23波形生成与音频输出
PictureInPicture画中画功能26Android O新特性
ScreenCapture屏幕录制21MediaProjection API
VideoPlayer高级播放器26ExoPlayer + MediaSession

🚀 环境准备与项目构建

开发环境要求

  • Android Studio 4.0+
  • Android SDK 28+
  • Android Build Tools v28.0.3+
  • Gradle 4.10.1+
  • JDK 8+

快速开始

  1. 克隆项目
git clone https://gitcode.com/gh_mirrors/me/media-samples.git
cd media-samples
  1. 构建项目
# 构建单个项目(以MediaRecorder为例)
cd MediaRecorder
./gradlew assembleDebug

# 或使用Android Studio打开项目
  1. 运行应用
# 安装调试版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:时间同步动画器
解码流程解析

视频解码主要分为以下步骤,每个步骤都有其关键实现要点:

mermaid

关键代码实现

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) {
        // 纹理更新回调
    }
});
性能优化要点
  1. 缓冲区管理:合理设置缓冲区大小,避免频繁内存分配
  2. 同步策略:使用TimeAnimator而非Handler.postDelayed,确保与显示帧同步
  3. 错误处理:添加完整的异常捕获和资源释放机制
  4. 格式检测:提前检查视频格式支持情况,提供友好的错误提示

2. VideoPlayer:构建企业级视频播放应用

VideoPlayer项目展示了如何构建功能完善的视频播放器,支持本地和远程视频播放、播放列表管理、MediaSession集成以及画中画功能。该项目采用了ExoPlayer作为核心播放引擎,是构建现代Android视频应用的理想参考。

核心功能模块

mermaid

关键实现: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及以上版本的画中画功能实现,让应用能够在小窗口中继续播放视频,同时允许用户使用其他应用。这在视频通话、视频播放类应用中尤为重要。

实现画中画的核心步骤
  1. 清单配置
<activity
    android:name=".MovieActivity"
    android:supportsPictureInPicture="true"
    android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
    android:resizeableActivity="true">
</activity>
  1. 实现活动逻辑
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信号监听:

mermaid

核心代码实现

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(包络)控制和音频输出功能,是理解音频合成原理的绝佳案例。

音频合成核心组件

mermaid

振荡器实现

振荡器是合成器的核心,负责生成基本波形:

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等。这是实现多屏互动的基础技术。

媒体路由工作原理

mermaid

核心实现代码

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显示预览。

录制流程解析

mermaid

核心实现代码

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捕获设备屏幕内容,实现类似屏幕录制工具的功能。这在游戏录制、教程制作等场景中非常有用。

屏幕捕获实现步骤
  1. 请求捕获权限
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();
        }
    }
}
  1. 开始屏幕捕获
private void startCapture() {
    // 创建虚拟显示
    mVirtualDisplay = mMediaProjection.createVirtualDisplay(
        "ScreenCapture",
        mWidth, mHeight, mDensity,
        DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
        mSurface, null, null);
    
    // 开始录制
    mMediaRecorder.start();
}
  1. 停止屏幕捕获
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. 媒体应用架构设计

构建健壮的媒体应用需要合理的架构设计,以下是推荐的架构模式:

mermaid

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. 性能优化指南

媒体应用对性能要求较高,以下是一些关键优化技巧:

  1. 使用硬件加速:优先使用硬件编解码器,避免软件编解码的性能开销
  2. 异步处理:所有媒体操作放在后台线程,避免阻塞UI
  3. 资源复用:复用MediaCodec、MediaExtractor等对象,减少创建销毁开销
  4. 缓冲区管理:合理设置缓冲区大小,避免频繁内存分配
  5. 电量优化:闲置时释放相机、麦克风等耗电资源
  6. 网络优化:实现自适应码率,根据网络状况调整视频质量

🔚 总结与展望

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

【免费下载链接】media-samples Multiple samples showing the best practices in media APIs on Android (audio, video, etc.). 【免费下载链接】media-samples 项目地址: https://gitcode.com/gh_mirrors/me/media-samples

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值