HI3518EV300-将h264码流封装成MP4文件

HI3518EV300-如何将h264码流封装成MP4文件

    • 前言
    • 应用层代码分析

前言

还在琢磨3518ev300的单板,遵循规则–RTFSC。
middleware层-将h264码流封装成MP4文件主要分这几步:
1、HI_MP4_Create:MP4 实例创建。
2、HI_MP4_CreateTrack:为 MP4 实例创建 track。
3、HI_MP4_WriteFrame:MP4 实例中写帧数据。
4、HI_MP4_DestroyAllTracks:为 MP4 实例销毁 track。

不理解H264_NALU的可以看下这2篇文章:
H264码流中NALU sps pps IDR帧的理解
对H264 startcode一些粗浅的认识

现在开始分析海思滴代码:

应用层代码分析

#define MUXER_SAMPLE_SRC_H264    "/tmp/1.h264"  //码流路径
static HI_MW_PTR pMP4Muxer = NULL;      //MP4句柄
typedef struct TrackInfo  				//轨道信息
{
    HI_MW_PTR hTrack;    				//track 句柄 
    HI_VOID *pHandle;					//打开码流文件返回的结构体信息
}HI_TRACK_INFO_S;

//---------------- h264 stream into mp4 file----------------------------//
HI_S32 SAMPLE_MP4_MuxerAVC()
{
    HI_S32 s32Ret = HI_SUCCESS;
    HI_U64 u64Duration = 0;
	//H.264 称为 AVC(advanced video codes)
    HI_TRACK_INFO_S stAVCTrackInfo;
    AVC_Handle_S stAvcHandle;  //
    stAVCTrackInfo.pHandle = &stAvcHandle; //path_handle 赋 stAvcHandle结构体变量的地址 

    HI_MP4_TRACK_INFO_S stVideo;					    //定义 track 信息。
    memset(&stVideo, 0x00, sizeof(HI_MP4_TRACK_INFO_S));
    stVideo.enTrackType = HI_MP4_STREAM_VIDEO;          //视频类型。  踪迹类型
    stVideo.stVideoInfo.enCodecID = HI_MP4_CODEC_ID_H264;
    stVideo.fSpeed = 1.0f;								//播放速率。								
    stVideo.stVideoInfo.u32BitRate = 3000;				//码率
    stVideo.stVideoInfo.u32FrameRate = 30;				//目的帧率。
    stVideo.stVideoInfo.u32Height = 1080;				//视频高分辨率
    stVideo.stVideoInfo.u32Width = 1920;				//视频宽分辨率。
    stVideo.u32TimeScale = 120000;							//Track 的 TimeScale,时间精度,即一秒被分为
//TimeScale 份,一般 video 和 data 的 TimeScale 为120000,audio 的 TimeScale 和音频采样率一致(例如音
//频采样率 sampleRate 为 48000,则音频 TimeScale 为48000)
    snprintf(stVideo.aszHdlrName, HI_MP4_MAX_HDLR_NAME, "%s", "Hisilicon VIDEO");

    HI_MP4_CONFIG_S stMuxerCfg;          				//定义 MP4 配置参数结构体。 (容器)
    memset(&stMuxerCfg, 0x00, sizeof(HI_MP4_CONFIG_S));  //清空结构体 
    snprintf(stMuxerCfg.aszFileName, HI_MP4_MAX_FILE_NAME, "%s/%s", MOUNT_POINT, "test_avc.mp4");//文件名,包含文件路径。
	//配置类型枚举:封装实例、解封装实例、追加封装实例配置信息。
    stMuxerCfg.enConfigType = HI_MP4_CONFIG_MUXER;  //文件封装配置信息。
   	//stMuxerConfig : 定义 MP4 封装配置参数结构体。
	//当封装的视频 track 为 H.264、H.265 码流时,请设置 muxer config 中的 enFormatProfile 为HI_MP4_FORMAT_MP42,即为 base media/version 2 格式 MP4 文件。   	
   	stMuxerCfg.stMuxerConfig.enFormatProfile=HI_MP4_FORMAT_MP42;  
    stMuxerCfg.stMuxerConfig.u32PreAllocUnit = 20*1024*1024;//预分配单元大小,单位:字节,当前采用多次预分配方案,为0 时表示不开启预分配功能,取值范围:[0, 100M]
    stMuxerCfg.stMuxerConfig.u32VBufSize = 1024*1024;       //文件 IO 写数据缓冲大小
    stMuxerCfg.stMuxerConfig.bConstantFps = HI_TRUE; 	    //是否采用固定帧率模式计算文件时长。
    stMuxerCfg.stMuxerConfig.bCo64Flag = HI_FALSE; 			//是否开启 64 位 chunk offset 封装标志。
    stMuxerCfg.stMuxerConfig.u32BackupUnit = 1*1024*1024;   //备份数据单元,每 u32BackupUnit 字节数据在 MP4 文件中存入
															//一份分段索引表(stbl group),用于修复时重组 moov 信息
    /*create muxer 流*/
    printf("create muxer\n");
    s32Ret =  HI_MP4_Create(&pMP4Muxer, &stMuxerCfg);							//MP4 实例创建 (容器)
    if (HI_SUCCESS != s32Ret)
    {
        printf("create muxer fail %d \n", s32Ret);
        return HI_FAILURE;
    }
    /*create stream 源*/
    printf("create video\n");
    s32Ret =  HI_MP4_CreateTrack(pMP4Muxer, &stAVCTrackInfo.hTrack, &stVideo);  //为 MP4 实例创建 track (理解为MP4 track -> 源对象.h264),返回track handle
    if (HI_SUCCESS != s32Ret)
    {
        printf("HI_MP4_CreateTrack fail %d \n", s32Ret);
        goto MUXER_DESTROY;
    }

    /*start write frame data*/
    if (AVC_Open(stAVCTrackInfo.pHandle, MUXER_SAMPLE_SRC_H264) < 0)//打开 "/tmp/1.h264" 文件 	,返回AVC_Handle_S-pHandle结构体信息 
    {
        printf("AVC_Open %s fail %d \n", MUXER_SAMPLE_SRC_H264, s32Ret);
        goto TRACK_DESTROY;
    }

    /* start video read and mux thread */
    pthread_t AVCThread;
 //   pthread_t AACThread;
    pthread_create(&AVCThread, HI_NULL, Sample_ReadAVCThread, (void*)&stAVCTrackInfo);  //Sample_ReadAVCThread的入参:stAVCTrackInfo  	//进程读.h264码流数据,并写入MP4容器 
//    sleep(1000);
    pthread_join(AVCThread, HI_NULL);  // 等待一个线程的结束,线程间同步的操作

    printf("muxer end, wait before write tail\n");

    AVC_Close(stAVCTrackInfo.pHandle);
    /*write tail and destroy stream*/
    s32Ret = HI_MP4_DestroyAllTracks(pMP4Muxer, NULL);
    if (HI_SUCCESS != s32Ret)
    {
        printf("HI_MP4_DestroyAllTracks fail %d \n", s32Ret);
    }

    /*destroy muxer*/
    printf("destroy muxer\n");
    s32Ret = HI_MP4_Destroy(pMP4Muxer, &u64Duration);
    if (HI_SUCCESS != s32Ret)
    {
        printf("HI_MP4_Destroy fail  %d \n", s32Ret);
    }
    printf("destroy muxer duration %lld \n", u64Duration);

    return s32Ret;
TRACK_DESTROY:
    s32Ret = HI_MP4_DestroyAllTracks(pMP4Muxer, NULL);
    if (HI_SUCCESS != s32Ret)
    {
        printf("HI_MP4_DestroyAllTracks fail %d \n", s32Ret);
    }
MUXER_DESTROY:
    s32Ret = HI_MP4_Destroy(pMP4Muxer, &u64Duration);
    if (HI_SUCCESS != s32Ret)
    {
        printf("HI_MP4_Destroy fail %d \n", s32Ret);
    }

    return s32Ret;
}

//-----------------------------线程分析-----------------------------------------//

static HI_VOID* Sample_ReadAVCThread(void* pArg)
{
    prctl(PR_SET_NAME, "Sample_ReadAVCThread", 0, 0, 0);
    HI_U32 u32FrameNum = 0;
    HI_S32 s32Ret = HI_SUCCESS;
    HI_U8* u8Frame = HI_NULL;
    HI_U32 u32FrameLen = 0;
    HI_U8 u8KeyFrame = HI_FALSE;
    struct timeval tv;
    HI_TRACK_INFO_S *pTrackInfo = (HI_TRACK_INFO_S*)pArg;
    AVC_Handle_S* avcHandle = (AVC_Handle_S*)(pTrackInfo->pHandle);
    HI_MW_PTR hAvcTrack = pTrackInfo->hTrack;

    HI_MP4_FRAME_DATA_S stFrameData;   //帧数据信息结构体。
    memset(&stFrameData, 0x00, sizeof(HI_MP4_FRAME_DATA_S));
    printf("Sample_ReadAVCThread start read frame \n");
    while ( HI_TRUE )          //注意 是 死循环 噢 
    {
        s32Ret = AVC_ReadFrame(avcHandle, &u8Frame, &u32FrameLen, &u8KeyFrame);
        if (HI_SUCCESS != s32Ret)
        {
            printf("AVC_ReadFrame failed\n");
            break;
        }
        gettimeofday(&tv, NULL);
        stFrameData.pu8DataBuffer = u8Frame;  //媒体流帧数据 buffer。
        stFrameData.u32DataLength = u32FrameLen; //数据流帧长度。
        stFrameData.bKeyFrameFlag = (HI_BOOL)u8KeyFrame;  //媒体流数据是否为 I 帧。
        stFrameData.u64TimeStamp = (HI_U64)tv.tv_sec * 1000000 + tv.tv_usec; //媒体流帧数据时间戳。
        //请在创建 MP4 实例及 track 之后调用该接口HI_MP4_WriteFrame。
        //pMP4Muxer:MP4实例句柄; hAvcTrack:Track 句柄(.264源); stFrameData:NALU单元数据
        s32Ret = HI_MP4_WriteFrame(pMP4Muxer, hAvcTrack, &stFrameData); //MP4 中写入帧数据
        if (HI_SUCCESS != s32Ret)
        {
            printf("Sample_ReadAVCThread HI_MP4_WriteFrame fail %d \n", s32Ret);
        }
        u32FrameNum++;
        usleep(5*1000);
    }
    return NULL;
}

//----------------------------------------获取h264_NALU单元数据---------------------------------------------------//
int32_t AVC_ReadFrame(AVC_Handle_S *pstHandle, uint8_t **ppFrame, uint32_t *pu32FrameLen, uint8_t *pbKeyFrame)
{
    if(pstHandle->stStream.pFile == NULL){
        printf("pstHandle->stStream.pFile is null\n");
        return -1;
    }
    fseek(pstHandle->stStream.pFile, pstHandle->stStream.u32FileReadOffset, SEEK_SET);//偏移到stStream.u32FileReadOffset地址(文件可读的偏移地址)
    memset(pstHandle->stStream.pu8BufHeader, 0x00, pstHandle->stStream.u32BufLen);
    uint32_t readLen = fread(pstHandle->stStream.pu8BufHeader, 1, pstHandle->stStream.u32BufLen,
                             pstHandle->stStream.pFile);//从pstHandle->stStream.pFile开始读,读出数据长度赋值给readLen。
    if (readLen <= 0) {
        printf("AVC_ReadFrame read file failed\n");
        return -1;
    }
    pstHandle->stStream.u32AvailReadLen = readLen; //有效长度赋值
    uint32_t firstNaltartCodeLen = 0;  //第一个NALU的头长度 4/3
    uint32_t SecondNalstartCodeLen = 0;//第二个NALU的头长度        4/3
    uint8_t *pBufPtr = pstHandle->stStream.pu8BufHeader; 				//buf头
    uint8_t *pEndPtr = pBufPtr + pstHandle->stStream.u32AvailReadLen; 	//buf尾
    uint8_t *pFirstNalPtr = GetNextNal(pBufPtr, pstHandle->stStream.u32AvailReadLen, &firstNaltartCodeLen);  //第一个NALU起始地址
    uint8_t *pSecondNalPtr = GetNextNal(pFirstNalPtr + firstNaltartCodeLen, pEndPtr - (pFirstNalPtr + firstNaltartCodeLen), &SecondNalstartCodeLen);//第二个NALU起始地址 
    uint8_t bKeyFrame = 0;  //I帧标志位

    if (!pFirstNalPtr) {  //判断是否连第一个NAL起始地址都木有
        printf("avc read error\n");
        pstHandle->stStream.u32FileReadOffset = 0;  //清空偏移位
        return -1;
    }
    uint8_t *pNalByte = (pFirstNalPtr + firstNaltartCodeLen);  //获取NALU BYTE 位的 地址
    int32_t s32NalType = pNalByte[0] & 0x1F;	 			   //计算出类型值
//  其实这里搞不懂为啥 判断sps类型然后要找到5IDR(关键帧), s32NalType有好多其它值又不用判断。	
// 我先理解为 这个 AVC_ReadFrame 函数 每次都是 拿 SPS、PPS、I帧 一个完整的NALU单元数据,因为SPS一般是编码的第一帧。
    if (7 == s32NalType) {    //SPS 7
        int32_t s32FrameType = (pSecondNalPtr + SecondNalstartCodeLen)[0] & 0x1F;
//-----------------------------注意这里有个死循环 ------------------------------------//		
        while (5 != s32FrameType && pSecondNalPtr && pSecondNalPtr < pEndPtr)  // 5 IDR  
	//	科普:   I 帧(关键帧):帧内编码帧 ,I 帧表示关键帧,你可以理解为这一帧画面的完整保留;解码时只需要本帧数据就可以完成(因为包含完整画面)
        {
            pSecondNalPtr = GetNextNal(pSecondNalPtr + SecondNalstartCodeLen, pEndPtr - (pSecondNalPtr + SecondNalstartCodeLen),
                &SecondNalstartCodeLen);
            s32FrameType = (pSecondNalPtr + SecondNalstartCodeLen)[0] & 0x1F;
        }
        if (5 == s32FrameType) {         	//IDR 帧类型 
            bKeyFrame = 1;//关键帧
            pSecondNalPtr = GetNextNal(pSecondNalPtr + SecondNalstartCodeLen, pEndPtr - (pSecondNalPtr + SecondNalstartCodeLen),
                &SecondNalstartCodeLen);
        }
    } else if (5 == s32NalType) {
        bKeyFrame = 1;//关键帧
    }

    if (!pSecondNalPtr) {             //pSecondNalPtr==0, 没有下一个NAL单元,估计是H264码流文件数据提取到底了。
        printf("read second nal error\n");
        *pu32FrameLen = pstHandle->stStream.u32AvailReadLen;
        *ppFrame = pFirstNalPtr;
        *pbKeyFrame = bKeyFrame;  
    } else {        
		//提取一个完整的NALU单元
        *pu32FrameLen = (pSecondNalPtr - pFirstNalPtr);
        *ppFrame = pFirstNalPtr;
        *pbKeyFrame = bKeyFrame;   
    }
    pstHandle->stStream.u32FileReadOffset += *pu32FrameLen;  //偏移位加这次的NALU帧长度,下次从这地址开始运算
    return 0;
}

程序运行终端打印信息:

<input cmd:>[app_main.2216]
create muxer
create video
Sample_ReadAVCThread start read frame 
read second nal error
AVC_ReadFrame read file failed
AVC_ReadFrame failed
muxer end, wait before write tail
destroy muxer
[00:00:25:275[FILEIO] close ftruncate spend 63 ms
destroy muxer duration 12833 

END

ps:好记性,不如烂笔头!

你可能感兴趣的:(海思)