http://www.cesclub.com/bw/jishuzhongxin/bianchengyuyan/2012/1118/46838.html
我愿我能在横过孩子心中的路子上,摆脱了一切的束厄局促;……在那儿,理智以它的法令造为纸鸢而飞放,真谛也使事实从枷锁中了。
给别人和保护本身的,两者同样是高贵的事业此法度文章献给刚进公司的须要帮助的法度员,
申明: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__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)
{
_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__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_(
boost::asio::buffer(data_, max_length), sender_endpoint_,
boost::bind(&CodeReceive2::handle_receive__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
","setrtpframe 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刷屏,并且参加音频的解码,属于第二篇。
未完待续。。。。。。
泰戈尔
《更多网站建设信息、网站开发资讯,敬请咨询百微信息科技,021-57700304》