Android MediaCodec 解码H264码流播放

视频编解码,编的是什么码?解的又是什么码?有没有想过?现在主流的就是H264码流,Android 采集摄像头原始帧数据
这篇博客讲解的是如何从摄像头从提取YUV画面色值,然后由MediaCodec进行编码压缩,最后生成的就是H264码流,我们先了解下H264码流格式。
Android MediaCodec 解码H264码流播放_第1张图片
可以看到一个个NALU单元组成了H264码流,NALU单元又包含头数据部分和帧数据部分。
每一个头开始都包含0x 00000001或者0x000001.
我选取了上面讲的采集摄像头画面进行编码后的H264文件,打开其字节文件,码流格式数据如下
Android MediaCodec 解码H264码流播放_第2张图片
可以看到手机编码后的码流每个NAL开头起始码为0x00000001
所以我要做的工作就是提取出每一个NAL单元,然后送给MediaCodec进行解码。
提取出NAL 单元的代码函数如下

     private byte[] getNALU() {
        try {
            int curpos = 0;
            //一般NAL不超过100000字节
            byte[] bb = new byte[100000];
            //先读取4个字节
            rf.read(bb, 0, 4);
            //判断是否是0x00000001开头
            if (findStartCode4(bb, 0)) {
                curpos = 4;
            } else {
                rf.seek(0);
                rf.read(bb, 0, 3);
                //判断是否是0x000001开头
                if (findStartCode3(bb, 0)) {
                    curpos = 3;
                }
            }
            //标志是否找到NAL单元开头
            boolean findNALStartCode = false;
            //下一个NAL单元的开始位置
            int nextNalStartPos = 0;
            //找到适合标记开头的长度
            int reWind = 0;
            while (!findNALStartCode) {
                int hex = rf.read();
                if (curpos >= bb.length) {
                    break;
                }
                bb[curpos++] = (byte) hex;
                if (hex == -1) {
                    nextNalStartPos = curpos;
                }
                if (findStartCode4(bb, curpos - 4)) {
                    findNALStartCode = true;
                    reWind = 4;
                    nextNalStartPos = curpos - reWind;

                } else if (findStartCode3(bb, curpos - 3)) {
                    findNALStartCode = true;
                    reWind = 3;
                    nextNalStartPos = curpos - reWind;
                }
            }
            byte[] nal = new byte[nextNalStartPos];
            System.arraycopy(bb, 0, nal, 0, nextNalStartPos);
            long pos = rf.getFilePointer();
            long setPos = pos - reWind;
            //退回rewind长度字节
            rf.seek(setPos);
            return nal;

        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //find match "00 00 00 01"
    private boolean findStartCode4(byte[] bb, int offSet) {
        if (offSet < 0) {
            return false;
        }
        if (bb[offSet] == 0 && bb[offSet + 1] == 0 && bb[offSet + 2] == 0 && bb[offSet + 3] == 1) {
            return true;
        }
        return false;
    }

    //find match "00 00 01"
    private boolean findStartCode3(byte[] bb, int offSet) {
        if (offSet <= 0) {
            return false;
        }
        if (bb[offSet] == 0 && bb[offSet + 1] == 0 && bb[offSet + 2] == 1) {
            return true;
        }
        return false;
    }

封装其数据,读取每一NAL单元

 /**
     * 读取每一帧数据
     * @param buffer
     * @return
     */
    public int readSampleData(ByteBuffer buffer) {
        byte[] nal = getNALU();
        buffer.put(nal);
        return nal.length;
    }

MediaCodec开启解码线程,和 Android MediaCodec,MediaExtractor解码播放MP4文件中解码一样,将NAL单元数据送给解码器即可。

  /**
     *  解析播放H264码流
     */
    private class DecoderH264Thread extends Thread {
        long pts = 0;

        @Override
        public void run() {
            super.run();
            while (!isDecodeFinish) {
                int inputIndex = mediaCodec.dequeueInputBuffer(-1);
                if (inputIndex >= 0) {
                    ByteBuffer byteBuffer = mediaCodec.getInputBuffer(inputIndex);
                    int sampSize = DecodeH264File.getInstance().readSampleData(byteBuffer);
                    long time = computePresentationTime();
                    if (sampSize > 0 && time > 0) {
                        mediaCodec.queueInputBuffer(inputIndex, 0, sampSize, time, 0);
                        try {
                            sleep(30);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                }
                BufferInfo bufferInfo = new BufferInfo();
                int outIndex = mediaCodec.dequeueOutputBuffer(bufferInfo, 0);
                if (outIndex >= 0) {
                    mediaCodec.releaseOutputBuffer(outIndex, true);
                }
            }
        }

    }

好了,到这里结束了,有什么不明白的,欢迎留言~~
GitHub
https://github.com/zxd1991/AndroidMedia

你可能感兴趣的:(Android,音视频技术,Android,解码H264码流)