此程序文章献给刚进公司的需要帮助的程序员,
说明: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));
}
}
用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刷屏,并且加入音频的解码,属于第二篇。
未完待续。。。。。。