gnash-commit
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Gnash-commit] /srv/bzr/gnash/trunk r11961: Add a simple RTMP implementa


From: Benjamin Wolsey
Subject: [Gnash-commit] /srv/bzr/gnash/trunk r11961: Add a simple RTMP implementation that is usable in Gnash core. Implement
Date: Wed, 24 Feb 2010 11:37:56 +0100
User-agent: Bazaar (2.0.3)

------------------------------------------------------------
revno: 11961 [merge]
committer: Benjamin Wolsey <address@hidden>
branch nick: trunk
timestamp: Wed 2010-02-24 11:37:56 +0100
message:
  Add a simple RTMP implementation that is usable in Gnash core. Implement
  rtmpget to mimic the behaviour of a SWF for downloading video files.
  
  The RTMP implementation correctly communicates with an RTMP server. This
  interface is complete enough to use.  Its interface for retrieving
  messages for the core (metadata, remote calls, and FLV packages) is
  incomplete, and currently only for use with rtmpget.
  
  The Socket class currently blocks; this needs work to prevent Gnash from
  hanging.
  
  Socket and RTMP both use non-portable system calls, which need to be
  implemented for other platforms.
  
  rtmpget works only for rtmp streams. Its FLV writing powers only write
  recorded streams correctly; this seems to be a bug with the FLV processing,
  not with the packets received via RTMP.
removed:
  utilities/rtmpget.cpp
added:
  libbase/RTMP.cpp
  libbase/RTMP.h
  libbase/Socket.cpp
  libbase/Socket.h
  utilities/rtmpget.cpp
modified:
  libbase/AMF.h
  libbase/Makefile.am
  utilities/Makefile.am
=== modified file 'libbase/AMF.h'
--- a/libbase/AMF.h     2010-02-23 21:30:34 +0000
+++ b/libbase/AMF.h     2010-02-24 10:37:56 +0000
@@ -73,7 +73,6 @@
         :
         GnashException(msg)
     {}
-private:
 };
 
 /// Read a number from an AMF buffer
@@ -140,6 +139,14 @@
 /// it!
 void write(SimpleBuffer& buf, const std::string& str);
 
+/// Write a C string to an AMF buffer.
+//
+/// The overload is necessary to prevent const char* being resolved to the
+/// boolean overload.
+inline void write(SimpleBuffer& buf, const char* str) {
+    return write(buf, std::string(str));
+}
+
 /// Write a number to an AMF buffer.
 //
 /// This function writes the type byte and the double value.

=== modified file 'libbase/Makefile.am'
--- a/libbase/Makefile.am       2010-02-20 07:55:10 +0000
+++ b/libbase/Makefile.am       2010-02-23 15:28:45 +0000
@@ -63,6 +63,7 @@
        GnashImageJpeg.cpp \
        GnashFileUtilities.cpp \
        AMF.cpp \
+       RTMP.cpp \
        log.cpp \
        memory.cpp \
        rc.cpp \
@@ -71,6 +72,7 @@
        string_table.cpp \
        tu_file.cpp \
        IOChannel.cpp \
+       Socket.cpp \
        ClockTime.cpp \
        NamingPolicy.cpp \
        WallClockTimer.cpp \
@@ -140,6 +142,7 @@
        tree.hh \
        tu_file.h \
        IOChannel.h \
+       Socket.h \
        tu_opengl_includes.h \
        GnashSystemFDHeaders.h \
        GnashSystemNetHeaders.h \
@@ -172,6 +175,7 @@
        GC.h \
        GnashException.h \
        AMF.h \
+       RTMP.h \
        dsodefs.h \
        utility.h \
        log.h \

=== added file 'libbase/RTMP.cpp'
--- a/libbase/RTMP.cpp  1970-01-01 00:00:00 +0000
+++ b/libbase/RTMP.cpp  2010-02-24 09:56:02 +0000
@@ -0,0 +1,1112 @@
+//
+//   Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+#include <cstdlib>
+#include <cstring>
+#include <cassert>
+#include <cstdio>
+
+// Replace!!
+#include <sys/times.h>
+#include <netinet/in.h>
+
+#include "RTMP.h"
+#include "log.h"
+#include "AMF.h"
+#include "GnashAlgorithm.h"
+#include "URL.h"
+#include "ClockTime.h"
+
+namespace gnash {
+    namespace rtmp {
+
+        namespace {
+
+            bool sendBytesReceived(RTMP* r);
+
+    // Not sure we ever want to do this.
+    bool sendServerBW(RTMP& r);
+
+    void handleMetadata(RTMP& r, const boost::uint8_t *payload,
+            unsigned int len);
+    void handleChangeChunkSize(RTMP& r, const RTMPPacket& packet);
+    void handleControl(RTMP& r, const RTMPPacket& packet);
+    void handleServerBW(RTMP& r, const RTMPPacket& packet);
+    void handleClientBW(RTMP& r, const RTMPPacket& packet);
+    
+    void setupInvokePacket(RTMPPacket& packet);
+    boost::uint32_t getUptime();
+
+    boost::int32_t decodeInt32LE(const boost::uint8_t* c);
+    int encodeInt32LE(boost::uint8_t *output, int nVal);
+    unsigned int decodeInt24(const boost::uint8_t* c);
+    boost::uint8_t* encodeInt16(boost::uint8_t *output, boost::uint8_t 
*outend, short nVal);
+    boost::uint8_t* encodeInt24(boost::uint8_t *output, boost::uint8_t *outend,
+            int nVal);
+    boost::uint8_t* encodeInt32(boost::uint8_t *output, boost::uint8_t *outend,
+            int nVal);
+
+
+    static const int packetSize[] = { 12, 8, 4, 1 };
+ 
+}
+
+namespace {
+
+/// A random generator for generating the signature.
+//
+/// TODO: do this properly (it's currently not very random).
+struct RandomByte
+{
+    bool operator()() const {
+        return std::rand() % 256;
+    }
+};
+
+}
+
+RTMPPacket::RTMPPacket(size_t reserve)
+    :
+    header(),
+    buffer(new SimpleBuffer(reserve + RTMPHeader::headerSize)),
+    bytesRead(0)
+{
+    // This is space for the header be filled in later.
+    buffer->resize(RTMPHeader::headerSize);
+}
+
+RTMPPacket::RTMPPacket(const RTMPPacket& other)
+    :
+    header(other.header),
+    buffer(other.buffer)
+{}
+
+const size_t RTMPHeader::headerSize;
+
+RTMP::RTMP()
+    :
+    _inChunkSize(RTMP_DEFAULT_CHUNKSIZE),
+    m_stream_id(-1),
+    m_mediaChannel(0),
+    m_nClientBW2(2),
+    _bytesIn(0),
+    _bytesInSent(0),
+    _serverBandwidth(2500000),
+    _bandwidth(2500000),
+    _outChunkSize(RTMP_DEFAULT_CHUNKSIZE)
+{
+}
+
+bool
+RTMP::hasPacket(ChannelType t, size_t channel) const
+{
+    const ChannelSet& set = (t == CHANNELS_OUT) ? _outChannels : _inChannels;
+    return set.find(channel) != set.end();
+}
+
+RTMPPacket&
+RTMP::getPacket(ChannelType t, size_t channel)
+{
+    ChannelSet& set = (t == CHANNELS_OUT) ? _outChannels : _inChannels;
+    return set[channel];
+}
+
+RTMPPacket&
+RTMP::storePacket(ChannelType t, size_t channel, const RTMPPacket& p)
+{
+    ChannelSet& set = (t == CHANNELS_OUT) ? _outChannels : _inChannels;
+    RTMPPacket& stored = set[channel];
+    stored = p;
+    return stored;
+}
+
+bool
+RTMP::connected() const
+{
+    return _socket.connected();
+}
+
+void
+RTMP::setBufferTime(size_t size)
+{
+    sendCtrl(*this, CONTROL_BUFFER_TIME, m_stream_id, size);
+}
+
+void
+RTMP::call(const SimpleBuffer& amf)
+{
+    RTMPPacket p(amf.size());
+    setupInvokePacket(p);
+    
+    // Copy the data.
+    p.buffer->append(amf.data(), amf.size());
+    sendPacket(p);
+}
+
+bool
+RTMP::connect(const URL& url)
+{
+    log_debug("Connecting to %s", url.str());
+
+    // Basic connection attempt.
+    if (!_socket.connect(url)) {
+        log_error("Initial connection failed");
+        return false;
+    }
+    
+    if (!handShake()) {
+        log_error( "handshake failed.");
+        close();
+        return false;
+    }
+
+    return true;
+}
+
+void
+RTMP::update()
+{
+    if (!connected()) {
+        log_debug("Not connected!");
+        return;
+    }
+    
+    const size_t reads = 10;
+
+    for (size_t i = 0; i < reads; ++i) {
+
+        RTMPPacket p;
+        readPacket(p);
+    
+        if (isReady(p)) {
+            handlePacket(p);
+            return;
+        }
+    }
+}
+
+void
+RTMP::handlePacket(const RTMPPacket& packet)
+{
+    const PacketType t = packet.header.packetType;
+
+    log_debug("Received %s", t);
+
+    switch (t) {
+
+        case PACKET_TYPE_CHUNK_SIZE:
+            handleChangeChunkSize(*this, packet);
+            break;
+    
+        case PACKET_TYPE_BYTES_READ:
+            break;
+    
+        case PACKET_TYPE_CONTROL:
+            handleControl(*this, packet);
+            break;
+
+        case PACKET_TYPE_SERVERBW:
+            handleServerBW(*this, packet);
+            break;
+
+        case PACKET_TYPE_CLIENTBW:
+            handleClientBW(*this, packet);
+            break;
+    
+        case PACKET_TYPE_AUDIO:
+            if (!m_mediaChannel) m_mediaChannel = packet.header.channel;
+            break;
+
+        case PACKET_TYPE_VIDEO:
+            if (!m_mediaChannel) m_mediaChannel = packet.header.channel;
+            break;
+
+        case PACKET_TYPE_FLEX_STREAM_SEND:
+            LOG_ONCE(log_unimpl("unsupported packet %s received"));
+            break;
+
+        case PACKET_TYPE_FLEX_SHARED_OBJECT:
+            LOG_ONCE(log_unimpl("unsupported packet %s received"));
+            break;
+
+        case PACKET_TYPE_FLEX_MESSAGE:
+        {
+            LOG_ONCE(log_unimpl("partially supported packet %s received"));
+            _messageQueue.push_back(packet.buffer);    
+            break;
+        }
+    
+        case PACKET_TYPE_METADATA:
+            handleMetadata(*this, payloadData(packet), payloadSize(packet));
+            break;
+
+        case PACKET_TYPE_SHARED_OBJECT:
+            LOG_ONCE(log_unimpl("packet %s received"));
+            break;
+
+        case PACKET_TYPE_INVOKE:
+            _messageQueue.push_back(packet.buffer);
+            break;
+
+        case PACKET_TYPE_FLV:
+            _flvQueue.push_back(packet.buffer);
+            break;
+    
+        default:
+            log_error("Unknown packet %s received", t);
+    
+    }
+  
+}
+
+int
+RTMP::readSocket(boost::uint8_t* buffer, int n)
+{
+    int toRead = n;
+    while (toRead) {
+        const std::streamsize bytesRead = _socket.read(buffer, toRead);
+
+        if (bytesRead < 0) return -1;
+        
+        if (!bytesRead) {
+            return -1;
+        }
+
+        _bytesIn += bytesRead;
+        toRead -= bytesRead;
+
+        // Report bytes recieved every time we reach half the bandwidth.
+        // Doesn't seem very likely to be the way the pp does it.
+        if (_bytesIn > _bytesInSent + _bandwidth / 2) {
+            sendBytesReceived(this);
+            log_debug("Sent bytes received");
+        }
+        buffer += bytesRead;
+    }
+    return n - toRead;
+}
+
+void
+RTMP::play(const SimpleBuffer& buf, int streamID)
+{
+    RTMPPacket packet(buf.size());
+  
+    packet.header.channel = CHANNEL_VIDEO;
+    packet.header.packetType = PACKET_TYPE_INVOKE;
+  
+    packet.header._streamID = streamID;
+  
+    packet.buffer->append(buf.data(), buf.size());
+    sendPacket(packet);
+}
+
+/// Fills a pre-existent RTMPPacket with information.
+//
+/// This is either read entirely from incoming data, or copied from a
+/// previous packet in the same channel. This happens when the header type
+/// is less than RTMP_PACKET_SIZE_LARGE.
+//
+/// It seems as if new packets can add to the data of old ones if they have
+/// a minimal, small header.
+bool
+RTMP::readPacket(RTMPPacket& packet)
+{
+      
+    RTMPHeader& hr = packet.header;
+
+    boost::uint8_t hbuf[RTMPHeader::headerSize] = { 0 };
+    boost::uint8_t* header = hbuf;
+  
+    if (readSocket(hbuf, 1) == 0) {
+        log_error( "%s, failed to read RTMP packet header", __FUNCTION__);
+        return false;
+    }
+
+    //log_debug("Packet is %s", boost::io::group(std::hex, (unsigned)hbuf[0]));
+
+    const int htype = ((hbuf[0] & 0xc0) >> 6);
+    //log_debug("Thingy whatsit (packet size type): %s", htype);
+
+    const int channel = (hbuf[0] & 0x3f);
+    //log_debug("Channel: %s", channel);
+
+    hr.headerType = static_cast<PacketSize>(htype);
+    hr.channel = channel;
+    ++header;
+
+    if (hr.channel == 0) {
+        if (readSocket(&hbuf[1], 1) != 1) {
+          log_error("failed to read RTMP packet header 2nd byte");
+          return false;
+        }
+        hr.channel = hbuf[1] + 64;
+        ++header;
+    }
+    else if (hr.channel == 1) {
+        if (readSocket(&hbuf[1], 2) != 2) {
+            log_error("Failed to read RTMP packet header 3nd byte");
+             return false;
+        }
+      
+        const boost::uint32_t tmp = (hbuf[2] << 8) + hbuf[1];
+        hr.channel = tmp + 64;
+        log_debug( "%s, channel: %0x", __FUNCTION__, hr.channel);
+        header += 2;
+    }
+  
+    // This is the size in bytes of the packet header according to the
+    // type.
+    int nSize = packetSize[htype];
+
+    /// If we didn't receive a large header, the timestamp is relative
+    if (htype != RTMP_PACKET_SIZE_LARGE) {
+
+        if (!hasPacket(CHANNELS_IN, hr.channel)) {
+            log_error("Incomplete packet received on channel %s", channel);
+            return false;
+        }
+
+        // For all other header types, copy values from the last message of
+        // this channel. This includes any payload data from incomplete
+        // messages. 
+        packet = getPacket(CHANNELS_IN, hr.channel);
+    }
+  
+    --nSize;
+  
+    if (nSize > 0 && readSocket(header, nSize) != nSize) {
+        log_error( "Failed to read RTMP packet header. type: %s",
+                static_cast<unsigned>(hbuf[0]));
+        return false;
+    }
+
+    // nSize is predicted size - 1. Add what we've read already.
+    int hSize = nSize + (header - hbuf);
+
+    if (nSize >= 3) {
+
+        const boost::uint32_t timestamp = decodeInt24(header);
+
+        // Make our packet timestamp absolute. If the value is 0xffffff,
+        // the absolute value comes later.
+        if (timestamp != 0xffffff) {
+            if (htype != RTMP_PACKET_SIZE_LARGE) {
+                packet.header._timestamp += timestamp;
+            }
+            else {
+                packet.header._timestamp = timestamp;
+            }
+        }
+
+        // Have at least a different size payload from the last packet.
+        if (nSize >= 6) {
+
+            // We do this in case there was an incomplete packet in the
+            // channel already.
+            clearPayload(packet);
+            hr.dataSize = decodeInt24(header + 3);
+
+            // More than six: read packet type
+            if (nSize > 6) {
+                hr.packetType = static_cast<PacketType>(header[6]);
+     
+                // Large packets have a streamID.
+                if (nSize == 11) {
+                    hr._streamID = decodeInt32LE(header + 7);
+                }
+            }
+        }
+    }
+
+    if (hr._timestamp == 0xffffff) {
+      if (readSocket(header+nSize, 4) != 4) {
+              log_error( "%s, failed to read extended timestamp",
+              __FUNCTION__);
+              return false;
+            }
+          hr._timestamp = AMF::readNetworkLong(header+nSize);
+          hSize += 4;
+        
+    }
+        
+    const size_t bufSize = hr.dataSize + RTMPHeader::headerSize;
+
+    // If the packet does not have a payload, it was a complete packet stored 
in
+    // the channel for reference. This is the only case when a packet should
+    // exist but have no payload. We re-allocate in this case.
+    if (!hasPayload(packet)) {
+        packet.buffer.reset(new SimpleBuffer(bufSize));
+
+        // Why do this again? In case it was copied from the old packet?
+        hr.headerType = static_cast<PacketSize>(htype);
+    }
+    
+    // Resize anyway. If it's different from what it was before, we should
+    // already have cleared it.
+    packet.buffer->resize(bufSize);
+
+    const size_t bytesRead = packet.bytesRead;
+
+    const int nToRead = hr.dataSize - bytesRead;
+
+    const int nChunk = std::min<int>(nToRead, _inChunkSize);
+    assert(nChunk >= 0);
+
+    if (readSocket(payloadData(packet) + bytesRead, nChunk) != nChunk) {
+        log_error( "Failed to read RTMP packet payload. len: %s", bytesRead);
+        return false;
+    }
+
+    packet.bytesRead += nChunk;
+
+    // Store a copy of the packet for later additions and as a reference for
+    // future sends.
+    RTMPPacket& storedpacket = storePacket(CHANNELS_IN, hr.channel, packet);
+  
+    // If the packet is complete, the stored packet no longer needs to
+    // keep the data alive.
+    if (isReady(packet)) {
+        // The timestamp should be absolute by this stage.
+        clearPayload(storedpacket);
+    }
+        
+    return true;
+}
+
+bool
+RTMP::handShake()
+{
+
+    /// It is a size type, but our socket functions return int.
+    const int sigSize = 1536;
+
+    boost::uint8_t clientbuf[sigSize + 1];
+    boost::uint8_t* ourSig = clientbuf + 1;
+
+    // Not encrypted
+    clientbuf[0] = 0x03;
+    
+    // TODO: do this properly.
+    boost::uint32_t uptime = htonl(getUptime());
+    std::memcpy(ourSig, &uptime, 4);
+
+    std::fill_n(ourSig + 4, 4, 0);
+
+    // Generate 1536 random bytes.
+    std::generate(ourSig + 8, ourSig + sigSize, RandomByte());
+
+    // Send it to server.
+    if (_socket.write(clientbuf, sigSize + 1) != sigSize + 1) {
+        return false;
+    }
+
+    // Expect the same byte as we sent.
+    boost::uint8_t type;
+    if (readSocket(&type, 1) != 1) {
+        return false;
+    }
+
+    log_debug( "%s: Type Answer   : %02X", __FUNCTION__, (int)type);
+
+    if (type != clientbuf[0]) {
+        log_error( "%s: Type mismatch: client sent %d, server answered %d",
+            __FUNCTION__, clientbuf[0], type);
+    }
+    
+    boost::uint8_t serverSig[sigSize];
+
+    // Read from server.
+    if (readSocket(serverSig, sigSize) != sigSize) {
+        return false;
+    }
+
+    // decode server response
+    boost::uint32_t suptime;
+
+    memcpy(&suptime, serverSig, 4);
+    suptime = ntohl(suptime);
+
+    log_debug("Server Uptime : %d", suptime);
+    log_debug("FMS Version   : %d.%d.%d.%d",
+            +serverSig[4], +serverSig[5], +serverSig[6], +serverSig[7]);
+
+    // Send what we received from server.
+    if (_socket.write(serverSig, sigSize) != sigSize) {
+        return false;
+    }
+
+    // Expect it back again.
+    if (readSocket(serverSig, sigSize) != sigSize) {
+        return false;
+    }
+
+    const bool match = std::equal(serverSig, serverSig + arraySize(serverSig),
+                                  ourSig);
+
+    if (!match) {
+        log_error( "Signatures do not match during handshake!");
+    }
+    return true;
+}
+
+
+bool
+RTMP::sendPacket(RTMPPacket& packet)
+{
+    // Set the data size of the packet to send.
+    RTMPHeader& hr = packet.header;
+
+    hr.dataSize = payloadSize(packet);
+
+    // This is the timestamp for our message.
+    const boost::uint32_t uptime = getUptime();
+    
+    // Look at the previous packet on the channel.
+    bool prev = hasPacket(CHANNELS_OUT, hr.channel);
+
+    // The packet shall be large if it contains an absolute timestamp.
+    //      * This is necessary if there is no previous packet, or if the
+    //        timestamp is smaller than the last packet.
+    // Else it shall be medium if data size and packet type are the same
+    // It shall be small if ...
+    // It shall be minimal if it is exactly the same as its predecessor.
+
+    // All packets should start off as large. They will stay large if there
+    // is no previous packet.
+    assert(hr.headerType == RTMP_PACKET_SIZE_LARGE);
+
+    if (!prev) {
+        hr._timestamp = uptime;
+    }
+    else {
+
+        const RTMPPacket& prevPacket = getPacket(CHANNELS_OUT, hr.channel);
+        const RTMPHeader& oldh = prevPacket.header;
+        const boost::uint32_t prevTimestamp = oldh._timestamp;
+
+        // If this timestamp is later than the other and the difference fits
+        // in 3 bytes, encode a relative one.
+        if (uptime >= oldh._timestamp && uptime - prevTimestamp < 0xffffff) {
+            //log_debug("Shrinking to medium");
+            hr.headerType = RTMP_PACKET_SIZE_MEDIUM;
+            hr._timestamp = uptime - prevTimestamp;
+
+            // It can be still smaller if the data size is the same.
+            if (oldh.dataSize == hr.dataSize &&
+                    oldh.packetType == hr.packetType) {
+                //log_debug("Shrinking to small");
+                hr.headerType = RTMP_PACKET_SIZE_SMALL;
+                // If there is no timestamp difference, the minimum size
+                // is possible.
+                if (hr._timestamp == 0) {
+                    //log_debug("Shrinking to minimum");
+                    hr.headerType = RTMP_PACKET_SIZE_MINIMUM;
+                }
+            }
+        }
+        else {
+            // Otherwise we need an absolute one, so a large header.
+            hr.headerType = RTMP_PACKET_SIZE_LARGE;
+            hr._timestamp = uptime;
+        }
+    }
+
+    assert (hr.headerType < 4);
+  
+    int nSize = packetSize[hr.headerType];
+  
+    int hSize = nSize;
+    boost::uint8_t* header;
+    boost::uint8_t* hptr;
+    boost::uint8_t* hend;
+    boost::uint8_t c;
+
+    // If there is a payload, the same buffer is used to write the header.
+    // Otherwise a separate buffer is used. But as we write them separately
+    // anyway, why do we do that?
+
+    // Work out where the beginning of the header is.
+    header = payloadData(packet) - nSize;
+    hend = payloadData(packet);
+  
+    // The header size includes only a single channel/type. If we need more,
+    // they have to be added on.
+    const int channelSize = hr.channel > 319 ? 3 : hr.channel > 63 ? 1 : 0;
+    header -= channelSize;
+    hSize += channelSize;
+
+    /// Add space for absolute timestamp if necessary.
+    if (hr.headerType == RTMP_PACKET_SIZE_LARGE && hr._timestamp >= 0xffffff) {
+        header -= 4;
+        hSize += 4;
+    }
+
+    hptr = header;
+    c = hr.headerType << 6;
+    switch (channelSize) {
+        case 0:
+            c |= hr.channel;
+            break;
+        case 1:
+            break;
+        case 2:
+            c |= 1;
+            break;
+    }
+    *hptr++ = c;
+
+    if (channelSize) {
+        const int tmp = hr.channel - 64;
+        *hptr++ = tmp & 0xff;
+        if (channelSize == 2) *hptr++ = tmp >> 8;
+    }
+
+    if (hr.headerType == RTMP_PACKET_SIZE_LARGE && hr._timestamp >= 0xffffff) {
+        // Signify that the extended timestamp field is present.
+        const boost::uint32_t t = 0xffffff;
+        hptr = encodeInt24(hptr, hend, t);
+    }
+    else if (hr.headerType != RTMP_PACKET_SIZE_MINIMUM) { 
+        // Write absolute or relative timestamp. Only minimal packets have
+        // no timestamp.
+        hptr = encodeInt24(hptr, hend, hr._timestamp);
+    }
+
+    /// Encode dataSize and packet type for medium packets.
+    if (nSize > 4) {
+        hptr = encodeInt24(hptr, hend, hr.dataSize);
+        *hptr++ = hr.packetType;
+    }
+
+    /// Encode streamID for large packets.
+    if (hr.headerType == RTMP_PACKET_SIZE_LARGE) {
+        hptr += encodeInt32LE(hptr, hr._streamID);
+    }
+
+    // Encode extended absolute timestamp if needed.
+    if (hr.headerType == RTMP_PACKET_SIZE_LARGE && hr._timestamp >= 0xffffff) {
+        hptr += encodeInt32LE(hptr, hr._timestamp);
+    }
+
+    nSize = hr.dataSize;
+    boost::uint8_t *buffer = payloadData(packet);
+    int nChunkSize = _outChunkSize;
+
+    std::string hx = hexify(header, payloadEnd(packet) - header, false);
+
+    while (nSize + hSize) {
+
+        if (nSize < nChunkSize) nChunkSize = nSize;
+
+        // First write header.
+        if (header) {
+            const int chunk = nChunkSize + hSize;
+            if (_socket.write(header, chunk) != chunk) {
+                return false;
+            }
+            header = NULL;
+            hSize = 0;
+        }
+      
+        else {
+            // Then write data.
+            if (_socket.write(buffer, nChunkSize) != nChunkSize) {
+                return false;
+          }
+        
+        }
+  
+        nSize -= nChunkSize;
+        buffer += nChunkSize;
+ 
+        if (nSize > 0) {
+            header = buffer - 1;
+            hSize = 1;
+            if (channelSize) {
+                header -= channelSize;
+                hSize += channelSize;
+            }
+
+            *header = (0xc0 | c);
+            if (channelSize) {
+                int tmp = hr.channel - 64;
+                header[1] = tmp & 0xff;
+                if (channelSize == 2) header[2] = tmp >> 8;
+            }
+        }
+    }
+
+    /* we invoked a remote method */
+    if (hr.packetType == PACKET_TYPE_INVOKE) {
+        assert(payloadData(packet)[0] == AMF::STRING_AMF0);
+        const boost::uint8_t* pos = payloadData(packet) + 1;
+        const boost::uint8_t* end = payloadEnd(packet);
+        const std::string& s = AMF::readString(pos, end);
+        log_debug( "Calling remote method %s", s);
+    }
+
+    RTMPPacket& storedpacket = storePacket(CHANNELS_OUT, hr.channel, packet);
+
+    // Make it absolute for the next delta.
+    storedpacket.header._timestamp = uptime;
+
+    return true;
+}
+
+   
+void
+RTMP::close()
+{
+    _socket.close();
+    _inChannels.clear();
+    _outChannels.clear();
+
+    m_stream_id = -1;
+    _inChunkSize = RTMP_DEFAULT_CHUNKSIZE;
+    _outChunkSize = RTMP_DEFAULT_CHUNKSIZE;
+    _bytesIn = 0;
+    _bytesInSent = 0;
+    _bandwidth = 2500000;
+    m_nClientBW2 = 2;
+    _serverBandwidth = 2500000;
+}
+
+/// The type of Ping packet is 0x4 and contains two mandatory parameters
+/// and two optional parameters. The first parameter is
+/// the type of Ping and in short integer. The second parameter is the
+/// target of the ping. As Ping is always sent in Channel 2
+/// (control channel) and the target object in RTMP header is always 0 whicj
+/// means the Connection object, it's necessary to put an extra parameter
+/// to indicate the exact target object the Ping is sent to. The second
+/// parameter takes this responsibility. The value has the same meaning
+/// as the target object field in RTMP header. (The second value could also
+/// be used as other purposes, like RTT Ping/Pong. It is used as the
+/// timestamp.) The third and fourth parameters are optional and could be
+/// looked upon as the parameter of the Ping packet. 
+bool
+sendCtrl(RTMP& r, ControlType t, unsigned int nObject, unsigned int nTime)
+{
+    log_debug( "Sending control type %s %s", +t, t);
+  
+    RTMPPacket packet(256);
+  
+    packet.header.channel = CHANNEL_CONTROL1;
+    packet.header.headerType = RTMP_PACKET_SIZE_LARGE;
+    packet.header.packetType = PACKET_TYPE_CONTROL;
+      
+    // type 3 is the buffer time and requires all 3 parameters.
+    // all in all 10 bytes.
+    int nSize = (t == CONTROL_BUFFER_TIME ? 10 : 6);
+    if (t == CONTROL_RESPOND_VERIFY) nSize = 44;
+    
+    SimpleBuffer& buf = *packet.buffer;
+  
+    buf.appendNetworkShort(t);
+  
+    if (t == CONTROL_RESPOND_VERIFY) { }
+    else {
+        if (nSize > 2) buf.appendNetworkLong(nObject);
+        if (nSize > 6) buf.appendNetworkLong(nTime);
+    }
+    return r.sendPacket(packet);
+}
+
+namespace {
+
+/// Send the server bandwidth.
+//
+/// Why would we want to send this?
+bool
+sendServerBW(RTMP* r)
+{
+    RTMPPacket packet(4);
+  
+    packet.header.channel = CHANNEL_CONTROL1;
+    packet.header.packetType = PACKET_TYPE_SERVERBW;
+  
+    SimpleBuffer& buf = *packet.buffer;
+  
+    buf.appendNetworkLong(r->serverBandwidth());
+    return r->sendPacket(packet);
+}
+
+
+bool
+sendBytesReceived(RTMP* r)
+{
+    RTMPPacket packet(4);
+  
+    packet.header.channel = CHANNEL_CONTROL1;
+    packet.header.packetType = PACKET_TYPE_BYTES_READ;
+  
+    SimpleBuffer& buf = *packet.buffer;
+  
+    buf.appendNetworkLong(r->_bytesIn);
+    r->_bytesInSent = r->_bytesIn;
+  
+    return r->sendPacket(packet);
+}
+
+
+void
+handleMetadata(RTMP& /*r*/, const boost::uint8_t* /* payload*/, 
+        unsigned int /*len*/)
+{
+    return;
+}
+
+void
+handleChangeChunkSize(RTMP& r, const RTMPPacket& packet)
+{
+    if (payloadSize(packet) >= 4) {
+        r._inChunkSize = AMF::readNetworkLong(payloadData(packet));
+        log_debug( "Changed chunk size to %d", r._inChunkSize);
+    }
+}
+
+void
+handleControl(RTMP& r, const RTMPPacket& packet)
+{
+
+    const size_t size = payloadSize(packet);
+
+    if (size < 2) {
+        log_error("Control packet too short");
+        return;
+    }
+    
+    const ControlType t = 
+        static_cast<ControlType>(AMF::readNetworkShort(payloadData(packet)));
+    
+    if (size < 6) {
+        log_error("Control packet (%s) data too short", t);
+        return;
+    }
+    
+    const int arg = AMF::readNetworkLong(payloadData(packet) + 2);
+    log_debug( "Received control packet %s with argument %s", t, arg);
+  
+    switch (t)
+    {
+  
+        case CONTROL_CLEAR_STREAM:
+            // TODO: handle this.
+            break;
+  
+        case CONTROL_CLEAR_BUFFER:
+            // TODO: handle this.
+            break;
+  
+        case CONTROL_STREAM_DRY:
+            break;
+  
+        case CONTROL_RESET_STREAM:
+            log_debug("Stream is recorded: %s", arg);
+            break;
+  
+        case CONTROL_PING:
+            sendCtrl(r, CONTROL_PONG, arg, 0);
+            break;
+  
+        case CONTROL_BUFFER_EMPTY:
+            // TODO: handle.
+            break;
+  
+        case CONTROL_BUFFER_READY:
+            // TODO: handle
+            break;
+  
+        default:
+            log_error("Received unknown or unhandled control %s", t);
+            break;
+    }
+  
+}
+
+void
+handleServerBW(RTMP& r, const RTMPPacket& packet)
+{
+    const boost::uint32_t bw = AMF::readNetworkLong(payloadData(packet));
+    log_debug( "Server bandwidth is %s", bw);
+    r.setServerBandwidth(bw);
+}
+
+void
+handleClientBW(RTMP& r, const RTMPPacket& packet)
+{
+    const boost::uint32_t bw = AMF::readNetworkLong(payloadData(packet));
+
+    r.setBandwidth(bw);
+
+    if (payloadSize(packet) > 4) r.m_nClientBW2 = payloadData(packet)[4];
+    else r.m_nClientBW2 = -1;
+      
+    log_debug( "Client bandwidth is %d %d", r.bandwidth(), +r.m_nClientBW2);
+}
+
+
+
+boost::int32_t
+decodeInt32LE(const boost::uint8_t* c)
+{
+    return (c[3] << 24) | (c[2] << 16) | (c[1] << 8) | c[0];
+}
+
+int
+encodeInt32LE(boost::uint8_t *output, int nVal)
+{
+    output[0] = nVal;
+    nVal >>= 8;
+    output[1] = nVal;
+    nVal >>= 8;
+    output[2] = nVal;
+    nVal >>= 8;
+    output[3] = nVal;
+    return 4;
+}
+
+void
+setupInvokePacket(RTMPPacket& packet)
+{
+    RTMPHeader& hr = packet.header;
+    // Control channel
+    hr.channel = CHANNEL_CONTROL2;
+    // Invoke
+    hr.packetType = PACKET_TYPE_INVOKE;
+}
+
+unsigned int
+decodeInt24(const boost::uint8_t *c)
+{
+    unsigned int val;
+    val = (c[0] << 16) | (c[1] << 8) | c[2];
+    return val;
+}
+
+boost::uint8_t*
+encodeInt16(boost::uint8_t *output, boost::uint8_t *outend, short nVal)
+{
+    if (output+2 > outend) return NULL;
+  
+    output[1] = nVal & 0xff;
+    output[0] = nVal >> 8;
+    return output + 2;
+}
+
+boost::uint8_t*
+encodeInt24(boost::uint8_t *output, boost::uint8_t *outend, int nVal)
+{
+    if (output + 3 > outend) return NULL;
+
+    output[2] = nVal & 0xff;
+    output[1] = nVal >> 8;
+    output[0] = nVal >> 16;
+    return output+3;
+}
+
+boost::uint8_t*
+encodeInt32(boost::uint8_t *output, boost::uint8_t *outend, int nVal)
+{
+    if (output+4 > outend) return NULL;
+
+    output[3] = nVal & 0xff;
+    output[2] = nVal >> 8;
+    output[1] = nVal >> 16;
+    output[0] = nVal >> 24;
+    return output + 4;
+}
+
+boost::uint32_t
+getUptime()
+{
+    struct tms t;
+    return times(&t) * 1000 / sysconf(_SC_CLK_TCK);
+}
+
+} // anonymous namespace
+
+std::ostream&
+operator<<(std::ostream& o, PacketType p)
+{
+    switch(p) {
+        case PACKET_TYPE_CHUNK_SIZE:
+            return o << "<chunk size packet>";
+        case PACKET_TYPE_BYTES_READ:
+            return o << "<bytes read packet>";
+        case PACKET_TYPE_CONTROL:
+            return o << "<control packet>";
+        case PACKET_TYPE_SERVERBW:
+            return o << "<server bw packet>";
+        case PACKET_TYPE_CLIENTBW:
+            return o << "<client bw packet>";
+        case PACKET_TYPE_AUDIO:
+            return o << "<audio packet>";
+        case PACKET_TYPE_VIDEO:
+            return o << "<video packet>";
+        case PACKET_TYPE_FLEX_STREAM_SEND:
+            return o << "<flex stream send packet>";
+        case PACKET_TYPE_FLEX_SHARED_OBJECT:
+            return o << "<flex sharedobject packet>";
+        case PACKET_TYPE_FLEX_MESSAGE:
+            return o << "<flex message packet>";
+        case PACKET_TYPE_METADATA:
+            return o << "<metadata packet>";
+        case PACKET_TYPE_SHARED_OBJECT:
+            return o << "<sharedobject packet>";
+        case PACKET_TYPE_INVOKE:
+            return o << "<invoke packet>";
+        case PACKET_TYPE_FLV:
+            return o << "<flv packet>";
+        default:
+            return o << "<unknown packet type " << +p << ">";
+    }
+}
+
+std::ostream&
+operator<<(std::ostream& o, ControlType t)
+{
+    switch (t) {
+
+        case CONTROL_CLEAR_STREAM:
+            return o << "<clear stream>";
+        case CONTROL_CLEAR_BUFFER:
+            return o << "<clear buffer>";
+        case CONTROL_STREAM_DRY:
+            return o << "<stream dry>";
+        case CONTROL_BUFFER_TIME:
+            return o << "<buffer time>";
+        case CONTROL_RESET_STREAM:
+            return o << "<reset stream>";
+        case CONTROL_PING:
+            return o << "<ping>";
+        case CONTROL_PONG:
+            return o << "<pong>";
+        case CONTROL_REQUEST_VERIFY:
+            return o << "<verify request>";
+        case CONTROL_RESPOND_VERIFY:
+            return o << "<verify response>";
+        case CONTROL_BUFFER_EMPTY:
+            return o << "<buffer empty>";
+        case CONTROL_BUFFER_READY:
+            return o << "<buffer ready>";
+        default:
+            return o << "<unknown control " << +t << ">";
+    }
+}
+
+} // namespace rtmp
+} // namespace gnash

=== added file 'libbase/RTMP.h'
--- a/libbase/RTMP.h    1970-01-01 00:00:00 +0000
+++ b/libbase/RTMP.h    2010-02-24 09:52:37 +0000
@@ -0,0 +1,499 @@
+//
+//   Copyright (C) 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+#ifndef GNASH_RTMP_H
+#define GNASH_RTMP_H
+
+#include <boost/cstdint.hpp>
+#include <boost/shared_ptr.hpp>
+#include <deque>
+#include <string>
+#include <map>
+
+#include "log.h"
+#include "SimpleBuffer.h"
+#include "Socket.h"
+#include "URL.h"
+
+#define RTMP_DEFAULT_CHUNKSIZE 128
+
+namespace gnash {
+
+namespace rtmp {
+
+/// Known control / ping codes
+//
+/// See http://jira.red5.org/confluence/display/docs/Ping (may not exist).
+//
+/// 0x00: Clear the stream. No third and fourth parameters. The second
+///       parameter could be 0. After the connection is established, a
+///       Ping 0,0 will be sent from server to client. The message will
+///       also be sent to client on the start of Play and in response of
+///       a Seek or Pause/Resume request. This Ping tells client to
+///       re-calibrate the clock with the timestamp of the next packet
+///       server sends.
+/// 0x01: Tell the stream to clear the playing buffer.
+/// 0x02: Stream dry (not sure what this means!)
+/// 0x03: Buffer time of the client. The third parameter is the buffer
+///       time in milliseconds.
+/// 0x04: Reset a stream. Used together with type 0 in the case of VOD.
+///       Often sent before type 0.
+/// 0x06: Ping the client from server. The second parameter is the current
+///       time.
+/// 0x07: Pong reply from client. The second parameter is the time the
+///       server sent with his ping request.
+/// 0x1a: SWFVerification request
+/// 0x1b: SWFVerification response
+/// 0x1f: Not sure, maybe buffer empty.
+/// 0x20: Buffer ready.
+enum ControlType
+{
+    CONTROL_CLEAR_STREAM = 0x00,
+    CONTROL_CLEAR_BUFFER = 0x01,
+    CONTROL_STREAM_DRY = 0x02,
+    CONTROL_BUFFER_TIME = 0x03,
+    CONTROL_RESET_STREAM = 0x04,
+    CONTROL_PING = 0x06,
+    CONTROL_PONG = 0x07,
+    CONTROL_REQUEST_VERIFY = 0x1a,
+    CONTROL_RESPOND_VERIFY = 0x1b,
+    CONTROL_BUFFER_EMPTY = 0x1f,
+    CONTROL_BUFFER_READY = 0x20
+};
+
+/// The known channels.
+//
+/// CHANNEL_CONTROL1 is for internal controls:
+///     sendCtrl
+///     send server BW
+///     send bytes received.
+//
+/// These contain no AMF data.
+//
+/// CHANNEL_CONTROL2 is for ActionScript controls
+///     _checkbw: AS
+///     _result:
+///     connect: Maybe from ASNative(2100, 0) (connect)
+///     createStream: AS
+///     deleteStream: Maybe ASNative(2100, 1) (close)
+///     FCSubscribe: Don't know.
+//
+/// These all contain AMF data.
+enum Channels
+{
+    CHANNEL_CONTROL1 = 0x02,
+    CHANNEL_CONTROL2 = 0x03,
+    CHANNEL_VIDEO = 0x08
+};
+
+/// The known packet types.
+enum PacketType
+{
+    PACKET_TYPE_NONE = 0x00,
+    PACKET_TYPE_CHUNK_SIZE = 0x01,
+    PACKET_TYPE_BYTES_READ = 0x03,
+    PACKET_TYPE_CONTROL = 0x04,
+    PACKET_TYPE_SERVERBW = 0x05,
+    PACKET_TYPE_CLIENTBW = 0x06,
+    PACKET_TYPE_AUDIO = 0x08,
+    PACKET_TYPE_VIDEO = 0x09,
+    PACKET_TYPE_FLEX_STREAM_SEND = 0x0f,
+    PACKET_TYPE_FLEX_SHARED_OBJECT = 0x10,
+    PACKET_TYPE_FLEX_MESSAGE = 0x11,
+    PACKET_TYPE_METADATA = 0x12,
+    PACKET_TYPE_SHARED_OBJECT = 0x13,
+    PACKET_TYPE_INVOKE = 0x14,
+    PACKET_TYPE_FLV = 0x16
+};
+
+/// The PacketSize specifies the number of fields contained in the header.
+//
+/// 1. A large packet has all header fields.
+///     We would expect the first packet on any channel to be large.
+/// 2. A medium packet has the same m_nInfoField2 and packet type.
+/// 3. A small packet has the same data size.
+/// 4. A minimal packet has all fields the same.
+//
+/// The minimal and small data packets can be used to send a payload in more
+/// than one packet. If the data received is smaller than the specified data
+/// size, the packet remains in the channel with its payload. Data from each
+/// new packet is added to this stored payload until all the advertised data
+/// is read.
+//
+/// These names are taken from rtmpdump.
+enum PacketSize {
+    RTMP_PACKET_SIZE_LARGE = 0,
+    RTMP_PACKET_SIZE_MEDIUM = 1,
+    RTMP_PACKET_SIZE_SMALL = 2,
+    RTMP_PACKET_SIZE_MINIMUM = 3,
+};
+
+/// The RTMPHeader contains all the fields for the packet header.
+struct RTMPHeader
+{
+    /// The maximum header size of an RTMP packet.
+    static const size_t headerSize = 18;
+
+    RTMPHeader()
+        :
+        headerType(RTMP_PACKET_SIZE_LARGE),
+        packetType(PACKET_TYPE_NONE),
+        _timestamp(0),
+        _streamID(0),
+        channel(0),
+        dataSize(0)
+    {}
+
+    PacketSize headerType;
+    PacketType packetType;
+
+    /// The timestamp.
+    //
+    /// This is encoded either as in the 3-byte relative timestamp field or the
+    /// 4 byte extended (absolute) timestamp field.
+    boost::uint32_t _timestamp;
+
+    /// This seems to be used for NetStream.play.
+    boost::uint32_t _streamID;
+
+    size_t channel;
+
+    // The size of the data.
+    size_t dataSize;
+
+};
+
+/// An RTMPPacket class contains a full description of an RTMP packet.
+//
+/// This comprises:
+///     header information
+///     an AMF payload.
+//
+/// An RTMPPacket may be copied without a large penalty. This is to allow
+/// storage in the RTMP client's channels.
+struct RTMPPacket
+{
+    /// Construct a packet with an optional reserved memory allocation.
+    //
+    /// @param reserve      The amount of space in bytes to reserve for the
+    ///                     message body. This can save reallocations when
+    ///                     appending AMF data. Space for the header is
+    ///                     always reserved and is not affected by this
+    ///                     parameter.
+    explicit RTMPPacket(size_t reserve = 0);
+    
+    /// Copy constructor.
+    //
+    /// Creates an identical RTMPPacket with shared ownership of the
+    /// buffer.
+    explicit RTMPPacket(const RTMPPacket& other);
+
+    ~RTMPPacket() {}
+
+    RTMPHeader header;
+
+    /// A buffer with enough storage to write the entire message.
+    //
+    /// This always includes at least the header. Storage for the message
+    /// payload is added as necessary.
+    boost::shared_ptr<SimpleBuffer> buffer;
+
+    size_t bytesRead;
+};
+
+
+/// Check whether an RTMPPacket has a payload.
+//
+/// Only stored packets may not have a payload. A packet without a payload
+/// has already been processed and is only used for its header information.
+bool
+hasPayload(const RTMPPacket& p)
+{
+    return (p.buffer.get());
+}
+
+/// Clear the message body and the bytes read of an RTMPPacket.
+//
+/// This is to be used to free used information from packets in a channel.
+/// The header information must be preserved for future packets, but the
+/// payload is no longer needed once read.
+void
+clearPayload(RTMPPacket& p)
+{
+    p.buffer.reset();
+    p.bytesRead = 0;
+}
+
+/// The current size of the space allocated for the message payload.
+//
+/// For messages we create, this matches the exact size of the payload. For
+/// messages we read, this is the expected size of the data.
+size_t
+payloadSize(const RTMPPacket& p)
+{
+    assert(hasPayload(p));
+    const SimpleBuffer& buf = *p.buffer;
+    assert(buf.size() >= RTMPHeader::headerSize);
+    return buf.size() - RTMPHeader::headerSize;
+}
+
+/// Access the payload data section of the buffer.
+boost::uint8_t*
+payloadData(RTMPPacket& p)
+{
+    assert(hasPayload(p));
+    SimpleBuffer& buf = *p.buffer;
+    return buf.data() + RTMPHeader::headerSize;
+}
+
+/// Access the payload data section of the buffer.
+const boost::uint8_t*
+payloadData(const RTMPPacket& p)
+{
+    assert(hasPayload(p));
+    const SimpleBuffer& buf = *p.buffer;
+    return buf.data() + RTMPHeader::headerSize;
+}
+
+/// Get the end of the allocated payload data section of the buffer.
+//
+/// Note that this is only valid for packets we create, and for packets
+/// we have fully read. Stored packets that have not yet received all data
+/// have allocated space that has not yet been written.
+const boost::uint8_t*
+payloadEnd(const RTMPPacket& p)
+{
+    assert(hasPayload(p));
+    SimpleBuffer& buf = *p.buffer;
+    return buf.data() + buf.size();
+}
+
+/// Check if a packet is ready for processing.
+//
+/// A packet is ready for processing if its payload size matches the data
+/// size in its header. It may take several successive packets to form a 
+/// complete packet. 
+bool
+isReady(const RTMPPacket& p) {
+    return p.bytesRead == p.header.dataSize;
+}
+
+
+/// This class is for handling the RTMP protocol.
+//
+/// What should happen:
+//
+/// The RTMP object should connect to a server.
+//
+/// Once that is done the caller should call "play" on a stream. This (and
+/// other commands) can be called as long as the connection is connected.
+//
+/// It can be closed and reconnected.
+struct DSOEXPORT RTMP
+{
+
+    /// Construct an RTMP handler.
+    RTMP();
+
+    ~RTMP() {}
+
+    /// Initiate a network connection.
+    //
+    /// Note that this only creates the TCP connection and carries out the
+    /// handshake. You must send a "connect" request before doing anything
+    /// else.
+    bool connect(const URL& url);
+
+    /// This is used for sending call requests from the core.
+    //
+    /// These are sent as invoke packets on CHANNEL_CONTROL2. The AMF data
+    /// should contain:
+    /// 1. method name,
+    /// 2. callback number,
+    /// 3. null,
+    /// 4. arg0..argn
+    void call(const SimpleBuffer& amf);
+
+    /// This is used for sending NetStream requests.
+    //
+    /// These include play and pause. They are sent as invoke packets on the
+    /// video channel. 
+    //
+    /// @param id       The stream ID to control. This is encoded in the 
header,
+    ///                 not the AMF payload.
+    void play(const SimpleBuffer& amf, int id);
+
+    /// Instruct server to buffer this much data.
+    //
+    /// @param time     time in milliseconds.
+    void setBufferTime(size_t time);
+
+    /// Whether we have a basic connection to a server.
+    //
+    /// This does not mean we are ready to send or receive streams. It does
+    /// mean that messages sent via call() will be transmitted to the server.
+    bool connected() const;
+
+    /// Called to do receives.
+    //
+    /// TODO: this is the least satisfactory part. Currently sends are
+    /// executed immediately. This is probably correct. Data are only
+    /// received when update() is called, and then only one packet at a
+    /// time.
+    void update();
+
+    /// Handle an RTMPPacket.
+    void handlePacket(const RTMPPacket& packet);
+
+    /// Close the connection.
+    //
+    /// A new connection may now be opened.
+    void close();
+    
+    /// Read from the socket.
+    int readSocket(boost::uint8_t* dst, int num);
+
+    /// Send an RTMPPacket on the connection.
+    bool sendPacket(RTMPPacket& packet);
+
+    /// Get an AMF message received from the server.
+    //
+    /// TODO: this returns the whole RTMP message, which is ugly. And it
+    /// only returns one at time, and can return a null pointer. We need
+    /// a better way to retrieve the messages.
+    boost::shared_ptr<SimpleBuffer> getMessage() {
+        if (_messageQueue.empty()) return boost::shared_ptr<SimpleBuffer>();
+        boost::shared_ptr<SimpleBuffer> b = _messageQueue.front();
+        _messageQueue.pop_front();
+        return b;
+    }
+    
+    /// Get an FLV packet received from the server
+    //
+    /// TODO: this returns the whole RTMP message, which is ugly. And it
+    /// only returns one at time, and can return a null pointer. We need
+    /// a better way to retrieve the frames.
+    boost::shared_ptr<SimpleBuffer> getFLVFrame() {
+        if (_flvQueue.empty()) return boost::shared_ptr<SimpleBuffer>();
+        boost::shared_ptr<SimpleBuffer> b = _flvQueue.front();
+        _flvQueue.pop_front();
+        return b;
+    }
+
+    /// Store the server bandwidth
+    //
+    /// Not sure why we need this.
+    void setServerBandwidth(boost::uint32_t bw) {
+        _serverBandwidth = bw;
+    }
+
+    /// Get the stored server bandwidth.
+    boost::uint32_t serverBandwidth() const {
+        return _serverBandwidth;
+    }
+
+    /// Store our bandwidth
+    void setBandwidth(boost::uint32_t bw) {
+        _bandwidth = bw;
+    }
+
+    /// Get our bandwidth.
+    boost::uint32_t bandwidth() const {
+        return _bandwidth;
+    }
+
+    int _inChunkSize;
+    int m_stream_id;
+    int m_mediaChannel;
+    boost::uint8_t m_nClientBW2;
+    size_t _bytesIn;
+    size_t _bytesInSent;
+
+private:
+
+    enum ChannelType {
+        CHANNELS_IN,
+        CHANNELS_OUT
+    };
+    
+    /// Read an RTMP packet from the connection.
+    bool readPacket(RTMPPacket& packet);
+
+    /// Check whether a packet exists on a channel.
+    bool hasPacket(ChannelType t, size_t channel) const;
+
+    /// Retrieve a stored packet.
+    //
+    /// If no packet exists on the channel, a new one will be created and
+    /// returned. Use hasPacket() to check whether a previous packet exists.
+    RTMPPacket& getPacket(ChannelType t, size_t channel);
+
+    /// Store a packet in a channel.
+    //
+    /// A copy of the packet is stored and returned. Any data is shared
+    /// between the copies until explicitly reset.
+    RTMPPacket& storePacket(ChannelType t, size_t channel, const RTMPPacket& 
p);
+
+    /// Carry out the handshake.
+    //
+    /// This is called internally on connect().
+    bool handShake();
+
+    /// A set of channels. An RTMP handler has two sets.
+    //
+    /// Packets are stored on these channels. As soon as a packet has been
+    /// processed, its payload is removed. The header remains in memory to
+    /// allow compression of later packets.
+    //
+    /// RTMPPackets must be stored with an absolute timestamp.
+    typedef std::map<size_t, RTMPPacket> ChannelSet;
+    
+    Socket _socket;
+
+    /// A set of channels for receiving packets.
+    ChannelSet _inChannels;
+
+    /// A set of channels for sending packets.
+    ChannelSet _outChannels;
+    
+    std::deque<boost::shared_ptr<SimpleBuffer> > _messageQueue;
+    std::deque<boost::shared_ptr<SimpleBuffer> > _flvQueue;
+
+    /// Stored server bandwidth (reported by server).
+    boost::uint32_t _serverBandwidth;
+
+    /// Stored client bandwidth (ours), reported by server.
+    boost::uint32_t _bandwidth;
+
+    /// Chunk size for sending.
+    size_t _outChunkSize;
+
+};
+
+/// Send a control packet
+bool sendCtrl(RTMP& r, ControlType, unsigned int nObject, unsigned int nTime);
+
+/// Logging assistance for PacketType.
+std::ostream& operator<<(std::ostream& o, PacketType p);
+
+/// Logging assistance for ControlType.
+std::ostream& operator<<(std::ostream& o, ControlType t);
+
+} // namespace rtmp
+
+} // namespace gnash
+#endif

=== added file 'libbase/Socket.cpp'
--- a/libbase/Socket.cpp        1970-01-01 00:00:00 +0000
+++ b/libbase/Socket.cpp        2010-02-23 15:28:45 +0000
@@ -0,0 +1,243 @@
+// Socket.cpp - an IOChannel for sockets
+//
+//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
+//   Foundation, Inc
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+//
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/times.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <boost/cstdint.hpp>
+#include <cstring>
+
+#include "URL.h"
+#include "Socket.h"
+#include "log.h"
+#include "GnashAlgorithm.h"
+
+namespace gnash
+{
+
+Socket::Socket()
+    :
+    _socket(0),
+    _size(0),
+    _pos(0),
+    _timedOut(false)
+{}
+
+
+void
+Socket::close()
+{
+    if (_socket) ::close(_socket);
+    _socket = 0;
+    _size = 0;
+}
+
+bool
+Socket::connect(const URL& url)
+{
+
+    if (connected()) {
+        log_error("Connection attempt while already connected");
+        return false;
+    }
+
+    const std::string& hostname = url.hostname();
+    if (hostname.empty()) return false;
+
+    struct sockaddr_in addr;
+    std::memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+
+    addr.sin_addr.s_addr = inet_addr(hostname.c_str());
+    if (addr.sin_addr.s_addr == INADDR_NONE) {
+        struct hostent* host = gethostbyname(hostname.c_str());
+        if (!host || !host->h_addr) {
+            return false;
+        }
+        addr.sin_addr = *reinterpret_cast<in_addr*>(host->h_addr);
+    }
+
+    const std::string& port = url.port();
+    const int p = port.empty() ? 0 : boost::lexical_cast<int>(port);
+    addr.sin_port = htons(p);
+
+    _timedOut = false;
+
+    _socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+    if (_socket != -1) {
+        const struct sockaddr* a = reinterpret_cast<struct sockaddr*>(&addr);
+
+        if (::connect(_socket, a, sizeof(struct sockaddr)) < 0) {
+            const int err = errno;
+            log_error("Failed to connect socket: %s", std::strerror(err));
+            close();
+            return false;
+        }
+
+    }
+
+    // Magic timeout number. Use rcfile ?
+    struct timeval tv = { 120, 0 };
+
+    if (setsockopt(_socket, SOL_SOCKET, SO_RCVTIMEO,
+                reinterpret_cast<unsigned char*>(&tv), sizeof(tv))) {
+        log_error("Setting socket timeout failed");
+    }
+
+    const boost::int32_t on = 1;
+    setsockopt(_socket, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
+
+    assert(_socket);
+    return true;
+}
+
+
+std::streamsize
+Socket::fillCache()
+{
+
+    // If there are no unprocessed bytes, start from the beginning.
+    if (!_size) {
+        _pos = _cache;
+    }
+
+    while (1) {
+
+
+        // Read up to the end of the cache if possible.
+        const int toRead = arraySize(_cache) - _size - (_pos - _cache);
+
+        const int bytesRead = recv(_socket, _pos + _size, toRead, 0);
+        
+        if (bytesRead == -1) {
+            const int err = errno;
+            log_debug("Socket receive error %s", std::strerror(err));
+            //if (err == EINTR) continue;
+            
+            if (err == EWOULDBLOCK || err == EAGAIN) {
+                // Nothing read.
+                _timedOut = true;
+                return 0;
+            }
+        }
+        _size += bytesRead;
+        return bytesRead;
+    }
+}
+
+
+// Do a single read and report how many bytes were read.
+std::streamsize 
+Socket::read(void* dst, std::streamsize num)
+{
+    
+    int toRead = num;
+    _timedOut = false;
+
+    boost::uint8_t* ptr = static_cast<boost::uint8_t*>(dst);
+
+    if (!_size) {
+        if (fillCache() < 1) {
+            if (_timedOut) {
+                return -1;
+            }
+        }
+    }
+
+    const int thisRead = std::min(_size, toRead);
+    if (thisRead > 0) {
+        std::copy(_pos, _pos + thisRead, ptr);
+        _pos += thisRead;
+        _size -= thisRead;
+    }
+    return thisRead;
+}
+
+std::streamsize
+Socket::readNonBlocking(void* dst, std::streamsize num)
+{
+    return read(dst, num);
+}
+
+std::streamsize
+Socket::write(const void* src, std::streamsize num)
+{
+    assert(!bad());
+
+    int bytesSent = 0;
+    int toWrite = num;
+
+    const boost::uint8_t* buf = static_cast<const boost::uint8_t*>(src);
+
+    while (toWrite > 0) {
+        bytesSent = ::send(_socket, buf, toWrite, 0);
+        //log_debug("Bytes sent %s", bytesSent);
+        if (bytesSent < 0) {
+            const int err = errno;
+            log_error("Socket send error %s", std::strerror(err));
+            close();
+            return -1;
+            break;
+        }
+        if (bytesSent == 0) break;
+        toWrite -= bytesSent;
+        buf += bytesSent;
+    }
+    return num - toWrite;
+}
+
+std::streampos
+Socket::tell() const
+{
+    log_error("tell() called for Socket");
+    return static_cast<std::streamsize>(-1);
+}
+
+bool
+Socket::seek(std::streampos)
+{
+    log_error("seek() called for Socket");
+    return false;
+}
+
+void
+Socket::go_to_end()
+{
+    log_error("go_to_end() called for Socket");
+}
+
+bool
+Socket::eof() const
+{
+    log_error("eof() called for Socket");
+    return false;
+}
+
+bool
+Socket::bad() const
+{
+    return !_socket || _timedOut;
+}
+
+} // namespace gnash

=== added file 'libbase/Socket.h'
--- a/libbase/Socket.h  1970-01-01 00:00:00 +0000
+++ b/libbase/Socket.h  2010-02-23 15:28:45 +0000
@@ -0,0 +1,119 @@
+// Socket.h - a virtual IO channel, for Gnash
+// 
+//   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010 Free Software
+//   Foundation, Inc
+// 
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+// 
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+#ifndef GNASH_SOCKET_H
+#define GNASH_SOCKET_H
+
+#include "dsodefs.h"
+#include <boost/cstdint.hpp>
+#include "IOChannel.h"
+
+namespace gnash {
+    class URL;
+}
+
+namespace gnash {
+
+/// A simple IOChannel subclass for reading and writing sockets.
+//
+/// The only important functions here are read and write.
+class DSOEXPORT Socket : public IOChannel
+{
+public:
+
+    Socket();
+
+    virtual ~Socket() {}
+
+    bool connect(const URL& url);
+
+    void close();
+
+    bool connected() const {
+        return (_socket);
+    }
+
+    bool timedOut() const {
+        return (_timedOut);
+    }
+
+    /// Read the given number of bytes from the stream
+    //
+    /// Return the number of bytes actually read. 
+    virtual std::streamsize read(void* dst, std::streamsize num);
+
+    /// The same as read().
+    virtual std::streamsize readNonBlocking(void* dst, std::streamsize num);
+
+    /// Write the given number of bytes to the stream
+    virtual std::streamsize write(const void* src, std::streamsize num);
+    
+    /// Return current stream position
+    //
+    /// Meaningless for a Socket.
+    virtual std::streampos tell() const;
+
+    /// Seek to the specified position
+    //
+    /// Meaningless for a Socket.
+    virtual bool seek(std::streampos p);
+
+    /// Seek to the end of the stream
+    //
+    /// Not Socket.
+    virtual void go_to_end();
+
+    /// Return true if the end of the stream has been reached.
+    //
+    /// Not implemented for Socket.
+    virtual bool eof() const;
+    
+    /// Return true if the stream is in an error state
+    virtual bool bad() const;
+
+private:
+
+    /// Fill the cache.
+    std::streamsize fillCache();
+
+    /// A cache for received data.
+    boost::uint8_t _cache[16384];
+
+    /// The socket ID.
+    int _socket;
+
+    /// Unprocessed bytes.
+    int _size;
+
+    /// Current read position in cache.
+    boost::uint8_t* _pos;
+
+    bool _timedOut;
+};
+
+} // namespace gnash
+
+#endif // GNASH_IOCHANNEL_H
+
+
+// Local Variables:
+// mode: C++
+// indent-tabs-mode: t
+// End:

=== modified file 'utilities/Makefile.am'
--- a/utilities/Makefile.am     2010-02-08 15:47:55 +0000
+++ b/utilities/Makefile.am     2010-02-24 06:45:26 +0000
@@ -101,7 +101,7 @@
 endif
 endif
 
-bin_PROGRAMS = gprocessor soldumper flvdumper
+bin_PROGRAMS = gprocessor soldumper flvdumper rtmpget
 #check_PROGRAMS = gdebug.swf
 
 if USE_GST_ENGINE
@@ -117,6 +117,8 @@
 gprocessor_LDFLAGS = -export-dynamic
 gprocessor_LDADD = $(GNASH_LIBS) $(AM_LDFLAGS)
 
+rtmpget_SOURCES = rtmpget.cpp 
+rtmpget_LDADD =  $(GNASH_LIBS) $(AM_LDFLAGS)
 
 #dumpshm_SOURCES = dumpshm.cpp
 #dumpshm_LDADD = $(GNASH_LIBS) $(AM_LDFLAGS)

=== added file 'utilities/rtmpget.cpp'
--- a/utilities/rtmpget.cpp     1970-01-01 00:00:00 +0000
+++ b/utilities/rtmpget.cpp     2010-02-24 09:56:02 +0000
@@ -0,0 +1,732 @@
+// rtmpdump.cpp:  RTMP file downloader utility
+// 
+//   Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
+// 
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 3 of the License, or
+// (at your option) any later version.
+// 
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+// 
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+// 
+
+#include "RTMP.h"
+#include <string>
+#include "log.h"
+#include "arg_parser.h"
+#include "SimpleBuffer.h"
+#include "AMF.h"
+#include "GnashAlgorithm.h"
+
+#include <boost/cstdint.hpp>
+#include <iomanip>
+#include <map>
+#include <algorithm>
+#include <iterator>
+#include <fstream>
+
+using namespace gnash;
+
+/// The play command is initiated by NetStream.play. This must specify a
+/// play path, and can add three further optional arguments. These are:
+///     1. start: start offset, or -1 for a live stream, or -2 for either
+///        a live stream if found, or a recorded stream if not.
+///     2. length: how long to play, or -1 for all of a live stream, or 0 for
+///                a single frame.
+///     3. reset: (object) no documentation.
+/// This class should not care about these! NetStream / NetConnection should
+/// encode all the play arguments and send them. The play packet should be
+/// on the video channel (what about audio?) and be an "invoke" packet.
+//
+/// ActionScript can send invoke packets directly using NetConnection call.
+//
+/// TODO:   This class needs a function to be called at heartbeat rate so that
+///         the data can be processed.
+/// TODO:   Work out which messages should be handled internally and which
+///         should be made available to the core (maybe all).
+///             1. Core events are those that should be available to AS. They
+///                include: onStatus.
+///             2. Internal events are not important to AS. We don't know
+///                if they ever get through. They certainly aren't documented
+///                and they may expect a certain response. They include:
+///                _onbwdone, _onbwcheck, _result. The _result function is
+///                almost certainly forwarded as onResult.
+///             3. The client should send createStream. This is builtin to
+///                to actionscript.
+// function NetStream(connection) {
+//  
+//     function OnCreate(nStream) {
+//          this.nStream = nStream;
+//     }
+//  
+//     // Some kind of type thing associating NetStream and NetConnection.
+//     ASnative(2101, 200)(this, connection);
+//  
+//     var _local2 = OnCreate.prototype;
+//  
+//     /// The server should send onResult with stream ID.
+//     //
+//     /// What does the function do? Stores the stream ID somewhere so it
+//     /// can 
+//     _local2.onResult = function (streamId) {
+//         ASnative(2101, 201)(this.nStream, streamId);
+//     };
+//  
+//      /// onStatus messages are forwarded to the NetStream object.
+//     _local2.onStatus = function (info) {
+//         this.nStream.onStatus(info);
+//     };
+// 
+//     /// Send invoke packet with createStream. The callback is the OnCreate
+//     /// object.
+//     connection.call("createStream", new OnCreate(this));
+// }
+
+namespace {
+    void usage(std::ostream& o);
+}
+
+class FakeNC
+{
+public:
+
+    FakeNC()
+        :
+        _callCount(0),
+        _seek(0),
+        _len(-1)
+    {}
+
+    size_t callNumber() {
+        return _callCount++;
+    }
+
+    void queueCall(size_t n, const std::string& call) {
+        _calls.insert(std::make_pair(n, call));
+    }
+
+    std::string getCall(size_t n) {
+        std::map<size_t, std::string>::iterator i = _calls.find(n);
+        if (i == _calls.end()) return "";
+
+        std::string s = i->second;
+        _calls.erase(i);
+        return s;
+    }
+
+    void setPlayPath(const std::string& p) {
+        _playpath = p;
+    }
+
+    const std::string& playpath() const {
+        return _playpath;
+    }
+
+    void setSeekTime(double secs) {
+        _seek = secs;
+    }
+
+    double seekTime() const {
+        return _seek;
+    }
+
+    void setLength(double len) {
+        _len = len;
+    }
+
+    double length() const {
+        return _len;
+    }
+
+private:
+    size_t _callCount;
+    std::map<size_t, std::string> _calls;
+
+    std::string _playpath;
+
+    double _seek, _len;
+};
+
+void
+writeFLVHeader(std::ostream& o)
+{
+    char flvHeader[] = {
+        'F',  'L',  'V',  0x01,
+        0x05,
+        0x00, 0x00, 0x00, 0x09,
+        0x00, 0x00, 0x00, 0x00 // first prevTagSize=0
+    };
+
+    o.write(flvHeader, arraySize(flvHeader));
+}
+
+
+bool handleInvoke(rtmp::RTMP& r, FakeNC& nc, const boost::uint8_t* payload,
+        const boost::uint8_t* end);
+
+/// These functions create an RTMP call buffer and send it. They mimic
+/// NetConnection.call() methods and replies to server calls.
+//
+/// If a call is initiated by us, we send our own call number.
+/// If we are replying to a server call, we send the server's call number back.
+void
+sendConnectPacket(rtmp::RTMP& r, FakeNC& nc, const std::string& app,
+        const std::string& ver, const std::string& swfurl,
+        const std::string& tcurl, const std::string& pageurl)
+{
+    log_debug("Sending connect packet.");
+    
+    log_debug("app      : %s", app);
+    log_debug("flashVer : %s", ver);
+    log_debug("tcURL    : %s", tcurl);
+    log_debug("swfURL   : %s", swfurl);
+    log_debug("pageURL  : %s", pageurl);
+
+    SimpleBuffer buf;
+
+    AMF::write(buf, "connect");
+    const size_t cn = nc.callNumber();
+    
+    /// Call number?
+    AMF::write(buf, static_cast<double>(cn));
+
+    buf.appendByte(AMF::OBJECT_AMF0);
+    if (!app.empty()) AMF::writeProperty(buf, "app", app);
+    if (!ver.empty()) AMF::writeProperty(buf, "flashVer", ver);
+    if (!swfurl.empty()) AMF::writeProperty(buf, "swfUrl", swfurl);
+    if (!tcurl.empty()) AMF::writeProperty(buf, "tcUrl", tcurl);
+    AMF::writeProperty(buf, "fpad", false);
+    AMF::writeProperty(buf, "capabilities", 15.0);
+    AMF::writeProperty(buf, "audioCodecs", 3191.0);
+    AMF::writeProperty(buf, "videoCodecs", 252.0);
+    AMF::writeProperty(buf, "videoFunction", 1.0);
+    if (!pageurl.empty()) AMF::writeProperty(buf, "pageUrl", pageurl);
+    buf.appendByte(0);
+    buf.appendByte(0);
+    buf.appendByte(AMF::OBJECT_END_AMF0);
+
+    nc.queueCall(cn, "connect");
+    r.call(buf);
+
+}
+
+void
+sendCheckBW(rtmp::RTMP& r, FakeNC& nc)
+{
+    SimpleBuffer buf;
+    
+    const size_t cn = nc.callNumber();
+
+    AMF::write(buf, "_checkbw");
+    AMF::write(buf, static_cast<double>(cn));
+    buf.appendByte(AMF::NULL_AMF0);
+
+    nc.queueCall(cn, "_checkbw");
+    r.call(buf);
+}
+
+void
+replyBWCheck(rtmp::RTMP& r, FakeNC& /*nc*/, double txn)
+{
+    // Infofield1?
+    SimpleBuffer buf;
+    AMF::write(buf, "_result");
+    AMF::write(buf, txn);
+    buf.appendByte(AMF::NULL_AMF0);
+    AMF::write(buf, 0.0);
+    
+    r.call(buf);
+
+}
+
+void
+sendPausePacket(rtmp::RTMP& r, FakeNC& /*nc*/, bool flag, double time)
+{
+    // TODO: store in NetStream or NC?
+    const int streamid = r.m_stream_id;
+
+    SimpleBuffer buf;
+
+    AMF::write(buf, "pause");
+
+    // What is this? The play stream? Call number?
+    AMF::write(buf, 0.0);
+    buf.appendByte(AMF::NULL_AMF0);
+
+    log_debug( "Pause: flag=%s, time=%d", flag, time);
+    AMF::write(buf, flag);
+
+    // "this.time", i.e. NetStream.time.
+    AMF::write(buf, time * 1000.0);
+
+    r.play(buf, streamid);
+}
+
+// Which channel to send on? Always video?
+//ASnative(2101, 202)(this, "play", null, name, start * 1000, len * 1000, 
reset);
+// This call is not queued (it's a play call, and doesn't have a callback).
+void
+sendPlayPacket(rtmp::RTMP& r, FakeNC& nc)
+{
+
+    // TODO: where should we store this?
+    const int streamid = r.m_stream_id;
+    const double seektime = nc.seekTime() * 1000.0;
+    const double length = nc.length() * 1000.0;
+
+    log_debug("Sending play packet. Stream id: %s, playpath %s", streamid,
+            nc.playpath());
+
+    SimpleBuffer buf;
+
+    AMF::write(buf, "play");
+
+    // What is this? The play stream? Call number?
+    AMF::write(buf, 0.0);
+    buf.appendByte(AMF::NULL_AMF0);
+
+    log_debug( "seekTime=%.2f, dLength=%d, sending play: %s",
+        seektime, length, nc.playpath());
+    AMF::write(buf, nc.playpath());
+
+    // Optional parameters start and len.
+    //
+    // start: -2, -1, 0, positive number
+    //  -2: looks for a live stream, then a recorded stream, if not found
+    //  any open a live stream
+    //  -1: plays a live stream
+    // >=0: plays a recorded streams from 'start' milliseconds
+    AMF::write(buf, seektime);
+
+    // len: -1, 0, positive number
+    //  -1: plays live or recorded stream to the end (default)
+    //   0: plays a frame 'start' ms away from the beginning
+    //  >0: plays a live or recoded stream for 'len' milliseconds
+    //enc += EncodeNumber(enc, -1.0); // len
+    AMF::write(buf, length);
+    
+    r.play(buf, streamid);
+}
+
+void
+sendCreateStream(rtmp::RTMP& r, FakeNC& nc)
+{
+    const size_t cn = nc.callNumber();
+
+    SimpleBuffer buf;
+    AMF::write(buf, "createStream");
+    AMF::write(buf, static_cast<double>(cn));
+    buf.appendByte(AMF::NULL_AMF0);
+    nc.queueCall(cn, "createStream");
+    r.call(buf);
+}
+void
+sendDeleteStream(rtmp::RTMP& r, FakeNC& nc, double id)
+{
+    const size_t cn = nc.callNumber();
+
+    SimpleBuffer buf;
+    AMF::write(buf, "deleteStream");
+
+    // Call number?
+    AMF::write(buf, static_cast<double>(cn));
+    buf.appendByte(AMF::NULL_AMF0);
+    AMF::write(buf, id);
+    nc.queueCall(cn, "deleteStream");
+    r.call(buf);
+}
+
+void
+sendFCSubscribe(rtmp::RTMP& r, FakeNC& nc, const std::string& subscribepath)
+{
+    const size_t cn = nc.callNumber();
+
+    SimpleBuffer buf;
+    AMF::write(buf, "FCSubscribe");
+
+    // What is this?
+    AMF::write(buf, static_cast<double>(cn));
+    buf.appendByte(AMF::NULL_AMF0);
+    AMF::write(buf, subscribepath);
+
+    nc.queueCall(cn, "FCSubscribe");
+    r.call(buf);
+}
+
+/// Some URLs to try are:
+//
+/// -u rtmp://tagesschau.fcod.llnwd.net:1935/a3705/d1
+/// with -p 2010/0216/TV-20100216-0911-2401.hi or
+///      -p 2010/0216/TV-20100216-0911-2401.lo
+//
+/// -u rtmp://ndr.fc.llnwd.net:1935/ndr/_definst_
+/// with -p ndr_fs_nds_hi_flv *and* -s -1 (live stream)
+int
+main(int argc, char** argv)
+{
+   const Arg_parser::Option opts[] =
+        {
+        { 'h', "help",          Arg_parser::no  },
+        { 'v', "verbose",       Arg_parser::no  },
+        { 'u', "url",           Arg_parser::yes  },
+        { 'p', "playpath",      Arg_parser::yes  },
+        { 's', "seek",          Arg_parser::yes  },
+        { 'l', "length",        Arg_parser::yes  },
+        { 'o', "outfile",       Arg_parser::yes  }
+        };
+
+    Arg_parser parser(argc, argv, opts);
+
+    if (!parser.error().empty())  {
+        std::cout << parser.error() << std::endl;
+        std::exit(EXIT_FAILURE);
+    }
+    gnash::LogFile& l = gnash::LogFile::getDefaultInstance();
+
+    std::string url;
+    std::string playpath;
+    std::string tc;
+    std::string swf;
+    std::string page;
+    std::string outf;
+
+    double seek = 0, len = -1;
+
+    for (int i = 0; i < parser.arguments(); ++i) {
+        const int code = parser.code(i);
+        try {
+            switch (code) {
+              case 'h':
+                  usage(std::cout);
+                  exit(EXIT_SUCCESS);
+              case 'u':
+                  url = parser.argument(i);
+                  break;
+              case 'p':
+                  playpath = parser.argument(i);
+                  break;
+              case 't':
+                  tc = parser.argument(i);
+                  break;
+              case 's':
+                  seek = parser.argument<double>(i);
+                  break;
+              case 'l':
+                  len = parser.argument<double>(i);
+                  break;
+              case 'o':
+                  outf = parser.argument(i);
+                  break;
+              case 'v':
+                  l.setVerbosity();
+                  break;
+            }
+        }
+        catch (Arg_parser::ArgParserException &e) {
+            std::cerr << _("Error parsing command line: ") << e.what() << "\n";
+            std::exit(EXIT_FAILURE);
+        }
+    }
+
+    if (url.empty() || playpath.empty()) {
+        std::cerr << "You must specify URL and playpath\n";
+        std::exit(EXIT_FAILURE);
+    }
+
+    if (outf.empty()) {
+        std::cerr << "No output file specified. Will connect anyway\n";
+    }
+
+    std::ofstream flv;
+
+    if (!outf.empty()) {
+        flv.open(outf.c_str());
+        if (flv) writeFLVHeader(flv);
+    }
+
+    URL playurl(url);
+    if (tc.empty()) tc = playurl.str();
+
+    const std::string app = playurl.path().substr(1);
+
+    std::string ver = "LNX 10,0,22,87";
+
+    gnash::rtmp::RTMP r;
+    FakeNC nc;
+    nc.setPlayPath(playpath);
+    nc.setLength(len);
+    nc.setSeekTime(seek);
+
+    log_debug("Initial connection");
+
+    if (!r.connect(url)) {
+        log_error("Initial connection failed!");
+        std::exit(EXIT_FAILURE);
+    }
+
+    /// 1. connect.
+    sendConnectPacket(r, nc, app, ver, swf, tc, page);
+ 
+    /// Check bandwidth.
+    sendCheckBW(r, nc);   
+
+    log_debug("Connect packet sent.");
+
+    while (1) {
+        r.update();
+
+        /// Retrieve messages.
+        boost::shared_ptr<SimpleBuffer> b = r.getMessage();
+        while (b.get()) {
+            handleInvoke(r, nc, b->data() + rtmp::RTMPHeader::headerSize,
+                    b->data() + b->size());
+            b = r.getMessage();
+        }
+
+        /// Retrive video packets.
+        boost::shared_ptr<SimpleBuffer> f = r.getFLVFrame();
+        while (f.get()) {
+            if (flv) {
+                const char* start = reinterpret_cast<const char*>(
+                        f->data() + rtmp::RTMPHeader::headerSize);
+                flv.write(start, f->size() - rtmp::RTMPHeader::headerSize);
+            }
+            f = r.getMessage();
+        }
+
+        if (!r.connected()) break;
+    }
+
+}
+
+bool
+handleInvoke(rtmp::RTMP& r, FakeNC& nc, const boost::uint8_t* payload,
+        const boost::uint8_t* end)
+{
+    assert(payload != end);
+
+    // make sure it is a string method name we start with
+    if (payload[0] != 0x02) {
+        log_error( "%s, Sanity failed. no string method in invoke packet",
+                __FUNCTION__);
+        return false;
+    }
+
+    ++payload;
+    std::string method = AMF::readString(payload, end);
+
+    log_debug("Invoke: read method string %s", method);
+    if (*payload != AMF::NUMBER_AMF0) return false;
+    ++payload;
+
+
+    log_debug( "%s, server invoking <%s>", __FUNCTION__, method);
+
+    bool ret = false;
+
+
+    /// _result means it's the answer to a remote method call initiated
+    /// by us.
+    if (method == "_result") {
+        
+        const double txn = AMF::readNumber(payload, end);
+        std::string calledMethod = nc.getCall(txn);
+
+        log_debug("Received result for method call %s (%s)",
+                calledMethod, boost::io::group(std::setprecision(15), txn));
+
+        if (calledMethod == "connect")
+        {
+            // Do here.
+            sendCreateStream(r, nc);
+        }
+
+        else if (calledMethod == "createStream") {
+            
+            log_debug("createStream invoked");
+            if (*payload != AMF::NULL_AMF0) return false;
+            ++payload;
+
+            r.m_stream_id = AMF::readNumber(payload, end);
+
+            log_debug("Stream ID: %s", r.m_stream_id);
+            r.m_stream_id = 1;
+
+            /// Issue NetStream.play command.
+            sendPlayPacket(r, nc);
+
+            /// Allows quick downloading.
+            r.setBufferTime(3600000);
+        }
+
+        else if (calledMethod == "play") {
+            log_debug("Play called");
+        }
+        return ret;
+    }
+
+    /// These are remote function calls initiated by the server .
+
+    const double txn = AMF::readNumber(payload, end);
+    log_debug("Received server call %s %s",
+            boost::io::group(std::setprecision(15), txn),
+            txn ? "" : "(no reply expected)");
+
+    /// This must return a value. It can be anything.
+    if (method == "onBWCheck") {
+        if (txn) replyBWCheck(r, nc, txn);
+        else {
+            log_error("Expected call number for onBWCheck");
+        }
+    }
+    
+    /// If the server sends this, we reply (the call should contain a
+    /// callback object!).
+    if (method == "_onbwcheck") {
+        if (txn) replyBWCheck(r, nc, txn);
+        else {
+            log_error("Server called _onbwcheck without a callback");
+        }
+        return ret;
+    }
+
+    // This should be called by the server when the bandwidth test is finished.
+    //
+    // It contains information, but we don't have to do anything.
+    if (method == "onBWDone") {
+        // This is a SWF implementation detail, not required by the protocol.
+        //sendCheckBW(r, nc);
+        return ret;
+    }
+
+    if (method == "_onbwdone") {
+
+        if (*payload != AMF::NULL_AMF0) return false;
+        ++payload;
+
+        log_debug("AMF buffer for _onbwdone: %s\n",
+                hexify(payload, end - payload, false));
+
+        double latency = AMF::readNumber(payload, end);
+        double bandwidth = AMF::readNumber(payload, end);
+        log_debug("Latency: %s, bandwidth %s", latency, bandwidth);
+        return ret;
+    }
+
+    /// Don't know when it sends this.
+    if (method == "onFCSubscribe") {
+        return ret;
+    }
+
+    /// Or this.
+    if (method == "onFCUnsubscribe") {
+        r.close();
+        ret = true;
+        return ret;
+    }
+
+    if (method ==  "_error") {
+        log_error( "rtmp server sent error");
+        std::exit(EXIT_FAILURE);
+    }
+    
+    if (method == "close") {
+        log_error( "rtmp server requested close");
+        r.close();
+        return ret;
+    }
+    
+    if (method == "onStatus") {
+        if (*payload != AMF::NULL_AMF0) return false;
+        ++payload;
+#if 1
+        log_debug("AMF buffer for onstatus: %s",
+                hexify(payload, end - payload, true));
+#endif
+        if (*payload != AMF::OBJECT_AMF0) {
+            log_debug("not an object");
+            return false;
+        }
+        ++payload;
+        if (payload == end) return false;
+
+        std::string code;
+        std::string level;
+        try {
+
+            // Hack.
+            while (payload < end && *payload != AMF::OBJECT_END_AMF0) {
+
+                const std::string& n = AMF::readString(payload, end);
+                if (n.empty()) continue;
+
+                //log_debug("read string %s", n);
+                if (payload == end) break;
+
+                if (*payload != AMF::STRING_AMF0) return false;
+                ++payload;
+                if (payload == end) break;
+
+                const std::string& v = AMF::readString(payload, end);
+                if (payload == end) break;
+                if (n == "code") code = v;
+                if (n == "level") level = v;
+            }
+        }
+        catch (const AMF::AMFException& e) {
+            throw;
+            return false;
+        }
+
+        if (code.empty() || level.empty()) return false;
+
+        //log_debug( "%s, onStatus: %s", __FUNCTION__, code);
+        if (code == "NetStream.Failed"
+                || code == "NetStream.Play.Failed"
+                || code == "NetStream.Play.StreamNotFound"
+                || code == "NetConnection.Connect.InvalidApp")
+        {
+            r.m_stream_id = -1;
+            r.close();
+            log_error( "Closing connection: %s", code);
+        }
+
+        if (code == "NetStream.Play.Start") {
+            log_debug("Netstream.Play.Start called");
+        }
+
+        // Return 1 if this is a Play.Complete or Play.Stop
+        if (code == "NetStream.Play.Complete" ||
+                code == "NetStream.Play.Stop") {
+            r.close();
+            ret = true;
+        }
+    }
+    return ret;
+}
+
+namespace {
+
+void
+usage(std::ostream& o)
+{
+    o << "usage: rtmpdump -u <app> -p playpath\n";
+    o << "\n";
+    o << "-u <url>  The full url, including port, of the rtmp application\n";
+    o << "-p <path> The play path of the stream (what is passed to "
+        "NetStream.play()\n";
+    o << "-s <sec>  Start at the given seek offset\n";
+    o << "-l <sec>  Retrieve only the specified length in seconds\n";
+}
+
+
+
+}
+

=== removed file 'utilities/rtmpget.cpp'
--- a/utilities/rtmpget.cpp     2010-01-01 17:48:26 +0000
+++ b/utilities/rtmpget.cpp     1970-01-01 00:00:00 +0000
@@ -1,430 +0,0 @@
-// rtmpget.cpp:  RTMP file downloader utility
-// 
-//   Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.
-// 
-// This program is free software; you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation; either version 3 of the License, or
-// (at your option) any later version.
-// 
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-// GNU General Public License for more details.
-// 
-// You should have received a copy of the GNU General Public License
-// along with this program; if not, write to the Free Software
-// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
-// 
-
-
-#ifdef HAVE_CONFIG_H
-#include "gnashconfig.h"
-#endif
-
-// classes internal to Gnash
-#include "gnash.h"
-#include "network.h"
-#include "log.h"
-#include "http.h"
-#include "limits.h"
-#include "netstats.h"
-#include "statistics.h"
-#include "gmemory.h"
-#include "arg_parser.h"
-#include "amf.h"
-#include "rtmp.h"
-#include "rtmp_client.h"
-#include "rtmp_msg.h"
-#include "buffer.h"
-#include "network.h"
-#include "element.h"
-#include "URL.h"
-
-#ifdef ENABLE_NLS
-# include <locale>
-#endif
-
-#include <string>
-#include <iostream>
-#include <sstream>
-#include <csignal>
-#include <vector>
-#include <sys/mman.h>
-#include <cerrno>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <fcntl.h>
-
-#include <boost/date_time/gregorian/gregorian.hpp>
-#include <boost/date_time/time_zone_base.hpp>
-#include <boost/date_time/posix_time/posix_time.hpp>
-#include <boost/thread/thread.hpp>
-#include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
-
-using gnash::log_debug;
-using namespace std;
-using namespace gnash;
-using namespace amf;
-
-static void usage();
-static void version_and_copyright();
-static void cntrlc_handler(int sig);
-
-void connection_handler(Network::thread_params_t *args);
-void admin_handler(Network::thread_params_t *args);
-
-LogFile& dbglogfile = LogFile::getDefaultInstance();
-
-// The rcfile is loaded and parsed here:
-RcInitFile& rcfile = RcInitFile::getDefaultInstance();
-
-// Toggles very verbose debugging info from the network Network class
-static bool netdebug = false;
-
-static struct sigaction  act;
-
-std::vector<std::string> infiles;
-
-// The next few global variables have to be global because Boost
-// threads don't take arguments. Since these are set in main() before
-// any of the threads are started, and it's value should never change,
-// it's safe to use these without a mutex, as all threads share the
-// same read-only value.
-
-typedef boost::shared_ptr<amf::Buffer> BufferSharedPtr;
-typedef boost::shared_ptr<amf::Element> ElementSharedPtr;
-
-// end of globals
-
-int
-main(int argc, char *argv[])
-{
-    // Initialize national language support
-#ifdef ENABLE_NLS
-    setlocale (LC_ALL, "");
-    bindtextdomain (PACKAGE, LOCALEDIR);
-    textdomain (PACKAGE);
-#endif
-    
-    // If no command line arguments have been supplied, do nothing but
-    // print the  usage message.
-    if (argc < 2) {
-        usage();
-        exit(EXIT_SUCCESS);
-    }
-
-   const Arg_parser::Option opts[] =
-        {
-        { 'h', "help",          Arg_parser::no  },
-        { 'V', "version",       Arg_parser::no  },
-        { 'p', "port-offset",   Arg_parser::yes },
-        { 'v', "verbose",       Arg_parser::no  },
-        { 'd', "dump",          Arg_parser::no  },
-        { 'a', "app",           Arg_parser::yes  },
-        { 'p', "path",          Arg_parser::yes  },
-        { 'f', "filename",      Arg_parser::yes  },
-        { 't', "tcurl",         Arg_parser::yes  },
-        { 's', "swfurl",        Arg_parser::yes  },
-        { 'u', "url",           Arg_parser::yes  },
-        { 'n', "netdebug",      Arg_parser::no  }
-        };
-
-    Arg_parser parser(argc, argv, opts);
-    if( ! parser.error().empty() )  
-    {
-        cout << parser.error() << endl;
-        exit(EXIT_FAILURE);
-    }
-
-    // Set the log file name before trying to write to
-    // it, or we might get two.
-    dbglogfile.setLogFilename("rtmpget-dbg.log");
-    
-    if (rcfile.verbosityLevel() > 0) {
-        dbglogfile.setVerbosity(rcfile.verbosityLevel());
-    }    
-
-#if 1
-    string app; // the application name
-    string path; // the path to the file on the server
-    string tcUrl; // the tcUrl field
-    string swfUrl; // the swfUrl field
-    string filename; // the filename to play
-#endif
-    
-    // Handle command line arguments
-    for( int i = 0; i < parser.arguments(); ++i ) {
-        const int code = parser.code(i);
-        try {
-            switch( code ) {
-              case 'h':
-                  version_and_copyright();
-                  usage();
-                  exit(EXIT_SUCCESS);
-              case 'V':
-                  version_and_copyright();
-                  exit(EXIT_SUCCESS);
-              case 'v':
-                  dbglogfile.setVerbosity();
-                  log_debug (_("Verbose output turned on"));
-                  break;
-              case 'a':
-                  app = parser.argument(i);
-                  break;
-              case 'p':
-                  path = parser.argument(i);
-                  break;
-              case 't':
-                  tcUrl = parser.argument(i);
-                  break;
-              case 's':
-                  swfUrl = parser.argument(i);
-                  break;
-              case 'f':
-                  filename = parser.argument(i);
-                  break;
-              case 'n':
-                  netdebug = true;
-                  break;
-              case 'd':
-                  rcfile.dump();
-                  exit(EXIT_SUCCESS);
-                  break;
-              case 0:
-                  infiles.push_back(parser.argument(i));
-                  break;
-              default:
-                  log_error (_("Extraneous argument: %s"), 
parser.argument(i).c_str());
-            }
-        }
-        
-        catch (Arg_parser::ArgParserException &e) {
-            cerr << _("Error parsing command line options: ") << e.what() << 
endl;
-            cerr << _("This is a Gnash bug.") << endl;
-        }
-    }
-    
-    if (infiles.empty()) {
-        cerr << _("Error: no input file was specified. Exiting.") << endl;
-        usage();
-        return EXIT_FAILURE;
-    }
-
-    // Trap ^C (SIGINT) so we can kill all the threads
-    act.sa_handler = cntrlc_handler;
-    sigaction (SIGINT, &act, NULL);
-
-    RTMPClient client;
-    client.toggleDebug(netdebug);
-    if (client.createClient(hostname, port) == false) {
-        log_error("Can't connect to RTMP server %s", hostname);
-        exit(EXIT_FAILURE);
-    }
-    
-    if (! client.handShakeRequest()) {
-        log_error("RTMP handshake request failed");
-        exit(EXIT_FAILURE);
-    }
-    
-    if (! client.clientFinish()) {
-        log_error("RTMP handshake completion failed");
-        exit(EXIT_FAILURE);
-    }
-    
-    // Make a buffer to hold the handshake data.
-    Buffer buf(1537);
-    // RTMP::rtmp_head_t *rthead = 0;
-    // int ret = 0;
-    log_debug("Sending NetConnection Connect message,");
-#if 0
-    BufferSharedPtr buf2 = client.encodeConnect(app.c_str(), swfUrl.c_str(), 
tcUrl.c_str(), RTMPClient::DEFAULT_AUDIO_SET,
-                     RTMPClient::DEFAULT_VIDEO_SET,
-                     RTMPClient::SEEK, pageUrl.c_str());
-#else
-    BufferSharedPtr buf2 = client.encodeConnect(infiles[0]);    
-#endif
-//    BufferSharedPtr buf2 = 
client.encodeConnect("video/2006/sekt/gate06/tablan_valentin", 
"mediaplayer.swf", 
"rtmp://velblod.videolectures.net/video/2006/sekt/gate06/tablan_valentin", 615, 
124, 1, "http://gnashdev.org";);
-//    BufferSharedPtr buf2 = client.encodeConnect("oflaDemo", 
"http://192.168.1.70/software/gnash/tests/ofla_demo.swf";, 
"rtmp://localhost/oflaDemo/stream", 615, 124, 1, 
"http://192.168.1.70/software/gnash/tests/index.html";);
-    //buf2->resize(buf2->size() - 6); // FIXME: encodeConnect returns the 
wrong size for the buffer!
-    size_t total_size = buf2->size();    
-    RTMPMsg *msg1 = client.sendRecvMsg(0x3, RTMP::HEADER_12, total_size, 
RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf2);
-    
-    if (!msg1) {
-        log_error("No response from INVOKE of NetConnection connect");
-        exit(EXIT_FAILURE);
-    }
-
-    msg1->dump();
-    if (msg1->getStatus() ==  RTMPMsg::NC_CONNECT_SUCCESS) {
-        log_debug("Sent NetConnection Connect message sucessfully");
-    } else {
-        log_error("Couldn't send NetConnection Connect message,");
-        //exit(EXIT_FAILURE);
-    }
-
-    // make the createStream for ID 3 encoded object
-    log_debug("Sending NetStream::createStream message,");
-    BufferSharedPtr buf3 = client.encodeStream(0x2);
-//    buf3->dump();
-    total_size = buf3->size();
-    RTMPMsg *msg2 = client.sendRecvMsg(0x3, RTMP::HEADER_12, total_size, 
RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf3);
-    double streamID = 0.0;
-
-    if (!msg2) {
-        log_error("No response from INVOKE of NetStream::createStream");
-        exit(EXIT_FAILURE);
-    }
-
-    log_debug("Sent NetStream::createStream message successfully:"); 
msg2->dump();
-    std::vector<ElementSharedPtr> hell = msg2->getElements();
-    if (hell.size() > 0) {
-        streamID = hell[0]->to_number();
-        log_debug("Stream ID returned from createStream is: %d", streamID);
-    } else {
-        if (msg2->getMethodName() == "close") { 
-            log_debug("Got close packet!!! Exiting...");
-            exit(EXIT_SUCCESS);
-        }
-        log_error("Got no properties from NetStream::createStream invocation, 
arbitrarily taking 0 as streamID");
-        streamID = 0.0;
-    }
-
-    int id = int(streamID);
-    
-    // make the NetStream::play() operations for ID 2 encoded object
-//    log_debug("Sending NetStream play message,");
-    BufferSharedPtr buf4 = client.encodeStreamOp(0, RTMP::STREAM_PLAY, false, 
filename.c_str());
-//    BufferSharedPtr buf4 = client.encodeStreamOp(0, RTMP::STREAM_PLAY, 
false, "gate06_tablan_bcueu_01");
-//     log_debug("TRACE: buf4: %s", hexify(buf4->reference(), buf4->size(), 
true));
-    total_size = buf4->size();
-    RTMPMsg *msg3 = client.sendRecvMsg(0x8, RTMP::HEADER_12, total_size, 
RTMP::INVOKE, RTMPMsg::FROM_CLIENT, buf4);
-    if (msg3) {
-        msg3->dump();
-        if (msg3->getStatus() ==  RTMPMsg::NS_PLAY_START) {
-            log_debug("Sent NetStream::play message sucessfully.");
-        } else {
-            log_error("Couldn't send NetStream::play message,");
-//          exit(EXIT_FAILURE);
-        }
-    }
-
-    int loop = 20;
-    do {
-        BufferSharedPtr msgs = client.recvMsg(1);   // use a 1 second timeout
-        if (msgs == 0) {
-            log_error("Never got any data!");
-            exit(EXIT_FAILURE);
-        }
-        RTMP::queues_t *que = client.split(msgs);
-        if (que == 0) {
-            log_error("Never got any messages!");
-            exit(EXIT_FAILURE);
-        }
-
-#if 0
-        deque<CQue *>::iterator it;
-        for (it = que->begin(); it != que->end(); it++) {
-            CQue *q = *(it);
-            q->dump();
-        }
-#endif
-        while (que->size()) {
-            cerr << "QUE SIZE: " << que->size() << endl;
-            BufferSharedPtr ptr = que->front()->pop();
-            if (!ptr->empty()) {
-                que->pop_front();   // delete the item from the queue
-                /* RTMP::rtmp_head_t *rthead = */ client.decodeHeader(ptr);
-                msg2 = client.decodeMsgBody(ptr);
-                if (msg2 == 0) {
-    //              log_error("Couldn't process the RTMP message!");
-                    continue;
-                }
-            } else {
-                log_error("Buffer size (%d) out of range at %d", ptr->size(), 
__LINE__);
-                break;
-            }
-        }
-    } while(loop--);
-
-//     std::vector<amf::Element *> hell = msg2->getElements();
-//     std::vector<amf::Element *> props = hell[0]->getProperties();
-
-//     cerr << "HELL Elements: " << hell.size() << endl;
-//     cerr << "HELL Properties: " << props.size() << endl;
-
-// //     cerr << props[0]->getName() << endl;
-// //     cerr << props[0]->to_string() << endl;
-//     cerr << props[0]->getName() << endl;
-// //    cerr << props[0]->to_number() << endl;
-//     cerr << props[1]->getName() << ": " << props[1]->to_string() << endl;
-//     cerr << props[2]->getName() << ": " << props[3]->to_string() << endl;
-//     cerr << props[3]->getName() << ": " << props[3]->to_string() << endl;
-
-//     Element *eell = hell[0]->findProperty("level");
-//     if (eell) {
-//  eell->dump();
-//     }
-//     *eell = hell[0]->findProperty("code");
-//     if (eell) {
-//  eell->dump();
-//     }
-
-#if 0
-    // Write the packet to disk so we can anaylze it with other tools
-    int fd = open("outbuf.raw",O_WRONLY|O_CREAT, S_IRWXU);
-    if (fd < 0) {
-        perror("open");
-    }
-    cout << "Writing packet to disk: \"outbuf.raw\"" << endl;
-//     write(fd, out, 12);
-//     write(fd, outbuf.begin(), amf_obj.totalsize());
-    write(fd, buf2->reference(), buf2->size());
-    write(fd, buf3->reference(), buf3->size());
-    close(fd);
-#endif    
-
-    exit(EXIT_SUCCESS);
-}
-
-// Trap Control-C so we can cleanly exit
-static void
-cntrlc_handler (int /*sig*/)
-{
-    log_debug(_("Got an interrupt"));
-
-    exit(EXIT_FAILURE);
-}
-
-static void
-version_and_copyright()
-{
-    cout << "rtmpget " << VERSION << endl
-        << endl
-        << _("Copyright (C) 2008, 2009, 2010 Free Software Foundation, Inc.\n"
-        "Cygnal comes with NO WARRANTY, to the extent permitted by law.\n"
-        "You may redistribute copies of Cygnal under the terms of the GNU 
General\n"
-        "Public License.  For more information, see the file named COPYING.\n")
-        << endl;
-}
-
-
-static void
-usage()
-{
-    cout << _("rtmpget -- a file downloaded that uses RTMP.") << endl
-        << endl
-        << _("Usage: rtmpget [options...] <url>") << endl
-        << _("  -h,  --help          Print this help and exit") << endl
-        << _("  -V,  --version       Print version information and exit") << 
endl
-        << _("  -v,  --verbose       Output verbose debug info") << endl
-        << _("  -n,  --netdebug      Verbose networking debug info") << endl
-        << _("  -d,  --dump          display init file to terminal") << endl
-        << endl;
-}
-
-// local Variables:
-// mode: C++
-// indent-tabs-mode: t
-// End:


reply via email to

[Prev in Thread] Current Thread [Next in Thread]