N を使わない新しいニコ生用の rtmpdump と ffmpeg(librtmp)


ニコ生の録画と配信方法の歴史 にあるように2013年11月14日から現在の仕様である、ユーザー生放送の生放送とタイムシフト、チャンネル生放送の生放送には -N オプションが必要で、ユーザー生放送のタイムシフトの保存には放送時間分だけ時間がかかっている。この仕様に対応した -N オプションではなく新しいオプションも備えて機能も拡大した rtmpdump のテストバージョンを公開している。テストバージョンは少し前から公開しているが、使い方を整理したり、新しいオプションのバグ取りになどに時間を取られ今日の記事公開となった。

主な特徴

  • 既存の -N オプションにも対応
  • タイムシフトのオフセット対応。これによりユーザー生放送のタイムシフトが時間指定で保存開始時間を決められる。同時に分割保存も可能になり分割数に応じて保存も早く済むようになる
  • 分割保存したファイルの連結に ffmpeg を使うが rtmpdump で保存した場合、動画時間のメタデータが修正されないので、rtmpdump から標準出力して ffmpeg に渡すか、ffmpeg 単体で保存する
  • 時間ぴったりに分割保存すると前後の重複部分または欠損部分が多少発生し、この問題の解決方法はあるが手間がかかる(後述)
  • librtmp に対応しているので ffmpeg で直接読み込んで遅延のないミラー放送したり、ffplay でニコ生を見たりできる
  • 現時点では使いやすくするフロントエンド(kakorokuR のようなもの)やコメントビューワのプラグインはないので直接コマンドを叩く
  • 差分ファイルも添付されているのでクロスプラットフォームに対応。公式のソースコードは Public Git Hosting – rtmpdump.git/log で、2015-12-15 compn update copyright year のソースコードを使う
  • 現在配布している rtmpdump でオフセットと -B を併用するとエラーが出るのでパッチを適用する

配布コミュニティ

ffmpeg, ffplay のサンプル(Windows用)

コマンド例

-N オプションを使う従来の方法は ニコ生の配信データを保存する rtmpdump のコマンド内容のまとめ を参照する。以下のコマンドは -N を必要としていた放送の新しいオプションでのコマンド例である。

ニコ生の新配信(β)と録画方法について

以前のようにタグのすべてが引数に割り当てらず “,” で区切って指定しているオプションがあるので、サンプルコマンド例を参考にする。

オフセットを使う場合や、出力時間を最後までではなく指定時間で終えるには rtmpdump では -B、ffmpeg では -t で時間指定する。

ユーザー生放送ライブ

rtmpdump -vr "rtmp://nleu13.live.nicovideo.jp:1935/liveedge/live_161013_22_7/lv278843100" -C S:151356:lv278843100:0:1476365625:8b0a9a56ed1c6b5d -E "nlPlayNotice,S:rtmp://nlpoca123.live.nicovideo.jp:1935/publicorigin/161013_22_1/|S:lv278843100?1476365625:30:c60d7f424e749fe5|S:lv278843100" -o output.flv
rtmpdump -vr "rtmp->url/lv[id]" -C S:rtmp->ticket -E "nlPlayNotice,S:stream->contents_list->contents[0]|S:stream->contents_list->contents[1]" -o output.flv

ffmpeg -f live_flv -i "rtmp://nleu13.live.nicovideo.jp:1935/liveedge/live_161013_22_7/lv278843100 live=1 nofcsub=1 conn=S:151356:lv278843100:0:1476365625:8b0a9a56ed1c6b5d cmdinv=nlPlayNotice cmdinvamf=S:rtmp://nlpoca123.live.nicovideo.jp:1935/publicorigin/161013_22_1/ cmdinvamf=S:lv278843100?1476365625:30:c60d7f424e749fe5 cmdinvamf=S:lv278843100 cmdinvamf=N:-2" -sn -c copy output.flv
ffmpeg -f live_flv -i "rtmp->url/lv[id] live=1 nofcsub=1 conn=S:rtmp->ticket cmdinv=nlPlayNotice cmdinvamf=S:stream->contents_list->contents[0] cmdinvamf=S:stream->contents_list->contents[1] cmdinvamf=S:lv[id] cmdinvamf=N:-2" -sn -c copy output.flv

以下同様に ffmpeg を ffplay に変えることで再生できるffplay -i "rtmp://nleu13.live.nicovideo.jp:1935/liveedge/live_161013_22_7/lv278843100 live=1 nofcsub=1 conn=S:151356:lv278843100:0:1476365625:8b0a9a56ed1c6b5d cmdinv=nlPlayNotice cmdinvamf=S:rtmp://nlpoca123.live.nicovideo.jp:1935/publicorigin/161013_22_1/ cmdinvamf=S:lv278843100?1476365625:30:c60d7f424e749fe5 cmdinvamf=S:lv278843100 cmdinvamf=N:-2"

ユーザー生放送TS

[offset] がオフセット秒指定。3カ所、同じ時間を指定する。コマンド例は10秒から。最初からは 0 を指定する。
rtmpdump -vr "rtmp://nleu22.live.nicovideo.jp:1935/liveedge/ts_161013_21_0/lv278802681.f4v_10" -C S:151356:lv278802681:0:1476361488:d1b030e28474edb6 -E "nlPlayNotice,S:rtmp://nlpoca132.live.nicovideo.jp:1935/fileorigin/ts_00|S:mp4:/content/20161013/lv278802681_133040579000_3_52e7d7.f4v?1476361488:30:483e59a2edb36984|S:lv278802681.f4v_10|N:10" -o output.flv
rtmpdump -vr "rtmp->url/lv[id].f4v_[offset]" -C S:rtmp->ticket -E "nlPlayNotice,S:stream->quesheet->que[0]|S:mp4:stream->quesheet->que[1]|S:lv[id].f4v_[offset]|N:[offset]" -o output.flv

ffmpeg -analyzeduration 30M -probesize 30M -f live_flv -i "rtmp://nleu22.live.nicovideo.jp:1935/liveedge/ts_161013_21_0/lv278802681_133040579000_3_52e7d7.f4v_10 live=1 nofcsub=1 conn=S:151356:lv278802681:0:1476361488:d1b030e28474edb6 cmdinv=nlPlayNotice cmdinvamf=S:rtmp://nlpoca132.live.nicovideo.jp:1935/fileorigin/ts_00 cmdinvamf=S:mp4:/content/20161013/lv278802681_133040579000_3_52e7d7.f4v?1476361488:30:483e59a2edb36984 cmdinvamf=S:lv278802681_133040579000_3_52e7d7.f4v_10 cmdinvamf=N:10" -sn -c copy output.flv
ffmpeg -analyzeduration 30M -probesize 30M -f live_flv -i "rtmp->url/stream->quesheet->que[1]_[offset] live=1 nofcsub=1 conn=S:rtmp->ticket cmdinv=nlPlayNotice cmdinvamf=S:stream->quesheet->que[0] cmdinvamf=S:mp4:stream->quesheet->que[1] cmdinvamf=S:stream->quesheet->que[1]_[offset] cmdinvamf=N:[offset]" -sn -c copy output.flv

チャンネル生放送ライブ

rtmpdump -vr rtmp://nlech02.live.nicovideo.jp:1935/onairliveedge/live_161013_21_2/lv278499331 -C S:151356:lv278499331:0:1476361786:cd197d8ed814643f -E "nlPlayNotice,S:rtmp://chnl02.ep.live.nicovideo.jp:1935/publicorigin/161013_08_3/|S:lv278499331?1476361786:30:c0771c76d1093d35|S:lv278499331|N:-2" -o output.flv
rtmpdump -vr "rtmp->url/lv[id]" -C S:rtmp->ticket -E "nlPlayNotice,S:stream->contents_list->contents[0]|S:stream->contents_list->contents[1]|S:lv[id]|N:-2" -o output.flv

ffmpeg -f live_flv -i "rtmp://nlech02.live.nicovideo.jp:1935/onairliveedge/live_161013_21_2/lv278499331 live=1 nofcsub=1 conn=S:151356:lv278499331:0:1476361786:cd197d8ed814643f cmdinv=nlPlayNotice cmdinvamf=S:rtmp://chnl02.ep.live.nicovideo.jp:1935/publicorigin/161013_08_3/ cmdinvamf=S:lv278499331?1476361786:30:c0771c76d1093d35 cmdinvamf=S:lv278499331 cmdinvamf=N:-2" -sn -c copy output.flv
ffmpeg -f live_flv -i "rtmp->url/lv[id] live=1 nofcsub=1 conn=S:rtmp->ticket cmdinv=nlPlayNotice cmdinvamf=S:stream->contents_list->contents[0] cmdinvamf=S:stream->contents_list->contents[1] cmdinvamf=S:lv[id] cmdinvamf=N:-2" -sn -c copy output.flv

分割ファイルの連結方法

ffmpeg にはファイル同士を連結する concat があるが、無劣化でコピーして連結するには demuxer の concat を使う。

concat を使い分ける
【ffmpeg】動画・音声を連結する concat の使い方 其の3

file プロトコルで入力順にファイル名を記述したテキストファイルを読み込ませる方法をとる。
ffmpeg -f concat -safe 0 -i input.txt -c copy output.flv

input.txt。# 行は実行されないコメント扱いになる。

file D:/video/test1.flv
file D:/video/test2.flv
# ffmpeg -f concat -safe 0 -i input.txt -c copy output.flv
# test1.flv, test2.flv の順番に連結

連結部分の重複を回避する

重複部分を調べる

ffmpeg にはキーフレームや I, P, B などのフレームタイプを調べたり、フレームサイズを調べたり、フレームの同一性を調べるハッシュを調べたりできるのでこれを利用して重複フレームを調べる。他のアプリケーションでも利用できるフォーマットでログを出力するには ffmpeg の標準出力ではなく ffprobe で出力形式を指定する。今回は JSON を利用する。

ffprobe Documentationb : Main options

ffprobe -of json -show_packets -show_data_hash adler32 -i input.flv -select_streams v > show_data_hash.json
ログ形式

        {
            "codec_type": "video",
            "stream_index": 0,
            "pts": 0,
            "pts_time": "0.000000",
            "dts": 0,
            "dts_time": "0.000000",
            "size": "11127",
            "pos": "574",
            "flags": "K_",
            "data_hash": "adler32:6696b483"
        },

フレーム毎に各配列にデータが収められているのでこのデータを利用する。ここで重要なのが pts, flags, data_hash で pts でカットする時間を決め、flags でキーフレームかどうかを調べ、data_hash でフレームの同一性を調べる。スクリプトを書いて自動で処理できるようになればこれで楽に処理できるが、目視ではフレーム数が多くわかりにくいので CSV で出力すると一覧性が増す。

目視なら ffprobe よりも見やすい
ffmpeg -i input.flv -c copy -an -f framehash -hash adler32 input.flv.csv

ではどのキーフレームを基準にカットする時間を決めるかだが、現在の仕様ではオフセット使用時での保存開始数秒は dts が正しく振られないこと(再生すると早送りになる)があるのでその数秒をカットしてその前の動画に含める必要がある。このカットする時間はコーデックや映像の GOP 間隔に依存するので静止画配信の場合では多く見積もって 20秒あればほぼ確実である。まとめると30分の配信(実際にはユーザー生放送で外部ツールを使うと29分で一度途切れる)を3分割して保存するには

  1. 0秒から620秒まで
  2. 600秒から1220秒まで
  3. 1200秒から最後まで

の3つのコマンドを実行して保存することになる。多少の重複時間は気にしない、見たら消すような放送ならこのような手間は自動化できていない現時点では不要である。

時間指定で連結する

FFmpeg Formats Documentation : concat を参照すると、オフセットや時間指定もできるのでこれを指定することで動画をカットしながら連結することができる。最初から最後まで連結するだけなら file にファイル名とパスを指定するだけで良い。開始時点を決める inpoint や、終了時間を決める outpoint を使う場合は、動画時間の duration を併用する。

cocnat.txt
file 000.flv
duration 34.95
outpoint 34.95
file 001.flv
inpoint 4.348
duration 29.95#34.298-4.348
outpoint 34.298
file 002.flv
inpoint 5

重複無くもれなく連結するには余分を持ってタイムシフトを保存して上の方法で調べたハッシュとキーフレームの pts を参考にする。

  1. 1入力のファイルの最後のキーフレームと、2入力のファイルの2番目(以降)の同じキーフレームをハッシュを元に探す
  2. 1入力のファイルのキーフレームの pts を duration, outpoint に指定するが、このままだと連結するとキーフレームが重複するので1フレーム時間減らす
  3. 2入力のファイルは inpoint を2番目(以降)のキーフレームの pts を指定
  4. 先ほどと同様に最後のキーフレームの手前までの pts と 最初の pts を引いたのを duration で指定
  5. 以下同様

まとめると1入力のファイルの最後のキーフレームを調べて、2入力のファイルに同じキーフレームがあるのを確認。確認できたら1入力のファイルの最後のキーフレーム前までの pts を duration, outpoint で指定し、2入力のファイルはそのキーフレームの pts から読み込めるように inpoint を指定し、以下連結数に応じて複数回繰り返す。

追記履歴

ffmpeg の -analyzeduration 30M -probesize 30M と字幕を保存しない -sn を追加した。2016年11月23日

コメントを残す

メールアドレスが公開されることはありません。