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