海思视频解码:海思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; }
相关文章推荐
- 解析JM代码比较理解H264语法元素解码ue(v),se(v),u(n)
- 海思视频sdk解析_1-hi3536介绍
- 基于DSP平台的音视频编解码C代码优化方法
- 一段ffmpeg视频解码为YUV420P的示例代码
- Android平台美颜相机/Camera实时滤镜/视频编解码/影像后期/人脸技术探索——2.4 滤镜以及配套代码的制作方法
- 面试高频算法题汇总「图文解析 + 教学视频 + 范例代码」之 二分 + 哈希表 + 堆 + 优先队列 合集
- 易语言通过解析破解艾奇学院的收费视频的代码
- 基于DSP平台的音视频编解码C代码优化方法
- 利用ffmpeg来进行视频解码的完整示例代码(H.264)
- 专题一:欧拉视频放大(线性)------MATLAB代码解析(二.amplify_spatial_lpyr_temporal_iir())
- [转载] 问题解决:FFmpeg视频编解码库,无法解析的外部信号
- 音视频同步原理解析;音频编码和解码原理
- 基于视频压缩的实时监控系统-A4:main.c代码解析
- 问题解决:FFmpeg视频编解码库,无法解析的外部信号
- 优酷电视剧爬虫代码实现一:下载解析视频网站页面(3)补充知识点:怎样获取XPath
- 【多媒体编解码】Android 视频解析MediaExtractor
- UTF8 编码解析/解码(附加代码)
- ffmpeg-3.1.5视频解码代码
- H.264视频编解码的代码移植和优化
- 基于视频压缩的实时监控系统-A6:cam.c代码解析