mediaCodec例子01-解码h264视频

333 阅读2分钟

解码流程

  • 1.获取从surfaceView初始化后得到的surface
  • 2.配置解码器mediaCodec,并在configure中设置surface
  • 3.获取解码的ByteBuffer,从h264数据中通过分隔符000001/00000001查找帧信息,将数据拷贝到ByteBuffer中,告知mediaCodec数据准备好了
  • 4.获取输出的BufferInfo和OutIndex
  • 5.releaseOutputBuffer并渲染

完整例子

H264DecodeActivity

public class H264DecodeActivity extends AppCompatActivity {
    public 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.READ_EXTERNAL_STORAGE,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
            }, 1);

        }
        return false;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_h264_decode);
        checkPermission();
        initSurface();
    }

    private H264Player h264Player;
    private void initSurface() {
        SurfaceView surface = (SurfaceView) findViewById(R.id.preview);
        surface.getHolder().addCallback(new SurfaceHolder.Callback() {


            @Override
            public void surfaceCreated(@NonNull SurfaceHolder surfaceHolder) {
                Surface surface1= surfaceHolder.getSurface();
                h264Player = new H264Player(
                        new File("/data/data/com.example.testh264/cache/out.h264")
                                .getAbsolutePath()
                        ,surface1);
                h264Player.play();
            }

            @Override
            public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

            }

            @Override
            public void surfaceDestroyed(@NonNull SurfaceHolder holder) {

            }
        });
    }
}

H264Player

public class H264Player implements Runnable{

    private  MediaCodec mediaCodec;
    private  String path;

    public H264Player(String absolutePath, Surface surface) {
        Log.d("hucaihua" , "exsit :" + new File(absolutePath).exists());
        try {
            this.path = absolutePath;
            //设置解码器使用的数据类型 h264
            mediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_VIDEO_AVC);
            MediaFormat mediaformat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC,64, 368);
            //设置关键帧间隔
            mediaformat.setInteger(MediaFormat.KEY_FRAME_RATE, 15);
            //加入的surface表示后续的内容输出到surface中,flag=0,如果是编码则需要设置flag为编码
            mediaCodec.configure(mediaformat, surface, null, 0);
        }catch (Exception e){
            e.printStackTrace();
        }
        Log.i("hucaihua", "配置成功");
    }

    public void play() {
        Log.d("hucaihua" , "start play");
        new Thread(this).start();
    }

    @Override
    public void run() {
        mediaCodec.start();

        try {
            decodeH264();
        } catch (Exception e) {
            Log.i("hucaihua", "run: "+e.toString());
        }
    }

    private void decodeH264() {
        try {
            byte[] bytes = getBytes(path);
            int startIndex = 0;
            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
            while (true){
                int nextFrameStart = findByFrame(bytes , startIndex + 2, bytes.length);
                int size = nextFrameStart -startIndex;
                if (size > 0 ){
                    //从mediaCodec中找到一块空闲的buffer,mediaCodec是为所有的app服务的,因此不能通过回调的形式传回处理好的数据
                    int bufferIndex =  mediaCodec.dequeueInputBuffer(10000);
                    if (bufferIndex >= 0){
                        //获取这块空闲的buffer
                        ByteBuffer byteBuffer = mediaCodec.getInputBuffer(bufferIndex);
                        Log.i("hucaihua", "decodeH264: 输入  "+size);
                        //往buffer中放入要解码的数据
                        byteBuffer.put(bytes , startIndex , size);
                        //解码不需要设置pts,因为文件中的顺序,刚好就是解码顺序。
                        //告知mediaCodec已经放置好数据,可以开始解码了
                        mediaCodec.queueInputBuffer(bufferIndex ,0 , size , 0 , 0);
                        startIndex = nextFrameStart;

                    }else {
                        Log.e("hucaihua" , "没有合适的Buffer");
                    }
                }

                // info用来装decode出来的数据的信息,它是远远大于我们输入的信息的。
                // 从mediaCodec中获取输出数据,输出数据的信息与输入数据信息不一样,因此mediaCodec通过BufferInfo来告知我们
                int outIndex = mediaCodec.dequeueOutputBuffer(info , 1100);
                if (outIndex >= 0){
                    //控制播放速度
                    Thread.sleep(33);
                    //将解码好的数据渲染到surface中
                    mediaCodec.releaseOutputBuffer(outIndex , true);
                }else{
                    break;
                }
            }

            mediaCodec.stop();


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 0x 00 00 00 01 或者 0x 00 00 01
    private int findByFrame( byte[] bytes, int start, int totalSize) {
        for (int i = start; i <= totalSize-4; i++) {
            if (((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x00) && (bytes[i + 3] == 0x01))
                    ||((bytes[i] == 0x00) && (bytes[i + 1] == 0x00) && (bytes[i + 2] == 0x01))) {
                return i;
            }
        }
        return -1;
    }

    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;
    }
}