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

right 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:

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 (kt1@a@39bi.erGsqk16wiezt@@1oqikax8i.dCfi) in such a case, though.

Finally, here's how to interpret the status line given when the -v option is used:

    488373.538 RTX:324->324 Drop:0 RTP:343908 MM:68782 K:3547 | 469.5 MB -> 461.6 MB
    ^          ^        ^   ^      ^          ^        ^        ^           ^_ amount of data
    ^          ^        ^   ^      ^          ^        ^        ^              saved to file
    ^          ^        ^   ^      ^          ^        ^        ^_ approximate amount of
    ^          ^        ^   ^      ^          ^        ^           network traffic received
    ^          ^        ^   ^      ^          ^        ^_ number of multimedia
    ^          ^        ^   ^      ^          ^           keyframe packets received
    ^          ^        ^   ^      ^          ^_ number of multimedia packets constructed
    ^          ^        ^   ^      ^_ number of rtp packets read
    ^          ^        ^   ^_ number of lost packets
    ^          ^_       ^_ number of retransmitted packets received
    ^          ^_ number of retransmitted packets requested
    ^_ playing 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 MS-RTSP 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 like
define('MY_OS',        'GNULinux');
define('MY_OSVERSION', 'Fedora 12');
define('USER_AGENT',   '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.'</c-osversion></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.
http://bisqwit.iki.fi/jutut/kuvat/rtspdump/rtspdump_rtpprocess_thu.png

This flowgraph was created with Microsoft Visio.

See also


Copyright © 2010,2013 Joel Yliluoma (http://iki.fi/bisqwit/)

Last edited: 2013-06-07 18:27:48