前の話、前の話の続きの更新版です。
色々やった結果、RTMPでの再生手法が確立しました。
(以下2012年5月時点での話)
[前書き]
Flashでの再生について
かつては最も確実でしたが、現在は最も不確実です。
手法については前の話を参照。
HTTP Live Streaming (HLS)による再生について
特に更新無し。
TwitCasting HLS Stream Loaderを使う方が便利ですが、別にFFmpegでもいいです。
手法については前の話を参照。
ブラウザでの再生に対するVLCでの再生の利点は
・画面の縦横比の補正が可能
・絵と音の同期ずれの補正が可能
・クロッピングが可能
・画質補正が可能
・リサイズの自由度が高い
・リサイズが綺麗
・PCへの負荷が低い
・再生の一時停止・スロー再生が可能
・バッファリングした分は早送りが可能
特に、縦横比の補正や音声同期ずれの補正は、マシンパワーが有り余っている環境でも非常に有用です。
[基本的なコマンド]
ここからが本題。
以下各コマンドはディレクトリパスを省略して説明しますが、実際の指定はフルパスで行います。
また、VLCには--play-and-exitオプションを付けて再生終了時に終了した方がいいです。
基本的にはRTMPDumpでダウンロードし、パイプでVLCに渡すだけですが(場合によりFFmpegを挟む)、それだけだと色々問題があるので以下のツールを使用します。
・Non-Blocking Pipe Buffer (nbpipebuffer.pl)
普通にパイプで繋ぐとブロッキングが発生し、VLCがRTMPDumpの足を引っ張ります。(例:再生を一時停止するとダウンロードが止まり接続が切れる)
これを避ける為に、パイプ間にノンブロッキングなバッファリング機構を挟みます。
・FLV Stream Dripper (flvsdrip.exe)
RTMPDumpが出力するFLVデータは破損していることがあるのでそれを修復します。
(2012/05/17時点では必要な機能がテスト版にしかないのでテスト版を使ってください)
基本的なコマンドは以下のようになります。
例1(fmsUrlの場合):
rtmpdump.exe -v -r "rtmp://サーバー名.ustream.tv/ustreamVideo/チャンネルID" -a "ustreamVideo/チャンネルID" -f "WIN 11.2.202.235" -y "streams/live" -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe | vlc.exe -
例2(cdnUrlの場合):
rtmpdump.exe -v -r "rtmp://ustream.fc.llnwd.net/ustream" -a "ustream" -f "WIN 11.2.202.235" -y "ustream_llnw_live_1_00000001" -s "http://static-cdn1.ustream.tv/swf/live/viewer.rsl:201.swf" -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe | vlc.exe -
RTMPDumpのオプションについては
http://cdngw.ustream.tv/Viewer/getStream/1/チャンネルID.amf
のデータに含まれるfmsUrlの値、又はcdnUrl、streamNameの値が必要です。(前の話の続きを参照)
このデータはバイナリデータですが、普通にブラウザでテキストとして表示しても何とかなる範囲です。
データをきちんと解析する場合は、UstreamのAMFデータ解析を参照してください。
なお、JSAMFを使えばパースは可能なので、ブラウザのみで解析することも可能です。(多分気が向いたら何か作る)
ただし、一部の公式チャンネルではfmsUrl、cdnUrlいずれも含まれていないことがあり、その場合は対処不能です。
[再生出来ない場合の対処]
上記基本的なコマンド例では再生されない場合があります。
以下に症状別の対処を説明します。
・再生が一瞬で終わってしまう
コーデックがH264とNellymoserの組み合わせの場合に発生します。
これはDTS重複時のVLCの仕様です。
(FFmpegでの Application provided invalid, non monotonically increasing dts to muxer エラーに相当)
この問題に対処する為FLV Stream DripperでDTSを修正していますが、VLCには関係無い模様。
対処としてはFFmpegを挟みます。
(再エンコードは行わないので画質は劣化せず、CPU負荷もかかりません。また、DTSは修正済みなのでFFmpegは落ちません)
コマンド例:
rtmpdump.exe [RTMPDumpオプション] -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe | ffmpeg.exe -f flv -i pipe: -vcodec copy -acodec copy -f flv pipe: -v warning | vlc.exe -
・上記対処で再生できるようになったが、やっぱり数分で再生が終わってしまう
Application provided invalid, non monotonically increasing dts to muxer in stream 1: エラーが発生
stream 1なので、音声側のDTSエラーで落ちています。
対処としてはFLV Stream DripperでDTSエラーを含む音声データを破棄します。(-dc aerrオプション)
コマンド例:
rtmpdump.exe [RTMPDumpオプション] -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe -dc aerr | ffmpeg.exe -f flv -i pipe: -vcodec copy -acodec copy -f flv pipe: -v warning | vlc.exe -
・再生されないが、RTMPDumpを終了すると再生される
音声コーデックがSpeexの場合に発生します。VLCとSpeexは相性がよくないようです。
対処としてはFFmpegで音声をADPCM又はMP3に再エンコードします。
FFmpegが大量のメッセージを吐くことがあるので、-v errorオプションを指定します。
コマンド例:
rtmpdump.exe [RTMPDumpオプション] -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe | ffmpeg.exe -f flv -i pipe: -vcodec copy -acodec adpcm_swf -f flv pipe: -v error | vlc.exe -
(ADPCMの場合はadpcm_swf、MP3の場合はlibmp3lameを指定します)
オーディオサンプリングレートが11kの場合は、-ar 22050オプションを追加して22kにする必要があります。
コマンド例:
rtmpdump.exe [RTMPDumpオプション] -o - | perl.exe nbpipebuffer.pl | flvsdrip.exe | ffmpeg.exe -f flv -i pipe: -vcodec copy -acodec adpcm_swf -ar 22050 -f flv pipe: -v error | vlc.exe -
・再生されないが、RTMPDumpを終了すると再生される
音声のみの場合に発生します。
対処としてはFFmpegで音声をADPCM又はMP3に再エンコードします。
コマンド例は上の例と同様です。
希に以上の方法でも再生出来ない場合があります。
その場合はHLS版を試しましょう。
コメント(0)| Track back(0) | 2012-05-17 23:04:50
[動画]
|
UstreamのFLVを見ていて気付きましたが、どうやら音声のみのFLVファイルはVLCではシークできないようです。
(プレイヤーによってはシーク可能です)
通常、シークできないFLVファイルはFLVMDIやyamdi等でメタデータを埋め込めばシーク可能になりますが、音声のみのFLVファイルの場合は上手くいきません。
(yamdiには音声のみのFLVファイルを処理するためのオプションが用意されていますが、これを利用しても正常に再生出来ません)
ということでFFmpegで何とかします(無劣化で)。
■方法1:ファイル形式(コンテナ)を変更する
音声コーデックがAACやMP3の場合は、AACやMP3、或いはMP4ファイルに入れ替えることでシーク可能になります。
(Speexの場合はOgg)
コンテナ形式の変換のみなので、音質は劣化しません。
ただし、Nellymoser(Asao)には対応するファイル形式やコンテナ形式が存在しないため、音質の劣化を伴うコーデックの変換が必要です。
例:
ffmpeg.exe -i audio.flv -acodec copy audio.mp4
■方法2:動画を追加する
動画+音声のFLVファイルはシーク可能なので、動画を追加してメタデータを埋め込みます。
動画を追加するだけなので、音質は劣化しません。
例:
ffmpeg.exe -i audio.flv -f lavfi -i "color=black:160x120:1" -vcodec libx264 -g 10 -keyint_min 2 -acodec copy -shortest audio.flv
(サイズ160x120、フレームレート1の黒背景動画を追加、シークは約10秒単位で可能)
※この状態でもVLCではシーク可能ですが、yamdi等でメタデータを埋め込んだ方がいいようです
現実的には音質が重要な場合にNellymoserが使われることは無いので、劣化を気にせず素直にMP3とかに変換した方が扱いが楽です。
コメント(0)| Track back(0) | 2012-04-04 19:23:20
[動画]
|
FFmpegだけでUstream配信ができると聞いたので試してみました。
ブラウザからFlashで配信する場合や、UstreamProducerで配信する場合よりも負荷は軽いです。
ただし、手元の環境では数分で切れます。
マシンパワー等が最近のPC並にあればいけるのかもしれません。
手元の環境で実用できないので、パラメーター等は精査していません。
例:SCFHでデスクトップ配信する場合
以下のコマンドで配信が可能
(H264、フレームレート12、MP3モノラル、画面サイズを0.75倍に縮小)
ffmpeg.exe -f dshow -video_size 640x360 -i video="SCFH DSF":audio="Realtek HD Audio rear input" -vcodec libx264 -r 12 -acodec libmp3lame -ar 44100 -ab 96k -ac 1 -filter scale="iw*0.75":"ih*0.75" -f flv "rtmp://x.xxxxxxxx.fme.ustream.tv/ustreamVideo/xxxxxxxx/ストリームキー flashver=FMLE/3.0\20(compatible;\20FMSc/1.0)" -v warning
コーデックはFFmpegでエンコード可能なもので、FLVが対応しているもので、Ustreamが対応しているものを選ぶ必要があります。
FFmpeg起動後にSCFHでキャプチャーサイズを変更すると上下又は左右に黒帯が入るので、起動時に入力サイズを指定する必要があります。
キャプチャーデバイス名は
ffmpeg.exe -list_devices true -f dshow -i dummy
で利用可能な一覧が表示されます。
出力先URLは、Ustreamの番組設定のリモートに書いてある、RTMP URLとストリームキーとUAをつないだものです。
FMLE(所謂FME)を指定していますが、
WirecastUSTREAM/FM 1.0 (compatible; FMSc/1.0)
Wirecast/FM 1.0 (compatible; FMSc/1.0)
等でもいいのかもしれません。
接続後は通常の配信画面から配信を開始できます。
またリモートコンソール画面を使えばFlashを使わずに配信が可能です。
(録画ボタンしかないが、録画を開始すると配信も開始される。ただしプレビューが一切無く、配信中かどうかすら判別不能。)
コメント(1)| Track back(0) | 2012-03-15 23:50:50
[動画]
|
前回の続き。
PerlでUstreamの録画ビデオのFLV URLを取得してみます。
ライブの場合は、特にデータを送る必要も無く単純にGETリクエストで得られたAMFデータを解析するだけですが、録画ビデオの場合はAMFデータをPOSTする必要があります。
今回は Data::AMF::Packet の serialize をベースにAMFデータのシリアライズ部分を書きます。
Data::AMF::Formatter::AMF0 も修正が必要なので、修正分を My::Data_AMF_Formatter_AMF0 として作成します。
ライセンスは Data::AMF と同様。
(testDeserializeとprintRef部分に関しては前回参照)
※PPM版の Data::AMF は古いので、最新版をダウンロードして lib 以下を use lib でライブラリパスに追加してください。
JavaScriptでも JSAMF を使ってがんばればできるはず。
とりあえずAMFデータのdecode(デシリアライズ)部分は動いたけど、シリアライズできるかは確認してない。
#!/usr/bin/perl
use lib './lib';
use strict;
use Encode;
use LWP::UserAgent;
use Data::AMF::IO;
use Data::AMF::Packet;
use Data::AMF::Message;
use Data::AMF::Type::Boolean;
use My::Data_AMF_Formatter_AMF0;
use utf8;
binmode(STDOUT, ':raw :encoding(cp932)'); # 標準出力の文字コードを指定
binmode(STDERR, ':raw :encoding(cp932)'); # 標準エラー出力の文字コードを指定
# ビデオIDを指定
&testUST_req( );
sub testUST_req {
my $vid = shift || return;
my $amf = Data::AMF::Packet->new();
my $value = [{
# autoplayはboolean、それ以外はstring
'rpin' => &generateRpin(),
'videoId' => $vid .'',
'locale' => 'en_US',
'autoplay' => Data::AMF::Type::Boolean->new(0), #古いバージョンだと使えない
'brandId' => '1'
}];
my $message = Data::AMF::Message->new(
target_uri => 'Viewer.getVideo',
response_uri => '/1',
value => $value,
#source => $data,
version => 0
);
$amf->messages( [$message] );
my $data = &testSerialize($amf);
my $browser = LWP::UserAgent->new;
$browser->agent( 'Shockwave Flash' );
my $response = $browser->post(
'http://rgw.ustream.tv/gateway.php',
x_flash_version => '11,1,102,63',
Content_Type => 'application/x-amf',
Content => $data
);
if ( !$response->is_success ) {
print STDERR $response->status_line ."\n";
return;
}
my $amfcontent = $response->content;
my $amfobj = &testDeserialize( $amfcontent );
&printRef( $amfobj );
}
sub generateRpin {
# rpinは乱数
return 'rpin.'. substr( (rand(1) * rand(1)), 2 );
}
##################################################################
# AMFシリアライズ
# 引数:Data::AMF::Packetオブジェクト
# 戻り値:バイナリデータ
##################################################################
sub testSerialize {
my $self = shift;
my $io = Data::AMF::IO->new( data => q[] );
$io->write_u16($self->version);
$io->write_u16(scalar @{ $self->headers });
#$io->write_u16(scalar @{ $self->messages });
for my $header (@{ $self->headers })
{
$io->write_utf8( $header->name );
$io->write_u32( $header->must_understand );
my $data;
# if ($self->version == 3)
# {
# my $formatter = Data::AMF::Formatter->new(version => 3)->new;
# $formatter->io->write_u8(0x11);
# $formatter->write($header->value);
#
# $data = $formatter->io->data;
# }
# else
# {
# $data = Data::AMF::Formatter->new(version => 0)->format($header->value);
# }
$data = My::Data_AMF_Formatter_AMF0->new()->format($header->value);
$io->write_u32(bytes::length($data));
$io->write($data);
}
$io->write_u16(scalar @{ $self->messages });
for my $message (@{ $self->messages })
{
$io->write_utf8($message->target_uri);
$io->write_utf8($message->response_uri);
my $data;
# if ($self->version == 3)
# {
# my $formatter = Data::AMF::Formatter->new(version => 3)->new;
# $formatter->io->write_u8(0x11);
# $formatter->write($message->value);
#
# $data = $formatter->io->data;
# }
# else
# {
# $data = Data::AMF::Formatter->new(version => 0)->format($message->value);
# }
$data = My::Data_AMF_Formatter_AMF0->new()->format($message->value);
$io->write_u32(bytes::length($data));
$io->write($data);
}
return $io->data;
}
My::Data_AMF_Formatter_AMF0
Data::AMF::Formatter::AMF0 の修正版
format を修正し、is_number と format_boolean を追加したもの。
Boolean型の修正と、数字文字列を数値型ではなく文字列型として扱うように修正。
今回使っていない部分の動作に関しては不明。
package My::Data_AMF_Formatter_AMF0;
use Any::Moose;
require bytes;
use Scalar::Util qw/looks_like_number blessed/;
use Data::AMF::IO;
has 'io' => (
is => 'rw',
isa => 'Data::AMF::IO',
lazy => 1,
default => sub {
Data::AMF::IO->new( data => q[] );
},
);
no Any::Moose;
sub format {
my ($self, @obj) = @_;
$self = $self->new unless blessed $self;
for my $obj (@obj) {
if (my $pkg = blessed $obj) {
if ($pkg eq 'Data::AMF::Type::Boolean') {
$self->format_boolean($obj->data);
}
else {
$self->format_typed_object($obj);
}
}
elsif (my $ref = ref($obj)) {
if ($ref eq 'ARRAY') {
$self->format_strict_array($obj);
}
elsif ($ref eq 'HASH') {
$self->format_object($obj);
}
else {
Carp::confess qq[cannot format "$ref" object];
}
}
else {
if (looks_like_number($obj) && &is_number($obj) ) { # && $obj !~ /^0\d/) {
$self->format_number($obj);
}
elsif (defined($obj)) {
$self->format_string($obj);
}
else {
$self->format_null($obj);
}
}
}
$self->io->data;
}
sub is_number {
my $val = shift;
if ( ($val ^ $val) eq '0' ) {
return 1;
} else {
return 0;
}
}
sub format_boolean {
my ($self, $obj) = @_;
$self->io->write_u8(0x01);
if ( $obj ) {
$self->io->write_u8(0x01);
} else {
$self->io->write_u8(0x00);
}
}
sub format_number {
my ($self, $obj) = @_;
$self->io->write_u8(0x00);
$self->io->write_double($obj);
}
sub format_string {
my ($self, $obj) = @_;
$self->io->write_u8(0x02);
$self->io->write_utf8($obj);
}
sub format_strict_array {
my ($self, $obj) = @_;
my @array = @{ $obj };
$self->io->write_u8(0x0a);
$self->io->write_u32( scalar @array );
for my $v (@array) {
$self->format($v);
}
}
sub format_object {
my ($self, $obj) = @_;
$self->io->write_u8(0x03);
for my $key (keys %$obj) {
my $len = bytes::length($key);
$self->io->write_u16($len);
$self->io->write($key);
$self->format($obj->{$key});
}
$self->io->write_u16(0x00);
$self->io->write_u8(0x09); # object-end marker
}
sub format_null {
my ($self, $obj) = @_;
$self->io->write_u8(0x05); # null marker
}
sub format_typed_object {
my ($self, $obj) = @_;
$self->io->write_u8(0x10);
my $class = blessed $obj;
$self->io->write_utf8($class);
$self->format_object($obj);
}
__PACKAGE__->meta->make_immutable;
__END__
=head1 NAME
Data::AMF::Formatter::AMF0 - AMF0 serializer
=head1 SYNOPSIS
my $amf0_data = Data::AMF::Formatter::AMF0->format($obj);
=head1 METHODS
=head2 format
=head2 format_number
=head2 format_string
=head2 format_strict_array
=head2 format_object
=head2 format_null
=head2 format_typed_object
=head1 AUTHOR
=head1 COPYRIGHT
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
The full text of the license can be found in the
LICENSE file included with this module.
=cut
serialize と deserialize の両方を修正したので、Data::AMF::Packet もまるごと差し替えた方がいいかも。
Data::AMF 関連への修正の必要性がどこに由来するのかがよく分からない。
Data::AMF 側の問題なのか、Flashが使うAMFはいわゆるAMFとは違うのかが分からない。
Data::AMF の人なら分かるだろうけどカヤックに知り合いいない。
参考仕様
○documentation:amf:envelopes:remoting [Open Source Flash]
○documentation:amf:astypes [Open Source Flash]
※Adobeが公開している公式の仕様は、ライセンス上見るだけでおかしな制限を受ける場合があるので見ないことをお勧めします。
コメント(0)| Track back(0) | 2012-03-10 18:34:30
[動画]
|
Bookmarks Sidebar Scroll Retainer を更新しました。
ブックマークサイドバーを閉じる際にスクロール位置を記憶し、再度表示した際にスクロールを復元するFirefox用の拡張機能です。
●詳細
●Bookmarks Sidebar Scroll Retainer :: Add-ons for Firefox
(最新版は1.1.0です。審査が終わるまではバージョン一覧から指定してダウンロードする必要があります。)
動作イメージ

[更新内容]
・検索クリア時の位置復元機能を追加
・他のアドオンとの互換性を向上
・高負荷時の動作を改善
検索ボックスをクリアした際に、検索ボックス入力前に表示していた位置を復元する機能を追加しました。
コメント(0)| Track back(0) | 2012-03-07 01:08:50
[web]
|
ADrive Command を更新しました。
ADriveをコマンドラインから使う為のコマンドです。
ADriveは無料で使える50Gのストレージですが、無料アカウントではFTPもWebDAVも使えずブラウザ経由のみで、サーバーのデータ退避先として使うにはあまりに不便なので、ブラウザの通信を解析して作りました。
●詳細・ダウンロード
[更新内容]
・ADriveのアップデートに対応
今回のADriveのアップデートで、サーバー側でファイルのMD5ハッシュが生成されるようになりました。
今まで
アップロード=>ダウンロード=>ファイル比較
としていたのが
アップロード=>MD5比較
で済むようになりました。
コメント(0)| Track back(0) | 2012-03-01 21:09:30
[自作プログラム関係]
|
前回までのまとめ
UstreamのライブをRTMPDumpで読むには
http://cdngw.ustream.tv/Viewer/getStream/1/チャンネルID.amf
に含まれるfmsUrl、又はcdnUrl、streamNameが必要
ということでAMFデータをPerlで解析します。
用途的には Data::AMF::Packet がまさにそれですが、うまくいかなかったので、仕様とバイナリエディタを見比べながら修正。
見て分かる通り Data::AMF::Packet の deserialize 部分をとりあえず動くように弄って体裁を整えたものです。
ライセンスは Data::AMF と同様。
#!/usr/bin/perl
use strict;
use Encode;
use LWP::UserAgent;
use Data::AMF::IO;
use Data::AMF::Parser;
use utf8;
binmode(STDOUT, ':raw :encoding(cp932)'); # 標準出力の文字コードを指定
binmode(STDERR, ':raw :encoding(cp932)'); # 標準エラー出力の文字コードを指定
# チャンネルID未指定時はNHK World
&testUST();
sub testUST {
my $cid = shift || 8990235; # NHK World
my $amfurl = 'http://cdngw.ustream.tv/Viewer/getStream/1/'. $cid .'.amf';
my $browser = LWP::UserAgent->new;
$browser->agent( 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0)' );
#$browser->proxy( 'http', 'http://localhost:8888/' );
my $response = $browser->get( $amfurl );
if ( !$response->is_success ) {
print STDERR $response->status_line ."\n";
return;
}
# ここでUTF8デコードするとパースできないので出力時にデコードする
#my $amfcontent = decode( 'utf8', $response->content );
my $amfcontent = $response->content;
my $amfobj = &testDeserialize( $amfcontent );
print "\nheaders:\n";
for my $header ( @{ $amfobj->{'headers'} } ) {
&printRef( $header, '', '' );
}
print "\nmessages:\n";
for my $message ( @{ $amfobj->{'messages'} } ) {
&printRef( $message, '', '' );
}
}
##################################################################
# AMFデシリアライズ
# 引数:AMFバイナリストリング
# 戻り値:{ 'headers', 'messages' }
##################################################################
sub testDeserialize {
my ($data) = @_;
my $io = Data::AMF::IO->new( data => $data );
my $ver = $io->read_u16;
my $header_count = $io->read_u16;
my $parser = Data::AMF::Parser->new( version => 0 );
my @headers;
for my $i (1 .. $header_count) {
my $name = $io->read_utf8;
my $must = $io->read_u8;
my $len = $io->read_u32;
my $data = $io->read($len);
my ($value) = $parser->parse($data); # parseはリストを返す
push( @headers, {$name => $value, 'required'=>$must} );
}
my $message_count = $io->read_u16;
my @messages;
for my $i (1 .. $message_count) {
my $target_uri = $io->read_utf8;
my $response_uri = $io->read_utf8;
my $len = $io->read_u32;
my $data = $io->read($len);
my ($value) = $parser->parse($data);
push( @messages, {'target'=>$target_uri, 'response'=>$response_uri, 'data'=>$value} );
}
return { 'headers' => \@headers, 'messages' => \@messages };
}
##################################################################
# データ表示
# 引数:変数(参照以外でも可)
# 戻り値:無し
##################################################################
sub printRef {
my ( $ref, $name, $idt ) = @_;
if ( $name ne '' ) {
$name .= ' ';
}
if ( ref($ref) eq '' ) {
print $idt . decode_utf8($ref) ."\n";
} elsif ( ref($ref) eq 'HASH' ) {
print $idt . $name .'['. ref($ref) ."]\n";
&printHashRef( $ref, $idt.'| ' );
} elsif ( ref($ref) eq 'ARRAY' ) {
print $idt . $name .'['. ref($ref) ."]\n";
&printArrayRef( $ref, $idt.'| ' );
} else {
print $idt . $name .'['. ref($ref) ."]\n";
}
}
sub printHashRef {
my ( $ref, $idt ) = @_;
foreach my $key ( keys %{$ref} ) {
if ( ref($ref->{$key}) eq '' ) {
print $idt . $key .' '. decode_utf8($ref->{$key}) ."\n";
} else {
&printRef( $ref->{$key}, $key, $idt );
}
}
}
sub printArrayRef {
my ( $ref, $idt ) = @_;
foreach my $item ( @{$ref} ) {
if ( ref($item) eq '' ) {
print $idt . decode_utf8( $item ) ."\n";
} else {
&printRef( $item, '', $idt );
}
}
}
パーサーは Data::AMF::Parser より Storable::AMF の方が恐らく高速なのでそっちを使いたかったが上手く動かなかった。
サーバーとバージョンを合わせる関係でPerl5.8.8系と古いモジュールを使っているので、最新版なら上手くいくのかもしれない。
Storable::AMF でパースできるのであれば、Data::AMF::IO 部分を書き直して Data::AMF 無しにした方が依存モジュール的に導入が手軽。
ちなみに、ライブではなく録画の場合は、 FlashVideo::Site::Ustream のAMFの送信部分が参考になるはず。
参考仕様
○documentation:amf:envelopes:remoting [Open Source Flash]
○documentation:amf:astypes [Open Source Flash]
※Adobeが公開している公式の仕様は、ライセンス上見るだけでおかしな制限を受ける場合があるので見ないことをお勧めします。
コメント(0)| Track back(0) | 2012-02-10 03:52:40
[動画]
|
前回のまとめ
HLSはFFmpegで読んでVLCにパイプで渡す。
RTMPはRTMPDumpで読んでVLCにパイプで渡す。
RTMP URLは
http://cdngw.ustream.tv/Viewer/getStream/1/チャンネルID.amf
に書いてあるfmsUrlの値である。
ただし、fmsUrlが無くcdnUrlしか含まれていない場合がある。
ということで、cdnUrlの場合のRTMPDumpのオプションについて、です。
CDNの場合は
URL:amfに含まれるcdnUrl
appName:amfに含まれるcdnUrlのpath部分(/以降)
playpath:amfに含まれる(cdnUrlに対応する)streamName
swfUrl:http://static-cdn1.ustream.tv/swf/live/viewer.rsl:123.swf
を指定します。
例
cdnUrl:rtmp://cp000001.live.edgefcs.net/live
streamName:ustream-XXXXXXX@00001
の場合は
rtmpdump.exe -v -r "rtmp://cp000001.live.edgefcs.net/live" -a "live" -f "WIN 11,0,1,152" -y "ustream-XXXXXXX@00001" -s "http://static-cdn1.ustream.tv/swf/live/viewer.rsl:123.swf" -o - > NUL
cdnUrl:rtmp://ustream.fc.llnwd.net/ustream
streamName:ustream_llnw_live_1_00000001
の場合は
rtmpdump.exe -v -r "rtmp://ustream.fc.llnwd.net/ustream" -a "ustream" -f "WIN 11,0,1,152" -y "ustream_llnw_live_1_00000001" -s "http://static-cdn1.ustream.tv/swf/live/viewer.rsl:123.swf" -o - > NUL
となります。
cdnUrlとstreamNameはamfファイルに複数含まれている場合があるので、それぞれ対応するものを使う必要があります。
swfUrlの値を実行時に取得する手段がないのが懸念材料。
(2012/05/17追記)
swfUrlの値(のviewer.rsl以下)は割と頻繁に変わります。
現在の値は
http://static-cdn1.ustream.tv/swf/live/viewer.rsl:201.swf
です。
ただし古い値を使用しても特に目立った問題は無いようです。
コメント(0)| Track back(0) | 2012-02-07 00:35:30
[動画]
|
Ustreamのライブ配信はRTMPとHLS形式なので、それをブラウザ上ではなくVLCで再生してみます。
利点は
・軽い
・リサイズされないので画質が綺麗
・一時停止・早送り等が可能
・圧倒的に軽い
欠点は
・不安定(配信によっては再生不可)
です。
(以下2012年2月においての話です。)
[Flash]
通信を解析する際のスタート地点。
ブラウザを使うと余計な通信が多いので、Flashのスタンドアローンプレーヤーを使います。
Flashのスタンドアローンプレーヤーで再生する場合のURLは
http://www.ustream.tv/flash/live/1/チャンネルID
です。
(ちなみに録画の場合は http://www.ustream.tv/flash/viewer.swf?autoplay=false&vid=ビデオID )
チャンネルIDは番組名からAPIを使って取得できます。(DeveloperKey不要)
APIを使わなくても番組ページのソース中に含まれているchannelIdやcidの値なので、link rel="image_src" や link rel="video_src" から持ってくることが多いです。
実際Flashを使うのが一番確実ですが
・重い
・実際の画面サイズが分からず、しかもリサイズが汚い
・最前面表示ができない
等の問題があります。
特に高品質配信なのに画質が悪く感じられるのは、画面が縮小されておりしかもリサイズが汚いせいです。
[HTTP Live Streaming (HLS)]
画質はRTMPに劣りますが、その分遅い回線でも再生可能です。
URLは
http://iphone-streaming.ustream.tv/uhls/チャンネルID/streams/live/iphone/playlist.m3u8
です。
アクセスがあってからHLS用のファイルを生成しているらしく、再生されるまで時間がかかることがあります。
HLSに対応していない配信もあるようです。(が、暫くしてからアクセスすると再生できたりする)
VLCナイトリービルドの2.0系及び2.1系を試しましたが、VLCのHLS対応機能はまだライブ配信の再生に使えるレベルではない感じなので、FFmpegで読んでパイプでVLCに渡します。
コマンドは以下(実際はフルパスで。以下同様)
ffmpeg.exe -i "http://iphone-streaming.ustream.tv/uhls/チャンネルID/streams/live/iphone/playlist.m3u8" -vcodec copy -acodec copy -f mpegts pipe: -v warning | vlc.exe -
ただしパイプで繋ぐとブロッキングが発生し、VLCがFFmpegの足を引っ張ります。(例:一時停止すると読み込みが止まる)
これを避ける為にNon-Blocking Pipe Bufferを挟みます。
ffmpeg.exe -i "http://iphone-streaming.ustream.tv/uhls/チャンネルID/streams/live/iphone/playlist.m3u8" -vcodec copy -acodec copy -f mpegts pipe: -v warning | perl.exe nbpipebuffer.pl | vlc.exe -
[RTMP]
RTMPの場合は、配信によっては数秒で切れたりします。
基本的に不安定です。
URLは
http://cdngw.ustream.tv/Viewer/getStream/1/チャンネルID.amf
に含まれているfmsUrlの値で
rtmp://サーバー名.ustream.tv/ustreamVideo/チャンネルID
形式です。
ただし、cdnUrlのみでfmsUrlがamfファイルに存在しない場合もあります。
RTMPDumpで読んでVLCに渡します。
rtmpdump.exe -v -r "rtmp://サーバー名.ustream.tv/ustreamVideo/チャンネルID" -a "ustreamVideo/チャンネルID" -f "WIN 11,0,1,152" -y "streams/live" -o - | perl.exe nbpipebuffer.pl | vlc.exe -
FFmpegを挟む場合は
rtmpdump.exe -v -r "rtmp://サーバー名.ustream.tv/ustreamVideo/チャンネルID" -a "ustreamVideo/チャンネルID" -f "WIN 11,0,1,152" -y "streams/live" -o - | ffmpeg.exe -f flv -i pipe: -vcodec copy -acodec copy -f flv pipe: -v warning | perl.exe nbpipebuffer.pl | vlc.exe -
音が出ない等の問題が無い限り、FFmpegを挟む必要は特にはありませんが、バッファリング時の画面の乱れが多少綺麗になります。
VLC側では数秒で再生が終了するがデータの読み込み自体は続いている場合は、VLC以外のプレーヤーを使うと再生できる場合があります。
コメント(0)| Track back(0) | 2012-02-03 23:38:00
[動画]
|
ツイキャスとUstreamのコメントを表示するサイトを作りました。
●Simul Comment View [サイマルキャスト用コメント表示]
無駄にHTML5
ツイキャスとUstream同時配信しているライブのコメントを表示するためのものです。(どちらか一方だけも可能)
或いは、ブラウザ以外で再生している場合にコメントのみを表示するためのものです。
コメントを投稿する機能はありません。
Ustreamは公開APIの呼び出し回数のカウントがIPアドレス単位ではなくDeveloperKey単位なので、公開APIを使わず非公開APIを解析して使っています。
ちなみに公開APIではコメントは一度に10件しか取れませんが、非公開APIだと20件まで取れます。
ツイキャス、Ustreamともに更新時に前回取得時以降のコメントが多すぎて全て取得できなかった場合は単純に取りこぼします。(オフセットを指定した再取得は行いません)
ちなみに広告とアクセス解析部分を消してローカルに保存しても動きます。
ツイキャスはsinceパラメータを指定した場合、取りこぼしがあるのかないのか判断するのがめんどう。
一度に取得できるコメントは最大10件だが、取得対象が10件以上あってもNGワードの関係上10件取れない場合がある、と明示されている為。
前回の最後のコメントID -1の値を指定した場合、何故かsinceパラメーターは機能しない。結果的に問題無いからいいけど。
前回の最後の一つ前のコメントIDを指定しろということか。
コメント(0)| Track back(0) | 2012-02-02 03:16:30
[動画]
|