最近需要做一个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完整的代码:

头文件:

  1. /********************************************************************
  2. filename:   CRTSPPlayer.h
  3. created:    2013-03-25
  4. author:     firehood
  5. purpose:    ffmpeg库实现的RTSP视频播放器
  6. *********************************************************************/
  7. #pragma once
  8. #include "windows.h"
  9. extern "C"
  10. {
  11. #include "libavformat\avformat.h"
  12. #include "libavcodec\avcodec.h"
  13. #include "libswscale\swscale.h"
  14. };
  15. // 播放状态
  16. enum RTSP_PLAYSTATUS
  17. {
  18. RTSP_PLAYSTATUS_NONE,       // 未知状态(未播放)
  19. RTSP_PLAYSTATUS_PLAYING,    // 正在播放
  20. RTSP_PLAYSTATUS_PAUSE,      // 已暂停
  21. RTSP_PLAYSTATUS_STOP,       // 已停止
  22. };
  23. class CRTSPPlayer
  24. {
  25. public:
  26. CRTSPPlayer(HWND hWnd, LPRECT lpRect);
  27. ~CRTSPPlayer(void);
  28. public:
  29. // 打开媒体文件
  30. BOOL OpenMedia(LPCTSTR pFileName);
  31. // 播放
  32. void Play();
  33. // 暂停
  34. void Pause();
  35. // 停止
  36. void Stop();
  37. // 获取播放状态
  38. RTSP_PLAYSTATUS GetPlayStatus(void);
  39. private:
  40. // 解码初始化
  41. int DecodeInit(LPCTSTR pFileName);
  42. // 卸载
  43. void DecodeUninit();
  44. // 开始解码线程
  45. BOOL StartDecodeThread();
  46. // 停止解码线程
  47. void StopDecodeThread();
  48. // 解码线程
  49. static int WINAPI ThreadDecodeVideo(LPVOID lpParam);
  50. // 开始解码任务
  51. int BeginDecode();
  52. // 显示
  53. void Display();
  54. // 图像转换
  55. int ImgConvert(AVPicture * dst, PixelFormat dstFormt, const AVPicture * src, PixelFormat srcFormt, int src_width, int src_height);
  56. // 设置播放状态
  57. void SetPlayStatus(RTSP_PLAYSTATUS playStatus);
  58. private:
  59. HANDLE  m_hDecodeThread;
  60. BOOL    m_bExitDecodeThread;
  61. TCHAR   m_strFilePath[MAX_PATH];
  62. AVFormatContext* m_pFormatContext;
  63. AVCodecContext*  m_pCodecContext;
  64. AVCodec* m_pCodec;
  65. AVPacket m_struPacket;
  66. int m_nStreamIndex;
  67. AVFrame* m_pFrameYUV;
  68. AVFrame* m_pFrameRGB;
  69. int     m_nFrameWidth;
  70. int     m_nFrameHeight;
  71. BYTE*   m_pBufRGB;        // 解码后的RGB数据
  72. RTSP_PLAYSTATUS  m_nPlayStatus;
  73. HWND    m_hWnd;
  74. RECT    m_rcWnd;
  75. };

源文件:

  1. /********************************************************************
  2. filename:   CRTSPPlayer.cpp
  3. created:    2013-03-25
  4. author:     firehood
  5. purpose:    ffmpeg库实现的RTSP视频播放器
  6. *********************************************************************/
  7. #include "StdAfx.h"
  8. #include "RTSPPlayer.h"
  9. #pragma comment(lib, "avformat.lib")
  10. #pragma comment(lib, "avcodec.lib")
  11. #pragma comment(lib, "swscale.lib")
  12. #pragma comment(lib, "avutil.lib")
  13. #define SHOW_TITLE
  14. const char* WcharToUtf8(const wchar_t *pwStr)
  15. {
  16. if (pwStr == NULL)
  17. {
  18. return NULL;
  19. }
  20. int len = WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, NULL, 0, NULL, NULL);
  21. if (len <= 0)
  22. {
  23. return NULL;
  24. }
  25. char *pStr = new char[len];
  26. WideCharToMultiByte(CP_UTF8, 0, pwStr, -1, pStr, len, NULL, NULL);
  27. return pStr;
  28. }
  29. CRTSPPlayer::CRTSPPlayer(HWND hWnd, LPRECT lpRect):
  30. m_hWnd(hWnd),
  31. m_rcWnd(*lpRect),
  32. m_hDecodeThread(NULL),
  33. m_bExitDecodeThread(FALSE),
  34. m_nFrameWidth(0),
  35. m_nFrameHeight(0),
  36. m_pFormatContext(NULL),
  37. m_pCodecContext(NULL),
  38. m_pCodec(NULL),
  39. m_nStreamIndex(-1),
  40. m_pFrameYUV(NULL),
  41. m_pFrameRGB(NULL),
  42. m_pBufRGB(NULL),
  43. m_nPlayStatus(RTSP_PLAYSTATUS_NONE)
  44. {
  45. memset(m_strFilePath,0,sizeof(m_strFilePath));
  46. }
  47. CRTSPPlayer::~CRTSPPlayer(void)
  48. {
  49. DecodeUninit();
  50. }
  51. // 打开媒体文件
  52. BOOL CRTSPPlayer::OpenMedia(LPCTSTR pFileName)
  53. {
  54. if(pFileName == NULL)
  55. return FALSE;
  56. DecodeUninit();
  57. memcpy(m_strFilePath,pFileName,sizeof(m_strFilePath));
  58. DecodeInit(m_strFilePath);
  59. return TRUE;
  60. }
  61. // 播放
  62. void CRTSPPlayer::Play()
  63. {
  64. if(GetPlayStatus() == RTSP_PLAYSTATUS_STOP)
  65. {
  66. DecodeInit(m_strFilePath);
  67. }
  68. BOOL bRet = StartDecodeThread();
  69. if(bRet)
  70. {
  71. SetPlayStatus(RTSP_PLAYSTATUS_PLAYING);
  72. }
  73. }
  74. // 暂停
  75. void CRTSPPlayer::Pause()
  76. {
  77. StopDecodeThread();
  78. SetPlayStatus(RTSP_PLAYSTATUS_PAUSE);
  79. }
  80. // 停止
  81. void CRTSPPlayer::Stop()
  82. {
  83. StopDecodeThread();
  84. DecodeUninit();
  85. SetPlayStatus(RTSP_PLAYSTATUS_STOP);
  86. }
  87. BOOL CRTSPPlayer::StartDecodeThread()
  88. {
  89. if(m_hDecodeThread == NULL)
  90. {
  91. m_hDecodeThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadDecodeVideo, this, 0, NULL);
  92. }
  93. return m_hDecodeThread ? TRUE : FALSE;
  94. }
  95. void CRTSPPlayer::StopDecodeThread()
  96. {
  97. if(m_hDecodeThread)
  98. {
  99. m_bExitDecodeThread = TRUE;
  100. WaitForSingleObject(m_hDecodeThread,INFINITE);
  101. CloseHandle(m_hDecodeThread);
  102. m_hDecodeThread = NULL;
  103. }
  104. }
  105. int CRTSPPlayer::ImgConvert(AVPicture * dst, PixelFormat dst_pix_fmt, const AVPicture * src, PixelFormat src_pix_fmt, int src_width, int src_height)
  106. {
  107. unsigned char * srcSlice[4];
  108. int srcStride[4] = {0};
  109. unsigned char * dstSlice[4];
  110. int dstStride[4] = {0};
  111. for (int i=0; i<4; i++)
  112. {
  113. srcSlice[i] = src->data[i];
  114. srcStride[i] = src->linesize[i];
  115. dstSlice[i] = dst->data[i];
  116. dstStride[i] = dst->linesize[i];
  117. }
  118. SwsContext *pSwsContext = sws_getContext(src_width, src_height, src_pix_fmt, src_width, src_height, dst_pix_fmt, SWS_BICUBIC, NULL, NULL, NULL);
  119. int nRet = sws_scale(pSwsContext, srcSlice, srcStride, 0, src_height, dstSlice, dstStride);
  120. if (pSwsContext != NULL)
  121. {
  122. sws_freeContext(pSwsContext);
  123. }
  124. return nRet;
  125. }
  126. int WINAPI CRTSPPlayer::ThreadDecodeVideo(LPVOID lpParam)
  127. {
  128. CRTSPPlayer *pPlayer = (CRTSPPlayer*)lpParam;
  129. pPlayer->BeginDecode();
  130. return 0;
  131. }
  132. int CRTSPPlayer::DecodeInit(LPCTSTR pFileName)
  133. {
  134. if(pFileName == NULL)
  135. {
  136. return -1;
  137. }
  138. av_register_all();
  139. #ifdef  UNICODE
  140. const char *filePath = WcharToUtf8(pFileName);
  141. // Open video
  142. if (av_open_input_file(&m_pFormatContext, filePath, NULL, 0, NULL) != 0)
  143. {
  144. return -2; // Couldn't open file
  145. }
  146. delete[] filePath;
  147. #else
  148. // Open video
  149. if (av_open_input_file(&m_pFormatContext, pFileName, NULL, 0, NULL) != 0)
  150. {
  151. return -2; // Couldn't open file
  152. }
  153. #endif
  154. // Retrieve stream information
  155. if (av_find_stream_info(m_pFormatContext) < 0)
  156. {
  157. return -3; // Couldn't find stream information
  158. }
  159. // Find the first video stream
  160. for (UINT i=0; i<m_pFormatContext->nb_streams; i++)
  161. {
  162. if (m_pFormatContext->streams[i]->codec->codec_type == CODEC_TYPE_VIDEO)
  163. {
  164. m_nStreamIndex = i;
  165. break;
  166. }
  167. }
  168. if (m_nStreamIndex == -1)
  169. {
  170. return -4; // Didn't find a video stream
  171. }
  172. // Get a pointer to the codec context for the video stream
  173. m_pCodecContext = m_pFormatContext->streams[m_nStreamIndex]->codec;
  174. // Find the decoder for the video stream
  175. m_pCodec = avcodec_find_decoder(m_pCodecContext->codec_id);
  176. if (m_pCodec == NULL)
  177. {
  178. return -5 ; // Codec not found
  179. }
  180. // Inform the codec that we can handle truncated bitstreams -- i.e.,
  181. // bitstreams where frame boundaries can fall in the middle of packets
  182. if (m_pCodec->capabilities & CODEC_CAP_TRUNCATED)
  183. {
  184. m_pCodecContext->flags |= CODEC_FLAG_TRUNCATED;  // we do not send complete frames
  185. }
  186. // Open codec
  187. if (avcodec_open(m_pCodecContext, m_pCodec) < 0)
  188. {
  189. return -6; // Could not open codec
  190. }
  191. // Allocate video frame
  192. m_pFrameYUV = avcodec_alloc_frame();
  193. // Allocate an AVFrame structure
  194. m_pFrameRGB = avcodec_alloc_frame();
  195. // Determine required buffer size and allocate buffer
  196. int numBytes = avpicture_get_size(PIX_FMT_BGR24, m_pCodecContext->width, m_pCodecContext->height);
  197. m_pBufRGB = new BYTE [numBytes];
  198. memset(m_pBufRGB,0,numBytes);
  199. // Assign appropriate parts of buffer to image planes in m_pFrameRGB
  200. avpicture_fill((AVPicture *)m_pFrameRGB, m_pBufRGB, PIX_FMT_BGR24, m_pCodecContext->width, m_pCodecContext->height);
  201. m_nFrameWidth  = m_pCodecContext->width;
  202. m_nFrameHeight = m_pCodecContext->height;
  203. return 0;
  204. }
  205. void CRTSPPlayer::DecodeUninit()
  206. {
  207. // Close the codec
  208. if (m_pCodecContext)
  209. {
  210. avcodec_close(m_pCodecContext);
  211. //av_free(m_pCodec);
  212. m_pCodecContext = NULL;
  213. m_pCodec = NULL;
  214. }
  215. // Close the video file
  216. if (m_pFormatContext)
  217. {
  218. av_close_input_file(m_pFormatContext);
  219. m_pFormatContext = NULL;
  220. }
  221. if (m_pFrameYUV)
  222. {
  223. av_free(m_pFrameYUV);
  224. m_pFrameYUV = NULL;
  225. }
  226. if (m_pFrameRGB)
  227. {
  228. av_free(m_pFrameRGB);
  229. m_pFrameRGB = NULL;
  230. }
  231. if (m_pBufRGB)
  232. {
  233. delete [] m_pBufRGB;
  234. m_pBufRGB = NULL;
  235. }
  236. }
  237. int CRTSPPlayer::BeginDecode()
  238. {
  239. int bytesRemaining = 0, bytesDecoded;
  240. BYTE * rawData = NULL;
  241. int frameFinished = 0;
  242. m_struPacket.data = NULL;
  243. m_struPacket.size = 0;
  244. m_bExitDecodeThread = FALSE;
  245. while (!m_bExitDecodeThread && m_pFormatContext)
  246. {
  247. // Read the next packet, skipping all packets that aren't for this stream
  248. do
  249. {
  250. // Read new packet
  251. if (av_read_frame(m_pFormatContext, &m_struPacket) < 0)
  252. {
  253. return -2;
  254. }
  255. } while (m_struPacket.stream_index != m_nStreamIndex);
  256. bytesRemaining = m_struPacket.size;
  257. rawData = m_struPacket.data;
  258. // Work on the current packet until we have decoded all of it
  259. while (bytesRemaining > 0)
  260. {
  261. // Decode the next chunk of data
  262. bytesDecoded = avcodec_decode_video(m_pCodecContext, m_pFrameYUV, &frameFinished, rawData, bytesRemaining);
  263. // Was there an error?
  264. if (bytesDecoded < 0)
  265. {
  266. return -1;
  267. }
  268. bytesRemaining -= bytesDecoded;
  269. rawData += bytesDecoded;
  270. // Did we finish the current frame? Then we can return
  271. if (frameFinished)
  272. {
  273. ImgConvert(
  274. (AVPicture *)m_pFrameRGB,
  275. PIX_FMT_BGR24,
  276. (AVPicture *)m_pFrameYUV,
  277. m_pCodecContext->pix_fmt,
  278. m_pCodecContext->width,
  279. m_pCodecContext->height);
  280. Display();
  281. }
  282. }
  283. }
  284. m_hDecodeThread = NULL;
  285. return 0;
  286. }
  287. void CRTSPPlayer::Display()
  288. {
  289. HDC hdc = GetDC(m_hWnd);
  290. // 创建内存DC
  291. HDC hMemDc = CreateCompatibleDC(hdc);
  292. // 创建位图
  293. BITMAPINFOHEADER bmpHdr = {0};
  294. bmpHdr.biSize = sizeof (BITMAPINFOHEADER);
  295. bmpHdr.biWidth = m_nFrameWidth;
  296. bmpHdr.biHeight = -m_nFrameHeight;
  297. bmpHdr.biPlanes = 1;
  298. bmpHdr.biBitCount = 24;
  299. bmpHdr.biCompression = BI_RGB;
  300. BYTE *pData = NULL;
  301. HBITMAP hBitmap = CreateDIBSection (NULL, (BITMAPINFO *)&bmpHdr, DIB_RGB_COLORS, (void**)&pData, NULL, 0);
  302. try
  303. {
  304. memcpy(pData, m_pBufRGB, m_nFrameWidth * m_nFrameHeight * 3);
  305. }
  306. catch (CMemoryException* e)
  307. {
  308. }
  309. HBITMAP hOldBitmap = (HBITMAP)SelectObject(hMemDc, hBitmap);
  310. #ifdef SHOW_TITLE
  311. // 设置字体参数
  312. LOGFONT logfont;
  313. memset(&logfont, 0, sizeof(LOGFONT));
  314. logfont.lfHeight = 40;
  315. logfont.lfWidth = 0;
  316. logfont.lfEscapement = 0;
  317. logfont.lfOrientation = 0;
  318. logfont.lfWeight = 30;
  319. logfont.lfItalic = 0;
  320. logfont.lfUnderline = 0;
  321. logfont.lfStrikeOut = 0;
  322. logfont.lfCharSet = DEFAULT_CHARSET;
  323. logfont.lfOutPrecision= OUT_DEFAULT_PRECIS;
  324. logfont.lfClipPrecision= OUT_DEFAULT_PRECIS;
  325. logfont.lfQuality = DEFAULT_QUALITY;
  326. logfont.lfPitchAndFamily= DEFAULT_PITCH;
  327. // 创建字体并选入环境
  328. HFONT hFont = CreateFontIndirect(&logfont);
  329. HFONT hOldFont = (HFONT)SelectObject(hMemDc, hFont);
  330. // 设置绘图环境
  331. SetBkMode(hMemDc, TRANSPARENT);
  332. SetTextColor(hMemDc, RGB(255, 255, 0));
  333. // 绘制文字
  334. TextOut(hMemDc,0,0,m_strFilePath,_tcslen(m_strFilePath));
  335. // 恢复环境释放字体
  336. SelectObject(hMemDc, hOldFont);
  337. #endif
  338. StretchBlt(
  339. hdc,
  340. m_rcWnd.left,
  341. m_rcWnd.top,
  342. m_rcWnd.right-m_rcWnd.left,
  343. m_rcWnd.bottom-m_rcWnd.top,
  344. hMemDc,
  345. 0,
  346. 0,
  347. m_nFrameWidth,
  348. m_nFrameHeight,
  349. SRCCOPY);
  350. // 恢复并释放环境
  351. SelectObject(hMemDc,hOldBitmap);
  352. DeleteObject(hBitmap);
  353. DeleteDC(hMemDc);
  354. }
  355. // 获取播放状态
  356. RTSP_PLAYSTATUS CRTSPPlayer::GetPlayStatus(void)
  357. {
  358. return m_nPlayStatus;
  359. }
  360. // 设置播放状态
  361. void CRTSPPlayer::SetPlayStatus(RTSP_PLAYSTATUS playStatus)
  362. {
  363. m_nPlayStatus = playStatus;
  364. }
 
 
 

嵌入式 RTSP流媒体播放器实现的更多相关文章

  1. EasyPlayerPro Windows流媒体播放器(RTSP/RTMP/HTTP/HLS/File/TCP/RTP/UDP都能播)发布啦

    EasyPlayerPro简介 EasyPlayerPro是一款全功能的流媒体播放器,支持RTSP.RTMP.HTTP.HLS.UDP.RTP.File等多种流媒体协议播放.支持本地文件播放,支持本地 ...

  2. RTSP/RTMP/HLS/HTTP流媒体播放器EasyPlayer

    EasyPlayer播放器系列项目 EasyPlayer是由EasyDarwin开源团队开发和维护的一个流媒体播放器系列项目,随着多年不断的发展和迭代,不断基于成功的实践经验,发展出包括有: Easy ...

  3. 基于Live555,ffmpeg的RTSP播放器直播与点播

    基于Live555,ffmpeg的RTSP播放器直播与点播 多路RTSP高清视频播放器下载地址:http://download.csdn.net/detail/u011352914/6604437多路 ...

  4. EasyPlayer RTSP播放器运行出现: Unable to load DLL 找不到指定的模块。exception from HRESULT 0x8007007E 解决方案

    最近有EasyPlayer RTSP播放器的开发者反馈,在一台新装的Windows Server 2008的操作系统上运行EasyPlayer RTSP播放器出现"Unable to loa ...

  5. EasyPlayer RTSP播放器OCX RegSvr32注册报错,DllRegisterServer调用失败,错误代码为0x80040200 解决方法

    问题描述 模块"EasyPlayer-RTSPWebActiveX.ocx" 已加载,但对DLLRegisterServer调用失败,错误代码为0x80040200. 解决方法 是 ...

  6. EasyPlayer windows RTSP播放器OCX插件使用说明

    鉴于大家对于EasyPlayer插件的使用还不太熟悉,特此写一篇插件的使用文档,供大家参考:EasyPlayer插件有两种,一种是基于IE的ActiveX控件,一种是基于FireFox(也支持多浏览器 ...

  7. EasyPlayer RTSP播放器对RTSP播放地址url的通用兼容修改意见

    问题反馈 最近在线上遇到一位老朋友咨询关于EasyPlayer播放器的事情,大概现象就是分别用EasyPlayer和vlc播放大华摄像机的RTSP流,流地址是:rtsp://admin:admin12 ...

  8. EasyPlayerPro(Windows)流媒体播放器开发之框架讲解

    EasyPlayerPro for Windows是基于ffmpeg进行开发的全功能播放器,开发过程中参考了很多开源的播放器,诸如vlc和ffplay等,其中最强大的莫过于vlc,但是鉴于vlc框架过 ...

  9. EasyPlayerPro(Windows)流媒体播放器开发之跨语言调用

    下面我们来讲解一下关于EasyPlayerPro接口的调用,主要分为C++和C#两种语言,C++也可以基于VC和QT进行开发,C++以VC MFC框架为例进行讲解,C#以Winform框架为例进行讲解 ...

随机推荐

  1. ubuntu14.04安装MATLAB R2014a

    1. 首先现在matlab2014a,http://pan.baidu.com/s/1pJGF5ov [Matlab2014a(密码:en52).该文件下载解压后如下所示: 2. 解压解压包(用lin ...

  2. Jquery DataTables warning : Requested unknown from the data source for row 0

    昨天在做 Jquery DataTables 的时候,遇到的一个问题,我使用MVC,在tables上加入了一个actionlink的href.但是在运行起来的时候,报错: DataTables war ...

  3. Android 核心分析 之七Service深入分析

    Service深入分析 上一章我们分析了Android IPC架构,知道了Android服务构建的一些基本理念和原理,本章我们将深入分析Android的服务.Android体系架构中三种意义上服务: ...

  4. 一个简单的ObjC和JavaScript交互工具

    https://github.com/changjianfeishui/XBWebBridge ObjectiveC与Js交互是常见的需求,可对于新手或者所谓的高手而言,其实并不是那么简单明了.这里只 ...

  5. QTreeView使用点点滴滴

    QTreeView比较复杂,在这里记下所有用到的操作: ------------------------------------------------------------------------ ...

  6. Struts2笔记——初次框架配置

    1.Struts2简介   Struts 2是Struts的下一代产品,是在 struts 1和WebWork的技术基础上进行了合并的全新的Struts 2框架.其全新的Struts 2的体系结构与S ...

  7. Linux系统下如何禁止ping命令或允许ping命令的方法

    1.禁止pingecho 1 >/proc/sys/net/ipv4/icmp_echo_ignore_all 2.允许ping echo 0 >/proc/sys/net/ipv4/ic ...

  8. linux中U盘umonut时出现“Device is busy”的解决方法

    问题: #umount /dev/sda1 umount: /mnt/usb: device is busy 查找占用目录进程: #lsof |grep /mnt/usb bash 1971 root ...

  9. java post 请求

    新公司的分词为post调用方式,以前还没用过post,这次上网查了下,比较简单,但还是写篇博客记录下,代码为网上找的,非原创. package com.chuntent.tool; import ja ...

  10. 【Tech】Ganglia安装配置

    基础配置: Hadoop 2.2.0,Hbase 0.96. 四台集群机器,一台master,三台slave. 三台slave上分别装gmond:namenode机器上设置datasource. 客户 ...