最近需要做一個RTSP流媒體播放器,研究了一下,封裝了一個RTSP播放類CRTSPPlayer,解碼庫採用ffmpeg。由於需求比較簡單,時間也有限,目前只實現了播放、停止、暫停幾個基本的介面。下面是基於CRTSPPlayer類實現的簡單RTSP播放器。
目前視頻只測試了H264格式,其它格式的視頻還未做測試。播放器也支援直接開啟本地視頻播放,但播放的幀率和原始視頻的碼率不同步。目前還不清楚如何處理這個問題,希望懂這方面的大俠指教。
另外,還有一個開源的庫VLC也可以用來開發流媒體播放器,它支援多種流媒體協議,如RTP、RTSP等,CodeProject上已經有牛人在VLCLib的基礎上封裝可更易使用的庫VLCWrapper(地址:http://www.codeproject.com/Articles/38952/VLCWrapper-A-Little-C-wrapper-Around-libvlc)。用它可以很方便的開發視頻播放器。
以下是CRTSPPlayer完整的代碼:
標頭檔:
/******************************************************************** filename: CRTSPPlayer.h created: 2013-03-25 author: firehood purpose: ffmpeg庫實現的RTSP視頻播放器*********************************************************************/ #pragma once#include "windows.h"extern "C"{#include "libavformat\avformat.h"#include "libavcodec\avcodec.h"#include "libswscale\swscale.h"};// 播放狀態enum RTSP_PLAYSTATUS{RTSP_PLAYSTATUS_NONE, // 未知狀態(未播放)RTSP_PLAYSTATUS_PLAYING, // 現正播放 RTSP_PLAYSTATUS_PAUSE, // 已暫停RTSP_PLAYSTATUS_STOP, // 已停止};class CRTSPPlayer{public:CRTSPPlayer(HWND hWnd, LPRECT lpRect);~CRTSPPlayer(void);public:// 開啟媒體檔案BOOL OpenMedia(LPCTSTR pFileName);// 播放void Play();// 暫停void Pause();// 停止void Stop();// 擷取播放狀態RTSP_PLAYSTATUS GetPlayStatus(void);private:// 解碼初始化int DecodeInit(LPCTSTR pFileName);// 卸載void DecodeUninit();// 開始解碼線程 BOOL StartDecodeThread(); // 停止解碼線程void StopDecodeThread(); // 解碼線程static int WINAPI ThreadDecodeVideo(LPVOID lpParam); // 開始解碼任務int BeginDecode(); // 顯示void Display(); // 映像轉換int ImgConvert(AVPicture * dst, PixelFormat dstFormt, const AVPicture * src, PixelFormat srcFormt, int src_width, int src_height); // 設定播放狀態 void SetPlayStatus(RTSP_PLAYSTATUS playStatus);private: HANDLE m_hDecodeThread;BOOL m_bExitDecodeThread; TCHAR m_strFilePath[MAX_PATH];AVFormatContext* m_pFormatContext;AVCodecContext* m_pCodecContext;AVCodec* m_pCodec;AVPacket m_struPacket;int m_nStreamIndex;AVFrame* m_pFrameYUV;AVFrame* m_pFrameRGB;int m_nFrameWidth; int m_nFrameHeight;BYTE* m_pBufRGB; // 解碼後的RGB資料RTSP_PLAYSTATUS m_nPlayStatus;HWND m_hWnd;RECT m_rcWnd;};
源檔案:
/******************************************************************** filename: CRTSPPlayer.cpp created: 2013-03-25 author: firehood purpose: ffmpeg庫實現的RTSP視頻播放器*********************************************************************/ #include "StdAfx.h"#include "RTSPPlayer.h"#pragma comment(lib, "avformat.lib")#pragma comment(lib, "avcodec.lib")#pragma comment(lib, "swscale.lib")#pragma comment(lib, "avutil.lib")#define SHOW_TITLEconst char* WcharToUtf8(const wchar_t *pwStr) { if (pwStr == NULL) { return NULL; } int len = WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, NULL, 0, NULL, NULL); if (len <= 0) { return NULL; } char *pStr = new char[len]; WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, pStr, len, NULL, NULL); return pStr; } CRTSPPlayer::CRTSPPlayer(HWND hWnd, LPRECT lpRect):m_hWnd(hWnd),m_rcWnd(*lpRect),m_hDecodeThread(NULL),m_bExitDecodeThread(FALSE),m_nFrameWidth(0),m_nFrameHeight(0),m_pFormatContext(NULL),m_pCodecContext(NULL),m_pCodec(NULL),m_nStreamIndex(-1),m_pFrameYUV(NULL),m_pFrameRGB(NULL),m_pBufRGB(NULL),m_nPlayStatus(RTSP_PLAYSTATUS_NONE){memset(m_strFilePath,0,sizeof(m_strFilePath));}CRTSPPlayer::~CRTSPPlayer(void){DecodeUninit();}// 開啟媒體檔案BOOL CRTSPPlayer::OpenMedia(LPCTSTR pFileName){if(pFileName == NULL)return FALSE;DecodeUninit();memcpy(m_strFilePath,pFileName,sizeof(m_strFilePath));DecodeInit(m_strFilePath);return TRUE;}// 播放void CRTSPPlayer::Play(){ if(GetPlayStatus() == RTSP_PLAYSTATUS_STOP){DecodeInit(m_strFilePath);}BOOL bRet = StartDecodeThread();if(bRet){SetPlayStatus(RTSP_PLAYSTATUS_PLAYING);}}// 暫停void CRTSPPlayer::Pause(){StopDecodeThread();SetPlayStatus(RTSP_PLAYSTATUS_PAUSE);}// 停止void CRTSPPlayer::Stop(){StopDecodeThread();DecodeUninit();SetPlayStatus(RTSP_PLAYSTATUS_STOP);}BOOL CRTSPPlayer::StartDecodeThread(){if(m_hDecodeThread == NULL){m_hDecodeThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadDecodeVideo, this, 0, NULL);}return m_hDecodeThread ? TRUE : FALSE;}void CRTSPPlayer::StopDecodeThread(){if(m_hDecodeThread){m_bExitDecodeThread = TRUE;WaitForSingleObject(m_hDecodeThread,INFINITE);CloseHandle(m_hDecodeThread);m_hDecodeThread = NULL;}}int CRTSPPlayer::ImgConvert(AVPicture * dst, PixelFormat dst_pix_fmt, const AVPicture * src, PixelFormat src_pix_fmt, int src_width, int src_height){unsigned char * srcSlice[4];int srcStride[4] = {0};unsigned char * dstSlice[4];int dstStride[4] = {0};for (int i=0; i<4; i++){srcSlice[i] = src->data[i];srcStride[i] = src->linesize[i];dstSlice[i] = dst->data[i];dstStride[i] = dst->linesize[i];}SwsContext *pSwsContext = sws_getContext(src_width, src_height, src_pix_fmt, src_width, src_height, dst_pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);int nRet = sws_scale(pSwsContext, srcSlice, srcStride, 0, src_height, dstSlice, dstStride);if (pSwsContext != NULL){sws_freeContext(pSwsContext);}return nRet;}int WINAPI CRTSPPlayer::ThreadDecodeVideo(LPVOID lpParam){CRTSPPlayer *pPlayer = (CRTSPPlayer*)lpParam;pPlayer->BeginDecode();return 0;}int CRTSPPlayer::DecodeInit(LPCTSTR pFileName){ if(pFileName == NULL){return -1;}av_register_all();#ifdef UNICODE const char *filePath = WcharToUtf8(pFileName); // Open videoif (av_open_input_file(&m_pFormatContext, filePath, NULL, 0, NULL) != 0){return -2; // Couldn't open file}delete[] filePath;#else// Open videoif (av_open_input_file(&m_pFormatContext, pFileName, NULL, 0, NULL) != 0){return -2; // Couldn't open file}#endif// Retrieve stream informationif (av_find_stream_info(m_pFormatContext) < 0){return -3; // Couldn't find stream information}// Find the first video streamfor (UINT i=0; i<m_pFormatContext->nb_streams; i++){if (m_pFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO){m_nStreamIndex = i;break;}}if (m_nStreamIndex == -1){return -4; // Didn't find a video stream}// Get a pointer to the codec context for the video streamm_pCodecContext = m_pFormatContext->streams[m_nStreamIndex]->codec;// Find the decoder for the video streamm_pCodec = avcodec_find_decoder(m_pCodecContext->codec_id);if (m_pCodec == NULL){return -5 ; // Codec not found}// Inform the codec that we can handle truncated bitstreams -- i.e.,// bitstreams where frame boundaries can fall in the middle of packetsif (m_pCodec->capabilities & CODEC_CAP_TRUNCATED){m_pCodecContext->flags |= CODEC_FLAG_TRUNCATED; // we do not send complete frames}// Open codecif (avcodec_open(m_pCodecContext, m_pCodec) < 0){return -6; // Could not open codec}// Allocate video framem_pFrameYUV = avcodec_alloc_frame();// Allocate an AVFrame structurem_pFrameRGB = avcodec_alloc_frame();// Determine required buffer size and allocate bufferint numBytes = avpicture_get_size(PIX_FMT_BGR24, m_pCodecContext->width, m_pCodecContext->height);m_pBufRGB = new BYTE [numBytes];memset(m_pBufRGB,0,numBytes);// Assign appropriate parts of buffer to image planes in m_pFrameRGBavpicture_fill((AVPicture *)m_pFrameRGB, m_pBufRGB, PIX_FMT_BGR24, m_pCodecContext->width, m_pCodecContext->height);m_nFrameWidth = m_pCodecContext->width;m_nFrameHeight = m_pCodecContext->height;return 0;}void CRTSPPlayer::DecodeUninit(){// Close the codecif (m_pCodecContext){avcodec_close(m_pCodecContext);//av_free(m_pCodec);m_pCodecContext = NULL;m_pCodec = NULL;}// Close the video fileif (m_pFormatContext){av_close_input_file(m_pFormatContext);m_pFormatContext = NULL;}if (m_pFrameYUV){av_free(m_pFrameYUV);m_pFrameYUV = NULL;}if (m_pFrameRGB){av_free(m_pFrameRGB);m_pFrameRGB = NULL;}if (m_pBufRGB){delete [] m_pBufRGB;m_pBufRGB = NULL;}}int CRTSPPlayer::BeginDecode(){int bytesRemaining = 0, bytesDecoded;BYTE * rawData = NULL;int frameFinished = 0;m_struPacket.data = NULL;m_struPacket.size = 0;m_bExitDecodeThread = FALSE;while (!m_bExitDecodeThread && m_pFormatContext){// Read the next packet, skipping all packets that aren't for this streamdo{// Read new packetif (av_read_frame(m_pFormatContext, &m_struPacket) < 0){return -2;}} while (m_struPacket.stream_index != m_nStreamIndex);bytesRemaining = m_struPacket.size;rawData = m_struPacket.data;// Work on the current packet until we have decoded all of itwhile (bytesRemaining > 0){// Decode the next chunk of databytesDecoded = avcodec_decode_video(m_pCodecContext, m_pFrameYUV, &frameFinished, rawData, bytesRemaining);// Was there an error?if (bytesDecoded < 0){return -1;}bytesRemaining -= bytesDecoded;rawData += bytesDecoded;// Did we finish the current frame? Then we can returnif (frameFinished){ImgConvert((AVPicture *)m_pFrameRGB,PIX_FMT_BGR24,(AVPicture *)m_pFrameYUV,m_pCodecContext->pix_fmt,m_pCodecContext->width,m_pCodecContext->height);Display();}}}m_hDecodeThread = NULL;return 0;}void CRTSPPlayer::Display(){HDC hdc = GetDC(m_hWnd);// 建立記憶體DC HDC hMemDc = CreateCompatibleDC(hdc); // 建立位元影像BITMAPINFOHEADER bmpHdr = {0}; bmpHdr.biSize = sizeof (BITMAPINFOHEADER); bmpHdr.biWidth = m_nFrameWidth; bmpHdr.biHeight = -m_nFrameHeight; bmpHdr.biPlanes = 1; bmpHdr.biBitCount = 24; bmpHdr.biCompression = BI_RGB; BYTE *pData = NULL; HBITMAP hBitmap = CreateDIBSection (NULL, (BITMAPINFO *)&bmpHdr, DIB_RGB_COLORS, (void**)&pData, NULL, 0); try{memcpy(pData, m_pBufRGB, m_nFrameWidth * m_nFrameHeight * 3);}catch (CMemoryException* e){}HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDc, hBitmap);#ifdef SHOW_TITLE// 設定字型參數LOGFONT logfont;memset(&logfont, 0, sizeof(LOGFONT));logfont.lfHeight = 40;logfont.lfWidth = 0;logfont.lfEscapement = 0;logfont.lfOrientation = 0;logfont.lfWeight = 30;logfont.lfItalic = 0;logfont.lfUnderline = 0;logfont.lfStrikeOut = 0;logfont.lfCharSet = DEFAULT_CHARSET; logfont.lfOutPrecision= OUT_DEFAULT_PRECIS; logfont.lfClipPrecision= OUT_DEFAULT_PRECIS; logfont.lfQuality = DEFAULT_QUALITY; logfont.lfPitchAndFamily= DEFAULT_PITCH; // 建立字型並選入環境HFONT hFont = CreateFontIndirect(&logfont);HFONT hOldFont = (HFONT)SelectObject(hMemDc, hFont);// 設定繪圖環境SetBkMode(hMemDc, TRANSPARENT); SetTextColor(hMemDc, RGB(255, 255, 0));// 繪製文字TextOut(hMemDc,0,0,m_strFilePath,_tcslen(m_strFilePath));// 恢複環境釋放字型SelectObject(hMemDc, hOldFont);#endifStretchBlt( hdc, m_rcWnd.left, m_rcWnd.top, m_rcWnd.right-m_rcWnd.left, m_rcWnd.bottom-m_rcWnd.top, hMemDc, 0, 0, m_nFrameWidth, m_nFrameHeight, SRCCOPY); // 恢複並釋放環境 SelectObject(hMemDc,hOldBitmap); DeleteObject(hBitmap); DeleteDC(hMemDc); }// 擷取播放狀態RTSP_PLAYSTATUS CRTSPPlayer::GetPlayStatus(void){return m_nPlayStatus;}// 設定播放狀態void CRTSPPlayer::SetPlayStatus(RTSP_PLAYSTATUS playStatus){m_nPlayStatus = playStatus;}