Free streaming from Microsoft Windows Media Server

advertisement
Free streaming from Microsoft Windows Media
Server
rtspdump is a program which downloads multimedia stream (such as live broadcasts) from a Microsoft
WMServer.
Are you stumped by error messages from all open source video playing tools when you try to access a
MMS or RTSP form URL? (See examples of such error messages below.)
You are probably trying to access a Windows Media Server that nowadays only supports RTSP traffic;
the old MMS protocol is discarded.
The catch is, being Microsoft, they have used a RTSP-like implementation, which is modified with
Microsoft's own quirks. So it is not standard form anymore.
Table of contents [expand all] [collapse all]
 Solution
 Download
 List of features
 Tips and other comments
 What is wrong with existing tools?
 Examples (streaming from WMServer)
 Examples (streaming from DSS)
 Source code
 Miscellaneous developer documentation
 See also
Solution
Using packet dumps recorded with WireShark and with Microsoft's adequate specifications, I wrote a
RTSP recording program on PHP.
It can be run on Linux, Windows, and practically any OS where commandline PHP can be run.
Download
You can download it here:
 http://bisqwit.iki.fi/src/rtspdump.zip (Version 2.7, Release date: 2013-06-07)
 License: zlib license. Summarized: as-is, for-any-purpose, do-not-mispresent, compatible-withgpl
 You can also load it from a Git repository: git://bisqwit.iki.fi/rtspdump.git . Be sure to switch to
master branch after cloning.
Packages required to run this program on various Linux distributions: Fedora: php-cli; Debian:
php5-cli
On some distributions, you need to install the socket extension to PHP separately. It might be called
php-socket or something like that. The same may apply to getopt(), you may need php-getopt.
If you use Windows, you need PHP version 5.3.0 or newer.
I wrote this program in PHP for three reasons: quick to implement, I'm familiar with it, and portability. C
socket API is different in POSIX and in Windows, and getopt_long() for commandline parsing is only
available on GNU systems; other means are rather complex.
If someone wants to translate it to Perl, I welcome such submissions, but I hope they don't sacrifice
readability in the process.
List of features
Supported features:









Streaming from Microsoft WMServer (STABLE)
Streaming from DSS (Darwin Streaming Server) (EXPERIMENTAL)
UDP streaming (default)
TCP streaming
RTX: Requesting resending of lost RTP packets (UDP only)
Saving to a file
Feeding another program through stdout
Keep-alives (both UDP and TCP; contrary to MS docs, they are also required in TCP mode)
Timeshifting
Unsupported features:











Streaming from other types of RTSP servers
Streaming using other protocols than RTSP
Listener reports (not required in MS-RTSP)
Pausing
Seeking
Forward error correction (FEC)
Bandwidth estimation
Any authentication schemes
Switching between streams
Recovering from loss of connection / resuming a previous download
Re-encoding / non-ASF containers (please pipe the output to MEncoder/ffmpeg if you need to do
that)
Tips and other comments
Note: You can also use this program to download content from those WMV streams which MPlayer can
already play. E.g. if the URL is mms://something, or even http:// , you can try to pass it to rtspdump and
most likely it will work.
Tip: If you are quickly getting a very high level of packet loss (the Drop: value when you use the -v
option), try increasing the buffer length (for example, -b700), or switch to TCP mode. This sometimes
helps.
Using the source code of this program as referential material for perfecting the streaming support in other
media player tools is explicitly allowed and encouraged. The author would appreciate feedback
(eJn9efebissqgwiu05t@@X835ee7/zikjt6@i.Kae2rfi<!-s='';for(i=16;i<30;){document.getElementById('eml'+i++).innerHTML='';s+=document.getElementById
('eml'+i++).innerHTML;}document.getElementById('eml'+i).innerHTML='<'+'a
href="mailto:'+s+'">'+s+'<'+'/a>';
-->) in such a case, though.
Finally, here's how to interpret the status line given when the -v option is used:
488373.538
^
amount of data
^
to file
^
amount of
^
received
^
^
^
constructed
^
^
^
^
^_ playing
RTX:324->324 Drop:0 RTP:343908 MM:68782 K:3547 | 469.5 MB -> 461.6 MB
^
^
^
^
^
^
^
^_
^
^
^
^
^
^
^
saved
^
^
^
^
^
^
^_ approximate
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^
^_ number of multimedia
^
keyframe packets received
^_ number of multimedia packets
network traffic
^
^
^
^_ number of rtp packets read
^
^
^_ number of lost packets
^_
^_ number of retransmitted packets received
^_ number of retransmitted packets requested
position in seconds (this matches the number you see in MPlayer).
What is wrong with existing tools?
This:
Are these error messages familiar to you (from MPlayer)?
STREAM_ASF, URL: mms://w.glc.us.com/High/
Connecting to server w.glc.us.com[204.250.252.170]: 1755...
connection timeout
No stream found to handle url mms://w.glc.us.com/High/
...
rtsp_session: unsupported RTSP server. Server type is 'WMServer/9.5.6001.18281'.
STREAM_LIVE555, URL: rtsp://w.glc.us.com/High/
Failed to initiate "audio/X-ASF-PF" RTP subsession: RTP payload format unknown or
not supported
Failed to initiate "video/X-ASF-PF" RTP subsession: RTP payload format unknown or
not supported
...
Unknown MPlayer format code for MIME type "audio/X-ASF-PF"
Unknown MPlayer format code for MIME type "video/X-ASF-PF"
Or (from VLC):
main audio output warning: audio drift is too big (147533), dropping buffer
main audio output warning: buffer is 42920 in advance, triggering downsampling
main audio output warning: buffer is 62200 late, triggering upsampling
main audio output warning: input PTS is out of range (8198414), trashing
main audio output warning: output date isn't PTS date, requesting resampling
(75396)
main audio output warning: output PTS is out of range (8510424), clearing out
main audio output warning: PTS is out of range (616922), dropping buffer
main audio output warning: resampling stopped after 987853 usec (drift: 17781)
main audio output warning: timing screwed, stopping resampling
main video output warning: late picture skipped (10474818 > -461)
...
live555 demux warning: no data received in 10s, eof ?
asf demux warning: cannot peek while getting new packet, EOF ?
When I wrote this tool, there was no other free solution that offers as high quality of streaming of MSRTSP as does Windows Media Player for Windows or Quicktime for Mac.
Examples (streaming from WMServer)
Like this.
$ php rtspdump.php -r mms://live.tv7.fi/tv7_live -o - -atcp | cvlc VLC media player 1.0.4 Goldeneye
<... video starts playing ...>
Or:
$ php rtspdump.php -r rtsp://w.glc.us.com/High -o - | mplayer -demuxer asf -vc
ffwmv3 -cache 900 MPlayer SVN-r29800-4.4.2 (C) 2000-2009 MPlayer Team
Playing -.
Reading from stdin...
ASF file format detected.
[asfheader] Audio stream found, -aid 1
[asfheader] Video stream found, -vid 2
VIDEO: [WMV3] 320x240 24bpp 1000.000 fps 500.0 kbps (61.0 kbyte/s)
Clip info:
title: GLC Live Broadcast
copyright: 2009
==========================================================================
Forced video codec: ffwmv3
Opening video decoder: [ffmpeg] FFmpeg's libavcodec codec family
[wmv3 @ 0x87475c0]Extra data: 8 bits left, value: 0
Selected video codec: [ffwmv3] vfm: ffmpeg (FFmpeg WMV3/WMV9)
==========================================================================
==========================================================================
Opening audio decoder: [ffmpeg] FFmpeg/libavcodec audio decoders
AUDIO: 48000 Hz, 2 ch, s16le, 63.0 kbit/4.10% (ratio: 7875->192000)
Selected audio codec: [ffwmav2] afm: ffmpeg (DivX audio v2 (FFmpeg
==========================================================================
AO: [alsa] 48000Hz 2ch s16le (2 bytes per sample)
Starting playback...
Movie-Aspect is undefined - no prescaling applied.
VO: [xv] 320x240 => 320x240 Planar YV12
A:231424.6 V:231424.9 A-V: -0.319 ct: -0.337 102/102 11% 3% 2.9% 0 0
Through the pipe may work, the lack of backward seeking may cause problems in some situations. It is
recommended to do time-shifting, i.e. start saving the stream into a file and play that stream while or
after it is being recorded, like this:
$ php rtspdump.php -r rtsp://w.glc.us.com/Med -o saved.wmv
^Z (hit ctrl-Z to suspend the program)
$ bg
$ mplayer saved.wmv
Live seeking, or pausing, is not supported for now. However, in timeshifted video (stored into a disk file),
seeking is possible within the content that has been downloaded.
Example use in Windows (through the command prompt):
C:\rtspdump>c:\www\php5\php rtspdump.php -r rtsp://w.glc.us.com/Med -o glc2.wmv v
Connecting to 204.250.252.170, 554 for rtsp://w.glc.us.com/Med
Sending Describe
Sending SelectStream audio
Sending SelectStream rtx
Sending SelectStream video
Sending LogConnect
Sending Play
Opening output file, glc2.wmv
Beginning streaming
584356.979 RTX:16 Drop:0 RTP:1322 MM:336
K:25 | 0.5 MB -> 0.5 MB
In Windows though, you may want to change the process priority in order to prevent unnecessary packet
loss when the heavy GUI pre-empts php.
Examples (streaming from DSS)
Example of streaming from DSS (H.264 and AAC):
$ php rtspdump.php -r rtsp://live.net.treet.tv/slcnlive.sdp -v
Connecting to 66.207.140.113, 554 for rtsp://live.net.treet.tv/slcnlive.sdp
Sending Describe
Sending SelectStream trackID=1
Sending SelectStream trackID=2
Sending LogConnect
RTSP protocol warning: Server does not seem to agree with our intents on
LogConnect
Sending Play
Opening output file, dump.audio.raw
Opening output file, dump.video.raw
Beginning streaming
3619505.350 RTX:0->0 Drop:0 RTP:304
MM:399
K:0 | 0.3 MB -> 0.2 MB
dump.audio.raw will contain an AAC stream, and dump.video.raw will contain a H.264 stream.
Here's how to convert the dual-file output into an AVI container:
$ mencoder dump.video.raw -audiofile dump.audio.raw -ac ffaac \
-oac copy -ovc copy -fafmttag 0xa106 -o tmp.avi
Or MP4:
$ mencoder dump.video.raw -audiofile dump.audio.raw -ac ffaac \
-oac copy -ovc copy -fafmttag 0xff -o tmp.mp4 \
-lavfopts format=mp4 -of lavf
DSS "reliable UDP" mechanism is currently disabled, because it is not working properly, meaning that
with DSS, lost packets are never recovered.
Source code
The source code of this program quickly grew outside one-pager limits as I added features, but I'll
include a LITE VERSION here.
You can download the FULL program here.
This LITE VERSION below only does TCP traffic with MS-RTSP.
<?php
/* rtspdump-lite.php -- MS-RTSP streaming program
version 2.1, February 3rd, 2010
Copyright (C) 2010 Joel Yliluoma
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
4. Relicensing under GPL is explicitly allowed.
Joel Yliluoma bisqwit@iki.fi
*/
// Change these as you
define('MY_OS',
define('MY_OSVERSION',
define('USER_AGENT',
like
'GNULinux');
'Fedora 12');
'rtspdump-lite-php v2.1');
$stream
= '';
$output_file = 'dump.wmv';
$run_for = 999999999; // some 31.689 years
$debug
= 0;
$verbose = 0;
// These variables are automatically deduced from server:
$rtsp_timeout
= 60; // Maximum mandatory interval for keep-alives
$asf_header
= ''; // Binary string for asf file header
$maxps
= 1600; // Maximum ASF packet size, all are padded to this
// These variables are used in RTSP traffic, automatically deduced from server:
$session = '';
// Statistics:
$n_mm_packets
= 0;
$n_rtp_packets
= 0;
$n_mm_keyframes = 0;
$n_mm_bytes
= 0;
$n_net_bytes
= 0;
/* Debug bitmasks. If you change these, change the --help page too. */
define('DEBUG_RTSP',
1);
define('DEBUG_RTP_INCOMING',
2);
define('DEBUG_RTP_REORDERING', 4);
define('DEBUG_RTP_AV',
8);
define('DEBUG_MISC',
16);
/********************************
* Parse commandline arguments
*/
$opts = @getopt('hVr:o:d::vt:',
Array('help', 'version', 'rtsp:', 'output:',
'debug::', 'verbose',
'time:'));
$do_help = false;
foreach($opts as $opt => $values)
switch($opt)
{
case 'V': case 'version': print USER_AGENT."\n"; exit;
case 'h': case 'help': $do_help = true; break;
case 'r': case 'rtsp':
if(is_array($values))
{ print "Can only specify one URL\n"; exit; }
$stream = $values;
break;
case 'o': case 'output':
if(is_array($values))
{ print "Can only specify one output file\n"; exit; }
$output_file = $values;
if($values == '-') $output_file = 'php://stdout';
break;
case 't': case 'time':
if(is_array($values))
{ print "Can only specify running time once\n"; exit; }
$run_for = $values;
break;
case 'd': case 'debug':
if($values == false)
$debug = ~0;
elseif(is_array($values))
foreach($values as $v)
$debug |= $v;
else
$debug = $values;
break;
case 'v': case 'verbose':
$verbose += 1;
}
if($output_file == 'php://stdout')
{
// Change the script's output to stderr in order to
// prevent mangling the outputted multimedia stream.
$debug_out = fopen('php://stderr', 'w');
ob_start(create_function('$buf','global $debug_out;fwrite($debug_out,$buf);return
"";'),
32);
}
if($debug & DEBUG_MISC) printf("Commandline options: %s\n", json_encode($opts));
if(!$stream)
{ print "Please specify URL using the -r option\n"; $do_help = true; }
if($do_help)
{
print USER_AGENT." - Stream recorder for Microsoft's RTSP variant\n". <<<EOF
Copyright (C) 2010/01-02, Joel Yliluoma - http://iki.fi/bisqwit/
Usage: php rtspdump-lite.php [<options>]
-h, --help
This help
-V, --version
Print version number
-r, --rtsp <url>
Specify stream URL (example: mms://w.glc.us.com/Med/)
This must point to a Microsoft Media Server (MS-RTSP).
Other servers, such as Realmedia servers,
are not supported. Use Live555 for them.
-o, --output <file> Specify output filename (default: dump.wmv)
In order to dump to stdout, you can specify "as the output filename. If you use MEncoder/MPlayer to
read this stream, be sure to pass the "-demuxer asf"
option to it; otherwise it will complain a lot about
backward seeking.
-d, --debug <int>
Debug traffic (use following values, or sum thereof):
1 = RTSP responses
2 = incoming RTP packets
4 = packet reordering
8 = A/V packet construction
16 = miscellaneous
-t, --time <int>
Stop recording after number of seconds
-v, --verbose
Increase verbosity
This LITE version only does TCP traffic.
Note that some PHP versions don't accept long options
(e.g. --help). Use short ones instead (e.g. -h).
EOF;
print "\n\n";
exit;
}
/********************************
* Begin RTSP protocol stuff
*/
function socket_readline($sock)
{
$line = '';
for(;;)
{
$c = socket_read($sock, 1);
if($c === false || $c === '')
{ print "RTSP socket error: ". socket_strerror(socket_last_error()); return
"\r\n"; }
$line .= $c;
if($c == "\n") break;
}
return $line;
}
function GetRTSPResponse($sock)
{
$length = 0;
$result = '';
for(;;)
{
$line = socket_readline($sock);
if($line == "\r\n") break;
$result .= $line;
if(preg_match('/^Content-length:/i', $line))
$length = (int)preg_replace('@^.*:@', '', $line);
}
while($length > 0)
{
$s = socket_read($sock, $length);
if($s === false || $s === '')
{ print "RTSP socket error: ". socket_strerror(socket_last_error()). "\n";
return ""; }
$result .= $s;
$length -= strlen($s);
}
return $result;
}
function SendRTSPrequest($sock, $command, $request)
{
global $debug, $verbose;
if($verbose) print "Sending {$command}\n";
if($debug & DEBUG_RTSP) print $request;
for($remain = $request; strlen($remain) > 0; )
{
$s = socket_write($sock, $remain);
if($s === false) { print "RTSP socket error: ".
socket_strerror(socket_last_error()). "\n"; return; }
$remain = substr($remain, $s);
}
}
function ReadRTSPresponse($sock, $command, $request, $prefix = '', $args = Array())
{
global $debug, $verbose;
$s = $prefix . GetRTSPResponse($sock);
$c = count($args);
foreach(explode("\r\n", $s) as $line)
{
$captures = Array();
for($a=3; $a<$c; ++$a)
{
$arg = $args[$a];
if(is_array($arg[0])) $captures = $arg; else $captures[] = $arg;
}
foreach($captures as $arg)
{
$tmp = preg_match($arg[1], $line, $mat);
if($tmp)
{
$tmp = $mat[1];
$var = $arg[0];
global $$var;
eval('$$var = '.str_replace('$1', '$tmp', $arg[2]).';');
if($debug & DEBUG_MISC)
{
if($var == 'asf_header')
printf("\$%-16s set to binary string (%d bytes)\n", $var,
strlen($$var));
else
printf("\$%-16s set to %s\n", $var, $$var);
}
}
}
}
if($debug & DEBUG_RTSP)
print "[$command]\n$s\n\n";
if(substr($s, 0, 13) != 'RTSP/1.0 200 ')
{
$type = 'error';
if($command == 'LogConnect'
|| $command == 'KeepAlive') $type = 'warning';
print "RTSP protocol $type: Server does not seem to agree with our intents on
$command\n";
if($type == 'error')
{
if(!($debug & DEBUG_RTSP))
{
print
preg_replace('/^/m', ' ',
"---Failing request:---\n$request\n---Response indicating failure:--\n$s\n");
}
exit;
}
}
return $s;
}
function DoRTSPrequest($sock, $command, $request)
{
global $debug, $verbose;
SendRTSPrequest($sock, $command, $request);
$args = func_get_args();
return ReadRTSPresponse($sock, $command, $request, '', $args);
}
/* Connect to the RTSP server */
$url_components = parse_url($stream);
$dst_host = $url_components['host'];
$dst_addr = gethostbyname($dst_host);
$rtsp_port = @$url_components['port'];
if(!$rtsp_port) $rtsp_port = 554;
// Change protocol to rtsp in case it was given as mms:// or http:// .
$stream = "rtsp://$dst_host{$url_components['path']}";
// WMS won't care about the host name, but it's nice to keep it.
if($verbose) print "Connecting to {$dst_addr}, $rtsp_port for $stream\n";
$rtsp_sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if(!socket_connect($rtsp_sock, $dst_addr, $rtsp_port))
{
print "RTSP connect error: ". socket_strerror(socket_last_error()) . "\n";
exit;
}
/////////////////
/* A DESCRIBE request is not required to get streaming
* started, if you already know all the required information.
* But for ASF, we really need the asfv1 and maxps fields.
* Otherwise we cannot produce working ASF files.
*/
$has_video = false;
$has_audio = false;
$s = DoRTSPrequest($rtsp_sock, 'Describe',
"DESCRIBE {$stream} RTSP/1.0\r\n".
"User-Agent: ".USER_AGENT."\r\n".
"\r\n",
Array('asf_header', '/.*asfv1;base64,(.*)/', 'base64_decode($1)'), // ASF file
header
Array('maxps',
'/.*maxps:(.*)/',
'(int)$1'),
// Maximum
packet size
Array('has_video',
'/control:video/', 'true'),
Array('has_audio',
'/control:audio/', 'true')
);
$slash = '/';
if(preg_match('@/$@', $stream)) $slash = '';
if($has_video)
DoRTSPrequest($rtsp_sock, "SelectStream video",
"SETUP $stream{$slash}video RTSP/1.0\r\n".
"User-Agent: ".USER_AGENT."\r\n".
"Session: $session\r\n".
"Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n".
"\r\n",
Array('session',
'/^Session: *([^;]*)/', '$1'),
Array('rtsp_timeout',
'/timeout=([0-9]+)/', '(int)$1')
);
if($has_audio)
DoRTSPrequest($rtsp_sock, "SelectStream audio",
"SETUP $stream{$slash}audio RTSP/1.0\r\n".
"User-Agent: ".USER_AGENT."\r\n".
"Session: $session\r\n".
"Transport: RTP/AVP/TCP;unicast;interleaved=0-1\r\n".
"\r\n",
Array('session',
'/^Session: *([^;]*)/', '$1'),
Array('rtsp_timeout',
'/timeout=([0-9]+)/', '(int)$1')
);
/////////////////
/* This is completely optional, but the concept of
* making "Linux" appear in the media server's logs
* for the first time is intriguing. (MS-RTSP only)
*/
$param = '<XML><c-os>'.MY_OS.'</c-os><c-osversion>'.MY_OSVERSION.'</cosversion></XML>';
DoRTSPrequest($rtsp_sock, 'LogConnect',
"SET_PARAMETER {$stream} RTSP/1.0\r\n".
"User-Agent: ".USER_AGENT."\r\n".
"Session: $session\r\n".
"Content-type: application/x-wms-Logconnectstats;charset=UTF-8\r\n".
"Content-length: ".strlen($param)."\r\n".
"\r\n".
$param
);
DoRTSPrequest($rtsp_sock, 'Play',
"PLAY {$stream} RTSP/1.0\r\n".
"User-Agent: ".USER_AGENT."\r\n".
"Session: $session\r\n".
"Range: npt=0.000-\r\n".
"Bandwidth: 2147483647\r\n".
"X-Accelerate-Streaming: AccelDuration=8000;AccelBandwidth=5912000\r\n".
"\r\n"
);
function TCP_buffer_to_RTP($data)
{
/* In the TCP stream, each RTP packet is encapsulated
* with a 4-byte packet, where two unknown-meaning bytes
* prefix a two-byte length value of the RTP packet.
* Because TCP is streaming, RTP packets may be split
* between different recv()s, and we must therefore
* use a buffering scheme.
* In UDP, the length is that of the packet itself.
*/
static $remain = 0, $buffer = '', $rest = '', $handling_rtsp_response = false;
$pos
= 0;
if($rest != '') { $rest .= $data; $data = $rest; $rest = ''; }
$length = strlen($data);
while($pos < $length)
{
if(!$remain)
{
if($pos+4 > $length) return null;
if($handling_rtsp_response)
{
$pos = strpos($data, "\r\n\r\n", $pos);
if($pos === false)
{
$rest = substr($data, -3);
return null;
}
$pos += 4;
$handling_rtsp_response = false;
continue;
}
if(substr($data, $pos, 9) == 'RTSP/1.0 ')
{
$handling_rtsp_response = true;
$pos += 9;
continue;
}
$tmp = unpack('Ca/Cb/nlength', substr($data, $pos, 4));
$pos += 4;
$remain = $tmp['length'];
continue;
}
$take = $remain;
if($pos+$take > $length) $take = $length-$pos;
$buffer .= substr($data, $pos, $take);
$remain -= $take;
$pos += $take;
if($remain == 0)
{ $rest .= substr($data, $pos); $result = $buffer; $buffer = ''; return
$result; }
}
return null;
}
/**************************/
/*
*/
/* MAIN LOOP - SAVE VIDEO */
/*
*/
/**************************/
if($verbose) print "Opening output file, $output_file\n";
$fp = fopen($output_file, 'w');
fwrite($fp, $asf_header);
$last_wakeup = time()-3;
$run_until = time() + $run_for;
$asfpacket = '';
if($verbose) print "Beginning streaming\n";
for(;;)
{
$cur_time = time();
if($cur_time >= $run_until) break;
if($cur_time > $last_wakeup + $rtsp_timeout - 5) // -5 just in case.
{
$last_wakeup = $cur_time;
/* This is basically "wake-up" or "ping" in MS-RTSP */
/* Sanctioned in the official specification. */
if($debug & DEBUG_MISC)
print
str_pad('',60)."\r".
date('Y-m-d H:i:s ')."Sending keep-alive\n";
SendRTSPrequest($rtsp_sock, 'KeepAlive',
"GET_PARAMETER {$stream} RTSP/1.0\r\n".
"Session: $session\r\n".
"\r\n");
// The response will be dealt by TCP_buffer_to_RTP().
}
// Receive RTP packet
do {
$rtp = socket_read($rtsp_sock, $maxps+64);
if($rtp === false)
{
print "Receive from socket failed:
".socket_strerror(socket_last_error())."\n";
exit;
}
if($rtp === '')
{
print "Receive from socket failed: Remote end closed connection\n";
exit;
}
$n_net_bytes += strlen($rtp)+20; // assume minimal length TCP packet
// Push the raw data into a buffer and parse it for any RTP packets
$rtp = TCP_buffer_to_RTP($rtp);
} while($rtp === null);
// Parse RTP packet header
$hdr = unpack('Cconfig/Cpayloadtype/nseqno/Ntimestamp/Nssrc', $rtp);
$hdr['mark']
= $hdr['payloadtype'] >= 0x80;
$hdr['payloadtype'] &= 0x7F;
$payload_offs = 12 + ($hdr['config'] & 0x0F) * 4;
$payload = substr($rtp, $payload_offs);
if($debug & DEBUG_RTP_INCOMING)
printf("Received RTP #%X (%d/%d bytes) -- TS=%d,SSRC=%X,PT=%u%s\n",
$hdr['seqno'], strlen($payload), strlen($rtp),
$hdr['timestamp'], $time['ssrc'], $hdr['payloadtype'],
$hdr['mark'] ? ',MARK' : '');
// Handle packet
$payloadtype = $hdr['payloadtype'];
if($payloadtype != 96)
printf("RECEIVED RTP PACKET FOR UNKNOWN PAYLOADTYPE %d, TREAD CAREFULLY\n",
$payloadtype);
/* We've now got a sequential RTP packet. */
// In TCP mode, we don't care about sequence numbers;
//
they are always assumed to be sequential.
$mark = $hdr['mark'];
/* The payload begins with a special prefix, followed by
* an ASF data packet piece. We want an ASF data packet,
* so we can put it into the file.
*/
$asf_hdr_bits = ord($payload[0]);
$contd = 4;
if($asf_hdr_bits & 0x20) $contd += 4; // skip relative timestamp
if($asf_hdr_bits & 0x10) $contd += 4; // skip duration
if($asf_hdr_bits & 0x08) $contd += 4; // skip locationid
if($debug & DEBUG_RTP_AV)
{
$format = ($asf_hdr_bits & 0x40) ? 'Nlength' : 'Noffset';
if($asf_hdr_bits & 0x20) $format .= '/Nrelativetimestamp';
if($asf_hdr_bits & 0x10) $format .= '/Nduration';
if($asf_hdr_bits & 0x08) $format .= '/Nlocationid';
$hdr2
= $hdr;
$hdr2['ssrc'] = sprintf('%X', $hdr2['ssrc']);
$hdr2['seqno'] = sprintf('%X', $hdr2['seqno']);
$hdr2['asfhdrbits'] = $asf_hdr_bits;
$hdr = unpack($format, $payload);
$hdr = array_merge($hdr2, $hdr);
$hdr['payloadlength'] = strlen($payload);
$s = 'ASF packet';
foreach($hdr as $k=>$v) $s .= ", $k=$v";
printf("%s\n", $s);
}
$asfpacket .= substr($payload, $contd);
if($mark)
{
// packet_finished
$asfpacket = str_pad($asfpacket, $maxps, chr(0));
#printf("Writing %d\n", strlen($asfpacket));
if($asf_hdr_bits & 0x80)
++$n_mm_keyframes;
$n_mm_bytes += strlen($asfpacket);
fwrite($fp, $asfpacket);
fflush($fp);
++$n_mm_packets;
$asfpacket = ''; // a new ASF packet begins here
}
++$n_rtp_packets;
if($verbose)
{
printf("%13.3f RTP:%-5d MM:%-5d K:%d | %.1f MB -> %.1f MB\r",
$hdr['timestamp']/1000.0,
$n_rtp_packets,
$n_mm_packets,
$n_mm_keyframes,
$n_net_bytes/1048576,
$n_mm_bytes/1048576);
flush();
}
}
fclose($fp);
if($verbose) print "Ending streaming\n";
Miscellaneous developer documentation
Functional flowgraph, detailing how rtspdump works.
This flowgraph was created with Microsoft Visio.
See also
 RTMPDump dumps rtmp:// form streams, used by Macromedia Flash streaming services. They've
been doing a great job. It is considerably a more complicated protocol than RTSP is.
Copyright © 2010,2013 Joel Yliluoma (http://iki.fi/bisqwit/)
Last edited at: 2018-01-15T11:20:48+02:00
Download