• 使用 MediaCodec 在 Android 上进行硬解码


    要使用 MediaCodec 在 Android 上进行硬解码,并获取 RGBA 数据,你可以按照以下步骤进行操作:

    创建 MediaExtractor 对象并设置要解码的 MP4 文件路径

    MediaExtractor extractor = new MediaExtractor();
    extractor.setDataSource(filePath);
    
    • 1
    • 2

    根据需要选择音频或视频轨道:

    int trackCount = extractor.getTrackCount();
    int trackIndex = -1;
    for (int i = 0; i < trackCount; i++) {
        MediaFormat format = extractor.getTrackFormat(i);
        String mime = format.getString(MediaFormat.KEY_MIME);
        if (mime.startsWith("video/")) {
            trackIndex = i;
            break;
        }
    }
    if (trackIndex >= 0) {
        extractor.selectTrack(trackIndex);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    创建 MediaCodec 对象并配置解码器

    MediaFormat format = extractor.getTrackFormat(trackIndex);
    String mime = format.getString(MediaFormat.KEY_MIME);
    MediaCodec codec = MediaCodec.createDecoderByType(mime);
    codec.configure(format, null, null, 0);
    codec.start();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    循环解码并获取 RGBA 数据:

    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
    boolean isEOS = false;
    while (!isEOS) {
        int inputBufferIndex = codec.dequeueInputBuffer(timeoutUs);
        if (inputBufferIndex >= 0) {
            ByteBuffer inputBuffer = codec.getInputBuffer(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(bufferInfo, timeoutUs);
        if (outputBufferIndex >= 0) {
            ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
            // 解码后的数据位于 outputBuffer 中,根据需要进行 RGBA 数据的提取和处理
            // outputBuffer 中的数据格式可能是 YUV 或其他格式,需要根据解码器设置的输出格式进行相应的转换
            codec.releaseOutputBuffer(outputBufferIndex, false);
        } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
            // 解码器输出格式已更改,可以通过 codec.getOutputFormat() 获取新的格式
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在上述代码中,你需要根据解码器输出的数据格式进行相应的转换,以获取 RGBA 数据。具体的转换流程和代码取决于解码器输出的数据格式和你的需求。

    需要注意的是,硬解码的支持和性能可能因设备、Android 版本和视频编码格式的不同而有所差异。确保你的设备支持硬解码,并且适当处理解码器的输入和输出缓冲区。

    以下是完整代码:

    import android.media.MediaCodec;
    import android.media.MediaExtractor;
    import android.media.MediaFormat;
    import android.util.Log;
    
    import java.nio.ByteBuffer;
    
    public class MP4Decoder {
        private static final int TIMEOUT_US = 10000;
        private static final String TAG = "MP4Decoder";
    
        public interface FrameCallback {
            void onFrameDecoded(byte[] rgbaData, int width, int height);
        }
    
        public static void decodeMP4(String filePath, FrameCallback callback) {
            new Thread(() -> {
                MediaExtractor extractor = new MediaExtractor();
                try {
                    extractor.setDataSource(filePath);
    
                    int trackIndex = selectVideoTrack(extractor);
                    if (trackIndex < 0) {
                        return;
                    }
    
                    MediaFormat format = extractor.getTrackFormat(trackIndex);
                    String mime = format.getString(MediaFormat.KEY_MIME);
                    MediaCodec codec = MediaCodec.createDecoderByType(mime);
                    codec.configure(format, null, null, 0);
                    codec.start();
    
                    MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
                    boolean isEOS = false;
                    while (!isEOS) {
                        int inputBufferIndex = codec.dequeueInputBuffer(TIMEOUT_US);
                        if (inputBufferIndex >= 0) {
                            ByteBuffer inputBuffer = codec.getInputBuffer(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(bufferInfo, TIMEOUT_US);
                        if (outputBufferIndex >= 0) {
                            ByteBuffer outputBuffer = codec.getOutputBuffer(outputBufferIndex);
                            // 解码后的数据位于 outputBuffer 中,根据需要进行 RGBA 数据的提取和处理
                            // outputBuffer 中的数据格式可能是 YUV 或其他格式,需要根据解码器设置的输出格式进行相应的转换
                            byte[] rgbaData = convertToRGBA(outputBuffer, format);
                            int width = format.getInteger(MediaFormat.KEY_WIDTH);
                            int height = format.getInteger(MediaFormat.KEY_HEIGHT);
                            callback.onFrameDecoded(rgbaData, width, height);
                            codec.releaseOutputBuffer(outputBufferIndex, false);
                        } else if (outputBufferIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                            // 解码器输出格式已更改,可以通过 codec.getOutputFormat() 获取新的格式
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    extractor.release();
                }
            }).start();
        }
    
        private static int selectVideoTrack(MediaExtractor extractor) {
            int trackCount = extractor.getTrackCount();
            int trackIndex = -1;
            for (int i = 0; i < trackCount; i++) {
                MediaFormat format = extractor.getTrackFormat(i);
                String mime = format.getString(MediaFormat.KEY_MIME);
                if (mime.startsWith("video/")) {
                    trackIndex = i;
                    break;
                }
            }
            if (trackIndex >= 0) {
                extractor.selectTrack(trackIndex);
            }
            return trackIndex;
        }
    
        private static byte[] convertToRGBA(ByteBuffer buffer, MediaFormat format) {
            // 根据解码器设置的输出格式进行 RGBA 数据的提取和处理
            // 这里只是一个示例,实际的转换过程可能会更复杂,取决于输出格式和需求
            int width = format.getInteger(MediaFormat.KEY_WIDTH);
            int height = format.getInteger(MediaFormat.KEY_HEIGHT);
            int remaining = buffer.remaining();
            byte[] rgbaData = new byte[remaining];
            buffer.rewind();
            buffer.get(rgbaData);
            Log.i(TAG, "convertToRGBA: count:" + width + "; " + height + "; " + remaining);
            // todo: 进行 YUV 到 RGBA 的转换
            return rgbaData;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
  • 相关阅读:
    测试工程师应具备何种心态?
    MySQL8.0优化 - 锁 - 从数据操作的类型划分:读锁、写锁
    游戏合作伙伴专题:BreederDAO 与 Ultiverse 建立了一个新的元宇宙
    QT学习日记22——翻金币游戏
    Python的collections包中的双端队列deque
    参加CSP-J第一轮后的感受
    【机械】基于simulink模拟给定系统的超前补偿器,比较补偿和未补偿系统对阶跃和斜坡输入的响应
    阿里AoneFlow分支管理
    利用对话式人工智能简化运营:案例研究
    矩阵类问题处理技巧
  • 原文地址:https://blog.csdn.net/mhhyoucom/article/details/138145437