美文网首页
MediaCodec 也能播视频?

MediaCodec 也能播视频?

作者: 毛先森 | 来源:发表于2020-12-14 15:56 被阅读0次

    目录

    • 是什么
    • 重要方法
    • 解码
    • 最佳实践:解码 h264 进行播放
    • 总结
    播放 h264 效果

    是什么

    MediaCodec 是 Android 原生提供的音视频编解码框架,由于直接与硬件交互(dsp),所以是以硬件编解码的方式工作

    重要方法

    • 创建编解码器 createDecoderByType(int type)、createEncoderByType(int type)
    • 创建多媒体格式 createVideoFormat,主要有帧率、帧宽高等信息,用于 MediaCodec.configure() 配置初始化信息
    • 初始化配置信息 configure(),设置格式,surface,加密,标志
    • start()启动
    • 获取所有 ByteBuffer 输入队列 dequeueInputBuffer(),该队列是由 dsp 内部维护,大小固定,我们先获取所有队列,然后判断哪些是当前未被占用的,如果返回为 -1 ,则没有可用的
    • 获取输出 ByteBuffer 的索引 dequeueOutputBuffer(),这里的输出队列的索引和输入队列并不一一对应,所以我们先去获取到输出的索引
    • 获取 ByteBuffer 中的数据内容 releaseOutputBuffer()

    解码

    将 h264等编码数据转换为 YUV 等原始数据,如果我们怎样手动把 h264 数据渲染到 SurfaceView 上呢?

    1. 创建 SurfaceView 控件,并获取到 Surface
    2. 初始化 MediaCodec ,获取解码器并且配置
    3. 获取文件路径,将文件转换为 byte 数组
    4. 获取当前可用的 ByteBuffer,并填入原始数据
    5. 获取输出 ByteBuffer,取出数据
    6. MediaCodec 初始化时与 Surface 绑定,只要在 releaseOutputBuffer() 中开启渲染,此时 SurfaceView 在就能正常出现解码后的视频数据

    最佳实践

    • 目标: 使用 MediaCodec 解码本地 h264 文件,并且播放视频到 SurfaceView
    • 通过这个实践,能够对 MediaCodec 的解码流程有个基本了解
    // 解码工具类
    
    public class H264Player implements Runnable {
    
        // 本地 h264 文件路径
        private String path;
        private Surface surface;
        private MediaCodec mediaCodec;
        private Context context;
    
        public H264Player(Context context, String path, Surface surface) {
    
            this.context = context;
            this.path = path;
            this.surface = surface;
            try {
                this.mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
                // 视频宽高暂时写死
                MediaFormat mediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 368, 384);
                mediaFormat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
                mediaCodec.configure(mediaFormat, surface, null, 0);
            } catch (IOException e) {
                // 解码芯片不支持,走软解
                e.printStackTrace();
            }
        }
    
    
        public void play() {
            mediaCodec.start();
            new Thread(this::run).start();
        }
    
        @Override
        public void run() {
            // 解码 h264
            decodeH264();
        }
    
        private void decodeH264() {
            byte[] bytes = null;
            try {
                bytes = getBytes(path);
            } catch (IOException e) {
                e.printStackTrace();
            }
            // 获取队列
            ByteBuffer[] byteBuffers = mediaCodec.getInputBuffers();
            int startIndex = 0;
            int nextFrameStart;
            int totalCount = bytes.length;
    
            while (true) {
                if (startIndex >= totalCount) {
                    break;
                }
                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
                nextFrameStart = findFrame(bytes, startIndex+1,totalCount);
                // 往 ByteBuffer 中塞入数据
                int index = mediaCodec.dequeueInputBuffer(10 * 1000);
                Log.e("index",index+"");
                // 获取 dsp 成功
                if (index >= 0) {
                    // 拿到可用的 ByteBuffer
                    ByteBuffer byteBuffer = byteBuffers[index];
                    byteBuffer.clear();
                    byteBuffer.put(bytes, startIndex, nextFrameStart - startIndex);
                    // 识别分隔符,找到分隔符对应的索引
                    mediaCodec.queueInputBuffer(index, 0, nextFrameStart - startIndex, 0, 0);
                    startIndex = nextFrameStart;
    
                }else {
                    continue;
                }
    
    
                // 从 ByteBuffer 中获取解码好的数据
                int outIndex = mediaCodec.dequeueOutputBuffer(info,10 * 1000);
                if (outIndex > 0){
                    try {
                        Thread.sleep(33);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    mediaCodec.releaseOutputBuffer(outIndex, true);
                }
    
            }
        }
    
        private int findFrame(byte[] bytes, int startIndex, int totalSize) {
            for (int i = startIndex; i < totalSize - 4; i++) {
                if (bytes[i] == 0x00 && bytes[i + 1] == 0x00 && bytes[i + 2] == 0x00 && bytes[i + 3] == 0x01) {
                    return i;
                }
    
            }
            return -1;
        }
    
    
        /**
         * 一次性读取文件
         *
         * @param path
         * @return
         * @throws IOException
         */
        public byte[] getBytes(String path) throws IOException {
            InputStream is = new DataInputStream(new FileInputStream(new File(path)));
            int len;
            int size = 1024;
            byte[] buf;
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            buf = new byte[size];
            while ((len = is.read(buf, 0, size)) != -1)
                bos.write(buf, 0, len);
            buf = bos.toByteArray();
            return buf;
        }
    
    
    }
    
    

    在 Activity 中播放

    
    public class MainActivity extends AppCompatActivity {
        private H264Player h264Player;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            checkPermission();
            initSurface();
        }
    
        private void initSurface() {
            SurfaceView surfaceView = findViewById(R.id.surface);
            SurfaceHolder surfaceHolder = surfaceView.getHolder();
            surfaceHolder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder surfaceHolder) {
                    h264Player = new H264Player(MainActivity.this, new File(Environment.getExternalStorageDirectory(), "test.h264").getAbsolutePath(), surfaceHolder.getSurface());
                    h264Player.play();
                }
    
                @Override
                public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
                }
    
                @Override
                public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
                }
            });
        }
    
        private boolean checkPermission() {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
                    && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                requestPermissions(new String[]{
                        Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE
                }, 1);
            }
            return false;
        }
    }
    

    总结

    这次我们播放的 h264 文件,需要先使用 FFmpeg 从 mp4 文件分离出 h264,放到手机储存卡根目录(也可以放到本地其他文件夹,文件路径对应修改就好了),通过创建 MediaCodec 解码器,绑定 Surface,获取 ByteBuffer 等一系列操作,最终实现了手动解码视频文件,并且渲染到 SurfaceView,这个 Demo 主要用于熟悉 Api,不适宜用于实际工业级别开发(比如整个读取文件是不可取的)

    相关链接

    android-mediacodec-google官方demo

    相关文章

      网友评论

          本文标题:MediaCodec 也能播视频?

          本文链接:https://www.haomeiwen.com/subject/flwvgktx.html