zoukankan      html  css  js  c++  java
  • 对H.264帧类型判断方法

    背景描述

    我们经常在网络直播推流或者客户端拉流的时候,需要对获取到的H.264视频帧进行判断后处理,我们经常获取到各种不同的视频数据0x67 0x68 0x65 0x61,0x27 0x28 0x25 0x21,0x47 0x48 0x45 0x41,各种不同的编码芯片有时间出来的NAL Header规则不大一样,那么我们怎么来以统一的方式判断帧的类型呢:sps、pps、IDR、P

    如下转自EasyDarwin开源贡献者Kim的博客:http://blog.csdn.net/jinlong0603/article/details/70170042

    H264

    H264在网络传输的是NALU,NALU的结构是:NAL头+RBSP,实际传输中的数据流如图所示:
    这里写图片描述

    其中NAL头占一个字节,其低5个bit位表示NAL type,具体如下表:

    NAL type NAL类型
    0 未使用
    1 非IDR的片
    2 片数据A分区
    3 片数据B分区
    4 片数据C分区
    5 IDR图像的片
    6 补充增强信息单元(SEI)
    7 序列参数集(SPS)
    8 图像参数集(PPS)
    9 分界符
    10 序列结束
    11 码流结束
    12 填充
    13..23 保留
    24..31 不保留

    RBSP 为原始字节序列载荷。

    NAL type为5,则此帧为I帧即关键帧,type为1时为非关键帧(P帧…)。
    在实际的H264数据帧中,往往帧NAL type前面带有00 00 00 01 或 00 00 01分隔符,一般来说编码器编出的首帧数据为PPS与SPS,接着为I帧,然后是P帧…

    EasyPusher/EasyRTMP 视频流推送

    EasyPusher和EasyRTMP是通过调用摄像机SDK、拉取RTSP流、读mp4文件等方式获取到H264视频流及音频视频流到本地作为视频源,再以RTSP、RTMP方式推送给流媒体服务器。它们都是支持Windows、Linux、Android、iOS、ARM等全平台的视频流推送程序。
    下面介绍下它们在获取到视频流到本地后如何区分I帧和P帧等,然后推送的:

    //这段代码是从文件中读取h264数据,然后推送给服务器
    unsigned char *ptr=new unsigned char [sample_size];
    fread(ptr, sample_size, 1, g_fin);
    
    //写一帧数据 --- 可以直接进行网络推送
    //fwrite(ptr, sample_size, 1, fout);
    EASY_AV_Frame   avFrame;
    memset(&avFrame, 0x00, sizeof(EASY_AV_Frame));
    /*
    *ptr的前4字节是帧分分割符00 00 00 01 , 第5个字节是NAL type
    */
    unsigned char naltype = ( (unsigned char)ptr[4] & 0x1F);
    
    avFrame.pBuffer = (unsigned char*)ptr;
    avFrame.u32AVFrameLen = sample_size;
    avFrame.u32VFrameType = (naltype==0x05)?EASY_SDK_VIDEO_FRAME_I:EASY_SDK_VIDEO_FRAME_P;
    avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
    avFrame.u32TimestampSec = lTimeStamp/1000000;
    avFrame.u32TimestampUsec = (lTimeStamp%1000000);

    如果视频源不是文件,而是IPCamera 或者RTSP流等,在他们的视频流回调中可能已经告知当前帧是I帧还是P帧,就省去了判断NAL type的步骤。

    HI_S32 NETSDK_APICALL OnStreamCallback(HI_U32 u32Handle,/* 句柄 */
                                    HI_U32 u32DataType,     /* 数据类型,视频或音频数据或音视频复合数据 */
                                    HI_U8* pu8Buffer,       /* 数据包含帧头 */
                                    HI_U32 u32Length,       /* 数据长度 */
                                    HI_VOID* pUserData      /* 用户数据*/
                                    )
    {
        HI_S_AVFrame* pstruAV = HI_NULL;
        HI_S_SysHeader* pstruSys = HI_NULL;
    
        if (u32DataType == HI_NET_DEV_AV_DATA)
        {
            pstruAV = (HI_S_AVFrame*)pu8Buffer;
    
            if (pstruAV->u32AVFrameFlag == HI_NET_DEV_VIDEO_FRAME_FLAG)
            {
                if(fPusherHandle == 0 ) return 0;
    
                if(pstruAV->u32AVFrameLen > 0)
                {
                    unsigned char* pbuf = (unsigned char*)(pu8Buffer+sizeof(HI_S_AVFrame));
    
                    EASY_AV_Frame  avFrame;
                    memset(&avFrame, 0x00, sizeof(EASY_AV_Frame));
                    avFrame.u32AVFrameLen = pstruAV->u32AVFrameLen;
                    avFrame.pBuffer = (unsigned char*)pbuf;
                    avFrame.u32VFrameType = (pstruAV->u32VFrameType==HI_NET_DEV_VIDEO_FRAME_I)?EASY_SDK_VIDEO_FRAME_I:EASY_SDK_VIDEO_FRAME_P;
                    avFrame.u32AVFrameFlag = EASY_SDK_VIDEO_FRAME_FLAG;
                    avFrame.u32TimestampSec = pstruAV->u32AVFramePTS/1000;
                    avFrame.u32TimestampUsec = (pstruAV->u32AVFramePTS%1000)*1000;
                    EasyPusher_PushFrame(fPusherHandle, &avFrame);
                }   
            }
            else
            if (pstruAV->u32AVFrameFlag == HI_NET_DEV_AUDIO_FRAME_FLAG)
            {
                if(fPusherHandle == 0 ) return 0;
    
                if(pstruAV->u32AVFrameLen > 0)
                {
                    //不同IPCamera,这里数据头不一样,需要根据各自的SDK跳过对应的大小。有些可能没有自定义数据
                    unsigned char* pbuf = (unsigned char*)(pu8Buffer+sizeof(HI_S_AVFrame));
    
                    EASY_AV_Frame  avFrame;
                    memset(&avFrame, 0x00, sizeof(EASY_AV_Frame));
                    avFrame.u32AVFrameLen = pstruAV->u32AVFrameLen-4;//去掉厂家自定义的4字节头
                    avFrame.pBuffer = (unsigned char*)pbuf+4;
                    avFrame.u32AVFrameFlag = EASY_SDK_AUDIO_FRAME_FLAG;
                    avFrame.u32TimestampSec = pstruAV->u32AVFramePTS/1000;
                    avFrame.u32TimestampUsec = (pstruAV->u32AVFramePTS%1000)*1000;
                    EasyPusher_PushFrame(fPusherHandle, &avFrame);
                }           
            }
        }
        else
        if (u32DataType == HI_NET_DEV_SYS_DATA)
        {
            pstruSys = (HI_S_SysHeader*)pu8Buffer;
            printf("Video W:%u H:%u Audio: %u 
    ", pstruSys->struVHeader.u32Width, pstruSys->struVHeader.u32Height, pstruSys->struAHeader.u32Format);
        }
        return HI_SUCCESS;
    }
    

    有了数据源,只需要调用libEasyPusher或libEasyRTMP就可以完成RTSP、RTMP直播推送了!

    下载

    EasyDarwin服务器下载:https://github.com/EasyDarwin/EasyDarwin或者国内镜像http://git.oschina.net/easydarwin/EasyDarwin
    EasyPusher 下载:https://github.com/EasyDarwin/EasyPusher
    EasyRTMP 下载:https://github.com/EasyDarwin/EasyRTMP

    获取更多信息

    邮件:support@easydarwin.org

    WEB:www.EasyDarwin.org

    Copyright © EasyDarwin.org 2012-2017

    EasyDarwin

  • 相关阅读:
    react native mapbox MarkView只显示一个子组件问题
    react native mapbox 截图压缩(@react-native-mapbox-glmaps)
    @react-native-mapbox-gl/maps语言插件汉化不完善问题
    SQLSERVER优化
    springboot+react整合
    sqlserver求小数取位
    C#中Math.Round() 的真实含义
    Java Nio学习总结(一)
    Linq去重(自定义字段)
    WPF学习记录(一):布局
  • 原文地址:https://www.cnblogs.com/babosa/p/7348777.html
Copyright © 2011-2022 走看看