Jrtplib收發H264文件 + FFMPEG解碼+VFW播放視頻

  最近看的文章和demo都是把H264文件用RTP協議發送出去後,用VLC的SDP文件測試播放,那麼如果

自己接收到RTP協議的H264包後如何解碼播放呢?

 

  關於FFMPEG解碼播放的示例,一般都是打開本地磁盤的某個文件,比如D:\test.h264,邊讀入數據,邊解碼播放,如果數據是RTP協議傳過來的H264包,如何解碼?因爲avformat_open_input()函數傳入的是文件路徑或URL,我們收到的是一個H264包,沒辦法傳入。

 

  查了幾天資料,搗騰了半天,終於用Jrtplib收發H264文件+ FFMPEG解碼+VFW播放視頻的方式把視頻播放出來了,有些文章只有關鍵代碼,沒有demo可參考,比如這個 DrawDibDraw()我百度了半天,才發現是VFW的東西,非資深人士真是傷不起啊!

 

發送端和接收端採用的是JrtpLib庫,當然自己寫UDP收發RTP包也可以,只是我參考的文章,原意是用JrtpLib庫傳輸H264文件(接收端只往磁盤寫數據,非播放),發送端不是重點,我原樣採用他人的代碼,只是負責推送數據而已.只是它發送的是帶起始碼(00 00 01 或00 00 00 01)的完整H264包,僞代碼如下:

	while((feof(fd)==0))
	{
		buff[pos++] = fgetc(fd);   //逐字節讀取fd文件
		if(header_flag == 1)
		{    //00 00 00 01
			if((buff[pos-1]==1)&&(buff[pos-2]==0)&&(buff[pos-3]==0)&&(buff[pos-4]==0))
			{
				sender.SendH264Nalu(&sess, buff,pos-4); // 發送一個完整的h264數據
				buff[0] = 0x00;
				buff[1] = 0x00;
				buff[2] = 0x00;
				buff[3] = 0x01;
				pos = 4;
				RTPTime::Wait(0.1); //間隔100毫秒
			}
		}
		else
		{   
			if((buff[pos-1]==1)&&(buff[pos-2]==0)&&(buff[pos-3]==0))
			{
				sender.SendH264Nalu(&sess, buff, pos-3);
				buff[0] = 0x00;
				buff[1] = 0x00;
				buff[3] = 0x01;
				pos = 3;
				RTPTime::Wait(0.1);
			}

		}
	}


接收端:接收端原來是控制檯程序,原來只是把收到的H264數據寫入磁盤即可,因爲要顯示視頻,我改造爲MFC的對話框程序,在對話框的OnInitDialog()函數裏,做了相關的初始化,如下:

	// TODO: 在此添加額外的初始化代碼
    m_DrawDib = DrawDibOpen();  // FVW用

	// ffmpeg初始化
	av_register_all();
	avformat_network_init();
	m_picture = av_frame_alloc();  
	m_pFrameRGB = av_frame_alloc();  
	if(!m_picture || !m_pFrameRGB)
	{  
		printf("Could not allocate video frame\n");  
		return FALSE;
	}  
	// --ffmpeg初始化 end ---

	// ffmpeg解碼器準備
	codec = avcodec_find_decoder(AV_CODEC_ID_H264);
	if(codec==NULL)
	{
		printf("Codec not found.(沒有找到解碼器)\n");
		return FALSE;
	}
	codecCtx = avcodec_alloc_context3(codec);
	if(avcodec_open2(codecCtx, codec,NULL)<0)
	{
		printf("Could not open codec.(無法打開解碼器)\n");
		return FALSE;
	}

	// NALU_t數據準備
	int buffersize=100000;
	h264node = (NALU_t*)calloc (1, sizeof (NALU_t));
	if (h264node == NULL)
	{
		printf("Alloc NALU Error\n");
		return 0;
	}

	h264node->max_size = buffersize;
	h264node->buf      = (char*)calloc (buffersize, sizeof (char));
	if (h264node->buf == NULL)
	{
		free (h264node);
		printf ("AllocNALU: n->buf");
		return 0;
	}
	//-- 準備結束


	//網絡初始化
	WSADATA dat;
	WSAStartup(MAKEWORD(2,2),&dat);

	sessparams.SetOwnTimestampUnit(1.0/10.0);         
	transparams.SetPortbase(12346);   //監聽端口
	int status = sess.Create(sessparams,&transparams);    
	checkerror(status);  

         最後,弄個定時器,收rtp包,顯示視頻

         SetTimer(666,5,NULL);


然後在定時器裏收包,檢查是否收到完整的H264包,是就對包進行解碼,然後顯示出來,定時器代碼如下:

void CShowH264_PictureDlg::OnTimer(UINT_PTR nIDEvent)
{
	if(666 == nIDEvent)
	{
		size_t len;  
		RTPPacket *pack;  
		int	status = sess.Poll();  // 主動收包
		checkerror(status);  
		sess.BeginDataAccess();  
		if (sess.GotoFirstSourceWithData())  
		{  
			do  
			{  
				while ((pack = sess.GetNextPacket()) != NULL)  
				{  
					//printf(" Get packet-> %d  \n ",pack->GetPayloadType());  
					uint8_t * loaddata = pack->GetPayloadData();  
					len      = pack->GetPayloadLength();  

					if(pack->GetPayloadType() == 96) //H264  
					{  
						if(pack->HasMarker()) // the last packet  
						{  
							//	printf(" write  packet-> %d  \n ",pack->GetPayloadType());  
							memcpy(&buff[pos],loaddata,len);      
							memcpy(Parsebuff,buff,pos+len);  //得到完整的h264 naul包   
							simplest_h264_parser(Parsebuff,pos+len,nCount); //解析完整h264 naul包
							nCount++;
							pos = 0;  
						}  
						else  
						{  
							memcpy(&buff[pos],loaddata,len);  //大的naul數據,分幾個包發送過來
							pos = pos + len;      
						}  
					}else  
					{  
						printf("!!!  GetPayloadType = %d !!!! \n ",pack->GetPayloadType());  // 非264數據包
					}  

					sess.DeletePacket(pack);  
				}  
			} while (sess.GotoNextSourceWithData());  
		} 

		sess.EndDataAccess();  

	}
	CDialogEx::OnTimer(nIDEvent);
}



得到完整的H264包後,處理的函數爲simplest_h264_parser(),這個函數 解析了H264包的nal_unit_type和nal_reference_idc屬性,這個功能對解碼和顯示沒什麼意義,是我從雷神的文章裏copy過來的,調試用的而已.


// 前2個參數是 一個完整naul包的buffer地址和長度,第3個參數是計數器而已
void  CShowH264_PictureDlg::simplest_h264_parser(unsigned char *Buf,int nLength,int packetIndex)
{
	if( 0 == Buf[0] & 0 == Buf[1] && 1 == Buf[2] ) 
	{
		h264node->startcodeprefix_len = 3;  // 0x000001開頭
	}
	else if( 0 == Buf[0] & 0 == Buf[1] && 0 == Buf[2]  && 1 == Buf[3])
	{
		h264node->startcodeprefix_len = 4;  //0x00000001開頭
	}
	else
	{
		printf("---->NALU head Error \n");
		return;
		// h264node->startcodeprefix_len = 0;  //未含起始碼.解析是有風險的
	}
	h264node->len = nLength - h264node->startcodeprefix_len;  //有效buffer的長度
	memcpy (h264node->buf, &Buf[h264node->startcodeprefix_len], h264node->len);   //拷貝有效值  
	h264node->forbidden_bit     = h264node->buf[0] & 0x80; //1 bit
	h264node->nal_reference_idc = h264node->buf[0] & 0x60; // 2 bit
	h264node->nal_unit_type     = (h264node->buf[0]) & 0x1f;// 5 bit

	char type_str[20]={0};
	switch(h264node->nal_unit_type)
	{
		case NALU_TYPE_SLICE:sprintf(type_str,"SLICE");break;
		case NALU_TYPE_DPA:sprintf(type_str,"DPA");break;
		case NALU_TYPE_DPB:sprintf(type_str,"DPB");break;
		case NALU_TYPE_DPC:sprintf(type_str,"DPC");break;
		case NALU_TYPE_IDR:sprintf(type_str,"IDR");break;
		case NALU_TYPE_SEI:sprintf(type_str,"SEI");break;
		case NALU_TYPE_SPS:sprintf(type_str,"SPS");break;  //SPS和PPS一般在H264的前2幀
		case NALU_TYPE_PPS:sprintf(type_str,"PPS");break;
		case NALU_TYPE_AUD:sprintf(type_str,"AUD");break;
		case NALU_TYPE_EOSEQ:sprintf(type_str,"EOSEQ");break;
		case NALU_TYPE_EOSTREAM:sprintf(type_str,"EOSTREAM");break;
		case NALU_TYPE_FILL:sprintf(type_str,"FILL");break;
	}
	char idc_str[20]={0};
	switch(h264node->nal_reference_idc>>5)
	{
		case NALU_PRIORITY_DISPOSABLE:sprintf(idc_str,"DISPOS");break;
		case NALU_PRIRITY_LOW:sprintf(idc_str,"LOW");break;
		case NALU_PRIORITY_HIGH:sprintf(idc_str,"HIGH");break;
		case NALU_PRIORITY_HIGHEST:sprintf(idc_str,"HIGHEST");break;
	}

	if(packetIndex<10) //顯示前10幀的類型
	{
		TRACE("\n %5d| %7s| %6s| %8d|",packetIndex,idc_str,type_str,h264node->len);
	}

	//經測試,PPS和SPS一般是頭2包必須處理,不然後面所有包都無法解碼,SPS包會穿插在後面的SLICE等包裏面
	//if( h264node->nal_unit_type <= NALU_TYPE_PPS) 這個限制條件可以取消
	{
		// 下面是解碼
		int len = nLength; 
		AVPacket packet;
		av_new_packet(&packet, len);
		memcpy(packet.data, Buf, len);
		int ret, got_picture;
		ret = avcodec_decode_video2(codecCtx, m_picture, &got_picture, &packet);
		if (ret > 0 )
		{
			if(got_picture)
			{
				m_PicBytes = avpicture_get_size(PIX_FMT_BGR24, codecCtx->width, codecCtx->height);  
				m_PicBuf = new uint8_t[m_PicBytes];  
				avpicture_fill((AVPicture *)m_pFrameRGB, m_PicBuf, PIX_FMT_BGR24, codecCtx->width, codecCtx->height);  
				if(!m_pImgCtx)
				{  
					m_pImgCtx = sws_getContext(codecCtx->width, codecCtx->height, codecCtx->pix_fmt, codecCtx->width, codecCtx->height, 

PIX_FMT_BGR24, SWS_BICUBIC, NULL, NULL, NULL);  
				}  
				m_picture->data[0] += m_picture->linesize[0]*(codecCtx->height-1);  
				m_picture->linesize[0] *= -1;                        
				m_picture->data[1] += m_picture->linesize[1]*(codecCtx->height/2-1);  
				m_picture->linesize[1] *= -1;  
				m_picture->data[2] += m_picture->linesize[2]*(codecCtx->height/2-1);  
				m_picture->linesize[2] *= -1;  
				sws_scale(m_pImgCtx, (const uint8_t* const*)m_picture->data, m_picture->linesize, 0, codecCtx->height, m_pFrameRGB->data, 

m_pFrameRGB->linesize);   

				display_pic(m_pFrameRGB->data[0], codecCtx->width, codecCtx->height);  

				delete[] m_PicBuf; //釋放內存.感覺它應該是avpicture_fill內部臨時用的
				//TRACE("\n -->準備顯示圖片 %d X %d",codecCtx->width,codecCtx->height);
			}
		}
		 av_free_packet(&packet);  //釋放包
	}
}


顯示視頻的函數是display_pic,如下:

void CShowH264_PictureDlg::display_pic(unsigned char* data, int width, int height)  
{  
	CRect  rc;  
	HDC hdc = GetDC()->GetSafeHdc();  
	GetClientRect(&rc);  

	if(m_height != height || m_width != width)
	{  
		m_height = height;  
		m_width  = width;  
		MoveWindow(0, 0, width, height, 0);  
		Invalidate();  
	}  
	init_bm_head();  

	//利用VFW來顯示
	DrawDibDraw(m_DrawDib,  
		hdc,  
		rc.left,  
		rc.top,  
		-1, // don't stretch  
		-1,  
		&bmiHeader,   
		(void*)data,   
		0,   
		0,   
		width,   
		height,   
		0);  
} 


注意,原文的發送端和接收端只是處理文件傳輸,並沒有嚴格按照RTP協議來打包(無需用VLC的SDP文件播放視頻),我們把接收到的H264數據自己解碼,自己顯示,都是自己處理,也就無所謂了.

 

本文demo下載地址如下:

http://download.csdn.net/detail/heker2010/9907427





參考文章:
《FFMPEG 實時解碼網絡H264碼流,RTP封裝》
http://blog.csdn.net/fang437385323/article/details/52336680


《linux 使用jrtplib收發h.264視頻文件》
http://blog.csdn.net/li_wen01/article/details/70435005


《FFMPEG如何解碼播放通過socket接收的網絡碼流(h264)》
https://www.oschina.net/question/217709_34304





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