美文网首页
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 也能播视频?

    目录 是什么 重要方法 解码 最佳实践:解码 h264 进行播放 总结 是什么 MediaCodec 是 Andr...

  • Android 音视频学习

    概述音频的采集与播放视频的采集与渲染MediaCodec 硬编解码MediaCodec 硬编 aac 参考 音视频...

  • NDK Mediacodec

    Mediacodec Android从API 16开始提供java层的MediaCodec视频硬解码接口;从API...

  • 音视频学习系列第(七)篇---MediaCodec的使用

    音视频系列 什么是MediaCodec MediaCodec是安卓官方提供的一套用于音视频编码和解码的API,该A...

  • 使用MediaCodec 播放视频

    MediaCodec框架剖析中讲到了可以使用MediaCodec来播放视频,我们现在就来试下如何使用MediaCo...

  • 利用 MediaCodec 进行转码

    前面的文章简单介绍了 MediaCodec 的使用说明,这篇文章会说明如何使用 MediaCodec 进行视频转码...

  • 第十八节、关于硬解与软解

    硬件解码视频(MediaCodec)、软件解码视频(FFMpeg) 硬件解码视频: 我们知道AVPacket中存放...

  • MediaCodec编码视频

    在Android 4.3系统之后,用MediaCodec编码视频成为了主流的使用场景,尽管Android的碎片化比...

  • 视频播放器本地代理服务设计

    场景 希望在播放视频的时候能边下边播。而不是等整个视频下好才能播缓存视频,对于播放过的视频能缓存住,下次不从网络获...

  • Android MediaCodec

    MediaCodec是什么? MediaCodec类为开发者提供了能访问到Android底层媒体Codec(Enc...

网友评论

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

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