您的位置:首页 > 编程语言

海思视频解码:海思Sample VDEC h264解码代码解析

2022-01-11 22:12 841 查看

1、参考

https://www.cnblogs.com/iFrank/p/15434438.html

(36条消息) 视频编解码(二):海思VDEC模块视频解码代码解析_夜风里唱的专栏-CSDN博客_vdec

2、参考3840x2160_8bit.h264

  可通过Elecard StreamEye Tools工具【例如 Elecard Stream Analyzer、Elecard StreamEye等】来分析H264码流

3、注意点

3.1、HI_MPI_VDEC_SendStream【描述】向视频解码通道发送码流数据

3.1.1、函数及参数定义

HI_S32 HI_MPI_VDEC_SendStream(VDEC_CHN VdChn, const VDEC_STREAM_S *pstStream, HI_S32 s32MilliSec);

typedef struct hiVDEC_STREAM_S
{
HI_U32 u32Len; /* W; stream len */
HI_U64 u64PTS; /* W; time stamp */
HI_BOOL bEndOfFrame; /* W; is the end of a frame */
HI_BOOL bEndOfStream; /* W; is the end of all stream */
HI_U8* ATTRIBUTE pu8Addr; /* W; stream address */
HI_BOOL bDisplay; /* W; is the current frame displayed. only
valid by VIDEO_MODE_FRAME */
}VDEC_STREAM_S;

  此接口通过改变 s32MilliSec 值支持阻塞方式、非阻塞方式、超时方式发送码流。

 

 

3.1.2、函数使用注意点  

  此接口通过改变 s32MilliSec 值支持阻塞方式、非阻塞方式、超时方式发送码流。     发送数据前必须保证已经调用 HI_MPI_VDEC_StartRecvStream 接口启动接收码流,否则直接返回该操作不允许的错误码 HI_ERR_VDEC_NOT_PERM。如果在发送数据过程中停止接收码流,就会立刻返回 HI_ERR_VDEC_NOT_PERM。     发送数据前必须保证通道已经被创建,否则直接返回通道未创建的错误码HI_ERR_VDEC_UNEXIST。如果在发送码流过程中复位通道或者销毁通道,就会立刻返回错误码 HI_ERR_VDEC_UNEXIST。     发送码流时需要按照创建解码通道时设置的发送方式进行发送。按帧发送时,调用此接口一次,必须发送完整的一帧码流,否则解码会出现错误。流式发送则无此限制。     不能发送 bEndOfStream 为 0 的空码流包(码流长度为 0 或码流地址为空),否则返回错误码 HI_ERR_VDEC_ILLEGAL_PARAM。     在发送完所码流后,可以发送bEndOfStream 为 1 的空码流包,表示当前码流文件结束,解码器会把所码流全部解完并输出全部图像。 除此之外,其它情况应该把 bEndOfStream 置为 0。     当码流 buffer 为空且装不下当前包码流时,会返回参数超出范围的错误码HI_ERR_VDEC_ILLEGAL_PARAM。     按帧/兼容模式发送码流时,解码图像的时间戳等于传入参数 pstStream 结构体中的时间戳,按流发送时,解码图像的时间戳等于 0。以非阻塞方式发送码流,如果码流缓冲区已满,会立刻返回错误码HI_ERR_VDEC_BUF_FULL。     以超时方式发送码流,到达设定的超时时间还不能成功发送码流会返回错误码HI_ERR_VDEC_BUF_FULL。     超时的效时间单位为10ms,因此实际最长等待时间为 10ms 倍数。

3.2、解码时,两个循环的作用

补充说明:
(1)IDR帧肯定为I帧
(2)I帧包含了SPS, PPS, I条带
(3)P帧包含P条带;B帧包含B条带

  结合两个for循环来看,其实就是找到一帧的数据长度,假设视频文件流为SPS, PPS, SEI, I Slice,P Slice,B Slice...... SPS, PPS, SEI, I Slice,经过两个循环,那么变量i 的位置就是P Slice的开始位置,因此变量i 就包括了SPS, PPS,SEI,I Slice的长度,这四者组成了一个I帧。

// 解码264视频文件
if (pstVdecThreadParam->s32StreamMode==VIDEO_MODE_FRAME && pstVdecThreadParam->enType == PT_H264)
{
// 找到一个条带(slice)位置
for (i=0; i<s32ReadLen-8; i++)
{
int tmp = pu8Buf[i+3] & 0x1F;
if (  pu8Buf[i] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1 &&
(  ((tmp == 0x5 || tmp == 0x1) && ((pu8Buf[i+4]&0x80) == 0x80)) ||
(tmp == 20 && (pu8Buf[i+7]&0x80) == 0x80) )
)
{
bFindStart = HI_TRUE;
i += 8;
break;
}
}

// 找到下一帧数据流的开始位置
for (; i<s32ReadLen-8; i++)
{
int tmp = pu8Buf[i+3] & 0x1F;
if (  pu8Buf[i] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1 &&
(tmp == 15 || tmp == 7 || tmp == 8 || tmp == 6 ||
((tmp == 5 || tmp == 1) && ((pu8Buf[i+4]&0x80) == 0x80))||
(tmp == 20 && (pu8Buf[i+7]&0x80) == 0x80) ))
{
bFindEnd = HI_TRUE;
break;
}
}

if(i>0)
s32ReadLen = i;

...............
}

3.3、文章评论

4、SAMPLE_COMM_VDEC_SendStream代码

HI_VOID * SAMPLE_COMM_VDEC_SendStream(HI_VOID *pArgs)
{
VDEC_THREAD_PARAM_S *pstVdecThreadParam =(VDEC_THREAD_PARAM_S *)pArgs;
HI_BOOL bEndOfStream = HI_FALSE;
HI_S32 s32UsedBytes = 0, s32ReadLen = 0;
FILE *fpStrm=NULL;
HI_U8 *pu8Buf = NULL;
VDEC_STREAM_S stStream;
HI_BOOL bFindStart, bFindEnd;
HI_U64 u64PTS = 0;
HI_U32 u32Len, u32Start;
HI_S32 s32Ret,  i;
HI_CHAR cStreamFile[256];

prctl(PR_SET_NAME, "VideoSendStream", 0,0,0);

snprintf(cStreamFile, sizeof(cStreamFile), "%s/%s", pstVdecThreadParam->cFilePath,pstVdecThreadParam->cFileName);
if(cStreamFile != 0)
{
fpStrm = fopen(cStreamFile, "rb");
if(fpStrm == NULL)
{
SAMPLE_PRT("chn %d can't open file %s in send stream thread!\n", pstVdecThreadParam->s32ChnId, cStreamFile);
return (HI_VOID *)(HI_FAILURE);
}
}
printf("\n \033[0;36m chn %d, stream file:%s, userbufsize: %d \033[0;39m\n", pstVdecThreadParam->s32ChnId,
pstVdecThreadParam->cFileName, pstVdecThreadParam->s32MinBufSize);

pu8Buf = malloc(pstVdecThreadParam->s32MinBufSize);
if(pu8Buf == NULL)
{
SAMPLE_PRT("chn %d can't alloc %d in send stream thread!\n", pstVdecThreadParam->s32ChnId, pstVdecThreadParam->s32MinBufSize);
fclose(fpStrm);
return (HI_VOID *)(HI_FAILURE);
}
fflush(stdout);

u64PTS = pstVdecThreadParam->u64PtsInit;
while (1)
{
if (pstVdecThreadParam->eThreadCtrl == THREAD_CTRL_STOP)
{
break;
}
else if (pstVdecThreadParam->eThreadCtrl == THREAD_CTRL_PAUSE)
{
sleep(1);
continue;
}

bEndOfStream = HI_FALSE;
bFindStart   = HI_FALSE;
bFindEnd     = HI_FALSE;
u32Start     = 0;
fseek(fpStrm, s32UsedBytes, SEEK_SET);
s32ReadLen = fread(pu8Buf, 1, pstVdecThreadParam->s32MinBufSize, fpStrm);
if (s32ReadLen == 0)
{
if (pstVdecThreadParam->bCircleSend == HI_TRUE)
{
memset(&stStream, 0, sizeof(VDEC_STREAM_S) );
stStream.bEndOfStream = HI_TRUE;
HI_MPI_VDEC_SendStream(pstVdecThreadParam->s32ChnId, &stStream, -1);

s32UsedBytes = 0;
fseek(fpStrm, 0, SEEK_SET);
s32ReadLen = fread(pu8Buf, 1, pstVdecThreadParam->s32MinBufSize, fpStrm);
}
else
{
break;
}
}

if (pstVdecThreadParam->s32StreamMode==VIDEO_MODE_FRAME && pstVdecThreadParam->enType == PT_H264)
{

//因此以上第一个for循环的作用是找到I、P 、B的开始位置,即当前的i值(并自加8),这里第一个for循环结束。
for (i=0; i<s32ReadLen-8; i++)
{
//因为此NALU开头为00 00 00 01,第一个i=0时不成立,后续i=1,
int tmp = pu8Buf[i+3] & 0x1F;

if (  pu8Buf[i    ] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1 &&
(
((tmp == 0x5 || tmp == 0x1) && ((pu8Buf[i+4]&0x80) == 0x80)) ||
(tmp == 20 && (pu8Buf[i+7]&0x80) == 0x80)
)
)
{
bFindStart = HI_TRUE;
i += 8;
break;
}
}

//因此第2个for循环就是要找到下一个NAL单元的开始位置,得到此时的位置值i,将i赋值给到s32ReadLen。
for (; i<s32ReadLen-8; i++)
{
int tmp = pu8Buf[i+3] & 0x1F;
if (  pu8Buf[i    ] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1 &&
(
tmp == 15 || tmp == 7 || tmp == 8 || tmp == 6 ||
((tmp == 5 || tmp == 1) && ((pu8Buf[i+4]&0x80) == 0x80)) ||
(tmp == 20 && (pu8Buf[i+7]&0x80) == 0x80)
)
)
{
bFindEnd = HI_TRUE;
break;
}
}

/*结合两个for循环来看,其实就是找到一帧的数据长度,假设视频文件流为SPS, PPS, I Slice,P Slice,B Slice..  I slice
那么i的位置就是i Slice的开始位置,因此i就包括了SPS, PPS, I Slice,P Slice,B Slice.. 的长度,这组成了一个I帧。*/
if(i>0)
s32ReadLen = i;

if (bFindStart == HI_FALSE)
{
SAMPLE_PRT("chn %d can not find H264 start code!s32ReadLen %d, s32UsedBytes %d.!\n",
pstVdecThreadParam->s32ChnId, s32ReadLen, s32UsedBytes);
}
if (bFindEnd == HI_FALSE)
{
s32ReadLen = i+8;
}

}
else if (pstVdecThreadParam->s32StreamMode==VIDEO_MODE_FRAME
&& pstVdecThreadParam->enType == PT_H265)
{
HI_BOOL  bNewPic = HI_FALSE;
for (i=0; i<s32ReadLen-6; i++)
{
HI_U32 tmp = (pu8Buf[i+3]&0x7E)>>1;
bNewPic = ( pu8Buf[i+0] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1
&& (tmp >= 0 && tmp <= 21) && ((pu8Buf[i+5]&0x80) == 0x80) );

if (bNewPic)
{
bFindStart = HI_TRUE;
i += 6;
break;
}
}

for (; i<s32ReadLen-6; i++)
{
HI_U32 tmp = (pu8Buf[i+3]&0x7E)>>1;
bNewPic = (pu8Buf[i+0] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 1
&&( tmp == 32 || tmp == 33 || tmp == 34 || tmp == 39 || tmp == 40 || ((tmp >= 0 && tmp <= 21) && (pu8Buf[i+5]&0x80) == 0x80) )
);

if (bNewPic)
{
bFindEnd = HI_TRUE;
break;
}
}
if(i>0)s32ReadLen = i;

if (bFindStart == HI_FALSE)
{
SAMPLE_PRT("chn %d can not find H265 start code!s32ReadLen %d, s32UsedBytes %d.!\n",
pstVdecThreadParam->s32ChnId, s32ReadLen, s32UsedBytes);
}
if (bFindEnd == HI_FALSE)
{
s32ReadLen = i+6;
}

}
else if (pstVdecThreadParam->enType == PT_MJPEG || pstVdecThreadParam->enType == PT_JPEG)
{
for (i=0; i<s32ReadLen-1; i++)
{
if (pu8Buf[i] == 0xFF && pu8Buf[i+1] == 0xD8)
{
u32Start = i;
bFindStart = HI_TRUE;
i = i + 2;
break;
}
}

for (; i<s32ReadLen-3; i++)
{
if ((pu8Buf[i] == 0xFF) && (pu8Buf[i+1]& 0xF0) == 0xE0)
{
u32Len = (pu8Buf[i+2]<<8) + pu8Buf[i+3];
i += 1 + u32Len;
}
else
{
break;
}
}

for (; i<s32ReadLen-1; i++)
{
if (pu8Buf[i] == 0xFF && pu8Buf[i+1] == 0xD9)
{
bFindEnd = HI_TRUE;
break;
}
}
s32ReadLen = i+2;

if (bFindStart == HI_FALSE)
{
SAMPLE_PRT("chn %d can not find JPEG start code!s32ReadLen %d, s32UsedBytes %d.!\n",
pstVdecThreadParam->s32ChnId, s32ReadLen, s32UsedBytes);
}
}
else
{
if((s32ReadLen != 0) && (s32ReadLen < pstVdecThreadParam->s32MinBufSize))
{
bEndOfStream = HI_TRUE;
}
}

stStream.u64PTS       = u64PTS;
stStream.pu8Addr      = pu8Buf + u32Start;
stStream.u32Len       = s32ReadLen;
stStream.bEndOfFrame  = (pstVdecThreadParam->s32StreamMode==VIDEO_MODE_FRAME)? HI_TRUE: HI_FALSE;
stStream.bEndOfStream = bEndOfStream;
stStream.bDisplay     = 1;

SendAgain:
s32Ret=HI_MPI_VDEC_SendStream(pstVdecThreadParam->s32ChnId, &stStream, pstVdecThreadParam->s32MilliSec);
if( (HI_SUCCESS != s32Ret) && (THREAD_CTRL_START == pstVdecThreadParam->eThreadCtrl) )
{
usleep(pstVdecThreadParam->s32IntervalTime);
goto SendAgain;
}
else
{
bEndOfStream = HI_FALSE;
s32UsedBytes = s32UsedBytes +s32ReadLen + u32Start;
u64PTS += pstVdecThreadParam->u64PtsIncrease;
}
usleep(pstVdecThreadParam->s32IntervalTime);
}

/* send the flag of stream end */
memset(&stStream, 0, sizeof(VDEC_STREAM_S) );
stStream.bEndOfStream = HI_TRUE;
HI_MPI_VDEC_SendStream(pstVdecThreadParam->s32ChnId, &stStream, -1);

printf("\033[0;35m chn %d send steam thread return ...  \033[0;39m\n", pstVdecThreadParam->s32ChnId);
fflush(stdout);
if (pu8Buf != HI_NULL)
{
free(pu8Buf);
}
fclose(fpStrm);

return (HI_VOID *)HI_SUCCESS;
}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: