有时候想看电视节目,但播放的时候自己可能在开会在吃饭在厕上等等,经常错过好多电视节目。每个学校都有电视转播的网站,从那里定时下载,到时候观看就啥都不耽误了,关键还能快进哈~开始搞起~
学校的电视直播地址是http://video.ustc.edu.cn/tv.php,比如用vlc看凤凰卫视:
cvlc rtsp://202.38.64.43/tv7打开wireshark截点包看看:
可以看到用的是RTSP协议,RTSP协议提供几个方法如下:
非常简单,和HTTP协议类似,使用554端口。到这里,我还不知道直播的视频流是什么格式的,于是在截的包里接着找,发现DESCRIBE的响应包非常大,用了4个TCP分段才组成这个PDU,把这个PDU数据包取出来如下(太大了,截取重要部分):
AccRTSP/1.0 200 OK Content-Type: application/sdp Vary: Accept X-Playlist-Gen-Id: 2222430 X-Broadcast-Id: 1989898 Content-Length: 4756 Date: Tue, 15 Oct 2013 03:40:28 GMT CSeq: 3 Server: WMServer/9.6.7600.16564 Supported: com.microsoft.wm.srvppair, com.microsoft.wm.sswitch, com.microsoft.wm.eosmsg, com.microsoft.wm.fastcache, com.microsoft.wm.packetpairssrc, com.microsoft.wm.startupprofile Last-Modified: Sat, 30 Dec 1899 00:00:00 GMT Cache-Control: x-wms-stream-type="broadcast", no-cache, no-user-cache, private v=0 o=- 201310150103460649 201310150103460649 IN IP4 127.0.0.1 s=TV Live10 c=IN IP4 0.0.0.0 b=AS:749 a=maxps:8000 t=0 0 a=control:rtsp://202.38.64.43/tv7/ a=etag:{19304D1E-37B9-8A6B-A6DB-2B3566BAFA98} a=range:npt=5.000-5.000 a=type:broadcast a=recvonly a=pgmpu:data:application/x-wms-contentdesc,8,language,31,0,,44,WMS_CONTENT_DESCRIPTION_SERVER_BRANDING_INFO,31,12,WMServer/9.6,6,author,31,7,USTCNIC,5,title,31,9,TV%20Live10,51,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_START_OFFSET,3,4,5000,47,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_DURATION,3,1,0,58,WMS_CONTENT_DESCRIPTION_COPIED_METADATA_FROM_PLAYLIST_FILE,3,1,1,42,WMS_CONTENT_DESCRIPTION_PLAYLIST_ENTRY_URL,31,1,/%0D%0A a=pgmpu:data:application/vnd.ms.wms-hdr.asfv1;base64,MCaydY5mzxGm2QCqAG
从最后一句可以看出,视频流格式就是ASF格式。后面有个base64,然后后面4个tcp包都是这样的字符,我猜是base64转码的头信息,结果证实了我的猜想~把base64后面的字符串解码成二进制文件(用linux自带的base64工具),如下:
这种ASF的视频格式我认为是落后的,所以没必要搞的很明白,能用就好。0x30 0x26就是ASF文件的开始,这个没错,果然是头信息。第二行前8个字节代表视频头的长度,这个地方很重要,我在这儿浪费了好长时间,它的长度不是0x94 0x0a 0x00 ... 0x00,而是翻过来,小端模式来看,和普通的TCP包段有所区别,很坑~所以它的包大小为:0x0a94,即2708字节。通过截取的数据包发现,SETUP发送两次,第一次先请求audio,同时获得sessionID,因为RTSP是有状态的协议,需要一个session来维持;第二次请求video,这时发送的请求包里必须带着sessionID。定义的代码如下:
import socket import time import re values={ 'ip':'202.38.64.43', 'port':554, 'url':'rtsp://202.38.64.43/tv5', 'userAgent':'LibVLC/2.0.8 (LIVE555 Streaming Media v2011.12.23)', } def OPTIONS(url,seq,userAgent): msg='OPTIONS '+url+' RTSP/1.0\r\n' msg+='CSeq:'+str(seq)+'\r\n' msg+='User-Agent:'+userAgent+'\r\n' msg+='\r\n' print(msg) msg=msg.encode('utf8') return msg def DESCRIBE(url,seq,userAgent): msg='DESCRIBE '+url+' RTSP/1.0\r\n' msg+='CSeq:'+str(seq)+'\r\n' msg+='User-Agent:'+userAgent+'\r\n' msg+='Accept:application/sdp\r\n' msg+='\r\n' print(msg) msg=msg.encode('utf8') return msg def SETUP_audio(url,seq,userAgent): msg='SETUP '+url+'/audio'+' RTSP/1.0\r\n' msg+='CSeq:'+str(seq)+'\r\n' msg+='User-Agent:'+userAgent+'\r\n' msg+='Transport:RTP/AVP;unicast;client_port=40816-40817\r\n' msg+='\r\n' print(msg) msg=msg.encode('utf8') return msg def SETUP_video(url,seq,userAgent,sessionID): msg='SETUP '+url+'/video'+' RTSP/1.0\r\n' msg+='CSeq:'+str(seq)+'\r\n' msg+='User-Agent:'+userAgent+'\r\n' msg+='Transport:RTP/AVP/TCP;unicast;interleaved=0-1\r\n' msg+='Session:'+sessionID+'\r\n' msg+='\r\n' print(msg) msg=msg.encode('utf8') return msg def PLAY(url,seq,userAgent,sessionID): msg='PLAY '+url+' RTSP/1.0\r\n' msg+='CSeq:'+str(seq)+'\r\n' msg+='User-Agent:'+userAgent+'\r\n' msg+='Session:'+sessionID+'\r\n' msg+='\r\n' print(msg) msg=msg.encode('utf8') return msg
函数的使用如下所示:
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) s.connect((values['ip'],values['port'])) seq=1 print('-------------OPTIONS----------------') s.send(OPTIONS(values['url'],seq,values['userAgent'])) seq+=1 data=s.recv(1024*10) message=data.decode('utf8') print(message) print(len(message)) print('-------------DESCRIBE----------------') s.send(DESCRIBE(values['url'],seq,values['userAgent'])) seq+=1 data=s.recv(1024*10) length=len(data) print(length) message=data.decode('utf8') while True: data=s.recv(1024*10) tmp=len(data) message+=data.decode('utf8') if tmp<length: break print(message) print(len(message)) print('-------------SETUP_audio----------------') s.send(SETUP_audio(values['url'],seq,values['userAgent'])) seq+=1 data=s.recv(1024*10) message=data.decode('utf8') print(message) print(len(message)) rule=re.compile('Session:(.*);') sessionID=rule.findall(message) if len(sessionID)!=0: sessionID=sessionID[0] print('-------------SETUP_video----------------') s.send(SETUP_video(values['url'],seq,values['userAgent'],sessionID)) seq+=1 data=s.recv(1024*10) message=data.decode('utf8') print(message) print(len(message)) print('-------------PLAY----------------') s.send(PLAY(values['url'],seq,values['userAgent'],sessionID)) seq+=1 data=s.recv(1024*10) message=data.decode('utf8') print(message) print(len(message)) f=open('video.asf','wb') while True: data=s.recv(1024*10) print(len(data)) f.write(data)
这个video.asf文件还是半成品,还需要把DESCRIBE的base64字符串解码成ASF文件头,拼接起来,这个很容易,关键这里面还有一个小问题,因为并不是把视频流直接放到每个TCP包中,而是每个TCP包还有一个可变长的包头,把它们去掉就是完整的视频,但是在我按位找每个位代表的意义的时候,我在bbs上的提问有了回答,感谢linux版主SoRSs的帮助,提供了rtspdump,用这个直接就可以搞起,那此时我的工作就显得毫无意义了哈~用法如下:
php rtspdump.php -r rtsp://202.38.64.43/tv5 -a tcp -o tv.asf
现在的码农大多数都不是从0开始闭门造车,各种代码资源git clone一下如探囊取物,很多自己想的东西都是有大神们之前做过了,何不直接拿来用呢?如果真想研究细致一点可以再细究,如果不想研究,比如我对这个ASF格式就不太喜欢,内心总感觉是注定要淘汰的东西,所以我也只是为了下载视频而做这件事儿,但有现成的程序(虽然是php的),我也就没有再做下去的理由了,花时间在更感兴趣的事情上罢~哈哈,这是我的小感悟,以后一定先搞好调研再搞起!最后,再感谢下linux版主~
转载请注明:转自http://blog.csdn.net/littlethunder/article/details/12875339