Android平台的视频解码学习资料

原创
08/01 21:59
阅读数 60

关于Android平台的视频解码

使用Android系统自带的mediaCodec

  • MediaCodec是Android的底层多媒体的硬编解码器,可以对音视频进行编解码,效率比软编解码高(这里相对于ffmpeg来说,这个说法引用网上大牛的说法,本人并没有亲自测试)。
  1. 由于项目需要,演示一遍MediaCodec对本地视频文件的解码,整个解码的逻辑流程如下:
    • 使用MeidaExtroctor选择出video所在轨道
    • 使用video的format创建MediaCodec
    • 给MediaCodec创建回调,在回调里进行数据的输入和输出的处理

// 获取指定的MediaExtroctor的音视频类型和轨道所在index

public int[] getSupportVideo() {
        // get MediaCodec support format
        int numCodecs = MediaCodecList.getCodecCount();
        MediaCodecInfo mediaCodecInfo = null;
        for (int i=0; i<numCodecs; i++) {
            boolean isFound = false;
            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
//                    Log.i(utils.LOG_TAG, "mediaCodec: " + info.getName() + " - " + info.isEncoder());
            if (!info.isEncoder()) {
                String[] types = info.getSupportedTypes();
                for (int j=0; j<types.length; j++) {
                    Log.i(utils.LOG_TAG, types[j]);
                    if (types[j].equals("video/avc")) {
                        isFound = true;
                        mediaCodecInfo = info;
                        break;
                    }
                }
                if (isFound) {
                    break;
                }
            }
        }
        MediaCodecInfo.CodecCapabilities codecCapabilities = mediaCodecInfo.getCapabilitiesForType("video/avc");
        // 下面的colorFormats数据元素对应的是MediaCodecInfo.CodecCapabilities定义的格式类型常量
        return codecCapabilities.colorFormats;
    }

public Map<String, Object> getMediaTrackInfo(MediaExtractor mediaExtractor) {
        Map<String, Object> infoMap = new HashMap<>();
        int trackCount = mediaExtractor.getTrackCount();
        String mimeInfo = "";
        MediaFormat mediaFormat;
        for (int i=0; i<trackCount; i++) {
            mediaFormat = mediaExtractor.getTrackFormat(i);
            mimeInfo = mediaFormat.getString("mime");
            if (mimeInfo.contains("audio")) {
                infoMap.put("audioIndex", i);
                infoMap.put("audioType", mimeInfo);
                infoMap.put("audioFormat", mediaFormat);
            }
            if (mimeInfo.contains("video")) {
                infoMap.put("videoIndex", i);
                infoMap.put("videoType", mimeInfo);
                infoMap.put("videoFormat", mediaFormat);
            }
        }
        return infoMap;
    }

mediaExtractor = new MediaExtractor();
                // get video and audio track
                try {
                    mediaExtractor.setDataSource("/sdcard/1.mp4");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                Map extractorInfoMap = getMediaTrackInfo(mediaExtractor);
                int audioTrack = Integer.valueOf(extractorInfoMap.get("audioIndex").toString());
                int videoTrack = Integer.valueOf(extractorInfoMap.get("videoIndex").toString());
                String videoType = extractorInfoMap.get("videoType").toString();
                String audioType = extractorInfoMap.get("audioType").toString();
//这里解析的是视频,因此选择视频轨道
                mediaExtractor.selectTrack(videoTrack);

                try {
                    mediaCodec = MediaCodec.createDecoderByType(videoType);
                    MediaFormat mediaFormat = (MediaFormat) extractorInfoMap.get("videoFormat");
					// 可以使用这个MediaFormat指定一些格式内容,如果解析后的内容需要在surface上面播放,就不要指定下面的COLOR_FORMAT,否则会造成格式不匹配,无法播放
					// 如果需要修改输出的COLOR_FORMAT,需要使用MeidaCodecList和MediaCodecInfo结合,获取支持的格式,这个支持的格式因手机型号而异,可以根据上面的getSupportVideoFormat方法进行判断
                    // mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar);
                    Log.i(utils.LOG_TAG, "video color: " + mediaFormat.toString());
					// 然后使用这个格式来创建MediaCodec,第二个参数是要显示的surface,这里要注意,如果设置了要显示的surface,则在onOutputBufferAvailable回调里,无法获取到解析后的数据
                    mediaCodec.configure(mediaFormat, null, null, 0);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                mediaCodec.setCallback(new MediaCodec.Callback() {
                    @Override
                    public void onInputBufferAvailable(@NonNull MediaCodec mCodec, int i) {
                        ByteBuffer inBuffer = mediaCodec.getInputBuffer(i);
                        inBuffer.clear();
                        int readSize = mediaExtractor.readSampleData(inBuffer, 0);
                        mediaExtractor.advance();
                        if (readSize < 0) {
                            mediaCodec.queueInputBuffer(i, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            Log.i(utils.LOG_TAG, "readCount: " + readCount);
                            return;
                        }
                        readCount++;
//                        Log.i(utils.LOG_TAG, "onInputBufferAvailable: " + readSize);
                        mediaCodec.queueInputBuffer(i, 0,  readSize, mediaExtractor.getSampleTime(), mediaExtractor.getSampleFlags());
                    }

                    @Override
                    public void onOutputBufferAvailable(@NonNull MediaCodec mCodec, int i, @NonNull MediaCodec.BufferInfo bInfo) {
                        ByteBuffer outBuffer = mediaCodec.getOutputBuffer(i);
                        MediaFormat format1 = mediaCodec.getOutputFormat(i);
                        Log.i(utils.LOG_TAG, "onOutputBufferAvailable, capility: " + outBuffer.capacity() + ", limit: " + outBuffer.limit() + ", position: " + outBuffer.position() + ", width: " + format1.getInteger(MediaFormat.KEY_WIDTH) + ", height: " + format1.getInteger(MediaFormat.KEY_HEIGHT) + ", color: " + format1.getInteger(MediaFormat.KEY_COLOR_FORMAT));
						
						// 下面的逻辑是保证解析的速度是正常速度,否则会造成解析速度快于正常速度
                        if (startMs == -1) {
                            startMs = System.currentTimeMillis();
                        }
                        long ptsTime = bInfo.presentationTimeUs / 1000;
                        long systemMs = System.currentTimeMillis() - startMs;
                        long diffentMs = ptsTime - systemMs;
                        if (diffentMs > 0) {
                            utils.sleep(diffentMs);
                        }
                        imgIndex++;
                        mCodec.releaseOutputBuffer(i, false);

                        if (bInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
                            Log.i(utils.LOG_TAG, "outData is end");
                            mediaCodec.stop();
                            mediaCodec.release();
                            mediaExtractor.release();
                        }
                    }

                    @Override
                    public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
                        Log.i(utils.LOG_TAG, "err: " + e.getMessage());
                    }

                    @Override
                    public void onOutputFormatChanged(@NonNull MediaCodec mCodec, @NonNull MediaFormat mFormat) {
                        Log.i(utils.LOG_TAG, "onOutputFormatChanged, width: " + mFormat.getInteger(MediaFormat.KEY_WIDTH) + ", height: " + mFormat.getInteger(MediaFormat.KEY_HEIGHT) + ", color: " + mFormat.getInteger(MediaFormat.KEY_COLOR_FORMAT));
                    }
                });
                mediaCodec.start();
展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部