视频会议中或者录播中使用RTP协议接收h264视频

6 篇文章 0 订阅
4 篇文章 0 订阅

此程序文章献给刚进公司的需要帮助的程序员,

       说明:1 该代码在windows上运行,用vs2010编译。

                   2 该代码要能解决移植的问题。 

                   3 rtp实时传输协议可以使用udp,也可以使用tcp协议

       首先,为了减小程序的难度,说明使用的库解码库为ffmpeg,刷视频数据的方法可以使用

                  1 SDL库 ,到sdl的源代码网站中下载并编译

                  2 直接使用gdi, 并且解决翻转问题。

                  3 使用opengl或者direct3d, 或者directdraw。

     基础知识:

      A  首先RTP 包结构一般为12字节,传输层协议中UDP协议和TCP协议是可选的,都可以用,多数使用了UDP协议,如果要扫盲,请链接到基维百 

          科http://zh.wikipedia.org/wiki/%E5%AE%9E%E6%97%B6%E4%BC%A0%E8%BE%93%E5%8D%8F%E8%AE%AE,使用tcp协议的好处是和rtsp协议

          相关联的,涉及到nat转换 路由方面的知识,我后面会讲,而UDP协议在h264等视频发送的时候要注意的是分包问题,主要是MTU最大传输单元的问题,h264的

          nalu如果超过最大传输单元,必须分割发送。

      B ffmpeg1.0 已经及其优秀,包含ffmpeg库不要忘了 extern “C”extern "C"{#include #include #include #include }为了使得快速开发出一个原型,使用boost的asio库,

         可以节省一些时间。并且使用回调函数来解码和刷屏,以下是使用asio库来接收网络的包,默认使用了组播地址,也就是说假设该h264视频会传送到组播地址上,传送到

         组播地址的好处是调试方便,在局域网内接收都可以。

 

这是网络接收类的一个头文件示例,读者完全可以不使用boost库,自行写出:


#pragma once
#include "CodeReceive.h"

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

#include "DrawRGB24.h"
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
}

#include "h264head/h264-x264.h"

class CodeReceive2:public CBaseReceive
{
friend DWORD WINAPI ThreadProcReceive(LPVOID param);
public:
	CodeReceive2();
	~CodeReceive2(void);
protected:
	void CreateiocpReceiver(const boost::asio::ip::address& listen_address,
      const boost::asio::ip::address& multicast_address,
	  const unsigned short port);
	
	void handle_receive_from_direct(const boost::system::error_code& error,
      size_t bytes_recvd);



	BOOL CreateDecode()
	{
		if(_pDecode==NULL)
		{
			_pDecode= new H264DecoderContext();

			if(_pDecode == NULL)
				return FALSE;
			if(!_pDecode->Initialize())
				return FALSE;
		}
		return TRUE;
	}
	void DeleteDecode()
	{
		if(_pDecode!=NULL)
		{
			delete _pDecode;
			_pDecode = NULL;
		}
	}


public:
	virtual int Pix_Fmt();
	virtual int Width()  ;
	virtual int Height() ;
	virtual BOOL StartReceive(string ip,unsigned short port) ;

    virtual void StopReceive() ;

	//这个画法是使用了SDL画法
	virtual void SetFunction(FrameCallback func) ;

	//这个是可以获取数据自己画,后面的版本是要用directshow vmr画法
	virtual void SetFunctionRGB24(FrameCallback_RGB24 func) ;

	//这个是内置的画法,普通GDI画,参考OpenCV源代码,预览画像
	virtual void SetDrawhWnd(HWND hWnd0,HWND hWnd1) ;

   // static DWORD ThreadProc_Recv(LPVOID param);

private:

    boost::asio::io_service io_service_;
	boost::asio::ip::udp::socket socket_;
    boost::asio::ip::udp::endpoint sender_endpoint_;
    enum { max_length = 1500 };
    char data_[max_length];
    unsigned short _multicast_port;

	string _multicast_ip;
private:
	H264DecoderContext* _pDecode;

	AVFrame * _pFrameRGB;
	uint8_t * _RGBBuffer;
	struct SwsContext *_img_convert_ctx;
	//同时画两个窗口
    CDrawRGB24 _Draw;
   //  HANDLE _ThreadHandle  ;
	HWND _hWnd0;
	HWND _hWnd1;

	FrameCallback_RGB24 _functionRGB24;
};



类的cpp文件的接收函数的关键函数

void CodeReceive2::handle_receive_from_direct(const boost::system::error_code& error,
      size_t bytes_recvd)
{
    if (!error)
    {
		
		AVFrame * frame =_pDecode->DecodeFrames((const u_char*)data_,bytes_recvd);
		if(frame!=NULL)
		{
			int Width  = this->Width();//_pDecode->GetContext()->width;
			int Height = this->Height();//_pDecode->GetContext()->height;

#if 0  //如果需要用sdl渲染画面,可以打开这个
			if(_function )
				_function(frame,_pDecode->GetContext()->pix_fmt,
					_pDecode->GetContext()->width,
					_pDecode->GetContext()->height
		
					);
#endif            
			
			if(_RGBBuffer == NULL)
			{
				int numBytes;

				numBytes=avpicture_get_size(
					//PIX_FMT_RGB24, 
					PIX_FMT_BGR24,
					Width,
					Height);
				_RGBBuffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

				if(_pFrameRGB == NULL)
					_pFrameRGB = avcodec_alloc_frame();
				avpicture_fill((AVPicture *)_pFrameRGB, _RGBBuffer, PIX_FMT_BGR24,   Width, Height); 

				_img_convert_ctx = sws_getContext(Width, Height, 
					_pDecode->GetContext()->pix_fmt,//PIX_FMT_YUV420P, 
					Width, 
					Height, 
					PIX_FMT_BGR24, 
					SWS_BICUBIC, 
					NULL, 
					NULL, 
					NULL);
			}

			sws_scale(_img_convert_ctx, frame->data, frame->linesize, 0, Height, _pFrameRGB->data, _pFrameRGB->linesize);

			if(_hWnd0!=NULL || _hWnd1!=NULL)
				_Draw.Draw2(_hWnd0,_hWnd1,_pFrameRGB->data[0],Width,Height);

			//Sleep(5);
			if(_functionRGB24)
			{

				_functionRGB24(_pFrameRGB->data[0],_pDecode->GetContext()->pix_fmt,Width,Height);
			}
	
		}
	

        socket_.async_receive_from(
          boost::asio::buffer(data_, max_length), sender_endpoint_,
          boost::bind(&CodeReceive2::handle_receive_from_direct, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

    }
  }


  这里有让刚做ffmpeg或者图像的程序员困惑的一些问题,比如图像为什么接收的是倒立的?RGB实际上是BGR,能不能直接刷屏yuv420等等。图像倒立的问题是直播取到的图像本身就是倒立的,还是过程中倒立了,这个问题比较难以回答,比如摄像头D70,高清的HD1等等有一个开关拨一下就正,再返回去就倒立,不过拿普通的USB摄像头,按照正常采集,经过一系列的变换,你发现图像也是倒得,如果拿一个正常录下的h264视频,有的程序员播放时发现也是倒立的,如果是倒立的,有两个方法解决,一是ffmepg的sws_scale函数可以解决这个问题,一个是gdi刷屏可以解决这个问题,有的程序员会重新memcpy以下,把图像倒过来,这样也是可以的,但如果是高清720P或者1080P的图像较大,比较费事,最好是直接在过程中就解决掉这个问题。

   用gdi刷屏时,把SrcH负过来就可以让图像正过来。

   当然,用gdi必然效率不会很好,尤其做画中画的时候或者多路图像的时候,不能用这个,windows上可以用directx和较新的dxva。

  用下面这个来倒立图像

m_lpBmpInfo->bmiHeader.biHeight=   -SrcH;


void CDrawRGB24::Draw2(HWND hWnd, HWND hWnd2,unsigned char * buffer, int SrcW, int SrcH)
{
	HDC hDCDst1 = NULL;
	HDC hDCDst2 = NULL;
	RECT destRect1;
	RECT destRect2;
	if(hWnd!=NULL)
	{
		hDCDst1 = GetDC(hWnd);
		GetClientRect(hWnd,&destRect1);
	}
	if(hWnd2!=NULL)
	{
		hDCDst2 = GetDC(hWnd2);
		GetClientRect(hWnd2,&destRect2);
	}

	if(!m_bInit)
	{
		m_bInit = true;
		m_lpBmpInfo=new BITMAPINFO;
		m_lpBmpInfo->bmiHeader.biSize  = sizeof(BITMAPINFOHEADER);
		m_lpBmpInfo->bmiHeader.biWidth =   SrcW;
		m_lpBmpInfo->bmiHeader.biHeight=   -SrcH;
		m_lpBmpInfo->bmiHeader.biPlanes= 1;
		m_lpBmpInfo->bmiHeader.biBitCount      = 24;
		m_lpBmpInfo->bmiHeader.biCompression   = 0;
		m_lpBmpInfo->bmiHeader.biSizeImage     = 0;
		m_lpBmpInfo->bmiHeader.biXPelsPerMeter = 0;
		m_lpBmpInfo->bmiHeader.biYPelsPerMeter = 0;
		m_lpBmpInfo->bmiHeader.biClrUsed=0;
		m_lpBmpInfo->bmiHeader.biClrImportant  = 0;

		//CDC * dc =  CDC::FromHandle(hDCDst);
		//m_pMemDC = new CMemDC(*dc,DestRect);
	}

	if(hDCDst1!=NULL)
	{
		int DstWidth  = destRect1.right-destRect1.left;
		int DstHeight = destRect1.bottom- destRect1.top;
		SetStretchBltMode(hDCDst1,STRETCH_HALFTONE);
		::StretchDIBits(
			//m_pMemDC->GetDC().GetSafeHdc(),
			hDCDst1,
			0, 0, DstWidth, DstHeight,
			0, 0, SrcW, SrcH,
			buffer, m_lpBmpInfo, DIB_RGB_COLORS, SRCCOPY );
		ReleaseDC(hWnd,hDCDst1);
	}
	if(hDCDst2!=NULL)
	{
		int DstWidth  = destRect2.right-destRect2.left;
		int DstHeight = destRect2.bottom- destRect2.top;
		SetStretchBltMode(hDCDst2,STRETCH_HALFTONE);
		::StretchDIBits(
			//m_pMemDC->GetDC().GetSafeHdc(),
			hDCDst2,
			0, 0, DstWidth, DstHeight,
			0, 0, SrcW, SrcH,
			buffer, m_lpBmpInfo, DIB_RGB_COLORS, SRCCOPY );
		ReleaseDC(hWnd2,hDCDst2);
	}

}

整个的过程是收包,拿到包头时间戳等信息,去掉包头12字节,拿到h264 nalu数据,用ffmpeg解码,时间戳问题主要集中在音频和视频同步的上面,而pts和dts是同步最重要的信息,解码过程为:

   

AVFrame* H264DecoderContext::DecodeFrames(const u_char * src, unsigned & srcLen)
{

  RTPFrame srcRTP(src, srcLen);
  if (!_rxH264Frame->SetFromRTPFrame(srcRTP, flags)) {
	  _rxH264Frame->BeginNewFrame();
	  //sprintf(dst,"%s\n","setfromrtpframe is not ok!");
	  flags = (_gotAGoodFrame ? requestIFrame : 0);
	  _gotAGoodFrame = false;
	  return NULL;
  }

  if (srcRTP.GetMarker()==0)
  {
		return NULL;
  } 

  if (_rxH264Frame->GetFrameSize()==0)
  {
	  _rxH264Frame->BeginNewFrame();
	  _skippedFrameCounter++;
	  flags = (_gotAGoodFrame ? requestIFrame : 0);
	  _gotAGoodFrame = false;
	  return NULL;
  }
  // look and see if we have read an I frame.
  if (_gotIFrame == 0)
  {
    _gotIFrame = 1;
  }
 

  int gotPicture = 0;
 // uint32_t bytesUsed = 0;

  // int bytesDecoded = avcodec_decode_video(_context,_outputFrame,&gotPicture,_rxH264Frame->GetFramePtr(),_rxH264Frame->GetFrameSize());

  int bytesDecoded = FFMPEGLibraryInstance.AvcodecDecodeVideo(_context, _outputFrame, &gotPicture, _rxH264Frame->GetFramePtr(), _rxH264Frame->GetFrameSize());
  _rxH264Frame->BeginNewFrame();
  if (!gotPicture) 
  {
	  _skippedFrameCounter++;
	  flags = (_gotAGoodFrame ? requestIFrame : 0);

	  _gotAGoodFrame = false;
	  return NULL;
  }

 //得到了一帧
  // w  = _context->width;
  // h  = _context->height;
  flags = 1;
  _frameCounter++;
  _gotAGoodFrame = true;


  return _outputFrame;
}

  代码暂时只是为了演示,并不完整,不过基本过程是非常清楚的。过程中其实还需要处理一个比较傲重要的问题就是分辨率改变的问题,音视频同步的问题,播放过快或者过慢的问题,如果要测试发送的视频是否正确,可以使用vlc来接收测试。

  这是第一篇基础,后面再准备比较完整的示例和用d3d,sdl刷屏,并且加入音频的解码,属于第二篇。

  未完待续。。。。。。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值