一、为什么要进行视频帧的分割?

通常与视频服务器进行数据请求的时候,服务器方面发过来的都是已经分割好的NAL单元数据。但是当我们没有视频服务器支持,也需要调试一下程序,视频源为本地文件,这个时候我们就需要用到NAL单元的分割了,不然你没办法扔给解码器。

二、H264的基础知识

1、 NAL全称Network Abstract Layer, 即网络抽象层。
在H.264/AVC视频编码标准中,整个系统框架被分为了两个层面:视频编码层面(VCL)和网络抽象层面(NAL)。其中,前者负责有效表示视频数据的内容,而后者则负责格式化数据并提供头信息,以保证数据适合各种信道和存储介质上的传输。因此我们平时的每帧数据就是一个NAL单元(SPS与PPS除外)。在实际的H264数据帧中,往往帧前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧……
这里写图片描述
2、如何判断帧类型(是图像参考帧还是I、P帧等)?
这里写图片描述
我们还是接着看最上面图的码流对应的数据来层层分析,以00 00 00 01分割之后的下一个字节就是NALU类型,将其转为二进制数据后,解读顺序为从左往右算,如下:
(1)第1位禁止位,值为1表示语法出错
(2)第2~3位为参考级别
(3)第4~8为是nal单元类型

计算公式为:比如0x65 & 0x1F = 5
例如上面00000001后有67,68以及65,41

其中0x67的二进制码为:
0110 0111
4-8为00111,转为十进制7,参考第二幅图:7对应序列参数集SPS

其中0x68的二进制码为:
0110 1000
4-8为01000,转为十进制8,参考第二幅图:8对应图像参数集PPS

其中0x65的二进制码为:
0110 0101
4-8为00101,转为十进制5,参考第二幅图:5对应IDR图像中的片(I帧)

其中0x41的二进制码为:
0100 0001
4-8为00001,转为十进制1,参考第二幅图:根据上图可知道这段码流是【不分区、非IDR图像的片】,在baseline的档次中就是P帧,因为baseline没有B帧。

所以判断是否为I帧的算法为: (NALU类型 & 0001 1111) = 5 即 NALU类型 & 31 = 5

比如0x65 & 31 = 5

3.以上区分不出B帧和P帧

三、实现方案

这里写图片描述

四、实现代码

/**
 * 描述:用来将本地h264数据分割成一帧一帧的数据
 * 作者:chezi008 on 2017/6/29 16:50
 * 邮箱:chezi008@163.com
 */

public class H264ReadRunable implements Runnable {
    private static final int READ_BUFFER_SIZE = 1024 * 5;
    private static final int BUFFER_SIZE = 1024 * 1024;

    private String TAG = getClass().getSimpleName();
    private H264ReadListener h264ReadListener;
    private DataInputStream mInputStream;

    public void setH264ReadListener(H264ReadListener h264ReadListener) {
        this.h264ReadListener = h264ReadListener;
    }

    private byte[] buffer;

    @Override
    public void run() {
        try {
            Log.d(TAG, "run: " + FileConstant.h264FilePath);
            mInputStream = new DataInputStream(new FileInputStream(FileConstant.h264FilePath));
            buffer = new byte[BUFFER_SIZE];

            int readLength;
            int naluIndex = 0;
            int bufferLength = 0;

            while ((readLength = mInputStream.read(buffer, bufferLength , READ_BUFFER_SIZE)) > 0) {

                bufferLength += readLength;
                for (int i = 5; i < bufferLength - 4; i++) {
                    if (buffer[i] == 0x00 &&
                            buffer[i + 1] == 0x00 &&
                            buffer[i + 2] == 0x00 &&
                            buffer[i + 3] == 0x01) {
                        naluIndex = i;
//                        Log.d(TAG, "run: naluIndex:"+naluIndex);
                        byte[] naluBuffer = new byte[naluIndex];
                        System.arraycopy(buffer,0,naluBuffer,0,naluIndex);
                        h264ReadListener.onFrameData(naluBuffer);
                        bufferLength -=naluIndex;
                        System.arraycopy(buffer,naluIndex,buffer,0,bufferLength );
                        i = 5;
                        Thread.sleep(30);
                    }
                }

            }
            h264ReadListener.onStopRead();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public interface H264ReadListener {
        void onFrameData(byte[] datas);

        void onStopRead();
    }
}

五、参考与引用

参考:h264基础知识:http://blog.csdn.net/dittychen/article/details/55509718

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐