EasyRTSPLive高效轉碼之EasyVideoDecoder高效軟解碼解決方案(附源碼)

在我之前寫的一篇文章《EasyRTSPLive傳統視頻監控互聯網+實現利器解決方案》中提到RTSP轉RTMP的轉流過程,簡化流程就是通過EasyRTSPClient拉RTSP流,獲取音視頻編碼數據,然後再通過EasyRTMP推出去,流程非常簡單;然後再實際開發過程中,我們發現其實這個過程並沒有想象中那麼簡單;首先,RTSP協議支持多種音視頻編碼格式,如音頻支持AAC,G711,G726,等,視頻支持H264,H625,MJPEG, MPEG等等各種格式,而EasyRTMP推流只支持H264(已擴展支持H265)格式,這時,音頻我們可以通過EasyAACEncoder將音頻轉碼成AAC格式,而視頻我們可以通過EasyVideoDecoder解碼成原始數據,然後再通過EasyVideoEncoder將原始數據轉碼成RTMP推送指定的格式,本文,我們將重點講述EasyVideoDecoder的軟解碼流程。

1. EasyVideoDecoder軟解碼接口聲明如下:
#ifndef __EASY_DECODER_API_H__
#define __FF_DECODER_API_H__


#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define EASYDECODER_API  __declspec(dllexport)
//=======================================================
//Decoder
#ifndef DECODER_H264
#define DECODER_H264			0x1C			//28
#endif
#ifndef DECODER_MPEG4
#define DECODER_MPEG4			0x0D			//13
#endif
#ifndef DECODER_MPEG2
#define DECODER_MPEG2			0x02			//2
#endif
#ifndef DECODER_MJPEG
#define DECODER_MJPEG			0x08			//8
#endif

#ifndef DECODER_MP3
#define DECODER_MP3				0x15001			//86017
#endif

#ifndef DECODER_AAC
#define DECODER_AAC				0x15002			//86018
#endif

//=======================================================
//輸出格式
#ifndef OUTPUT_PIX_FMT_YUV420P
#define OUTPUT_PIX_FMT_YUV420P		0
#endif
#ifndef OUTPUT_PIX_FMT_YUYV422
#define OUTPUT_PIX_FMT_YUYV422		1
#endif
#ifndef OUTPUT_PIX_FMT_RGB565LE
#define OUTPUT_PIX_FMT_RGB565LE		44
#endif
#ifndef OUTPUT_PIX_FMT_RGBA
#define OUTPUT_PIX_FMT_RGBA			28
#endif

//=======================================================
//圖像處理
//=======================================================
typedef enum __VIDEO_FILTER_TYPE
{
	VIDEO_ROTATION_90_0				=		0,	//順時針旋轉90度
	VIDEO_ROTATION_90_1,						//逆時針旋轉90度
	VIDEO_ROTATION_90_0_FLIP,					//順時針旋轉90度,再水平翻轉
	VIDEO_ROTATION_90_1_FLIP,					//逆時針旋轉90度,再垂直翻轉

	VIDEO_TEXT,

}VIDEO_FILTER_TYPE;




//=======================================================
typedef void *EASYDEC_HANDLE;


//=======================================================
extern "C"
{
	int	EASYDECODER_API	EASYDECODER_Init(EASYDEC_HANDLE *_handle);
	int	EASYDECODER_API	EASYDECODER_Deinit(EASYDEC_HANDLE *_handle);

	int EASYDECODER_API	EASYDECODER_SetVideoDecoderParam(EASYDEC_HANDLE _handle, int _width, int _height, int _decoder, int _outformat);
	int EASYDECODER_API	EASYDECODER_SetAudioDecoderParam(EASYDEC_HANDLE _handle, unsigned char _channel, unsigned int _sample_rate, unsigned int _decoder);

	int	EASYDECODER_API	EASYDECODER_GetVideoDecoderInfo(EASYDEC_HANDLE _handle, int *_decoder, int *_width, int *_height);

	int	EASYDECODER_API	EASYDECODER_DecodeVideo(EASYDEC_HANDLE _handle, char *pInBuf, int inputSize, char **pOutBuf, int dstW, int dstH);

	//desc:				解碼後的數據,直接送到指定的內存中
	int	EASYDECODER_API	EASYDECODER_DecodeVideo2Buf(EASYDEC_HANDLE _handle, char *_inbuf, int _bufsize, void *_outbuf[8], int _pitch);

	int	EASYDECODER_API	EASYDECODER_DecodeVideo3(EASYDEC_HANDLE _handle, char *_inbuf, int _bufsize, void *yuvbuf, int dstW, int dstH);
	int	EASYDECODER_API	EASYDECODER_DecodeVideoPacket(EASYDEC_HANDLE _handle, char *pCodecCtx, unsigned char *avPacket, char **_outbuf);


	int	EASYDECODER_API	EASYDECODER_DecodeAudio(EASYDEC_HANDLE _handle, char *pInBuf, int inputSize, char *pOutBuf, int *outSize);
	int	EASYDECODER_API	EASYDECODER_DecodeAudioPacket(EASYDEC_HANDLE _handle, char *pCodecCtx, unsigned char *avPacket, char *pOutBuf, int *outSize);
};

#endif
2. 軟解碼通過ffmpeg解碼實現流程

和網上大多數的ffmpeg解碼示例調用相似。軟解碼實現分四步走,詳細流程如下:

  • 第一步,全局註冊ffmpeg編解碼器
	avcodec_register_all();/*註冊所有的編碼解碼器*/
	av_register_all();// //註冊所有可解碼類型

  • 第二步,初始化視頻解碼參數
int	InitVideoDecoder(int _width, int _height, int _videoCodec, int _outformat)
{
	if (NULL != decoderObj.pVideoCodecCtx)		return -1;	//or call DeinitVideoDecoder();

	AVCodec			*pAVCodec;
	pAVCodec = avcodec_find_decoder((AVCodecID)_videoCodec);
	if (NULL == pAVCodec)		return -1;

	decoderObj.pVideoCodecCtx = avcodec_alloc_context3(pAVCodec);
	decoderObj.pVideoCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
	decoderObj.pVideoCodecCtx->width   = _width;
	decoderObj.pVideoCodecCtx->height  = _height;
	//decoderObj.pVideoCodecCtx->thread_count = 2;
	//decoderObj.pVideoCodecCtx->active_thread_type = decoderObj.pVideoCodecCtx->thread_type = FF_THREAD_FRAME;

	int ret = avcodec_open2(decoderObj.pVideoCodecCtx, pAVCodec, NULL);
	if (ret < 0)				goto $fail;

	int numBytes = avpicture_get_size(AV_PIX_FMT_YUV420P, _width, _height);
	decoderObj.pBuffYuv420 = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
	decoderObj.mVideoFrame420	= av_frame_alloc();
	if (avpicture_fill((AVPicture *)decoderObj.mVideoFrame420, decoderObj.pBuffYuv420, AV_PIX_FMT_YUV420P,
		decoderObj.width, decoderObj.height) < 0)
	{
	}
#ifdef ADD_VIDEO_FILTER
	SetVideoFilter(VIDEO_TEXT, NULL);
#endif
	av_init_packet(&decoderObj.avVidPacket);
	decoderObj.outputFormat = _outformat;
	decoderObj.codec	=	_videoCodec;
	decoderObj.width	=	_width;
	decoderObj.height	=	_height;	
	return 0;

$fail:
	{
		DeinitVideoDecoder();
		return -1;
	}
}
  • 第三步,直接解碼視頻幀並輸出指定色彩格式
int	DecodeVideo(char *_inbuf, int _bufsize, char **_outbuf, int dstW, int dstH)
{
	if (NULL == _inbuf)			return -1;
	if (1 > _bufsize)			return -1;

	//if (NULL == decoderObj.pSws_ctx)	return -2;

	if (NULL == decoderObj.mVideoFrame420)	decoderObj.mVideoFrame420 = av_frame_alloc();
	
	decoderObj.avVidPacket.size = _bufsize;
	decoderObj.avVidPacket.data	= (uint8_t*)_inbuf;

	int frameFinished = 0;
	int nDecode = avcodec_decode_video2(decoderObj.pVideoCodecCtx, decoderObj.mVideoFrame420, &frameFinished, &decoderObj.avVidPacket);//(uint8_t*)pInBuffer, inputSize);
	if (nDecode < 0)	return -3;
	if (!frameFinished)	return -4;

	if (NULL == decoderObj.pAvFrameYuv)
	{
		int numBytes = avpicture_get_size((AVPixelFormat)decoderObj.outputFormat,  dstW, dstH);
		decoderObj.pBuffYuv = (uint8_t *)av_malloc(numBytes * sizeof(uint8_t));
		decoderObj.pAvFrameYuv = av_frame_alloc();
		if (avpicture_fill((AVPicture *)decoderObj.pAvFrameYuv, decoderObj.pBuffYuv, (AVPixelFormat)decoderObj.outputFormat,
			dstW, dstH) < 0)
		{

		}
	}

	if (NULL == decoderObj.pSws_ctx)
	{
		decoderObj.pSws_ctx = sws_getCachedContext(decoderObj.pSws_ctx, decoderObj.width, decoderObj.height, (AVPixelFormat)AV_PIX_FMT_YUV420P, 
			//decoderObj.width/2, decoderObj.height/2, (PixelFormat)PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);
			dstW, dstH, (AVPixelFormat)decoderObj.outputFormat, SWS_BICUBIC, NULL, NULL, NULL);
	}

	if (NULL == decoderObj.pSws_ctx)		return -1;

	sws_scale(decoderObj.pSws_ctx, decoderObj.mVideoFrame420->data, decoderObj.mVideoFrame420->linesize, 0, decoderObj.pVideoCodecCtx->height, 
		decoderObj.pAvFrameYuv->data, decoderObj.pAvFrameYuv->linesize);

	//sws_freeContext(decoderObj.pSws_ctx);
	//decoderObj.pSws_ctx = NULL;

	*_outbuf = (char*)decoderObj.pAvFrameYuv->data[0];

	return 0;
}
  • 第四步,停止解碼後,銷燬解碼器申請的資源
void DeinitVideoDecoder()
{
	if (NULL != decoderObj.mVideoFrame420)
	{
		av_frame_free(&decoderObj.mVideoFrame420);
		decoderObj.mVideoFrame420 = NULL;
	}
	if (NULL != decoderObj.pBuffYuv420)
	{
		av_free(decoderObj.pBuffYuv420);
		decoderObj.pBuffYuv420 = NULL;
	}
	if (NULL != decoderObj.pAvFrameSws)
	{
		av_frame_free(&decoderObj.pAvFrameSws);
		decoderObj.pAvFrameSws = NULL;
	}
	if (NULL != decoderObj.pAvFrameYuv)
	{
		av_frame_free(&decoderObj.pAvFrameYuv);
		decoderObj.pAvFrameYuv = NULL;
	}
	if (NULL != decoderObj.pBuffYuv)
	{
		av_free(decoderObj.pBuffYuv);
		decoderObj.pBuffYuv = NULL;
	}

	if (NULL != decoderObj.pSws_ctx)
	{
		sws_freeContext(decoderObj.pSws_ctx);
		decoderObj.pSws_ctx = NULL;
	}

	if (NULL != decoderObj.pVideoCodecCtx)
	{
		avcodec_close(decoderObj.pVideoCodecCtx);
		av_free(decoderObj.pVideoCodecCtx);
		decoderObj.pVideoCodecCtx = NULL;
	}
}

有任何技術問題,歡迎大家和我技術交流:
[email protected]

大家也可以加入EasyPlayer流媒體播放器 QQ羣進行討論:
544917793

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章