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





  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值