还在琢磨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:好记性,不如烂笔头!