ps流提取H264並解碼播放


因為需要從海康ps流中提取H264數據並進行解碼播放,才有了這篇文章.因為是視頻編解碼領域的純入門新手,個別理解或者方法有誤,需要自行判斷,不過相關方法已經測試通過,對於
像我這樣的新手還是有一定的借鑒的.斷斷續續搞了很長一段時間,把相關經驗分享給各個新手.

---------------------------------------------------------------------------------------------------------

分為3個部分來說吧,僅供參考.
1. 接收並且解析RTP流部分
2. 解析ps流部分,包括解析海康部分私有格式
3. 將提取的標准H264流進行解碼播放

--------------------------------------------------------------------------------------------------------

接收並且解析RTP流:

當時想到了兩種方案:1.編寫udp-socket來接受流,然后自己解析rtp包. 2.使用jrtplib庫來接受流,自動解析rtp包. 由於第二種方案簡單易用,使用了第二種方案.實驗過程發現jrtplib會
出現丟包的情況,導致花屏的出現.具體丟包原因未查明.(jrtplib使用了最簡單的框架,摘自jrtplib的其中一個example,未精簡,未使用thread,效果還可以)

//該函數主要作用是使用jrtplib接收rtp流,並且解析出H264,將一幀幀的H264存入一個deque中
DWORD WINAPI GetSocketData(LPVOID lpparentet) 
{
	//////////////////////////////////////////////////////////////////////////jrtplib的相關初始化
	WSADATA dat;
	WSAStartup(MAKEWORD(2,2),&dat);
	RTPSession session;
	RTPSessionParams sessionparams;
	sessionparams.SetOwnTimestampUnit(1.0/3600.0);
	sessionparams.SetUsePollThread(true);
	RTPUDPv4TransmissionParams transparams;
	transparams.SetPortbase(6000);
	session.SetMaximumPacketSize(sessionparams.GetMaximumPacketSize()+1000);
	transparams.SetRTPReceiveBuffer(transparams.GetRTCPReceiveBuffer()*3);
	transparams.SetRTCPReceiveBuffer(transparams.GetRTCPReceiveBuffer()*3);
	int status = session.Create(sessionparams,&transparams);
	if (status < 0)
	{
		std::cerr << RTPGetErrorString(status) << std::endl;
		exit(-1);
	}
	uint8_t localip[]={127,0,0,1};
	RTPIPv4Address addr(localip,9000);
	status = session.AddDestination(addr);
	if (status < 0)
	{
		std::cerr << RTPGetErrorString(status) << std::endl;
		exit(-1);
	}
	session.SetDefaultPayloadType(96);
	session.SetDefaultMark(false);
	session.SetDefaultTimestampIncrement(160);
	uint8_t silencebuffer[160];
	for (int i = 0 ; i < 160 ; i++)
		silencebuffer[i] = 128;
	RTPTime delay(0.02);
	RTPTime starttime = RTPTime::CurrentTime();
	/////////////////////////////////////////////////////////////////////////收到rtp包之后進行數據處理
	bool full=true;
	bool done = false;
	int i=0;
	while (!done)
	{
		session.BeginDataAccess();
		if (session.GotoFirstSource())
		{
			do
			{
				RTPPacket *packet;
				while ((packet = session.GetNextPacket()) != 0)
				{
					//查找ps頭 0x000001BA
					if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xba)
					{
						if (i!=0)
						{
							//此包為ps新的一幀,每次到這里都先處理存儲好的前一幀
							int iPsLength=0;
							GetH246FromPs(jimbak,jimlen,&returnps,&iPsLength); //從ps流中提取h264
							//海康流特殊處理部分:分界符數據(nal_unit_type=9)或補充增強信息單元(nal_unit_type=6),如果直接送入解碼器,有可能會出現問題,直接舍棄.00 00 01 bd和 00 00 01 c0為私有標志和音頻數據舍棄
							if (returnps[0]>>5==0x06 || returnps[0]>>5==0x09 ||returnps[0]>>5==0x0a || returnps[0]>>5==0x0b ||returnps[0]>>5==0x0c)
							{
							}
							else  //這就是獲取的含有標准正常H264數據的幀 我們開始進行處理,將該幀存入Deque,該deque只負責存儲所有的一幀幀的數據
							{
								char *h264buffer=new char[iPsLength];
								memcpy(h264buffer,returnps,iPsLength);
								BUFFERINFO bi;
								bi.h264buf=h264buffer;
								bi.lLength=iPsLength;
								EnterCriticalSection(&g_cs);
								gDeque.push_back(bi);
								LeaveCriticalSection(&g_cs);
							}
						}
						//各個變量初始化,開始拼接下一個幀(可能收到的N個包才能組成一個幀,所以這里有一個拼接操作,主要是一個幀的頭部指針一直memcpy,把內存向后疊加)
						jimlen=0;
						jim=jimbak;
						memcpy(jim,(char *)packet->GetPacketData()+12,packet->GetPacketLength()-12);
						jim+=(packet->GetPacketLength()-12);
						jimlen+=packet->GetPacketLength()-12;
						i++;					
					}
					else  //當然如果開頭不是0x000001BA,默認為一個幀的中間部分,我們將這部分內存順着幀的開頭向后存儲
					{
						if (i!=0)
						{
							//排除音頻和私有數據
							if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xc0)
							{

							}
							else if (packet->GetPacketData()[12]==0x00 && packet->GetPacketData()[13]==0x00 && packet->GetPacketData()[14]==0x01 && packet->GetPacketData()[15]==0xbd)
							{

							}
							else   //這是正常的幀數據,像貪吃蛇一樣,將它放在幀開頭的后邊
							{
								memcpy(jim,(char *)packet->GetPacketData()+12,packet->GetPacketLength()-12);
								jim+=(packet->GetPacketLength()-12);
								jimlen+=packet->GetPacketLength()-12;
							}
						}
					}
					session.DeletePacket(packet);
					i++;
				}
			} while (session.GotoNextSource());
		}
		session.EndDataAccess();
		RTPTime::Wait(delay);
	}

	delay = RTPTime(10.0);
	session.BYEDestroy(delay,"Time's up",9);
	WSACleanup();
}

---------------------------------------------------------------------------------------------

海康PS流解析,可參考http://blog.csdn.net/wwyyxx26/article/details/15224879#

union littel_endian_size
{
	unsigned short int	length;
	unsigned char		byte[2];
};

struct pack_start_code
{
	unsigned char start_code[3];
	unsigned char stream_id[1];
};

struct program_stream_pack_header
{
	pack_start_code PackStart;// 4
	unsigned char Buf[9];
	unsigned char stuffinglen;
};

struct program_stream_pack_bb_header
{
	unsigned char head[4];
	unsigned char num1;
	unsigned char num2;
};

struct program_stream_map
{
	pack_start_code PackStart;
	littel_endian_size PackLength;//we mast do exchange
	//program_stream_info_length
	//info
	//elementary_stream_map_length
	//elem
};

struct program_stream_e
{
	pack_start_code		PackStart;
	littel_endian_size	PackLength;//we mast do exchange
	char				PackInfo1[2];
	unsigned char		stuffing_length;
};

#pragma pack()

int inline ProgramStreamPackHeader(char* Pack, int length, char **NextPack, int *leftlength)
{
	//cout <<WRITE_LOG(LOG_LEVEL_SUB_1, "%02x %02x %02x %02x",Pack[0],Pack[1],Pack[2],Pack[3]);
	//通過 00 00 01 ba頭的第14個字節的最后3位來確定頭部填充了多少字節
	program_stream_pack_header *PsHead = (program_stream_pack_header *)Pack;
	unsigned char pack_stuffing_length = PsHead->stuffinglen & '\x07';

	*leftlength = length - sizeof(program_stream_pack_header) - pack_stuffing_length;//減去頭和填充的字節
	*NextPack = Pack+sizeof(program_stream_pack_header) + pack_stuffing_length;

	//如果開頭含有bb 則去掉bb
	if(*NextPack && (*NextPack)[0]=='\x00' && (*NextPack)[1]=='\x00' && (*NextPack)[2]=='\x01' && (*NextPack)[3]=='\xBB')
	{
		program_stream_pack_bb_header *pbbHeader=(program_stream_pack_bb_header *)(*NextPack);
		unsigned char bbheaderlen=pbbHeader->num2;
		(*NextPack) = (*NextPack) + sizeof(program_stream_pack_bb_header)+bbheaderlen;
		*leftlength = length - sizeof(program_stream_pack_bb_header) - bbheaderlen;
		int a=0;
		a++;
	}

	if(*leftlength<4) return 0;

	//printf("[%s]2 %x %x %x %x\n", __FUNCTION__, (*NextPack)[0], (*NextPack)[1], (*NextPack)[2], (*NextPack)[3]);

	return *leftlength;
}

inline int ProgramStreamMap(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);

	program_stream_map* PSMPack = (program_stream_map*)Pack;

	//no payload
	*PayloadData = 0;
	*PayloadDataLen = 0;

	if(length < sizeof(program_stream_map)) return 0;

	littel_endian_size psm_length;
	psm_length.byte[0] = PSMPack->PackLength.byte[1];
	psm_length.byte[1] = PSMPack->PackLength.byte[0];

	*leftlength = length - psm_length.length - sizeof(program_stream_map);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if(*leftlength<=0) return 0;

	*NextPack = Pack + psm_length.length + sizeof(program_stream_map);

	return *leftlength;
}

inline int Pes(char* Pack, int length, char **NextPack, int *leftlength, char **PayloadData, int *PayloadDataLen)
{
	//printf("[%s]%x %x %x %x\n", __FUNCTION__, Pack[0], Pack[1], Pack[2], Pack[3]);
	program_stream_e* PSEPack = (program_stream_e*)Pack;

	*PayloadData = 0;
	*PayloadDataLen = 0;

	if(length < sizeof(program_stream_e)) return 0;

	littel_endian_size pse_length;
	pse_length.byte[0] = PSEPack->PackLength.byte[1];
	pse_length.byte[1] = PSEPack->PackLength.byte[0];

	*PayloadDataLen = pse_length.length - 2 - 1 - PSEPack->stuffing_length;
	if(*PayloadDataLen>0) 
		*PayloadData = Pack + sizeof(program_stream_e) + PSEPack->stuffing_length;

	*leftlength = length - pse_length.length - sizeof(pack_start_code) - sizeof(littel_endian_size);

	//printf("[%s]leftlength %d\n", __FUNCTION__, *leftlength);

	if(*leftlength<=0) return 0;

	*NextPack = Pack + sizeof(pack_start_code) + sizeof(littel_endian_size) + pse_length.length;

	return *leftlength;
}

int inline GetH246FromPs(char* buffer,int length, char **h264Buffer, int *h264length)
{
	int leftlength = 0;
	char *NextPack = 0;

	*h264Buffer = buffer;
	*h264length = 0;

	if(ProgramStreamPackHeader(buffer, length, &NextPack, &leftlength)==0)
		return 0;

	char *PayloadData=NULL; 
	int PayloadDataLen=0;

	while(leftlength >= sizeof(pack_start_code))
	{
		PayloadData=NULL;
		PayloadDataLen=0;

		if(NextPack 
			&& NextPack[0]=='\x00' 
			&& NextPack[1]=='\x00' 
			&& NextPack[2]=='\x01' 
			&& NextPack[3]=='\xE0')
		{
			//接着就是流包,說明是非i幀
			if(Pes(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen))
			{
				if(PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}
			}
			else 
			{
				if(PayloadDataLen)
				{
					memcpy(buffer, PayloadData, PayloadDataLen);
					buffer += PayloadDataLen;
					*h264length += PayloadDataLen;
				}

				break;
			}
		}
		else if(NextPack 
			&& NextPack[0]=='\x00' 
			&& NextPack[1]=='\x00'
			&& NextPack[2]=='\x01'
			&& NextPack[3]=='\xBC')
		{
			if(ProgramStreamMap(NextPack, leftlength, &NextPack, &leftlength, &PayloadData, &PayloadDataLen)==0)
				break;
		}
		else
		{
			//printf("[%s]no konw %x %x %x %x\n", __FUNCTION__, NextPack[0], NextPack[1], NextPack[2], NextPack[3]);
			break;
		}
	}
	return *h264length;
}

  H264解碼並播放

BOOL H264_Init_and_SDL()
{
	avcodec_init();
	av_register_all();
	AVCodec *pCodec=avcodec_find_decoder(CODEC_ID_H264);
	g_pCodecCtx=avcodec_alloc_context();

	g_pCodecCtx->time_base.num = 1; //這兩行:一秒鍾25幀 
	g_pCodecCtx->time_base.den = 25; 
	g_pCodecCtx->bit_rate = 0; //初始化為0 
	g_pCodecCtx->frame_number = 1; //每包一個視頻幀 
	g_pCodecCtx->codec_type = CODEC_TYPE_VIDEO; 
	g_pCodecCtx->width = 1280; //這兩行:視頻的寬度和高度 
	g_pCodecCtx->height = 720; 

	if (avcodec_open(g_pCodecCtx,pCodec)>=0)
	{
		g_pavfFrame=avcodec_alloc_frame();
		g_pYUVavfFrame=avcodec_alloc_frame();
	}

	//////////////////////////////////////////////////////////////////////////
	SDL_putenv("SDL_VIDEO_WINDOW_POS=0,0");
	if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {    
		printf( "Could not initialize SDL - %s\n", SDL_GetError());   
		return -1;  
	}   

	screen_w = g_pCodecCtx->width;  
	screen_h = g_pCodecCtx->height;  
	g_screen = SDL_SetVideoMode(screen_w, screen_h, 0,0);  

	if(!g_screen) 
	{    
		printf("SDL: could not set video mode - exiting:%s\n",SDL_GetError());    
		return FALSE;  
	}  

	g_bmp = SDL_CreateYUVOverlay(g_pCodecCtx->width, g_pCodecCtx->height,SDL_YV12_OVERLAY, g_screen);   

	rect.x = 0;      
	rect.y = 0;      
	rect.w = screen_w;      
	rect.h = screen_h;    
	//SDL End------------------------
	img_convert_ctx = sws_getContext(g_pCodecCtx->width, g_pCodecCtx->height, g_pCodecCtx->pix_fmt, g_pCodecCtx->width, g_pCodecCtx->height, PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL); 

	return (BOOL)g_pavfFrame;
}

BOOL H264_Decode(const PBYTE pSrcData, const DWORD dwDataLen, PBYTE pDeData,  int * pnWidth, int * pnHeight)
{
	//pSrcData – 待解碼數據 
	//dwDataLen – 待解碼數據字節數 
	//pDeData – 用來返回解碼后的YUV數據 
	//pnWidth, pnHeight – 用來返回視頻的長度和寬度 
	BOOL n_Got=FALSE;
	avcodec_decode_video(g_pCodecCtx,g_pavfFrame,(int *)&n_Got,(unsigned __int8*)pSrcData,dwDataLen);
	if (n_Got)
	{
		*pnWidth=g_pCodecCtx->width;
		*pnHeight=g_pCodecCtx->height;
		//ASSERT(g_pCodecCtx->pix_fmt==PIX_FMT_YUV420P);
		if (g_pCodecCtx->pix_fmt!=PIX_FMT_YUV420P)
		{
			return FALSE;
		}
		//轉為YUV
		int ndatalen=0;
		for (int i=0;i<3;i++)
		{
			int nShift=(i==0)?0:1;
			PBYTE pYUVData=(PBYTE)g_pavfFrame->data[i];
			for (int j=0;j<(g_pCodecCtx->height>>nShift);j++)
			{
				memcpy(&pDeData[ndatalen],pYUVData,(g_pCodecCtx->width >> nShift));
				pYUVData+=g_pavfFrame->linesize[i];
				ndatalen+=(g_pCodecCtx->width >> nShift);
			}
		}
		//////////////////////////////////////////////////////////////////////////
		SDL_LockYUVOverlay(g_bmp);  
		g_pYUVavfFrame->data[0]=g_bmp->pixels[0];  
		g_pYUVavfFrame->data[1]=g_bmp->pixels[2];  
		g_pYUVavfFrame->data[2]=g_bmp->pixels[1];       
		g_pYUVavfFrame->linesize[0]=g_bmp->pitches[0];  
		g_pYUVavfFrame->linesize[1]=g_bmp->pitches[2];     
		g_pYUVavfFrame->linesize[2]=g_bmp->pitches[1];  
		sws_scale(img_convert_ctx, g_pavfFrame->data, g_pavfFrame->linesize, 0, g_pCodecCtx->height, g_pYUVavfFrame->data, g_pYUVavfFrame->linesize);  
		SDL_UnlockYUVOverlay(g_bmp);
		SDL_DisplayYUVOverlay(g_bmp, &rect);   
		//Delay 40ms   
		//SDL_Delay(40);  

	}
	
	return n_Got;
}

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM