視訊會議中或者錄播中使用RTP協議接收h264視訊
此程式文章獻給剛進公司的需要幫助的程式設計師,
說明: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刷屏,並且加入音訊的解碼,屬於第二篇。
未完待續。。。。。。
相關文章
- 5┃音視訊直播系統之 WebRTC 中的協議UDP、TCP、RTP、RTCP詳解Web協議UDPTCP
- 音視訊通訊——直播協議和視訊推流協議
- 私有部署視訊會議伺服器保障銀行視訊會議的資料安全伺服器
- 如何使用 WebRTC 與 Kurento 建立視訊會議 AppWebAPP
- 如何實現在服務端錄製視訊會議?服務端
- 關鍵基礎設施網路安全建議—使用視訊會議
- 雲視訊會議哪家強?華為雲會議更專業“會”更好 !
- 混音器:視訊會議錄製不可或缺的元件元件
- 內網異地進行視訊會議內網
- 視訊會議的實現方式有哪些?
- 視訊會議系統升級改造方案
- 分分鐘讀懂tcp/ip通訊協議原理(含視訊)TCP協議
- 通訊協議協議
- jitsi視訊會議系統準備知識
- 搭建簡易多人線上視訊會議系統
- AR實踐:基於ARKit實現電影中的全息視訊會議
- 開發視訊會議系統:使用GPU解碼渲染影片GPU
- Zookeeper的核心:ZAB原子訊息廣播協議協議
- 訊眾及時會:加速視訊會議行業下沉和生態打造行業
- 開啟虛擬會議時代!聚好看科技入局視訊會議行業行業
- 視訊點播開發者實戰:視訊水印的基本使用
- 視訊會議一般用什麼軟體?
- 企業網路視訊會議室解決方案
- RTP協議的報文結構協議
- 音視訊同步!RTCP 協議解析及程式碼實現TCP協議
- Redis 通訊協議Redis協議
- HTTP通訊協議HTTP協議
- Mysql通訊協議MySql協議
- MQ通訊協議MQ協議
- web通訊協議Web協議
- H323協議和polycom,華為視訊會議終端除錯出現的問題協議除錯
- 網路通訊協議-ICMP協議詳解!協議
- 網路通訊協議-TCP協議詳解!協議TCP
- 網路通訊協議-HTTP協議詳解!協議HTTP
- 網路通訊協議-SMTP協議詳解!協議
- 思科WebEx線上視訊會議軟體曝命令注入漏洞Web
- 全套分享視訊會議系統建設的那些事
- 音訊和視訊流最佳選擇?SRT 協議解析及報文識別音訊協議