いものやま。

雑多な知識の寄せ集め

ニコニコ動画のダウンロードツールを改良してみた。(その1)

ということで、以前作ったニコニコ動画のダウンロードツールを改良し、RTMPで配信されている動画をダウンロードするときにも進捗度合いがわかるようにしてみた。

まず、ニコニコ動画のダウンロードツールRubyで書いた話は、以下から。

そして、これを改善するために、Ruby-FFIについて調べた話は、以下から。

最後に、rtmpdumpの実装について調べた話は、以下から。

今回の目指すところ

まず、今回の目指すところ(スコープ)は、以下。

  • ツールの使い方の変更はしない(=オプションを増やしたりはしない)
  • HTTPで配信されている動画は、これまで通りダウンロードでき、進捗度合いも表示される
  • rtmpdumpを使うのでなく、librtmpをRuby-FFI経由で使うようにする
  • RTMPで配信されている動画は、これまで通りダウンロードでき、さらに進捗度合いも表示されるようにする

さっそく仕事に取り掛かる。

librtmpの結びつけ

librtmpを使えるようにするために、LibRTMPというモジュールを用意し、ここでライブラリとの結びつけを行う。
なお、結びつける構造体や関数は、ヘッダファイルに書かれているもの全部ではなく、実際に使うものだけにした。

コードは以下。(無駄に長い・・・)

require 'ffi'
require 'uri'

# attach 'librtmp' interface

module NicovideoDL
  module LibRTMP
    extend FFI::Library
    ffi_lib 'librtmp'

    ### log.h ###

    # typedef enum
    # { RTMP_LOGCRIT=0, RTMP_LOGERROR, RTMP_LOGWARNING, RTMP_LOGINFO,
    #   RTMP_LOGDEBUG, RTMP_LOGDEBUG2, RTMP_LOGALL
    # } RTMP_LogLevel;
    # extern RTMP_LogLevel RTMP_debuglevel;
    RTMP_LogLevel = enum [
      :RTMP_LOGCRIT, 0, :RTMP_LOGERROR, :RTMP_LOGWARNING, :RTMP_LOGINFO,
      :RTMP_LOGDEBUG, :RTMP_LOGDEBUG2, :RTMP_LOGALL
    ]
    attach_variable :RTMP_debuglevel, RTMP_LogLevel

    # set debug level to 'quiet'
    LibRTMP.RTMP_debuglevel = RTMP_LogLevel[:RTMP_LOGCRIT]

    ### amf.h ###

    # typedef enum
    # { AMF_NUMBER = 0, AMF_BOOLEAN, AMF_STRING, AMF_OBJECT,
    #   AMF_MOVIECLIP,    /* reserved, not used */
    #   AMF_NULL, AMF_UNDEFINED, AMF_REFERENCE, AMF_ECMA_ARRAY, AMF_OBJECT_END,
    #   AMF_STRICT_ARRAY, AMF_DATE, AMF_LONG_STRING, AMF_UNSUPPORTED,
    #   AMF_RECORDSET,    /* reserved, not used */
    #   AMF_XML_DOC, AMF_TYPED_OBJECT,
    #   AMF_AVMPLUS,      /* switch to AMF3 */
    #   AMF_INVALID = 0xff
    # } AMFDataType;
    AMFDataType = enum [
      :AMF_NUMBER, 0, :AMF_BOOLEAN, :AMF_STRING, :AMF_OBJECT, :AMF_MOVIECLIP,
      :AMF_NULL, :AMF_UNDEFINED, :AMF_REFERENCE, :AMF_ECMA_ARRAY, :AMF_OBJECT_END,
      :AMF_STRICT_ARRAY, :AMF_DATE, :AMF_LONG_STRING, :AMF_UNSUPPORTED,
      :AMF_RECORDSET, :AMF_XML_DOC, :AMF_TYPED_OBJECT, :AMF_AVMPLUS, :AMF_INVALID, 0xff
    ]

    # typedef struct AVal
    # {
    #   char *av_val;
    #   int av_len;
    # } AVal;
    class AVal < FFI::Struct
      layout(
        :av_val, :pointer,
        :av_len, :int)
    end

    # struct AMFObjectProperty;
    class AMFObjectProperty < FFI::Struct; end

    # typedef struct AMFObject
    # {
    #   int o_num;
    #   struct AMFObjectProperty *o_props;
    # } AMFObject;
    class AMFObject < FFI::Struct
      layout(
        :o_num, :int,
        :o_props, AMFObjectProperty.ptr)
    end

    # typedef struct AMFObjectProperty
    # {
    #   AVal p_name;
    #   AMFDataType p_type;
    #   union
    #   {
    #     double p_number;
    #     AVal p_aval;
    #     AMFObject p_object;
    #   } p_vu;
    #   int16_t p_UTCoffset;
    # } AMFObjectProperty;
    class AMFObjectProperty < FFI::Struct
      class PropData < FFI::Union
        layout(
          :p_number, :double,
          :p_aval, AVal,
          :p_object, AMFObject)
      end
      layout(
        :p_name, AVal,
        :p_type, AMFDataType,
        :p_vu, PropData,
        :p_UTCoffset, :int16)
    end

    # unsigned int AMF_DecodeInt24(const char *data);
    # unsigned int AMF_DecodeInt32(const char *data);
    attach_function :AMF_DecodeInt24, [:pointer], :uint
    attach_function :AMF_DecodeInt32, [:pointer], :uint

    # int AMF_Decode(AMFObject * obj, const char *pBuffer, int nSize, int bDecodeName);
    # void AMF_Dump(AMFObject * obj);
    attach_function :AMF_Decode, [AMFObject.ptr, :pointer, :int, :int], :int
    attach_function :AMF_Dump, [AMFObject.ptr], :void

    # AMFObjectProperty *AMF_GetProp(AMFObject * obj, const AVal * name, int nIndex);
    attach_function :AMF_GetProp, [AMFObject.ptr, AVal.ptr, :int], AMFObjectProperty.ptr

    # double AMFProp_GetNumber(AMFObjectProperty * prop);
    # void AMFProp_GetString(AMFObjectProperty * prop, AVal * str);
    attach_function :AMFProp_GetNumber, [AMFObjectProperty.ptr], :double
    attach_function :AMFProp_GetString, [AMFObjectProperty.ptr, AVal.ptr], :void

    ### rtmp.h ###

    # #define RTMP_LIB_VERSION  0x020300  /* 2.3 */
    RTMP_LIB_VERSION = 0x020300 # 2.3

    # #define RTMP_FEATURE_HTTP 0x01
    # #define RTMP_FEATURE_ENC  0x02
    # #define RTMP_FEATURE_SSL  0x04
    # #define RTMP_FEATURE_MFP  0x08    /* not yet supported */
    # #define RTMP_FEATURE_WRITE  0x10  /* publish, not play */
    # #define RTMP_FEATURE_HTTP2  0x20  /* server-side rtmpt */
    RTMP_FEATURE_HTTP = 0x01
    RTMP_FEATURE_ENC = 0x02
    RTMP_FEATURE_SSL = 0x04
    RTMP_FEATURE_MFP = 0x08
    RTMP_FEATURE_WRITE = 0x10
    RTMP_FEATURE_HTTP2 = 0x20

    # #define RTMP_PROTOCOL_UNDEFINED -1
    # #define RTMP_PROTOCOL_RTMP      0
    # #define RTMP_PROTOCOL_RTMPE     RTMP_FEATURE_ENC
    # #define RTMP_PROTOCOL_RTMPT     RTMP_FEATURE_HTTP
    # #define RTMP_PROTOCOL_RTMPS     RTMP_FEATURE_SSL
    # #define RTMP_PROTOCOL_RTMPTE    (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC)
    # #define RTMP_PROTOCOL_RTMPTS    (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL)
    # #define RTMP_PROTOCOL_RTMFP     RTMP_FEATURE_MFP
    RTMP_PROTOCOL_UNDEFINED = -1
    RTMP_PROTOCOL_RTMP = 0
    RTMP_PROTOCOL_RTMPE = RTMP_FEATURE_ENC
    RTMP_PROTOCOL_RTMPT = RTMP_FEATURE_HTTP
    RTMP_PROTOCOL_RTMPS = RTMP_FEATURE_SSL
    RTMP_PROTOCOL_RTMPTE = (RTMP_FEATURE_HTTP|RTMP_FEATURE_ENC)
    RTMP_PROTOCOL_RTMPTS = (RTMP_FEATURE_HTTP|RTMP_FEATURE_SSL)
    RTMP_PROTOCOL_RTMFP = RTMP_FEATURE_MFP

    # #define RTMP_READ_HEADER  0x01
    # #define RTMP_READ_RESUME  0x02
    # #define RTMP_READ_NO_IGNORE 0x04
    # #define RTMP_READ_GOTKF   0x08
    # #define RTMP_READ_GOTFLVK 0x10
    # #define RTMP_READ_SEEKING 0x20
    RTMP_READ_HEADER = 0x01
    RTMP_READ_RESUME = 0x02
    RTMP_READ_NO_IGNORE = 0x04
    RTMP_READ_GOTKF = 0x08
    RTMP_READ_GOTFLVK = 0x10
    RTMP_READ_SEEKING = 0x20

    # #define RTMP_READ_COMPLETE  -3
    # #define RTMP_READ_ERROR -2
    # #define RTMP_READ_EOF -1
    # #define RTMP_READ_IGNORE  0
    RTMP_READ_COMPLETE = -3
    RTMP_READ_ERROR = -2
    RTMP_READ_EOF = -1
    RTMP_READ_IGNORE = 0

    # typedef struct RTMP_READ
    # {
    #   char *buf;
    #   char *bufpos;
    #   unsigned int buflen;
    #   uint32_t timestamp;
    #   uint8_t dataType;
    #   uint8_t flags;
    #   int8_t status;
    #   uint8_t initialFrameType;
    #   uint32_t nResumeTS;
    #   char *metaHeader;
    #   char *initialFrame;
    #   uint32_t nMetaHeaderSize;
    #   uint32_t nInitialFrameSize;
    #   uint32_t nIgnoredFrameCounter;
    #   uint32_t nIgnoredFlvFrameCounter;
    # } RTMP_READ;
    class RTMP_READ < FFI::Struct
      layout(
        :buf, :pointer,
        :bufpos, :pointer,
        :buflen, :uint,
        :timestamp, :uint32,
        :dataType, :uint8,
        :flags, :uint8,
        :status, :int8,
        :initialFrameType, :uint8,
        :nResumeTS, :uint32,
        :metaHeader, :pointer,
        :initialFrame, :pointer,
        :nMetaHeaderSize, :uint32,
        :nInitialFrameSize, :uint32,
        :nIgnoredFrameCounter, :uint32,
        :nIgnoredFlvFrameCounter, :uint32)
    end

    # typedef struct RTMPPacket
    # {
    #   uint8_t m_headerType;
    #   uint8_t m_packetType;
    #   uint8_t m_hasAbsTimestamp;  /* timestamp absolute or relative? */
    #   int m_nChannel;
    #   uint32_t m_nTimeStamp;  /* timestamp */
    #   int32_t m_nInfoField2;  /* last 4 bytes in a long header */
    #   uint32_t m_nBodySize;
    #   uint32_t m_nBytesRead;
    #   RTMPChunk *m_chunk;
    #   char *m_body;
    # } RTMPPacket;
    class RTMPPacket < FFI::Struct
      layout(
        :m_headerType, :uint8,
        :m_packetType, :uint8,
        :m_hasAbsTimestamp, :uint8,
        :m_nChannel, :int,
        :m_nTimeStamp, :uint32,
        :m_nInfoField2, :int32,
        :m_nBodySize, :uint32,
        :m_nBytesRead, :uint32,
        :m_chunk, :pointer,
        :m_body, :pointer)
    end

    # #define RTMP_BUFFER_CACHE_SIZE (16*1024)
    RTMP_BUFFER_CACHE_SIZE = (16 * 1024)

    # typedef struct RTMPSockBuf
    # {
    #   int sb_socket;
    #   int sb_size;    /* number of unprocessed bytes in buffer */
    #   char *sb_start; /* pointer into sb_pBuffer of next byte to process */
    #   char sb_buf[RTMP_BUFFER_CACHE_SIZE];  /* data read from socket */
    #   int sb_timedout;
    #   void *sb_ssl;
    # } RTMPSockBuf;
    class RTMPSockBuf < FFI::Struct
      layout(
        :sb_socket, :int,
        :sb_size, :int,
        :sb_start, :pointer,
        :sb_buf, [:char, RTMP_BUFFER_CACHE_SIZE],
        :sb_timedout, :int,
        :sb_ssl, :pointer)
    end

    # #define RTMP_LF_AUTH  0x0001  /* using auth param */
    # #define RTMP_LF_LIVE  0x0002  /* stream is live */
    # #define RTMP_LF_SWFV  0x0004  /* do SWF verification */
    # #define RTMP_LF_PLST  0x0008  /* send playlist before play */
    # #define RTMP_LF_BUFX  0x0010  /* toggle stream on BufferEmpty msg */
    # #define RTMP_LF_FTCU  0x0020  /* free tcUrl on close */
    # #define RTMP_LF_FAPU  0x0040  /* free app on close */
    RTMP_LF_AUTH = 0x0001
    RTMP_LF_LIVE = 0x0002
    RTMP_LF_SWFV = 0x0004
    RTMP_LF_PLST = 0x0008
    RTMP_LF_BUFX = 0x0010
    RTMP_LF_FTCU = 0x0020
    RTMP_LF_FAPU = 0x0040

    # #define RTMP_SWF_HASHLEN  32
    RTMP_SWF_HASHLEN = 32

    # typedef struct RTMP_LNK
    # {
    #   AVal hostname;
    #   AVal sockshost;
    #   AVal playpath0; /* parsed from URL */
    #   AVal playpath;  /* passed in explicitly */
    #   AVal tcUrl;
    #   AVal swfUrl;
    #   AVal pageUrl;
    #   AVal app;
    #   AVal auth;
    #   AVal flashVer;
    #   AVal subscribepath;
    #   AVal usherToken;
    #   AVal token;
    #   AVal pubUser;
    #   AVal pubPasswd;
    #   AMFObject extras;
    #   int edepth;
    #   int seekTime;
    #   int stopTime;
    #   int lFlags;
    #   int swfAge;
    #   int protocol;
    #   int timeout;    /* connection timeout in seconds */
    #   int pFlags;     /* unused, but kept to avoid breaking ABI */
    #   unsigned short socksport;
    #   unsigned short port;
    #   void *dh;       /* for encryption */
    #   void *rc4keyIn;
    #   void *rc4keyOut;
    #   uint32_t SWFSize;
    #   uint8_t SWFHash[RTMP_SWF_HASHLEN];
    #   char SWFVerificationResponse[RTMP_SWF_HASHLEN+10];
    # } RTMP_LNK;
    class RTMP_LNK < FFI::Struct
      layout(
        :hostname, AVal,
        :sockshost, AVal,
        :playpath0, AVal,
        :playpath, AVal,
        :tcUrl, AVal,
        :swfUrl, AVal,
        :pageUrl, AVal,
        :app, AVal,
        :auth, AVal,
        :flashVer, AVal,
        :subscribepath, AVal,
        :usherToken, AVal,
        :token, AVal,
        :pubUser, AVal,
        :pubPasswd, AVal,
        :extras, AMFObject,
        :edepth, :int,
        :seekTime, :int,
        :stopTime, :int,
        :lFlags, :int,
        :swfAge, :int,
        :protocol, :int,
        :timeout, :int,
        :pFlags, :int,
        :socksport, :ushort,
        :port, :ushort,
        :dh, :pointer,
        :rc4keyIn, :pointer,
        :rc4keyOut, :pointer,
        :SWFSize, :uint32,
        :SWFHash, [:uint8, RTMP_SWF_HASHLEN],
        :SWFVerificationResponse, [:char, RTMP_SWF_HASHLEN+10])
    end

    # typedef struct RTMP
    # {
    #   int m_inChunkSize;
    #   int m_outChunkSize;
    #   int m_nBWCheckCounter;
    #   int m_nBytesIn;
    #   int m_nBytesInSent;
    #   int m_nBufferMS;
    #   int m_stream_id;    /* returned in _result from createStream */
    #   int m_mediaChannel;
    #   uint32_t m_mediaStamp;
    #   uint32_t m_pauseStamp;
    #   int m_pausing;
    #   int m_nServerBW;
    #   int m_nClientBW;
    #   uint8_t m_nClientBW2;
    #   uint8_t m_bPlaying;
    #   uint8_t m_bSendEncoding;
    #   uint8_t m_bSendCounter;
    #   int m_numInvokes;
    #   int m_numCalls;
    #   RTMP_METHOD *m_methodCalls; /* remote method calls queue */
    #   int m_channelsAllocatedIn;
    #   int m_channelsAllocatedOut;
    #   RTMPPacket **m_vecChannelsIn;
    #   RTMPPacket **m_vecChannelsOut;
    #   int *m_channelTimestamp;  /* abs timestamp of last packet */
    #   double m_fAudioCodecs;    /* audioCodecs for the connect packet */
    #   double m_fVideoCodecs;    /* videoCodecs for the connect packet */
    #   double m_fEncoding;       /* AMF0 or AMF3 */
    #   double m_fDuration;       /* duration of stream in seconds */
    #   int m_msgCounter;         /* RTMPT stuff */
    #   int m_polling;
    #   int m_resplen;
    #   int m_unackd;
    #   AVal m_clientID;
    #   RTMP_READ m_read;
    #   RTMPPacket m_write;
    #   RTMPSockBuf m_sb;
    #   RTMP_LNK Link;
    # } RTMP;
    class RTMP < FFI::Struct
      layout(
        :m_inChunkSize, :int,
        :m_outChunkSize, :int,
        :m_nBWCheckCounter, :int,
        :m_nBytesIn, :int,
        :m_nBytesInSent, :int,
        :m_nBufferMS, :int,
        :m_stream_id, :int,
        :m_mediaChannel, :int,
        :m_mediaStamp, :uint32,
        :m_pauseStamp, :uint32,
        :m_pausing, :int,
        :m_nServerBW, :int,
        :m_nClientBW, :int,
        :m_nClientBW2, :uint8,
        :m_bPlaying, :uint8,
        :m_bSendEncoding, :uint8,
        :m_bSendCounter, :uint8,
        :m_numInvokes, :int,
        :m_numCalls, :int,
        :m_methodCalls, :pointer,
        :m_channelsAllocatedIn, :int,
        :m_channelsAllocatedOut, :int,
        :m_vecChannelsIn, :pointer,
        :m_vecChannelsOut, :pointer,
        :m_channelTimestamp, :pointer,
        :m_fAudioCodecs, :double,
        :m_fVideoCodecs, :double,
        :m_fEncoding, :double,
        :m_fDuration, :double,
        :m_msgCounter, :int,
        :m_polling, :int,
        :m_resplen, :int,
        :m_unackd, :int,
        :m_clientID, AVal,
        :m_read, RTMP_READ,
        :m_write, RTMPPacket,
        :m_sb, RTMPSockBuf,
        :Link, RTMP_LNK)
    end

    # int RTMP_ParseURL(const char *url, int *protocol,
    #   AVal *host, unsigned int *port, AVal *playpath, AVal *app);
    attach_function :RTMP_ParseURL, [
      :string, :pointer, AVal.ptr, :pointer, AVal.ptr, AVal.ptr], :int

    # void RTMP_SetBufferMS(RTMP *r, int size);
    # void RTMP_UpdateBufferMS(RTMP *r);
    attach_function :RTMP_SetBufferMS, [RTMP.ptr, :int], :void
    attach_function :RTMP_UpdateBufferMS, [RTMP.ptr], :void

    # int RTMP_SetOpt(RTMP *r, const AVal *opt, AVal *arg);
    # void RTMP_SetupStream(RTMP *r, int protocol,
    #   AVal *hostname, unsigned int port, AVal *sockshost,
    #   AVal *playpath, AVal *tcUrl, AVal *swfUrl,
    #   AVal *pageUrl, AVal *app, AVal *auth,
    #   AVal *swfSHA256Hash, uint32_t swfSize,
    #   AVal *flashVer, AVal *subscribepath, AVal *usherToken,
    #   int dStart, int dStop, int bLiveStream, long int timeout);
    attach_function :RTMP_SetOpt, [RTMP.ptr, AVal.ptr, AVal.ptr], :int
    attach_function :RTMP_SetupStream, [
      RTMP.ptr, :int, AVal.ptr, :uint, AVal.ptr,
      AVal.ptr, AVal.ptr, AVal.ptr,
      AVal.ptr, AVal.ptr, AVal.ptr,
      AVal.ptr, :uint32,
      AVal.ptr, AVal.ptr, AVal.ptr,
      :int, :int, :int, :long], :void

    # int RTMP_Connect(RTMP *r, RTMPPacket *cp);
    attach_function :RTMP_Connect, [RTMP.ptr, RTMPPacket.ptr], :int

    # int RTMP_IsConnected(RTMP *r);
    # int RTMP_IsTimedout(RTMP *r);
    # double RTMP_GetDuration(RTMP *r);
    # int RTMP_ToggleStream(RTMP *r);
    attach_function :RTMP_IsConnected, [RTMP.ptr], :int
    attach_function :RTMP_IsTimedout, [RTMP.ptr], :int
    attach_function :RTMP_GetDuration, [RTMP.ptr], :double
    attach_function :RTMP_ToggleStream, [RTMP.ptr], :int

    # int RTMP_ConnectStream(RTMP *r, int seekTime);
    # int RTMP_ReconnectStream(RTMP *r, int seekTime);
    attach_function :RTMP_ConnectStream, [RTMP.ptr, :int], :int
    attach_function :RTMP_ReconnectStream, [RTMP.ptr, :int], :int

    # void RTMP_Init(RTMP *r);
    # void RTMP_Close(RTMP *r);
    attach_function :RTMP_Init, [RTMP.ptr], :void
    attach_function :RTMP_Close, [RTMP.ptr], :void

    # int RTMP_LibVersion(void);
    attach_function :RTMP_LibVersion, [], :int

    # int RTMP_FindFirstMatchingProperty(
    #   AMFObject *obj, const AVal *name, AMFObjectProperty * p);
    attach_function :RTMP_FindFirstMatchingProperty, [
      AMFObject.ptr, AVal.ptr, AMFObjectProperty.ptr], :int
    
    # int RTMP_Read(RTMP *r, char *buf, int size);
    attach_function :RTMP_Read, [RTMP.ptr, :pointer, :int], :int

    ### rtmpdump.c ###

    # #define RD_SUCCESS    0
    # #define RD_FAILED   1
    # #define RD_INCOMPLETE   2
    # #define RD_NO_CONNECT   3
    RD_SUCCESS = 0
    RD_FAILED = 1
    RD_INCOMPLETE = 2
    RD_NO_CONNECT = 3

    # #define DEF_BUFTIME (10 * 60 * 60 * 1000) /* 10 hours default */
    DEF_BUFTIME = (10 * 60 * 60 * 1000)
  end
end

ラッパーメソッド

結びつけた関数を見てみると、第1引数に自分自身を表すオブジェクトを渡す関数が多いことが分かると思う。
これは、C言語オブジェクト指向的にコードを書く場合、第1引数に自分自身のオブジェクトを渡すというのがお約束だから。
Perlオブジェクト指向機能を使ったことがある人だと、この感覚は分かりやすいかも)

ただ、Rubyだとちょっと不自然な感じなので、各クラスにラッパーとなるインスタンスメソッドを用意しておく。

ついでに、Rubyの文字列とAValの変換を一々やるのは面倒なので、ラッパーメソッドではその辺りも解消してある。

# define wrapper methods

module NicovideoDL
  module LibRTMP
    class AVal
      def self.from_string(str)
        aval = self.new
        aval[:av_val] = FFI::MemoryPointer.from_string(str)
        aval[:av_len] = str.bytesize
        aval
      end

      def to_s
        self[:av_val].read_string(self[:av_len])
      rescue
        ""
      end

      def ==(other)
        self.to_s == other.to_s
      end
    end

    def decode_int24(str)
      buffer = FFI::MemoryPointer.new :char, str.bytesize
      buffer.put_bytes(0, str, 0, str.bytesize)
      return LibRTMP.AMF_DecodeInt24(buffer)
    end

    def decode_int32(str)
      buffer = FFI::MemoryPointer.new :char, str.bytesize
      buffer.put_bytes(0, str, 0, str.bytesize)
      return LibRTMP.AMF_DecodeInt32(buffer)
    end

    def parse_url(url)
      protocol_p = FFI::MemoryPointer.new :int
      host_aval = AVal.new
      port_p = FFI::MemoryPointer.new :uint
      playpath_aval = AVal.new
      app_aval = AVal.new

      if LibRTMP.RTMP_ParseURL(url, protocol_p, host_aval, port_p, playpath_aval, app_aval) <= 0
        raise "failed to parse url. [url: #{url}]"
      end

      return {
        protocol: protocol_p.read_int,
        host: host_aval.to_s,
        port: port_p.read_uint,
        playpath: playpath_aval.to_s,
        app: app_aval.to_s}
    end

    module_function :decode_int24, :decode_int32, :parse_url

    class AMFObject
      def decode(buffer, buffer_size, decode_name)
        LibRTMP.AMF_Decode(self, buffer, buffer_size, decode_name ? 1 : 0)
      end

      def dump
        LibRTMP.AMF_Dump(self)
      end

      def get_property(name, index)
        name_aval = name.nil? ? nil : AVal.from_string(name)
        LibRTMP.AMF_GetProp(self, name_aval, index)
      end

      def find_first_matching_property(name)
        name_aval = AVal.from_string(name)
        prop = AMFObjectProperty.new
        if LibRTMP.RTMP_FindFirstMatchingProperty(self, name_aval, prop) > 0
          prop
        else
          nil
        end
      end
    end

    class AMFObjectProperty
      def get_number
        LibRTMP.AMFProp_GetNumber(self)
      end

      def get_string
        str_aval = AVal.new
        LibRTMP.AMFProp_GetString(self, str_aval)
        str_aval.to_s
      end
    end

    class RTMP
      def set_buffer_ms(buffer_ms)
        LibRTMP.RTMP_SetBufferMS(self, buffer_ms)
      end

      def update_buffer_ms
        LibRTMP.RTMP_UpdateBufferMS(self)
      end

      def set_option(option, value)
        option_aval = AVal.from_string(option)
        value_aval = AVal.from_string(value)
        LibRTMP.RTMP_SetOpt(self, option_aval, value_aval)
      end

      def setup_stream(protocol, host_name, port, socks_host,
                       playpath, tc_url, swf_url,
                       page_url, app, auth,
                       swf_sha256_hash, swf_size,
                       flash_ver, subscribe_path, usher_token,
                       start, stop, is_live_stream, timeout)
        host_name_aval = AVal.from_string(host_name)
        socks_host_aval = AVal.from_string(socks_host)
        playpath_aval = AVal.from_string(playpath)
        tc_url_aval = AVal.from_string(tc_url)
        swf_url_aval = AVal.from_string(swf_url)
        page_url_aval = AVal.from_string(page_url)
        app_aval = AVal.from_string(app)
        auth_aval = AVal.from_string(auth)
        swf_sha256_hash_aval = AVal.from_string(swf_sha256_hash)
        flash_ver_aval = AVal.from_string(flash_ver)
        subscribe_path_aval = AVal.from_string(subscribe_path)
        usher_token_aval = AVal.from_string(usher_token)

        LibRTMP.RTMP_SetupStream(self, protocol, host_name_aval, port, socks_host_aval,
                                 playpath_aval, tc_url_aval, swf_url_aval,
                                 page_url_aval, app_aval, auth_aval,
                                 swf_sha256_hash_aval, swf_size,
                                 flash_ver_aval, subscribe_path_aval, usher_token_aval,
                                 start, stop, is_live_stream ? 1 : 0, timeout)
      end

      def connect(packet)
        LibRTMP.RTMP_Connect(self, packet)
      end

      def connected?
        LibRTMP.RTMP_IsConnected(self) > 0
      end

      def timedout?
        LibRTMP.RTMP_IsTimedout(self) > 0
      end

      def get_duration
        LibRTMP.RTMP_GetDuration(self)
      end

      def toggle_stream
        LibRTMP.RTMP_ToggleStream(self)
      end

      def connect_stream(seek_time)
        LibRTMP.RTMP_ConnectStream(self, seek_time)
      end

      def reconnect_stream(seek_time)
        LibRTMP.RTMP_ReconnectStream(self, seek_time)
      end

      def init
        LibRTMP.RTMP_Init(self)
      end

      def close
        LibRTMP.RTMP_Close(self)
      end

      def read(buffer, buffer_size)
        LibRTMP.RTMP_Read(self, buffer, buffer_size)
      end

      def add_connect_option(option)
        if self.set_option("conn", option) <= 0
          raise "faild to set connect option. [option: #{option}]"
        end
      end
    end
  end
end

中身がないのに無駄に長い・・・

今日はここまで!