本文介绍海思HEVC解码库的使用。作者对比海思与FFmpeg,对于H.265视频解码,海思性能要高于FFmpeg。
一、解码库包括以下几个文件:
二、海思的程序示例:
void *HW265D_Malloc(UINT32 channel_id, UINT32 size)
{
return (void *)malloc(size);
}
void HW265D_Free(UINT32 channel_id, void * ptr)
{
free(ptr);
}
void HW265D_Log( UINT32 channel_id, IHWVIDEO_ALG_LOG_LEVEL eLevel, INT8
*p_msg, ...)
{
printf("%s.\n", p_msg);
}
INT32 H265DecLoadAU(UINT8* pStream, UINT32 iStreamLen, UINT32* pFrameLen)
{
UINT32 i;
UINT32 state = 0xffffffff;
BOOL32 bFrameStartFound=0;
*pFrameLen = 0;
if( NULL == pStream || iStreamLen <= 4)
{
return -1;
}
for( i = 0; i < iStreamLen; i++)
{
if( (state & 0xFFFFFF7E) >= 0x100 &&
(state & 0xFFFFFF7E) <= 0x13E )
{
if( 1 == bFrameStartFound )
{
if( (pStream[i+1]>>7) == 1)
{
*pFrameLen = i - 4;
return 0;
}
} else
{
bFrameStartFound = 1;
}
}
/*find a vps, sps, pps*/
if( (state&0xFFFFFF7E) == 0x140 ||
(state&0xFFFFFF7E) == 0x142 ||
(state&0xFFFFFF7E) == 0x144)
{
if(1 == bFrameStartFound)
{
*pFrameLen = i - 4;
return 0;
}
else
{
bFrameStartFound = 1;
}
}
state = (state << 8) | pStream[i];
}
*pFrameLen = i;
return -1;
}
int main1(int argc, unsigned char** argv)
{
FILE *fpInFile = NULL;
FILE *fpOutFile = NULL;
INT32 iRet = 0;
UINT8 *pInputStream = NULL, *pStream;
UINT32 uiChannelId = 0;
UINT32 iFrameIdx = 0;
BOOL32 bStreamEnd = 0;
INT32 iFileLen;
IH265DEC_HANDLE hDecoder = NULL;
IHW265D_INIT_PARAM stInitParam = {0};
IH265DEC_INARGS stInArgs;
IH265DEC_OUTARGS stOutArgs = {0};
/* open input stream file and output yuv file */
fpInFile = fopen(argv[1], "rb");
fpOutFile = fopen(argv[2], "wb");
if (NULL == fpInFile || NULL == fpOutFile)
{
fprintf(stderr, "Unable to open h265 stream file %s or yuv file %s.\n",argv[1], argv[2]);
goto exitmain;
}
printf("decoding file: %s...\n", argv[1]);
printf("save yuv file: %s...\n", argv[2]);
/* malloc stream buffer */
fseek( fpInFile, 0, SEEK_END);
iFileLen = ftell( fpInFile);
fseek( fpInFile, 0, SEEK_SET);
pInputStream = (unsigned char *) malloc(iFileLen);
if (NULL == pInputStream)
{
fprintf(stderr, "Malloc failed! \n");
goto exitmain;
}
/* create decode handle */
stInitParam.uiChannelID = 0;
stInitParam.iMaxWidth = 1920;
stInitParam.iMaxHeight = 1088;
stInitParam.iMaxRefNum = 2;
stInitParam.eThreadType = IH265D_SINGLE_THREAD; // or IH265D_MULTI_THREAD;
stInitParam.eOutputOrder = IH265D_DECODE_ORDER; // or IH265D_DISPLAY_ORDER;
stInitParam.MallocFxn = HW265D_Malloc;
stInitParam.FreeFxn = HW265D_Free;
stInitParam.LogFxn = HW265D_Log;
iRet = IHW265D_Create(&hDecoder, &stInitParam);
if (IHW265D_OK != iRet)
{
fprintf(stderr, "Unable to create decoder.\n");
goto exitmain;
}
/* read H.265 stream to stream buffer */
fread(pInputStream, 1, iFileLen, fpInFile);
pStream = pInputStream;
/* decode process */
while (!bStreamEnd)
{
INT32 iNaluLen;
H265DecLoadAU(pStream, iFileLen, &iNaluLen);
stInArgs.eDecodeMode = (iNaluLen>0) ? IH265D_DECODE : IH265D_DECODE_END;
stInArgs.pStream = pStream;
stInArgs.uiStreamLen = iNaluLen;
pStream += iNaluLen;
iFileLen-= iNaluLen;
stOutArgs.eDecodeStatus = -1;
stOutArgs.uiBytsConsumed = 0;
while(stOutArgs.eDecodeStatus != IH265D_NEED_MORE_BITS)
{
// when decoder is empty, exit loop;
if(stOutArgs.eDecodeStatus == IH265D_NO_PICTURE)
{
bStreamEnd = 1;
break;
}
// output one picture
if (stOutArgs.eDecodeStatus == IH265D_GETDISPLAY)
{
// save yuv to output file
if (fpOutFile != NULL)
{
UINT32 i;
for (i=0; i>1); i++)
{
fwrite(stOutArgs.pucOutYUV[1]+i*stOutArgs.uiUVStride, 1,stOutArgs.uiDecWidth>>1, fpOutFile);
}
for (i=0; i<((stOutArgs.uiDecHeight)>>1); i++)
{
fwrite(stOutArgs.pucOutYUV[2]+i*stOutArgs.uiUVStride, 1,stOutArgs.uiDecWidth>>1, fpOutFile);
}
}
iFrameIdx++;
}
// continue to decode the rest of stream
stInArgs.pStream += stOutArgs.uiBytsConsumed;
stInArgs.uiStreamLen -= stOutArgs.uiBytsConsumed;
iRet = IHW265D_DecodeFrame(hDecoder, &stInArgs, &stOutArgs);
if ((iRet != IHW265D_OK) && (iRet != IHW265D_NEED_MORE_BITS))
{
if (0 == iFileLen)
{
bStreamEnd = 1;
}
break;
}
}
}
exitmain:
if (fpInFile != 0)
fclose(fpInFile);
if (fpOutFile != 0)
fclose(fpOutFile);
if (hDecoder != NULL)
{
IHW265D_Delete(hDecoder);
hDecoder = NULL;
}
if (pInputStream != NULL)
{
free(pInputStream);
pInputStream = NULL;
}
return 0;
}
三、以kxmovie为例,将解码部分替换为海思解码:
初始化解码器
- (void)initHWDecoder {
INT32 iRet = 0;
hDecoder = NULL;
IHW265D_INIT_PARAM stInitParam = {0};
stInitParam.uiChannelID = 0;//通道ID,用于标识通道信息
stInitParam.iMaxWidth = _videoCodecCtx->width;//最大宽度
stInitParam.iMaxHeight = _videoCodecCtx->height;//最大高度
stInitParam.iMaxRefNum = 2;//最大参考帧数
stInitParam.eThreadType = IH265D_SINGLE_THREAD;//线程类型 // or IH265D_MULTI_THREAD;
stInitParam.eOutputOrder = IH265D_DECODE_ORDER;//输出顺序 // or IH265D_DISPLAY_ORDER;
stInitParam.MallocFxn = HW265D_Malloc;
stInitParam.FreeFxn = HW265D_Free;
stInitParam.LogFxn = HW265D_Log;
iRet = IHW265D_Create(&hDecoder, &stInitParam);//创建解码器句柄
}
void *HW265D_Malloc(UINT32 channel_id, UINT32 size)
{
return (void *)malloc(size);
}
void HW265D_Free(UINT32 channel_id, void * ptr)
{
free(ptr);
}
void HW265D_Log( UINT32 channel_id, IHWVIDEO_ALG_LOG_LEVEL eLevel, INT8
*p_msg, ...)
{
printf("%s.\n", p_msg);
}
释放解码器
- (void) closeVideoStream
{
_videoStream = -1;
[self closeScaler];
if (_videoFrame) {
av_free(_videoFrame);
_videoFrame = NULL;
}
if (_videoCodecCtx) {
avcodec_close(_videoCodecCtx);
_videoCodecCtx = NULL;
}
IHW265D_Delete(hDecoder);
}
解码
if (packet.stream_index ==_videoStream) {
stInArgs.eDecodeMode = IH265D_DECODE;//解码模式
stInArgs.pStream = packet.data;//码流起始地址
stInArgs.uiStreamLen = packet.size;//码流长度
IHW265D_DecodeFrame(hDecoder, &stInArgs, &stOutArgs);//解码
KxVideoFrame *frame = [self handleHWVideoFrame:stOutArgs];
if (frame) {
[result addObject:frame];
_position = frame.position;
finished = YES;
}
}
- (KxVideoFrame *) handleHWVideoFrame:(IH265DEC_OUTARGS)stOutArgs
{
if (!stOutArgs.pucOutYUV[0])
return nil;
KxVideoFrame *frame;
if (_videoFrameFormat == KxVideoFrameFormatYUV) {
KxVideoFrameYUV *yuvFrame = [[KxVideoFrameYUV alloc] init];
yuvFrame.luma = copyFrameData(stOutArgs.pucOutYUV[0],
stOutArgs.uiYStride,
_videoCodecCtx->width,
_videoCodecCtx->height);
yuvFrame.chromaB = copyFrameData(stOutArgs.pucOutYUV[1],
stOutArgs.uiUVStride,
_videoCodecCtx->width / 2,
_videoCodecCtx->height / 2);
yuvFrame.chromaR = copyFrameData(stOutArgs.pucOutYUV[2],
stOutArgs.uiUVStride,
_videoCodecCtx->width / 2,
_videoCodecCtx->height / 2);
frame = yuvFrame;
}
else {
_videoFrame->data[0] = stOutArgs.pucOutYUV[0];
_videoFrame->data[1] = stOutArgs.pucOutYUV[1];
_videoFrame->data[2] = stOutArgs.pucOutYUV[2];
_videoFrame->linesize[0] = stOutArgs.uiYStride;
_videoFrame->linesize[1] = stOutArgs.uiUVStride;
_videoFrame->linesize[2] = stOutArgs.uiUVStride;
if (!_swsContext &&
![self setupScaler]) {
LoggerVideo(0, @"fail setup video scaler");
return nil;
}
sws_scale(_swsContext,
(const uint8_t **)_videoFrame->data,
_videoFrame->linesize,
0,
_videoCodecCtx->height,
_picture.data,
_picture.linesize);
KxVideoFrameRGB *rgbFrame = [[KxVideoFrameRGB alloc] init];
rgbFrame.linesize = _picture.linesize[0];
rgbFrame.rgb = [NSData dataWithBytes:_picture.data[0] length:rgbFrame.linesize * _videoCodecCtx->height];
frame = rgbFrame;
}
frame.width = _videoCodecCtx->width;
frame.height = _videoCodecCtx->height;
positionIndex += 3600;
frame.position = positionIndex * _videoTimeBase;
frame.duration = 1.0 / _fps;
return frame;
}
四、海思解码库logo水印问题:
海思在它码流里增加了水印信息,用于对海思解码库的版权保护。
1.海思的水印信息加在prefix sei nal里面,如果你读到的码流是海思的,那么海思解码库会检测到是它自己的,就不会出现它的logo水印,所以SEI这个nal不能被过滤或破坏。
2.如果想要去掉水印,可以尝试通过反汇编破解掉,跳过这个添加水印的步骤,参考:https://blog.csdn.net/meeku/article/details/50964507