いものやま。

雑多な知識の寄せ集め

rtmpdumpのコードを読んでみた。(その3)

昨日の続き。

今日はDownload()の詳細を見ていく。

Download()

Download()で行ってるのは、初期設定を行ったあと、データを取得してファイルに書き込み、進捗度合いを計算するくらい。

一応、流れを書いておくと、

  1. 終了判定(回避策)
  2. RTMP_READの設定
  3. データの取得、書き込み
  4. バッファ時間の更新
  5. 進捗度合いの計算
  6. 繰り返しのチェック

という感じ。

1. 終了判定(回避策)

データの取得を行う前に、動画全体の長さと最後のキーフレームのタイムスタンプがある場合には、終了判定を行っている。
これは、一番最後までデータを取得した状態でさらにデータを読もうとしたときに、何か問題があるらしく、その回避策とのこと。
具体的には、最後のキーフレームのタイムスタンプが動画全体の長さの99.9%以上になっていたら、終了と判定している。

なお、動画全体の長さの単位は秒で、キーフレームのタイムスタンプの単位はミリ秒なので、注意が必要。

2. RTMP_READの設定

RTMP_READは構造体RTMPの中に含まれる構造体で、データを取得する際の状態を保持するための構造体みたい。

/* RTMP_READのメンバflagsで立てれるビット */
#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のメンバstatusの値として取りうる値 */
#define RTMP_READ_COMPLETE -3
#define RTMP_READ_ERROR    -2
#define RTMP_READ_EOF  -1
#define 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;

typedef struct RTMP
{
  // (省略)
  RTMP_READ m_read;
  // (省略)
} RTMP;

ダウンロードを途中から再開する場合には、以下のようにする。

  • メンバflagsにRTMP_READ_RESUMEのフラグを立てる。
  • メンバtimestampに最後のキーフレームのタイムスタンプを設定。
  • メンバinitialFrameTypeに最後のキーフレームのタイプを設定。
  • メンバnResumeTSに最後のキーフレームのタイムスタンプを設定。
  • メンバmetaHeaderにファイルのメタデータを設定。
  • メンバinitialFrameに最後のキーフレームのデータを設定。
  • メンバnMetaHeaderSizeにメタデータのサイズを設定。
  • メンバnInitialFrameSizeに最後のキーフレームのサイズを設定。

3. データの取得、書き込み

バッファを用意して、RTMP_Read()でデータをバッファに読み込む。
なお、バッファのサイズは64KB(64 * 1024)としている。

RTMP_Read()のインタフェースは、以下。

int          // 取得したデータのサイズ
RTMP_Read(
  RTMP *r,   // [IN ] RTMPオブジェクト
  char *buf, // [OUT] バッファ
  int size); // [IN ] バッファのサイズ

データを取得したら、fwrite()でファイルに書き込んでいる。

そのあと、動画全体の長さを取得できていない場合には、RTMP_GetDuration()で動画全体の長さを取得している。

double
RTMP_GetDuration(
  RTMP *r); // [IN ] RTMPオブジェクト

なお、読み込んだデータのサイズが0以下の場合には、RTMP_READのメンバstatusをチェック。
もしRTMP_READ_EOFならデータの終端に達したので、ループを抜ける。

4. バッファ時間の更新

動画全体の時間が取得できている場合、必要ならバッファ時間の更新を行う。

バッファ時間が動画全体の時間より短い場合、(動画全体の時間(秒) * 1000 + 5000)(ミリ秒)を新たなバッファ時間として、RTMP_SetBufferMS()、RTMP_UpdateBufferMS()で更新している。

void
RTMP_UpdateBufferMS(
  RTMP *r); // [IN ] RTMPオブジェクト

5. 進捗度合いの計算

現在のタイムスタンプ(RTMP_READのメンバtimestampに入っている)と動画全体の時間から、進捗度合いの計算を行い、必要なら進捗具合を表示している。

6. 繰り返しのチェック

エラーが発生したり、接続が切れてしまったり、タイムアウトになっていない限り、3.に戻って、再びデータの取得と書き込みを行なっていく。

次回予告?

これでrtmpdumpのコードは読めたので、次はRuby-FFIを使ってlibrtmpをRubyから使えるようにし、ニコニコ動画のダウンロードツールRTMPの動画のダウンロードの進捗度合いを表示できるようにする予定。

今日はここまで!