基于RTSP的流媒体播放器制作
Posted 雨尘无痕
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于RTSP的流媒体播放器制作相关的知识,希望对你有一定的参考价值。
Section I Problem Specification
实验目的
本次实验室在MFC环境下使用socket制作的应用程序,实现对RTSP与RTP协议的解析并播放缓存的媒体流。实现一边下载一边播放的音乐播放器。客户端使用RTSP协议与LIVE555服务器进行通信,如果与服务器的通信无误就启动RTP线程开始缓存文件并进行播放。本次程序设计还包括一些其他功能:
1使用MFC进行界面的设计
2使用RTP将收到文件下载到本地,进行播放
3 播放控制,包括暂停,播放,终止,快进,快退,拖动播放时间播放,音量控制,播放时间条随播放时间变化。
实验背景
1 RTSP协议
RTSP(Real Time Streaming Protocol),实时流协议,是一种流媒体
控制协议。。RTSP是一个多媒体播放控制协议,默认使用554或8554端口,用来使用户在播放从因特网下载的实时数据时能够进行控制,即控制实时数据的传输。RTSP 仅仅是使媒体播放器能控制多媒体流的传送。也就是说,RTSP只用于控制媒体流的传输。尽管有时可以把RTSP控制信息和媒体数据流交织在一起传送,但一般情况RTSP本身并不用于转送媒体流数据。媒体数据的传送可通过RTP/RTCP等协议来完成。
本实验中RTSP基于Socket连接,在建立Socket连接后,发送RTSP命令来
获取所请求的文件。RTP协议用来接收传来的文件数据。
RTSP协议有两种,一种是请求消息(request),一是回应消息(response),两种消息的格式不同。
请求格式 响应格式
其中URL是请求的地址,有两种:
一般是rtsp://192.168.0.1:(你的服务器的地址)/视频流的名字
如果地址有域名类似格式rtsp://computing.cuc.edu.cn/Angel.mp3
首部行用来加入交互命令,如下是简易的交互过程。
交互详细信息:
OPTION, 目的是得到服务器提供的可用方法
请求:
OPTIONSrtsp://computing.cuc.edu.cn/Angel.mp3 RTSP/1.0
CSeq: 1 //每个消息都有序号来标记,第一个包通常是option请求消息
User-Agent: VLC media player (LIVE555 Streaming Mediav2016.01.05)
响应:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 1 //每个回应消息的cseq数值和请求消息的cseq相对应
Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE,SCALE,
GET_PARAMETER //服务器提供的可用的方法
DESCRIBE,为了得到会话描述信息(SDP)
请求:
DESCRIBE rtsp://computing.cuc.edu.cn/Angel.mp3RTSP/1.0
CSeq: 2
Accept: application/sdp
User-Agent: VLCmedia player (LIVE555 Streaming Media v2016.01.05)
响应:
RTSP/1.0 200 OK
Cseq: 2
Content-Length: 367
Content-Type: application/sdp
//以下都是sdp信息
a=range:npt=0-
SETUP,客户端提醒服务器建立会话,并确定传输模式
请求:
SETUP rtsp://computing.cuc.edu.cn/Angel.mp3/trackID=0RTSP/1.0
CSeq: 3
Transport:RTP/AVP/TCP;unicast;interleaved=0-1
User-Agent: VLCmedia player (LIVE555 Streaming Media v2016.01.05)
响应:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 3
Session: 6310936469860791894 //服务器回应的会话标识符
Cache-Control: no-cache
Transport: RTP/AVP/TCP;unicast;interleaved=0-1;ssrc=6B8B4567
PLAY,客户端发送播放请求
请求:
PLAYrtsp://computing.cuc.edu.cn/Angel.mp3 RTSP/1.0
CSeq: 4
Session: 6310936469860791894
Range: npt=0.000- //设置播放时间的范围
User-Agent: VLC mediaplayer (LIVE555 Streaming Media v2016.01.05)
响应:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 4
Session: 6310936469860791894
Range: npt=0.000000-
RTP-Info: url=trackID=0;seq=17040;rtptime=1467265309
TEARDOWN,客户端发起关闭请求
请求:
TEARDOWN rtsp://computing.cuc.edu.cn/Angel.mp3RTSP/1.0
CSeq: 5
Session: 6310936469860791894
User-Agent: VLCmedia player (LIVE555 Streaming Media v2016.01.05)
响应:
RTSP/1.0 200 OK
Server: UServer 0.9.7_rc1
Cseq: 5
Session: 6310936469860791894
Connection: Close
本实验中,RTP使用了jrtplib插件来接收数据。JRTPLIB 是一个用C++语言实现的RTP库,包括UDP通讯
2 VLC
VLC多媒体播放器(最初命名为VideoLAN客户端)是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影音光盘及各类流式协议。它也能作为unicast或 multicast的流式服务器在IPv4或 IPv6的高速网络连接下使用。它融合了FFmpeg计划的解码器与libdvdcss程序库使其有播放多媒体文件及加密DVD影碟的功能。
本实验中通过VLC的函数来播放音乐,并控制音乐的一些基本操作。
vlc一些基本方法:
libvlc_new():创建libvlc_instance_t。
libvlc_media_new_path()://通过文件路径创建一个媒体实例
libvlc_media_player_new_from_media()://创建VLC媒体播放器
libvlc_media_player_release():释放libvlc_media_player_t
libvlc_media_release():释放libvlc_media_t。
libvlc_release():释放libvlc_instance_t
libvlc_media_player_play():播放。
libvlc_media_player_pause():暂停。
libvlc_media_player_stop():停止。
Section II Solution Method and Design
本项目用到的开发平台和程序库主要有:
l Operating System: Microsoft Windows
l Programming Language: C++
l Development Platform: Microsoft Visual Studio 2010
winsock2
本次实验主要流程:
建立Socket连接->启动RTSP线程->启动RTP线程->启动播放线程->释放资源
建立Socket:
启动RTSP线程:
p->rtsp = RTSP(p->rtspURI.getURI(),&socket); // 创建RTSP报文类实例
//向服务器发送rtsp请求
p->rtsp.sendOption();
p->rtsp.recvOption();
p->rtsp.sendDescribe();
p->rtsp.recvDescribe();
p->rtsp.sendSetup(RTPPORT);
p->rtsp.recvSetup();
p->rtsp.sendPlay();
p->rtsp.recvPlay();
//开启RTP线程
Sleep(1000);
AfxBeginThread(RtpThread,p, THREAD_PRIORITY_NORMAL, 0, 0, NULL);
//RTSP不响应会断开
while (true)
{
Sleep(10000); // 每隔10秒钟,对线路进行一次保活操作
if(p->download) // 如果播放器还在下载
{
p->rtsp.sendParameter(); // 保活线路
p->rtsp.recvParameter();
}
else
{
p->MessageBox("文件传输完成!");
break;
}
}
RTP线程:
int length = socket.RecvUdp(data, sizeof(data)); // 利用UDP协议接收传输的数据
启动播放线程:
vlc =libvlc_new(vlc_argc, vlc_args);
media =libvlc_media_new_path(vlc, pMediaPathName); //通过文件路径创建一个媒体实例
player =libvlc_media_player_new_from_media(media); //创建VLC媒体播放器
libvlc_media_parse(media); //解析媒体实例
libvlc_media_player_set_media(player,media);
libvlc_media_player_play(player);
程序类图:
BlockSocket控制Socket连接方法
RTSP发送rtsp消息
URI用来解析输入的url地址,检测文件名正确性,获取文件名,端口
VlcCtrl控制播放媒体实例
CRTSPClientMFC控制主线程
Section III Test Cases and Results Analysis
两个版本,一个Win32控制台程序,一个MFC程序
Win32控制台程序:
1. 启动程序输入连接的地址,输错会打印错误信息,正确则利用socket获取主机地址,并打印出来,
然后自动开启RTSP线程和RTP线程,等待几秒后,开始播放
播放过程中,按程序提醒可以测试音量功能,播放暂停功能,快进快退功能。
MFC程序:
1. 程序主界面
程序开始,没有下载直接点播放按钮,会弹出如下错误
输入地址错误,则弹出格式错误。
停止键从头来播放,时间滚动条随播放时间增加,开始音量没有设置为0,拖动音量才有效果
|
Section IV Conclusion
问题:
Win32控制台中:
1、 使用strcmp("rtsp://",this->uri.substr(0,7).c_str())作比较时,开始没有加上c_str()则报错,原因是基本的string类不能和char*比较,而c_str()作用是c_str() 以 char* 形式传回 string内含字符串。
2、 字符串相加不能存在int型,需要转换,string option = "OPTIONS" + uri.getURI() +" RTSP/1.0\r\nCSeq: " + CSeq + "\r\n" + userAgent+"\r\n";
使用itoa函数char *itoa( int value, char *string,intradix); value:欲转换的数据。 string:目标字符串的地址。radix:转换后的进制数,可以是10进制、16进制等。atoi是把字符串转换成int型。
3、 开始写RTSP请求时,末尾只写了一个“\r\n”,结果收不到包,解决办法应该是写”\r\n\r\n”。
4、 环境配置出错,运行后出现mainlibvlc error:no plugins found !check your vlc installation错误,解决办法把libvlc.dll, libvlccore.lib和plugins文件放在DEBUG目录下。
5、 播放音乐时不出声音,由于RTP还没有获取传输文件,就开始播放,解决办法是在开启播放线程时先等几秒,然后再播放。
MFC中问题:
1、 上面的第五个问题,在MFC中Sleep几秒钟会延迟,主要表示是下面的列表信息等了几秒才显示,然后发现不用Sleep也能正常播放,说明在MFC中线程的处理比win32快。
2、 由于使用线程的时候没有用信号量控制,导致在写播放线程后,一些例如快进,播放等操作会比线程执行的更快,产生内存泄露,用了一个变量控制,写的不是很好。
3、 调节音量的地方有两个问题,一个是静态文本控件如何改变数值,一个就是使用滑动条拖动时libvlc_audio_set_volume(player, volume)报错,询问了一些人遇到了这个问题,可能是因为程序开始会执行这个函数,所以出错,加变量控制把第一次执行跳过就好了。
未解决问题:
由于时间和能力有限,程序还有一些未解决的问题。
播放时间拖动条的功能只实现了随播放时间变化,拖动到指定地方进行播放功能没有实现。
RTP在传数据时有时会有错误,出现杂音,Win32版本中出现的error就是这个原因
下载速度如果没有播放速度快,播放就会停止,没有继续缓存
写线程没有用事件控制,后期调试时,使用了很多变量控制,对程序的可读性和封装造成了一定影响。
Section VI Appendix
下面列出了实现系统功能的关键代码段。
1. URI地址分析
/*一般是rtsp://192.168.0.1:(你的服务器的地址)/视频流的名字
如果地址有域名类似格式rtsp://computing.cuc.edu.cn/Angel.mp3
采用默认的端口
*/
2. //判断地址是否正确
3. bool URI::uriIsCorrect()
4. {
5. //先检查头部
6. if(strcmp("rtsp://",uri.substr(0,7).c_str())!=0){
7. return false;
8. }
9. else
10. {
11. if(strcmp("",this->getFilename().c_str())!=0)
12. {
13. return true;
14. }
15. else
16. return false;
17. }
18. }
19.
20. //获取主机地址
21. string URI::getHostname()
22. {
23. int startPos = strlen("rstp://");
24. int tempPos = uri.find(":",startPos);
25. if(tempPos == string::npos)
26. {
27. tempPos= uri.find("/",startPos);
28. }
29. stringm_hostname = uri.substr(startPos,tempPos-startPos);
30. if (isalpha(m_hostname[0]))
31. {
32. //使用gethostname获取主机名
33. WSADATAwsaData;
34. if (WSAStartup(MAKEWORD(2,2), &wsaData)!= 0)
35. {
36. cout<< "WSAStartup failed"<< endl;
37. return NULL;
38. }
39. hostent*remoteHost = gethostbyname(m_hostname.c_str());
40. if (remoteHost != NULL)
41. {
42. int i = 0;
43. in_addraddr;
44. while (remoteHost->h_addr_list[i] != 0)
45. {
46. addr.S_un.S_addr= *(u_long *)remoteHost->h_addr_list[i++];
47. return inet_ntoa(addr);
48. }
49. }
50. WSACleanup();
51. return NULL;
52. }
53. return m_hostname;
54. }
55.
56. //获取端口号
57. string URI::getPort()
58. {
59. int startPos = strlen("rtsp://");
60. int tempPos = uri.find(":",startPos);
61. if(tempPos == string::npos)
62. {
63. return "554";
64. }
65. else
66. {
67. int endPos = uri.find("/",startPos);
68. return uri.substr(tempPos,endPos-tempPos);
69. }
70. }
71.
72. //获取文件名
73. string URI::getFilename()
74. {
75. int startPos=strlen("rtsp://");
76. int tempPos = uri.find("/",startPos);
77.
78. if(tempPos == string::npos) //如果没有找到
79. {
80. return "";
81. }
82. else //返回文件名
83. {
84. int endPos = uri.length()-1;
85. return uri.substr(tempPos+1,endPos-tempPos);
86. }
87. }
88.RTSP线程
89. RTSP::RTSP(string uri,BlockSocket *blockSocket)
90. {
91. this->uri = URI(uri);
92. this->blockSocket = blockSocket;
93. CSeq = 0;
94. userAgent ="User-Agent: VLC media player (LIVE555Streaming Media v2016.01.05)";
95. m_totalTime= 0;
96.
97. }
98.
99. int RTSP::getTotalTime()
100. {
101. return this->m_totalTime;
102. }
103.
104.
105. int RTSP::sendOption() //OPTION请求
106. {
107. CSeq++;
108. char c[5];
109. itoa(CSeq,c,10);
110. stringoption = "OPTIONS " + uri.getURI()+" RTSP/1.0\r\nCSeq: " + c + "\r\n" + userAgent + "\r\n\r\n";
111. if(blockSocket->Send((char*)option.c_str(),option.length()))
112. {
113. cout<<"OPTIONS发送失败!"<<endl;
114. return 1;
115. }
116. return 0;
117. }
118. char *RTSP::recvOption()
119. {
120. memset(data,‘\0‘,sizeof(data));
121. if(blockSocket->Recv(data,sizeof(data)))
122. {
123. cout<<"OPSTIONS接受失败!"<<endl;
124. return "";
125. }
126. return data;
127. }
128. int RTSP::sendDescribe() //DESCRIBE请求
129. {
130. CSeq++;
131. char c[5];
132. itoa(CSeq,c,10);
133. stringdescribe = "DESCRIBE " +uri.getURI() +" RTSP/1.0\r\nCSeq: "+ c + "\r\nAccept: application/sdp\r\n"+ userAgent + "\r\n\r\n";
134. if(blockSocket->Send((char*)describe.c_str(),describe.length()))
135. {
136. cout<<"DESCRIBE发送失败!"<<endl;
137. return 1;
138. }
139.
140. return 0;
141. }
142. char *RTSP::recvDescribe()
143. {
144. memset(data,‘\0‘,sizeof(data));
145. if(blockSocket->Recv(data,sizeof(data)))
146. {
147. cout<<"DESCRIBE接受失败!"<<endl;
148. return "";
149. }
150. //发现文件时间长度
151. stringtimes(data);
152. int startPos = times.find("a=range:npt=0-")+14;
153. int endPos = times.find("\r\n",startPos);
154. stringm_time = times.substr(startPos,endPos-startPos);
155. this->m_totalTime = atoi(m_time.c_str())*1000;
156. //cout<<m_time<<endl;
157. return data;
158. }
159. int RTSP::sendSetup(char*port) //SETUP请求
160. {
161. CSeq++;
162. char c[5];
163. itoa(CSeq,c,10);
164. int port1 = atoi(port)+1; //指定RTP端口号
165. char mport[6];
166. itoa(port1,mport, 10);
167. stringsetup = "SETUP " + uri.getURI() +" RTSP/1.0\r\nCSeq: " + c + "\r\n" + userAgent + "\r\nTransport: RTP/AVP;unicast;client_port="+ port +"-"+ mport +"\r\n\r\n";
168. if(blockSocket->Send((char*)setup.c_str(),setup.length()))
169. {
170. cout<<"SETUP发送失败!"<<endl;
171. return 1;
172. }
173. return 0;
174. }
175. char *RTSP::recvSetup()
176. {
177. memset(data,‘\0‘,sizeof(data));
178. if(blockSocket->Recv(data,sizeof(data)))
179. {
180. cout<<"SETUP接受失败!"<<endl;
181. return "";
182. }
183. stringsetupData(data);
184. int startPos = setupData.find("Session: ")+9; //还有一个空格
185. int temp=0;
186. while(data[startPos+temp]!=‘‘&& data[startPos+temp]!=‘\r‘)
187. {
188. temp++;
189. }
190. char m_session[100];
191. memset(m_session,‘\0‘, 100);
192. memcpy(m_session,data+startPos, temp);
193. this->session = m_session;
194. return data;
195. }
196. int RTSP::sendPlay() //PLAY请求
197. {
198. CSeq++;
199. char c[5];
200. itoa(CSeq,c,10);
201. stringplay = "PLAY " + uri.getURI() +" RTSP/1.0\r\nCSeq: " + c +"\r\n"+ userAgent+"\r\nSession: "+ this->session +"\r\nRange:npt=0.000-\r\n" +"\r\n\r\n";
202. if(blockSocket->Send((char*)play.c_str(),play.length()))
203. {
204. cout<<"PLAY发送失败!"<<endl;
205. return 1;
206. }
207. return 0;
208. }
209. char *RTSP::recvPlay()
210. {
211. memset(data,‘\0‘,sizeof(data));
212. if(blockSocket->Recv(data,sizeof(data)))
213. {
214. cout<<"PLAY接受失败!"<<endl;
215. return "";
216. }
217. return data;
218. }
219.
220. int RTSP::sendPause()
221. {
222. CSeq++;
223. char c[5];
224. itoa(CSeq,c,10);
225. string pause= "PAUSE " + uri.getURI() +" RTSP/1.0\r\nCSeq: " + c + "\r\nSession: "+ this->session +"\r\n"+userAgent +"\r\n\r\n";
226. if(blockSocket->Send((char*)pause.c_str(),pause.length()))
227. {
228. cout<<"PAUSE发送失败!"<<endl;
229. return 1;
230. }
231. return 0;
232. }
233. char *RTSP::recvPause()
234. {
235. memset(data,‘\0‘,sizeof(data));
236. if(blockSocket->Recv(data,sizeof(data)))
237. {
238. cout<<"PAUSE接受失败!"<<endl;
239. return "";
240. }
241. return data;
242. }
243. int RTSP::sendTeardown()
244. {
245. CSeq++;
246. char c[5];
247. itoa(CSeq,c,10);
248. stringteardown = "TEARDOWN " + uri.getURI()+" RTSP/1.0\r\nCSeq: " + c + "\r\nSession: "+ this->session +"\r\n"+userAgent +"\r\n\r\n";
249. if(blockSocket->Send((char*)teardown.c_str(),teardown.length()))
250. {
251. cout<<"TEARDOWN发送失败!"<<endl;
252. return 1;
253. }
254. return 0;
255. }
256. char *RTSP::recvTeardown()
257. {
258. memset(data,‘\0‘,sizeof(data));
259. if(blockSocket->Recv(data,sizeof(data)))
260. {
261. cout<<"TEARDOWN接受失败!"<<endl;
262. return "";
263. }
264. return data;
265. }
266. int RTSP::sendParameter()
267. {
268. CSeq++;
269. char c[5];
270. itoa(CSeq,c,10);
271. stringparameter = "GET_PARAMETER " + uri.getURI()+" RTSP/1.0\r\nCSeq: " + c + "\r\nSession: "+ this->session +"\r\n"+userAgent +"\r\n\r\n";
272. if(blockSocket->Send((char*)parameter.c_str(),parameter.length()))
273. {
274. cout<<"GET_PARAMETER发送失败!"<<endl;
275. return 1;
276. }
277. return 0;
278. }
279. char *RTSP::recvParameter()
280. {
281. memset(data,‘\0‘,sizeof(data));
282. if(blockSocket->Recv(data,sizeof(data)))
283. {
284. cout<<"GET_PARAMETER接受失败!"<<endl;
285. return "";
286. }
287. return data;
288. }
RTSP控制代码
UINTCRTSPClientMFCDlg::RtspThread(PVOID lpParam)
{
CRTSPClientMFCDlg *p = (CRTSPClientMFCDlg*)lpParam;
BlockSocket socket = BlockSocket();
if(socket.Initialize())
{
p->MessageBox("SOCKET打开失败!");
return0;
}
if(socket.HintsAndResult(p->rtspURI.getHostname().c_str(),p->rtspURI.getPort().c_str()))
{
p->MessageBox("获取地址失败!");
return0;
}
if(socket.Open())
{
p->MessageBox("SCOKET创建失败!");
return0;
}
if(socket.Connect())
{
p->MessageBox("SOCKET连接失败!");
return0;
}
p->rtsp = RTSP(p->rtspURI.getURI(),&socket); //创建RTSP报文实例
//向服务器发送RTSP请求
p->rtsp.sendOption();
p->rtsp.recvOption();
p->rtsp.sendDescribe();
p->rtsp.recvDescribe();
p->rtsp.sendSetup(RTPPORT);
p->rtsp.recvSetup();
p->rtsp.sendPlay();
p->rtsp.recvPlay();
p->hasRTSP = true;
//时间赋值
p->totalTime = p->rtsp.getTotalTime();
CString m_time;
m_time.Format("[00:00:00/%02d:%02d:%02d]",p->totalTime/3600000,p->totalTime/60000,(p->totalTime/1000-(p->totalTime/60000*60)));
p->strTime->SetWindowText(m_time);
//开启RTP线程
Sleep(1000);
p->m_listInfo.InsertItem(p->infoCount++,"RTSP请求成功开始传输数据...");
p->m_listInfo.InsertItem(p->infoCount++,"---------------------------------");
AfxBeginThread(RtpThread, p,THREAD_PRIORITY_NORMAL, 0, 0, NULL);
//RTSP不响应会断开
while (true)
{
Sleep(10000); // 每隔10秒激活一次
if(p->download) // 如果播放器还在下载
{
p->rtsp.sendParameter(); // 保活线路
p->rtsp.recvParameter();
}
else
{
p->MessageBox("文件传输完成");
break;
}
}
p->rtsp.sendTeardown();
p->rtsp.recvTeardown();
socket.Close();
socket.Cleanup();
return 0;
}
RTP控制线程:
UINTCRTSPClientMFCDlg::RtpThread(PVOID lpParam) // RTP线程
{
CRTSPClientMFCDlg *p = (CRTSPClientMFCDlg*)lpParam;
BlockSocket socket = BlockSocket();
if(socket.Initialize())
{
p->MessageBox("SOCKET打开连接失败!");
return0;
}
if(socket.BindUdp(RTPPORT))
{
p->MessageBox("绑定UDP失败!");
return0;
}
//传输文件
fstream file; // 创建文件流
file.open(p->rtspURI.getFilename(),ios::out|ios::binary|ios::trunc); // 打开文件流
chardata[MAX_BUFF_SIZE];
memset(data, ‘\0‘,sizeof(data));
// GetLocalTime(×end);
int length =socket.RecvUdp(data,sizeof(data)); // 利用UDP接收传输数据
// cout<<timesend.wHour<<":"<<timesend.wMinute<<":"<<timesend.wSecond<<":"<<timesend.wMilliseconds<<endl;
int sum = 0;
file.write(data+433, length-433); //第一次接收会有多余东西
sum += length - 433;
while (true)
{
memset(data, ‘\0‘,sizeof(data));
length = socket.RecvUdp(data, sizeof(data));
if(length <= 0)
{
p->download = false;
break;
}
else
{
file.write(data+16, length-16);
sum += length - 16;
}
}
return 0;
}
VLC控制代码:
void VlcCtrl::init()
{
int vlc_argc= 0;
char*vlc_args[100];
vlc_args[vlc_argc+
以上是关于基于RTSP的流媒体播放器制作的主要内容,如果未能解决你的问题,请参考以下文章
QT软件开发: 基于FFMPGE设计的流媒体播放器(rtmp/rtsp)