2005年07月10日

分離

もう少しで終わるだろうと思い続けていたけど、まだまだ続きそうな気がするので、カテゴリを新設。
ひとつのカテゴリにエントリーが増えると表示に時間がかかるので、これからは適度な数で分離していった方が良さそう。

投稿者 Takenori : 02:03 | トラックバック

2005年07月11日

バージョン情報が出ない?

krkr.eXe "-about"
で出るバージョン・著作権・環境ダイアログに出るはずの情報が出ない。
と言うか、何も文字が出ていない。
クリップボードにはコピーされる。
前も何も出ないダイアログボックスがあって気のせいかと思ったけど、どうやらそうじゃなかった様子。
やっぱり、C++Builder6でビルドすると表示されなくなってしまうようだ。
そこで最新の物にしようとソース取得してメイクしたら…
WaveImpl.cppでエラー
あれ?
std::sortでsortがstdのメンバではないと言われる。
なぜ? と思いつつ、#include <algorithm>を追加してみる。
通った。
単にヘッダーがなかっただけのようだ。
で、もう一度krkr.eXe "-about"とするがやはり出ない。
どこに問題があるのだろう…

投稿者 Takenori : 17:49 | コメント (4) | トラックバック

IsEventPastと[wp]と右クリック

periodイベントが発生しない時、デバッガで見るとIsEventPastフラグが立っている。
このフラグが立っていると、periodEventFrameを通過してもperiodイベントを投げない。
これは、現在のフレームより前にperiodイベントが設定された場合、このフラグを立てることによって設定した瞬間にイベントが発生してしまうのを回避するように作ったもの。
このフラグは、Rewindでムービーの初めに戻すか、periodEventFrameより前にフレームを設定するか、再度periodイベントを設定する時に設定しようとしているフレームが現在のフレームより後の場合にクリアされる。

そうか!!と、これを書いていて気付いた。
右クリックに入った時にtempsaveで一時保存した後、抜ける時にtemploadで復元している。
ムービーの途中保存を可能にした時にperiodEventFrameも保存対象にしている。
復元時は保存したperiodEventFrameを再設定している。
つまり、ここでこのフレームよりも先に現在のフレームが進んでしまっていると、IsEventPastフラグが立ち、periodイベントが送られなくなってしまう。
これが原因か。

投稿者 Takenori : 18:45 | トラックバック

2005年07月12日

復元順の変更

wpと右クリックの問題の解決策として、periodEventFrameの保存をやめようかと思ったけど、そうではなく単に順番を入れ替えれば済むことだと気付く。
今までは、open>frameの設定>periodEventFrameの設定と言う順番だったが、frameとperiodEventFrameの設定を入れ替えれば、IsEventPastフレームは立たない。
で、実際に動かしてみると、今まで発生していたようなケースでは発生しなくなった。

これでもうほとんど問題は解消されたかな。
結構いろいろとあった気がするけど。
もう少しテストしてソース整理したらコミットしよう。

投稿者 Takenori : 01:39 | トラックバック

ピリオドイベントなどについて

[wp]と右クリックの不具合を修正した物をコミットした。
これで直っているはず。


ピリオドイベントなどは、扱う時にいくつか気を付けておくことがある。

特定のフレームで文字を出したいような場合は次のようにする必要があると思われる。###はフレーム番号。

[videoevent frame=###]
フレーム中にしたい処理
[if exp="kag.movies[0].frame < ###"][wp for="period"][endif]

このようにしてフレームが進んでしまっていた場合にピリオドイベントを待たないようにする。

右クリックメニューに入ったらムービーをポーズした方が良い。
しないと勝手に進んでしまう。
上のようにifでwpを囲んでいれば、戻った時現在の位置まですっとんで行くと思うけど、たぶんそれは困るだろう。
とりあえずは、これぐらいかな。
他にも何かありそうな気はするけど。


セグメントループやピリオドイベントを駆使して1つのムービーでフレームを移動させてムービーを構成した場合、思っていた以上にスムーズにつながってびっくりした。
ただ、無駄にスクリプトが長くなる。
もうちょっと簡潔に書けるようなのを作った方が良いかも。マクロで何とかできるかなぁ? まあ考えよう。
後、字幕も冗長になる。
これも専用の簡潔に書けるものがあったほうがいいかも。

投稿者 Takenori : 23:51 | トラックバック

2005年07月13日

ダイアログを表示しているところ

W.Deeさんに聞いたので忘れないうちにメモ。
SysInitImpl.cppのTVPCheckAboutで"-about"オプションを付けた時に出るダイアログの処理をしているとのこと。

で、追うとVersionFormUnit.cppのTVPShowVersionFormでFormのMemoのLinesに文字列を入れている。
デバッガでform->Memo->Lines->Assignの後で値があるか確認しようとしたら・・・ 『エラー E2451 未定義のシンボル form』というダイアログボックスが出た。
なんだかよくわからない。
そこで、
list->Text = TVPGetAboutString().AsAnsiString();
の後に、
OutputDebugString(list->Text.c_str());
としてみた。
けど、何も出力されない。
あれ?

とりあえず、ここまで。

投稿者 Takenori : 00:43 | トラックバック

2005年07月14日

より短く書けるスクリプトを考えてみる

かなり特定の用途に特化しているかもしれないけど、ムービーを制御するためのスクリプトを考える。
用途は、ムービー内の特定フレーム間をループさせたり、指定フレームから指定フレームへ移動したりするもの。
また、音声は別収録のパターンも考える。
まあ、ムービーをつなげてゴリゴリやろうとした時に必要になりそうなものです。

[deflabel name="label_name" frame=###]
[defcut name="section_name" start="label" end="label"]
[defsound name="sound_name" storage="sound_file.ogg"]
[startchapter]
[section name="section_name" cut="section_name" sound="sound_name" sound_delay=### waitfor="" endbehavior=""]
[endchapter]

waitforには、ループ数などの終了条件を書く。
endbehaviorには、終了条件発生時の振る舞いを書く。
これでいろいろな用途に対応できるかな。
字幕やレイヤーの制御などは考えてないけど。

投稿者 Takenori : 21:48 | トラックバック

2005年07月15日

文字列の対策

-Aboutで出る、バージョン・著作権・環境ダイアログなどの対策が行われたようなのでソースを更新してメイク。
試してみると、出たけど一部文字化けが…

吉里吉里実行コアの使用/配布/改変は、
SDK 付属の license.txt に書かれているライセンスに従って行うことができます.

?????赳荒????/??/????
SDK ??? license.txt ???????莉??莓荘????赳?花??苅芫苜?.

になり

環境情報

諂???

になってた。
とりあえず、問題となっている部分の文字列の前にLをつけたらキチンと表示されるようになった。
でも、そこだけって言うのは美しくないな。
まあ、とりあえずはそれで表示されることは表示されるけど。

投稿者 Takenori : 12:49 | コメント (5) | トラックバック

2005年07月16日

バージョン固有の問題?

特定のパターン(?)を含んだMPEGムービーで、フレーム移動などの操作をすると特定のフレーム付近で吉里吉里を道連れにして突然落ちることがあるようだ。(ただし、ごく稀な様子)
Win2000 + DirectX 8.1 (4.08.01.0901) と言う組み合わせで起こる模様。
Win2000 + DirectX 9.0cだと起こらない。
エラーログで1度だけハードウェア例外が記録されていたので、それを見るとquartz.dllがゼロ番地を読もうとしてアクセス違反が発生したようだ。
そのquartz.dllのバージョンは、6.3.1.885。
何かしら不具合が含まれているのかもしれない。
このような症状があった場合は、DirectXのバージョンをあげてみると良いかもしれない。

投稿者 Takenori : 01:08 | トラックバック

ムービースクリプト

前回から仕様を少し変えて作りながら思う。
汎用的にはならなさそうだ。
と言うか、そうしない方が効率的かな。
汎用的なのは、現在のKAGを使ったもの。
少し冗長になるけど、いろいろと出来る。

投稿者 Takenori : 01:19 | トラックバック

スクリプト作らなくてもいい?

ムービー用スクリプトを作らなくても、TJSで直に辞書配列などを使って書いていってしまってもいいんじゃないかと気付いた。
そのムービーの塊待ちタグだけ新たに作れば、いい感じに制御できそう。

ムービーの制御部分は初めVMみたいな感じにしようかと思っていたが、Compositeパターンで組む事にした。
まだ途中だがいい感じかも。
入れ子になった複雑なムービーも制御できそう。

投稿者 Takenori : 19:23 | トラックバック

2005年07月20日

不用意に複雑かも

ムービーをコントロールスクリプトはとりあえず出来たが、妙に複雑。
単純に仕様が複雑で、判定が多くなってしまっているからのような気もするが、もう少しシンプルに出来ないだろうか?
Compositeパターンでenter、onPeriod、onSegmentLoopPeriod、onClickNextを処理しているが、各クラスでの処理が多すぎな気がする。
もう少し分散できるような構造にしないとメンテナンスが辛い。
検討しないと。

投稿者 Takenori : 14:54 | トラックバック

2005年07月27日

データの分離

やっぱり、専用のスクリプトがあったほうが良さそうだ。
TJSで直に書いてもいいが、そうすると内部構造の変更がデータにまで及んで、データも変更しないといけなくなる。
データ用のスクリプトを読み込むようにした方が良さそうだ。

現在の構造だと、特定のタイミングで何かをしようとしたらその都度クラスを拡張しないといけない。
汎用性を持たせるためには、もうちょっとというか、かなり構造を変更する必要がありそう。

投稿者 Takenori : 13:35 | トラックバック

2005年08月12日

ムービーをゴリゴリ動かす

現在のセグメントループやピリオドイベントは表示されているイメージと完全に同じフレーム番号で制御できているわけではない。
過去にいろいろと書いたと思うが、フィルタグラフ内部で描画しようとしているフレームの正確なフレーム番号を取得する方法がわからなかったので、再生が開始されたフレーム番号と再生が開始されてからのフレーム数によって現在のフレーム番号を得ている。
これには問題があって、再生レートなどを変更するとフレーム番号が狂ってしまう。(倍速時ピリオドイベントが機能しない 参照)
そして、ズレを考慮して、フィルタグラフ外の描画イベントで現在のフレームと指示されたフレームに2フレーム以上差がある場合は、現在のフレームを使うようにしている。
ただし、ダブルバッファリングしているので、現在のフレームと指示されたフレームは、1フレームずれているはず。
この関係で1フレーム早くセグメントループやピリオドイベントが処理されることがありうる。
また、内部的な時間はフレームではなく、doubleやlong longだったりするので、そこで誤差が生じている可能性もある。
処理落ちもある程度考えられる。

つまり、何が言いたいかと言うと、完全にフレームをつめてつなげたムービーをゴリゴリ動かすと、変なフレームが見えて、ちらついてしまうことがあるってこと。
また、ムービーの終わり付近にセグメントループの終端フレームを設定すると、セグメントループよりも、ムービー終端のイベントが処理されることもある。
だから、ムービーをつなげてセグメントループなどで制御する場合は、ムービーの区切りの間に2フレーム程度余裕を設けた方が良い。
ムービー終端付近でセグメントループする時も最後に5フレーム程度入れておくとよさそう。
入れるダミーフレームは、直前のフレームと同じ物にしておくと違和感は少ないように感じる。
とは言っても、ムービー終端付近のセグメントループ以外は、それほど問題は発生しない様子。


ピリオドイベントは、指定フレーム(eventFrame)が描画された瞬間に発生する。
セグメントループの終端イベントは、終端フレーム(goFrame)が描画されるべき時に発生する。(実際にはそのフレームは描画されない)
ピリオドイベントには、ムービー区切りの終端フレームを指定し、セグメントループの終端フレーム(goFrame)には、ムービー区切りの終端の次のフレームを指定するようにしておくと良さそうだ。

投稿者 Takenori : 13:38 | トラックバック

ムービーの制御構造の再検討

現在のはコンポジットパターンで作っていて、下図のような構造で制御できる。
20050812sequencial.png
カットはムービーの特定フレーム間を表し、シーケンシャルはコンポーネントを順番に再生する。
シーケンシャルやカットなどの単位でループなどができる。

ただ、現在の構造ではちょっと無理のある部分がある。
現在、上図の単位で言うと、カットがサウンドを持つようになっているのだが、シーケンシャルに入った時に1度だけサウンドを鳴らすようにするってので問題が発生した。
フラグで判断ってのも厳しい。シーケンシャル2の時は1度だけ、シーケンシャル1のループでは、再度鳴らしたい場合などに困る。
とりあえず、シーケンシャルもサウンドを持てるようにしてみたが、はっきり言って美しくない。

で、再考してみた。
サウンドも別コンポーネントにすれば、うまくいくのでは?と思った。
入った瞬間に音鳴らして、すぐに次のコンポーネントを再生するようにすれば、うまくいきそうだ。
しかし、カットの途中、つまり数フレーム遅れで音を鳴らしたい場合にうまくいかない事に気付いた。
そこで、パラレルコンポジットを思い付いた。
ムービーとサウンドを並列に再生されるためのものを作ればうまくいきそうだ。
また、今まではそのコンポーネントに入った時に呼ばれるonEnterで初期化をしていたが、別にIntializeを設け、シーケンシャルのonEnterで呼ぶようにし、そこでフラグなどのクリアをするようにすれば、音を1度だけ鳴らしたいという要求にも堪えられそうだ。

以上のことから再考したクラス図
20050813movman.png

プロパティやメソッドなどの詳細はもう一度よく考える予定。
でも、やっぱりフレームの更新タイミングでイベントを発生させるようにした方が良さそう。
VideoOverlayクラスにonFrameUpdateイベントを追加しようかな。

一番助かるのは、常にフレームの更新タイミングでイベントが発生してくれることだけど、現状のものでは難しいな。
フィルタグラフの中にそれを行うフィルタとムービーのコントロールを行うフィルタを追加すれば出来そうだけど、かなりややこしいことになりそう。
ま、これは保留だな。

ああそうだ。
これの途中で保存すること考えてない。

投稿者 Takenori : 23:37 | トラックバック

2005年08月19日

マルチビデオストリームを試してみる

マルチオーディオと同じようにマルチビデオを組み込んでみた。
切り替えにはやはり3~4秒程度かかっているようだ。
停止中に切り替えると、再生開始と同時にそのストリームが再生されるのもマルチオーディオと同じ。
レイヤー描画の場合、停止してもレイヤーに画像が残るので一時停止とあまり変わらない。
だから、一度停止してから、ビデオストリームを切り替えると瞬時に切り替わる。(当然、フレームの記憶と設定も行う)
ただし、音声が含まれているMPEGの時はどうしても一瞬音が途切れてしまう。

タイムラグがある関係で動的に使うにはやはり制限があるな。
音がなければ、それなりに使えると思うが。

投稿者 Takenori : 14:04 | トラックバック

onFrameUpdateを追加

ムービーのフレームが更新される時に呼ばれるイベントを追加した。
function onFrameUpdate(frame) とした。
これで少し作りやすくなる。
KAGからだと関係なしだけど。

投稿者 Takenori : 14:29 | トラックバック

2005年08月21日

あんまり用途ない? マルチビデオストリーム

マルチビデオストリームで現在有効なストリーム保存がまだなのでやらないとなーと思いつつ気付く、マルチビデオストリームって使えないんじゃ……
音の場合は、ビデオの方がはるかに容量がでかいので、ビデオは共通で音を何種類か用意しておくってことありそうだけど、逆はどうだろう?
一瞬でビデオが切り替わるのならまだしも、数秒のタイムラグがあるのが辛い。
すぐに切り替えようとしたら一度停止させないといけないので、それなら別ムービーにしてしまっても構わない気がする。
うーむぅ……
ま、保存は気が向いた時にでもやるか。
とりあえず必要としていないし。

投稿者 Takenori : 21:21 | トラックバック

ASF/WMV/WMAについて調べる

VMR9のサンプルを眺めていてなんとなく、ASF/WMV/WMAが気になった。
PlayWndASFってサンプルをビルドしようとすると
Windows Media Format SDK 7.1.1をインストールして、WMStub.LIBをSamples\C++\DirectShow\Commonへコピーしろと出た。
でも、いまさら7.1.1って、と思いWindows Media SDK コンポーネントを見て、Windows Media Format 9 シリーズ SDKをダウンロード。
それ以上だとXP限定になってしまうので、Win98 SE以降となる9にした。

WMF9 SDKのサンプルのDSPlayをビルドしようとしたら次のようなエラーが出た。
error LNK2019:未解決の外部シンボル "long __stdcall ATL::AtlWinModuleInit( …
error LNK2001: 外部シンボル ""class ATL::CAtlBaseModule ATl:: …

検索するとこのページで同じことが書いてあった。
解決策は「atls.lib か atlsd.lib をリンクするといいそうだ.これは VS .NET 2003 限定みたい.」とのこと。
やってみるとうまくいって再生も出来た。

次にDirectX9 SDKのPlayWndASFをビルドしようとするが、WMStub.LIBはない。調べると、これはもう必要なくなったとか。ただし、DRMを使う時は何かあるよう。
ま、使わないからそこは無視。
インクルードファイルとライブラリファイルのパスを通してビルドするがリンクエラーが大量に出る。
LIBCMTDを無視するように設定する。
が、まだWMCreateCertificateがないというエラーが残っている。
どうもDRM用っぽいので、その部分のコードをコメントアウト。
そこを通ったら毎回失敗するようにした。
これでやっとビルドが通って、wmvファイルの再生も出来た。
が、サンプルではWM ASF Reader Filterを使用している。
これと同じような働きをするフィルターを作らないとしたら結構大変だなー

少し調べるとWM ASF Reader FilterからIWMReaderAdvanced2インターフェイスを取得し、IWMReaderAdvanced2::OpenStreamを使うことでIStream経由でデータを読めそう。
でも、OpenStreamにはIWMReaderCallbackインターフェイスを渡す必要がある。
ReadFromStreamサンプルでIWMReaderCallbackの実装のサンプルがあるけど、OnSampleの中身は空っぽ。あなたのコードをここに追加してと書いてあるだけ。
これで動くんだろうか?
ま、やってみるか。

投稿者 Takenori : 23:06 | トラックバック

2005年08月22日

WMVは辛そう

CLSID_WMAsfReaderから取得したIWMReaderAdvanced2を使ってOpenStreamをコールするとE_NOTIMPLが返ってくる。
ReadFromStreamサンプルではWMCreateReaderでIWMReaderを作り、そこからIWMReaderAdvanced2を得て、そのIWMReaderAdvanced2のOpenStreamを使っている。
こちらはうまくいくようだ。
どうやらWMAsfReaderフィルタはOpenStreamをサポートしていない様子。
てことは、やっぱり……
これは、かなりしんどいな。

投稿者 Takenori : 15:50 | トラックバック

2005年08月23日

DirectShowを使わないほうが簡単?

IWMReaderは、Startを呼んだ後、自動的に指定された再生速度でIWMReaderCallbackのOnSampleを呼びながらデコードしていってくれるよう。
IWMSyncReaderは、アプリ側で読み取りながら再生するようになっている。
DirectShowで使うならIWMSyncReaderかと思ったが、WM ASF リーダー フィルタはスプリッタ フィルタも兼ねているため、プッシュ モデルになりそう。
なら、IWMReaderを使ったほうが簡単そうだ。

それよりも、DirectShowを使わないほうが楽かも。
特殊なフィルターをつなぐならDirectShowの方が良いが、そうでないのなら別にDirectShowでなくてもいい。
ただ、VMR9を使うのならDirectShowを使う必要がありそう。
やっぱりフィルタとして実装する方向で行くかな。

投稿者 Takenori : 00:40 | トラックバック

WMVを再生してみる

ReadFromStreamサンプルのOnSampleで、単純にパラメタとして入ってきたデータを標準出力へ書き出すように変えてみた。
が、OnSampleがコールされません。
デバッガで追ってみると、CReader::Start内で1回WaitForEventが足りない。
OnSampleの中に"Add your code here"って書いてるのに、そこに書いたコードは実行されないという……
で、WaitForEventを追加したらサンプルの情報がずらーっと表示された。

オーディオを再生するサンプルはWMF9 SDKについているのだが、ビデオはないので、これにビデオを表示する機能をつけることにした。
Open後、MediaTypeを取得し、ビデオストリームの番号とMVIDEOINFOHEADERを保持し、ビデオサイズのウィンドウを作って、OnSample内でビデオストリームが来た時にウィンドウへBitBlt。
比較的簡単に出来た。
きちんとした速度で再生されている様子。
フィルタにしなければもうほとんど出来たようなもの。

これをフィルタに持って行くと、ソースフィルタとスプリッタフィルタとデコーダーフィルタがくっついたフィルタになるのか。
つまり、レンダーフィルタだけがつながることになる。
なんとかなるかな。

投稿者 Takenori : 00:55 | トラックバック

空のフィルタを作る

CBaseOutputPinを継承したビデオ用とオーディオ用のOutput Pinと、CBaseFilterを継承したフィルタを作った。
WMVの再生機能などはなく、単にビデオレンダーフィルタと接続できるようにするのが目的。
とりあえず接続できるようにしてから、中身を作っていく。
で、接続してみたがピンのGetMediaTypeで返すCMediaTypeの中身が完全ではないので、接続処理の途中のビデオレンダーフィルタのSetMediaTypeがコールされた時に落ちてしまう。
やっぱり、中の情報は全部詰めとかないとダメか。
という事で、WMVのIStreamを使ったオープン処理を作ることに。

投稿者 Takenori : 23:29 | トラックバック

出力形式を変更できない?

WMVのリーダーからメディアタイプを取得するには、まずリーダーでIStreamを開き、IWMReader::GetOutputPropsでIWMOutputMediaPropsを取得して、そのIWMOutputMediaProps::GetMediaTypeでメディアタイプを取得する。
で、何もしない状態だと、ビデオはWMMEDIASUBTYPE_RGB24形式で出力されるようになっているようだ。(必ずそうなっているかどうかは知らない。Codecなどによって違いがありそう。)
だけど、バッファービデオレンダーが受け取る形式はRGB32。
そこで、上で取得したIWMOutputMediaPropsのSetMediaTypeをコールしてRGB32形式で出力されるようにしてみたが、うまくいかない。
SetMediaType自体はS_OKを返すし、その後GetMediaTypeで取得した情報はRGB32形式になっている。
でも、OnSampleでくるバッファの中身はRGB24形式。
IWMReader::SetOutputPropsで、独自のIWMOutputMediaPropsを渡さないとダメなのだろうか?
だとしたら、また面倒だな。

OnSample内では、ビデオレンダーが用意したバッファへコピーする作業が発生する。
だからそこでRGB24からRGB32へ変換しながらコピーしてしまってもいいかもしれない。
という事で、そうやってみたらRGB32で描画できた。
ま、当然か。

よく考えたら、ヘルプをあんまり読んでいない。
長い上に英語だからなぁ。
少し読んでみたが、出力形式を変更する方法は見当たらない。
DirectShowでも説明はなかった気がする。
インターフェイスの説明読みながらいろいろ試して何とかなったって感じだったはず。

書いていて気付いたが、WMF用のアロケーターをDirectShowのアロケーターをラップして作った方が良いかもしれない。
そうすれば、ダイレクトにビデオレンダーが用意したバッファへ書き込まれ、メモリコピー回数が減ってパフォーマンスがあがるはず。
でも、それはしばらく先だな。
出力形式の指定が出来ないと意味ないし。

とりあえずは、OnSample内で自分で変換しながらコピーする実装にしよう。

投稿者 Takenori : 23:36 | トラックバック

2005年08月26日

E_NOTIMPLTが返ってくる

IWMReaderCallbackに必要なメソッドと、ストリームからオープンするメソッドなどを組み込んで、接続しようとするがつながらず。
失敗しているのは前回と同じ場所。
見てみると、WMVファイルを開いて設定しているはずのWM_MEDIA_TYPEがNULLになってる。
追うとIWMReaderAdvanced2::OpenStreamで失敗している。
E_NOTIMPLが返ってきている。
おかしい。
フィルタとして実装していない方は問題なく動作している。
違いはIStreamの実装か。
サンプルでIStreamのメソッドの内、実装しているのはRead, Seek, Statの3つ。
ブレークを張ると、Read, Statと順に呼ばれている。
Statがくさい。
吉里吉里側は、StorageImpl.h/.cppのtTVPIStreamAdapterが実態のよう。
で、tTVPIStreamAdapter::Statの実装を見ると……
……
return E_NOTIMPL;
これかーっ!
もしかして、WM ASF Reader Filterでも、これが実装されていれば動いたんじゃ……

サンプルの実装では、STATSTGの内typeとcbSizeのみ設定している。
吉里吉里の方ではcbSizeのみ設定している。
とりあえず、type設定して、S_OKを返すようにしてみよう。

投稿者 Takenori : 21:50 | トラックバック

やはりWM ASF Reader Filterは無理

tTVPIStreamAdapter::Statで、typeを設定して、S_OKを返すようにし、ビルド。
DLL側でデバッグしてみると、問題の箇所は通過した。
やはり、ここが問題だったか。

WM ASF Reader Filterでも同じようにしてみるが、やっぱりE_NOTIMPLTが返ってきた。
やはり、WM ASF Reader FilterのIWMReaderAdvanced2::OpenStreamは使えないのか。
残念なような、無駄骨にならなくて良かったような。

次は、Run, Pause, Stopなどの実装と、IMediaSeekingの実装をしよう。
そうすれば、とりあえずは再生が出来る状態になるはず。
まだ、Audio側は何もしていないので、音は出せないが。

投稿者 Takenori : 22:38 | トラックバック

2005年08月30日

Statはラッパーで

吉里吉里本体のStorageImpl.h/.cppを変更するのではなく、DLL側でIStraemのラッパークラスを作り、Statの呼び出しだけ異なる動作をするようにしたほうが良いと思い、そのようにした。
動作も問題ないよう。
まあ、ほとんどそのまま呼び出しているだけなので、当たり前といえば当たり前か。

投稿者 Takenori : 21:03 | コメント (2) | トラックバック

2005年09月20日

WMVのライセンス

あんまり詳しく調べていなかったけど、気になる記述を見かけたので調べてみた。
Windows Media をサポートするカスタムのデジタル メディア アプリケーションを開発するによれば、WMVを再生するアプリケーションの開発と配布は、SDKをDLする時に表示されるEULAに同意すればOKのような、ダメなような。。。
2.1 Windows Media フォーマットのファイルの読み書きと編集を行う、Windows ベース アプリケーション (プレーヤー、またはコンテンツの制作、編集、エンコーディングのアプリケーション) を作成して配布するには、どのようにすればよいですか。を見ると、SDKをDLする時に表示されるEULAに同意すればOKとある。
DRMを使用する場合は、別途契約が必要とある。
DRMを使わなければ、EULAに同意することで開発と配布が出来るっぽい。
EULAは、まだ詳しく見ていない。
見ないと。

投稿者 Takenori : 15:25 | トラックバック

2005年09月25日

VMR9

VMRPlayer9サンプルをビルドして動かしてみた。
Pen4 3G HT + GeForce 6600 GT + WinXPで、640*480のMPEGムービーを2つブレンドしながら再生してもCPU負荷はほとんどない。
これはいいと思ったが、AthlonXP1600 + GeForce2 MX400 + Win2kでやると2つは全然無理。
1つでもかなり重い。
レイヤー描画で再生させた方が軽いぐらい。
HWアクセラレーションが効くかどうかとかあるのだろうか?
単純なオーバーレイ再生ならかなり軽くなるのだが……
ドライバか? と思って、最新のをDLしインストールしようとしたら途中で落ちた。。。
まだ詳しくはソースコードを見ていないが、期待していたほどの効果はないかも。
でも、もう少しいろんなビデオカードで動作させてみたいところだな。
他に今あるビデオカードはMGA Millenium G100とATI RAGE XLぐらい。
これはさすがに無理っぽいな。

投稿者 Takenori : 04:42 | トラックバック

レイヤー描画の方が軽いかも

オーバーレイレンダーフィルタをVMR9に差し替えて吉里吉里で動作させてみた。
単純にCPU使用率を見た限りでは、オーバーレイぐらいの負荷からレイヤー描画以上の負荷とばらつきがある。
GeForce 6600 GTの方では、オーバーレイ < VMR9 < レイヤー描画となった。

VMR9では、ビットマップとのミキシングが可能なので、メッセージレイヤーをビデオとブレンドできる。
HW支援が効いた場合は、レイヤー描画より低負荷で動作すると思われるが、そうでない場合は同じかより高負荷になりそう。

3つ目のオーバーレイモードmixerとかで実装してみるかな。

投稿者 Takenori : 20:53 | トラックバック

オーバーレイでもセグメントループを

VMR9を使えば複数の入力ストリームが扱えるので、ダミーストリームを作って、そこからデータが送られるタイミングでEC_UPDATEイベントを送ることによって、フレームの更新タイミングを取得すれば、セグメントループもピリオドイベントも使えるようになると考えていたが、そんなことをしなくてもインプレイスフィルタをレンダーの前に挿入して、そいつがEC_UPDATEイベントを送るようにすれば、更新タイミングを取得できることに気付いた。

描画周りの流れは……
フィルタのReceiveが呼び出されたらCTransInPlaceFilter::Receiveで、出力ピンにつながっている入力ピンのReceive を呼び出す。
入力ピンはレンダーのReceiveを呼び出し、
1. サンプルのレンダリングをスケジュールする (CBaseRenderer::PrepareReceive)。
2. スケジュールされた時間を待機する (CBaseRenderer::WaitForRenderTime)。
3. サンプルをレンダリングする (CBaseRenderer::Render)。
4. サンプルを解放する (CBaseRenderer::ClearPendingSample)。
という流れで処理する。
非同期で処理されている場合は、レンダリングされる前に返ってくる可能性があるが、その時は少しの誤差があるがあきらめることにする。
つまり、CTransInPlaceFilterを継承したクラスのReceiveで次のように書けばOKなはず。

HRESULT hr = CTransInPlaceFilter::Receive(pSample);
if( m_pSink )
  m_pSink->Notify( EC_UPDATE, INT_MAX, NULL );
return hr;

後は、このフィルタをグラフ構築時にレンダーの前に突っ込んでやれば、レンダリング時にイベントが来るはず。

投稿者 Takenori : 22:55 | トラックバック

単純なVMR9モード

単純にレンダーをVMR9にするだけのものを作った。
新たにモードを追加したので、
kag.movies[0].mode = vomMixer;
と言うようにすれば、オーバーレイやレイヤー描画の変わりにVMR9が使われるようになる。

後はミキシングするビットマップを設定するメソッドを追加し、UPDATEイベントを送るフィルタを追加すればOKかな。
どの程度使えるかは未知数だけど。

投稿者 Takenori : 23:53 | トラックバック

2005年09月26日

インプレイスフィルタの接続

フレーム更新時にイベントを送るインプレイスフィルタの入力ピンと出力ピンはどんなメディアタイプでも受け付けるようにして作った。
で、接続時はまずVMR9をつなげてから、デコーダーとの接続を切り、その間に挿入するようにしたが、なぜか絵が出ない。
接続自体はエラーも出ずに完了している。
そこで、graphedt.exeでどのような接続になっているのか見てみたら、デコーダーとインプレイスフィルタがつながっていない。。。
エラーもなく完了しているのに……

NullInPlaceフィルタサンプルのソースコードを見て、入力ピンのメディアタイプのチェックで、出力ピンが接続されていたら、出力ピンが受け付けるタイプかどうかチェックするように書き換え、先にレンダーを接続するように順序を変えて動かすと、なぜかインプレイスフィルタとレンダーの接続のところでエラー。
エラーメッセージは"これらのピンに共通するメディアの種類はありません。"。
なんだろう?
メディアタイプは出力ピンが提案するような仕組みだった気がするけど、そのような部分はまったく作っていないので、そのせいで接続が失敗してしまっているのだろうか?

他にもいろいろと試行錯誤していて気付いた。
IGraphBuilder::ConnectDirectでは、接続に使うメディア タイプへのポインタを指定出来る。
なら、最初の接続されている状態の時に、接続に使われているメディアタイプを取得して、接続解除した後のインプレイスフィルタとの接続時にそのメディアタイプを指定してやればつながるのではないかと。
で、早速試したところうまくいった。
一瞬、レンダーをラップするようなフィルターを作らないと出来ないかもと思ったが、接続出来てよかった。

後で考えるとそれはそうかと思う。
ピンの接続時には、いくつかのメディアタイプを順番に提示して、入力ピンがつなげると返したメディアタイプが使われる。
つまり、何でも受け入れるインプレイスフィルタは、デコーダーが最初に提示したメディアタイプで接続する。
そして、その後レンダーと接続する時は、そのメディアタイプ以外では接続できない。
が、何でも受け付けるので接続は完了する。
で、いざ再生となるとメディアタイプが一致せずにつながらない状態に。
と言うことだろう。
インプレイスフィルタが、メディアタイプの変換機能を持っていればそうはならないんだろうけど。

投稿者 Takenori : 01:57 | トラックバック

VMRでセグメントループ

インプレイスフィルタでは、UPDATEイベントをフレーム番号を指定せずに送り、アプリ側ではUPDATEイベントが着たらGetFrameし、そこで得られたフレーム番号を使うことにした。
で、modeがvomMixerの時にもセグメントループやピリオドイベントの処理を追加。
モードをvomMixerとして動作させてみたところ、指定した区間内でループしていることを確認。
ただし、厳密なフレームのつながりなどはまだ見ていない。
GetFrameで得たフレーム番号と実際に表示されているフレームでどの程度差があるのか確認しないと。

これでVMRやオーバーレイでのセグメントループとピリオドイベントの利用が現実的になった。

投稿者 Takenori : 02:44 | トラックバック

2005年09月27日

VMRでのBMPコピー先矩形の指定

VMR9AlphaBitmapで使われる、コピー先矩形の指定がテクスチャなどのように0.0~1.0方式だ……
使い手としては、ピクセル値の方がいいだろうから、内部的に変換するか。
基本的には幅や高さで割れば出るはずだけど、そのままやったら誤差が出たような。
確か、Direct3Dのテクスチャはラスタ化ルールの影響を受けたはず。
今回はDirectShowの話だけど、同じように0.0~1.0で指定するから同じ問題が発生しそう。
ビデオカードによっては異なる場合があるけど、そこは諦める。
実際の計算については、(left + 0.5) / 幅 というようにやればOKのはず。

投稿者 Takenori : 00:42 | トラックバック

メッセージレイヤーとのミキシング

VideoOverlay::setMixingLayer( Layer l )で指定されたレイヤーとミキシングするようにした。
ただし、setMixingLayerはレイヤー画像が更新されるたびにコールする必要がある。

ブレンド値は、Layer::opacityを使い
転送先矩形は、
dest.left = Layer::left + Layer::imageLeft;
dest.top = Layer::top + Layer::imageTop;
dest.right = dest.lerf + Layer::imageWidth;
dest.bottom = dest.top + Layer::imageHeight;
とした。

ミキシングするには、画像のHDCを指定する必要がある。
レイヤーが持っているBitmapのHDCを取得するためには次のようにすれば良いようだ。
tTJSNI_BaseLayer::GetMainImage()->GetBitmap()->GetBitmapDC()
でも、デフォルトではDIBSectionは使わないようになっており、実行時の引数に"-dibtype=dibsect"を指定してやればDIBSectionが使われるようになる。

"-dibtype=dibsect"を指定して実行し、kag.movies[0].setMixingLayer( kag.current );とすれば、メッセージレイヤーとブレンドして表示されるようになった。
負荷は、GeForce 6600 GTだとかなり少ない。
他はまだ試していない。

後は次の2つを組み込んでやれば、VMRモードを公開できるレベルになる。
1. DIBSectionでない時は、DCを作ってそこへコピーしてやる。
2. 対象レイヤーが更新される時、ムービーが再生中でかつvomMixingモードの時はsetMixingLayerをコールするようにする。
でも、1はいいとして、2はどうしよう?
KAGの方でLayer::onPaintをオーバーライドしてやればいいかな。
そしたら、自分が設定されているかどうかも知る必要があるか。
KAGのMovieでsetMixingLayerをオーバーライドして覚えるようにしておくか。
なんとかなりそうだな。

投稿者 Takenori : 02:54 | トラックバック

VMRの他の機能

現時点では、単純にレイヤーとブレンドできるオーバーレイとしか実装していないが、VMR9では次のようなことが出来る。
・Direct3D サーフェイスとのブレンド、
・ウィンドウレスモードで、ウィンドウへの描画
・レンダリングレス モードで、Direct3D サーフェイスへの描画
・複数ストリームの描画
・各ストリームの描画位置の指定
・各ストリームのブレンド率の指定
・独自ブレンド方法の実装
他にもあるかもしれないけど、詳しくは見ていない。

比較的実用的だと思われるのは、複数ムービーのブレンドだろうか。
ただ、途中から再生を始めるとかなると、かなり実装が大変そう。
でも、途中から出来ないと意味なしのような……

考えるといろいろと出来そうだけど、きりがないのでとりあえずは今のでいいか。

投稿者 Takenori : 03:04 | トラックバック

2005年09月28日

メディアタイプの色深度の確認コードメモ

AM_MEDIA_TYPE mt;
if( mt.formattype == FORMAT_VideoInfo ) {
    VIDEOINFOHEADER *vih = reinterpret_cast<VIDEOINFOHEADER *>(mt.pbFormat);
    char c[64];
    sprintf( c, "%d",vih->bmiHeader.biBitCount);
    OutputDebugString(c);
} else if( mt.formattype == FORMAT_VideoInfo2 ) {
    VIDEOINFOHEADER2 *vih = reinterpret_cast<VIDEOINFOHEADER2 *>(mt.pbFormat);
    char c[64];
    sprintf( c, "%d",vih->bmiHeader.biBitCount);
    OutputDebugString(c);
}

投稿者 Takenori : 18:10 | トラックバック

自前でBMP

tTJSNI_BaseLayer::GetMainImage()->GetBitmap()->GetBitmapDC()でHDCが取得できない時は、次のようにして作ることにした。

tTVPBitmap *bmp = Layer::GetMainImage()->GetBitmap();
HDC ref = GetDC(0);
HBITMAP myDIB = CreateDIBitmap( ref, bmp->GetBITMAPINFOHEADER(), CBM_INIT, bmp->GetBits(), bmp->GetBITMAPINFO(), bmp->Is8bit() ? DIB_PAL_COLORS : DIB_RGB_COLORS );
HDC hdc = CreateCompatibleDC( NULL );
HGDIOBJ hOldBmp = SelectObject( hdc, myDIB );

VideoOverlay->SetMixingBitmap( hdc, &dest, alpha );

SelectObject( hdc, hOldBmp );
DeleteObject( myDIB );


デバイス依存ビットマップ(DDB)を作成していので、色深度は画面モードと同じになってしまう。
CreateDIBSectionで作ることも考えたが、わざわざ画像をコピーするのが面倒なので、CreateDIBitmapにした。

最終的に描画される色深度は画面モードと同じになってしまうわけだが、VMRで拡縮された時、32bppで処理されてから16bppになるのと、初めから16bppで処理された場合とでは、初めから16bppの方が少し画質が悪くなりそうだ。
誤差などを考慮すると丸め処理などは出来るだけ後でやった方がいい。
ただ、この場合に認識できるぐらい画質が劣化するのかどうかは知らない。
そこで、VMRが接続されている時に使われているメディアタイプの色深度を確認してみた。
まず、画面モードを32bitにして実行し、確認。
16bitカラー……って、えっ? 16bitカラーで接続されてるの?
デコーダーの優先メディアタイプは16bitカラーなのだろうか?
なら、別に画面モードと同じになってもいいや。

投稿者 Takenori : 18:29 | トラックバック

2005年09月29日

更新タイミングはいつ?

KAGLayer::onPaintにVideoOverlay::setMixingLayerを呼ぶ処理を追加したが、文字を表示させてもonPaintには来ない様だ。
スクリプトからupdateが呼ばれたら、callOnPaintがtrueになり、onPaintがコールされるようだが、MessageLayer::processChはdrawTextやfillRectを呼んでおり、updateは呼んでいない。

Layer.imageModifiedがtrueなら、VideoOverlay::setMixingLayerを呼ぶようにしてみたが、falseにしないとずっとtrueのようだ。
そこでVideoOverlay::setMixingLayerの中でLayer.imageModified = falseにするようにしてみたのだが、なぜか画像がクリアされない。
文字を表示すると上書きされる。
ソースコードを追っても、imageModifiedで判断しているところは見当たらないと思ったら、MessageLayer.tjsのclearLayerでimageModifiedを参照して、imageModifiedがtrueなら消去している。
でも、その後 imageModified = falseにしているようだが……
その後の処理で何かがtrueにしているのだろう。
とりあえず、imageModifiedで判断している処理を常に行うようにし、EC_UPDATE内でレイヤーの設定を行った後にimageModifiedをfalseにしたら、更新された時のみ再設定されるようになった。
これで目的は達成されたが、imageModifiedの意味が変わってしまうのが問題。
そこで、Movie::onFrameUpdate内で処理するようにした。
でも、imageModifiedを変更してしまってるのは変わらない。
どうするかなぁ。

投稿者 Takenori : 02:30 | トラックバック

VMRで指定できるプロパティ

VMRでは、ビデオストリームのアルファ値、バックグラウンドカラー、出力矩形、Zオーダーが指定できるので、それぐらいは指定できたほうがいいかな。
他にコントラスト、輝度、色相、彩度の指定も可能だが、ハードウェアに依存するようなので、実装しないことにする。

mixingStreamAlpha
mixingStreamBGColor
mixingStreamLeft
mixingStreamTop
mixingStreamRight
mixingStreamBottom
mixingStreamZ
と言った感じかな。
でも、多いなぁ。
やっぱり、mixingStreamAlphaとmixingStreamBGColorぐらいでいいかな。

投稿者 Takenori : 02:50 | トラックバック

2005年10月05日

VMR用のプロパティ追加

プロパティにmixingMovieAlphaとmixingMovieBGColorを追加した。
mixingMovieAlphaは、0.0 (完全に透明) ~ 1.0 (完全に不透明)の値をとる。
mixingMovieBGColorは0xRRGGBB形式となる。

これで若干VMRの使い道が出来たかも。
レイヤーでも出来ることだけど、ハードウェア支援があったら軽くなる。

投稿者 Takenori : 19:53 | トラックバック

2005年10月06日

WMF SDK 9 EULA

Windows Media Format SDK 9のEULAを読んだ感じでは、今回のような用途であれば使用に差し支えなさそう。
ライセンス文は日本語でもわかりづらいのに、英語なのでさらにわかりづらく解釈が間違っているかもしれないが。

関連する部分を抜粋すると・・・
・再生のみを行う。
・再配布可能コンポーネントとサンプルソースを同梱しない。
・DRMで保護されたコンテンツを再生しない。
・サンプルソースから派生して作る場合は、オブジェクトコードでのみ配布可なので、サンプルソースから派生しないようにする。
・ACELP.net Codecを使用しない。
・リバースエンジニアリングしない。
・再配布可能コンポーネントを改変しない。
・ドキュメントに記述されたインターフェイスを通じてのみアクセスする。
以上の条件下で使用するのであれば、特に気にすることはなさそう。
まあ、すでにある程度制約があるともいえるが。

再配布可能コンポーネントを同梱する場合は、いろいろと面倒なので、別途Windows Media Player 9 以降をインストールしてくださいとした方が賢明。(同梱するアプリケーションのライセンスが適切かどうか確認するためにMSに連絡を取らなくてはならないなど)

この文章を元に判断した結果発生した問題について私 井元 武則は一切責任を負いません。
判断は自分の責任において行ってください。

投稿者 Takenori : 19:48 | トラックバック

Ogg Theoraを見てみる

途中、WMV面倒だなぁと思って、Ogg Theoraを見てみたが、まだアルファ版だった。
Directshow Filters for Ogg Vorbis, Speex, Theora and FLAC がインストールしてある環境でのみTheoraの再生をサポートすると言うのであれば、比較的楽に実装できるかも。
ソースが公開されているので、インストールなしにフィルタ部分を抜き出して内部でフィルタを生成してもいいが、少し面倒だな。
ま、もう少し見てみよう。

投稿者 Takenori : 20:06 | トラックバック

2005年10月07日

WMでストリームの例外?

ストリームオープン時に"tvpwin32.exe の 0x086c99cc で初回の例外が発生しました : 0xC0000005: 場所 0x00000000 に書き込み中にアクセス違反が発生しました。 。"と言う例外が出るようになった。
しかも、複数。
だけど、何事もなかったように進む。

前はこんなの出ていなかったような…… と思っていろいろと見てみる。
IStream::Read, Seek, Statが呼ばれた後からIWMStatusCallback::OnStatusが呼ばれるまでに起きている。
で、IStreamの実装を見てみたらIStreamのStatが実装されていたので、ラッパーを使わないようにしてみたが、やはり例外は発生する。
そこで、BCBの方でデバッグしてみると、例外が報告されない。
なんだろう?

例外文を良く見ると"ストリームオープン時に"と書いてある。
ファイル自体に何か問題があるのだろうか?
例外が発生するアドレスにブレークを張って呼び出し履歴を見てみると、wmvcore.dll内で例外が発生している様子。
前から発生していたのかなぁ? 記憶にないけど。
wmvcore.dllのバージョンが変わるようなのをインストールした記憶もないし……

ReadFromStreamサンプルの方で確認してみたら、同じ例外が出た。
なんだろう?
もともと発生していたのかな?

よくわからないので、とりあえずは様子見と言うことにしよう。

投稿者 Takenori : 18:05 | トラックバック

必要なインターフェイスは?

Run, Pause, Stopを実装して、OnSampleでIMediaSampleを得てから、その中へ画像データなどをコピーし出力ピンのDeliverをコールするようにしたが、再生が開始されない。
IMediaControl::Runの返り値を確認してみると、S_FALSEが返ってきている。
どうやら、CBaseFilterを継承しただけのフィルタではダメなようだ。
そこで、IMediaSeekingインターフェイスを実装するべく、CSourceSeekingを継承するも、CUnknownへのキャスト時にあいまいと出てコンパイルが通らない。
仕方ないので、CSourceSeekingを継承したクラスを作り、そのクラスをフィルタクラスのメンバーに保持し、QueryInterfaceでIID_IMediaSeekingへの問い合わせがあった時はそのメンバーへのポインタを返すようにする。
これでコンパイルが通るようになったが、再生は失敗する。

他にどのインターフェイスが必要なんだろう?
もしくは、いずれかのメソッドの呼び出しの返り値などがまずいのだろうか?
もう少し追ってみる必要がありそうだ。

投稿者 Takenori : 20:05 | トラックバック

Vorbis + TheoraのOgg動画ファイルを作る

昨日Theoraのことを書いたが、kikyou.infoさんのところの日記でも、Theoraを標準にとしてTheoraのことが書かれていた。
で、なんとなくトラックバック。
この辺りについてどのように考えていたかなどについては、また別エントリーにする予定。

最近、いくつか動画をWMVファイルにエンコードをしていたが、エンコードが異様に重い。
もうちょっと速いのがいいなぁとMPEG4やTheoraを検討。
MPEG4も、結構重かった記憶があるので、Theoraについて調べてみようと思ったが、何を使ってエンコードすればいいのか良くわからないってことで、GraphEditを使い、手動でフィルタグラフを構築してエンコードすることにした。
で、Directshow Filters for Ogg Vorbis, Speex, Theora and FLACを入れようとしたら、すでにインストールされてた。
1個古いバージョンだったが、とりあえずそのまま進めることにした。

GraphEditで作ったフィルタグラフは、次のようなもの。

Oggエンコードフィルタグラフ

WMVファイルを読んで、Ogg(Vorbis+Theora)ファイルを作っている。
で、再生してしばらく待つと、GraphEditが落ちた。(笑
でも、oggファイルは出来ていて、最後までエンコードも出来ているようだ。
まあ、DX9 SDKに付いているGraphEditはなぜかよく落ちるので、落ちたのはまあ気にしないことにする。(DX8に付いているGraphEditの方がなぜか安定している)

このことから、DirectShowのフィルタで読めるデータは、Ogg動画ファイルに出来そうだ。
また、エディタなどでもプラグインなどで出力フォーマットを増やせるのであれば、DirectShowを利用してダイレクトにOgg動画ファイルに出来そう。
現状Theoraがalpha版なので、サポートしているツールがどの程度あるかは謎だが。

エンコード時間を計るために、SSE2版へコンポーネントをインストールしなおして、フィルタグラフを作りエンコードしてみたら…… 途中で落ちた。
やっぱり、途中で落ちることもあるのか。
でも、コンポーネントのリリースされた時期を見てみると、まだlibtheoraはalpha5ではないよう。
この辺りを差し替えるともう少し安定するかもしれないな。
後、Theoraのクオリティなどをまったく設定できないのも難点。
Vorbis側はプロパティページを実装していて、ある程度設定できるようではあるが。
で、肝心の時間は、70%ぐらいまでしかエンコードできていないものの、15分程度で終わった。
640*480 約24分の動画をTMPGEnc 3.0 XPressで1パスVBRでWMVへエンコードした時は4時間弱もかかったのに。
換算すると約12倍ぐらい速い。
これは期待が持てるなぁ。
と思ったが、途中で終了した動画の時間を見ると16分ぐらいだった。
つまり、リアルタイムでエンコードされてた?
CPU負荷は、Pen4 3GHz 60%ぐらいだった。
もしかして、もっと速くエンコード出来る?
まあ、TMPGEnc 3.0の方ではインターレース除去などもしているので、まったく同じ状況だとは言えないが、WMVよりかなり速そうではある。ドロップフレームがあったのかもしれないけど。

このエンコード速度は結構期待できるかも。
エンコーダー作ろうかな。

投稿者 Takenori : 23:19 | トラックバック

2005年10月08日

インターフェイスを調べるも良くわからず

何が必要なのかさっぱりわからないので、QueryInterfaceにブレークを張って、どのようなインターフェイスが要求されるか見てみた。

フィルタ関係
IAMOpenProgress ネットワークを介してファイルを開く場合に使われるよう
IAMDeviceRemoval ?ドキュメントには書かれていない
IKsPropertySet 特に関係なさそう
IReferenceClock 基準クロックを提供するフィルタには必要
IMediaSeeking 実装済み
IBasicVideo 関係ない
IVideoWindow 関係ない
IBasicAudio 関係ない

IAMFilterMiscFlags
フィルタがソース フィルタかレンダラかを示す。ソースおよびレンダラ フィルタはこのインターフェイスを実装できる。
とある。
実装したら、問い合わせられるようになった。

IFileSourceFilter 一応実装したが、まったくクエリーで呼ばれない。
自動的にフィルタグラフを作る時以外は関係なさそう。


出力ピン関係
IAMPushSource ライブ ソースをレンダリングする時に使われるよう。関係なさそう。
IKsPropertySet 特に関係なさそう
IMediaSeeking CPosPassThruによって、ダウンストリームから要求されているようなので、実装した。
ただし、中身はフィルタのIMediaSeeking をコールしているのみ。


以上のようにいくつか実装してみたが、まだ再生できない。
いったい何が足りないのだろう?
もう少しヘルプをよく読んだ方がいいかも。
それでもわからなさそうな気はするが。

投稿者 Takenori : 01:30 | トラックバック

ログの出力

IGraphBuilder::SetLogFile メソッドは、ログ ファイルを設定する。このファイルには操作を実行しようとしたときに行われた各アクションが出力される。
このメソッドはデバッグに使い、フィルタ グラフの自動作成に失敗したときに、その原因を判断することを目的としている。

と言うのがあるので、使ってみることにした。
実行して終了してログを見てみると……

---

フィルタはグラフに追加されました。
フィルタはグラフに追加されました。

[EOF]
えーーーっ!
これじゃ何もわからないって。
自動作成時にどこでグラフが削除されたかとかその言うのを追えるんだろうか?
ま、なんにしても今回は使えなさそうだ。

投稿者 Takenori : 01:57 | トラックバック

WMVの画像が出るようになった

比較的似ているフィルタであるプッシュ ソース フィルタ サンプルのソースコードを追うイマイチよくわからない。
と言うより、CSourceStreamによってほとんどの機能が実装されているようだ。
と言うことで、CSourceStreamのソースコードを追うと、CBaseFilter::Pause内でCBaseOutputPin::Activeが呼ばれ、その中で再生を開始しているようだ。
つまり、CBaseFilter::Runが最初に呼ばれると言うわけではないのか。
さらに追うと、CBaseRenderer::Receiveが、ポーズ状態の時にコールされたら、イベントを投げて状態の移行を完了し、CBaseRenderer::GetStateからリターンするようだ。
で、そこからリターンした後に、各フィルタのCBaseFilter::Runが呼ばれているみたい。

で、そのように実装するとIMediaControl::Run()がコールされたしばらく後に、CBaseFilter::Runがコールされるようになった。
そういう風に実装するのか。
これでうまくいくかと思いきや、数フレーム再生された後、アクセス違反が発生する。
あれ? なんだろう?
wmvcore.dll内で発生しているようなので、そちらの問題かと思ったが、ReadFromStreamサンプルを実行してみても発生しないので、やっぱり何かまずいことをやっているようだ。

いろいろと調べるとBufferRendererの不具合だったようだ。
BufferRendererでは、最初に出力の対象となるバッファが設定されていない場合、自分で作る。(吉里吉里の初期化順序では、どうしても最初に設定されている状態にはならない)
そして、アロケーターでメディアサンプルが生成される時、バックバッファに設定されているポインタをメディアサンプルのバッファに設定する。
以降、メディアサンプルのバッファは、SwapBufferによって入れ替えるが、これはレンダリングされた直後に行われる。
問題は、アロケーターのメディアサンプル生成後、出力したいバッファを設定する時に、もともとあったバッファが自分で生成したものであった場合に開放するだけで、新たに設定されたバッファをメディアサンプルへ設定しないことにある。
そのせいで、メディアサンプルのバッファとして設定されるのは、最初にレンダリングされた後になってしまう。
つまり、最初のレンダリング時は開放されたメモリ領域に書き込みを行ってしまっていた。
そこで、出力したいバッファを設定する時に、それがバックバッファであったなら、メディアサンプルのバッファとして設定するようにした。
その後確認したら、問題なく再生できるようになった。
いやー、長かった。
今までBufferRendererの不具合がなぜ顕在化しなかったのかはなぞ。
デコーダーの作りが何か違うのだろうか? と言うか、それ以外考えづらいのだが。

出力したいバッファの設定は、レンダーフィルタの状態が停止か一時停止状態の時のみ行えるようにしておいた方が、より安全になりそうだ。
現在は再生中に出力したいバッファ変更するような使い方はしていないが、念のためにそのように変更した。

次にサウンド部分を作れば、一応WMVの再生が行えるようになる。

投稿者 Takenori : 05:23 | トラックバック

ソース フィルタの振る舞い

フィルタの状態に、フィルタ グラフ マネージャがどのように指示を出すかや、各フィルタの動作について説明があった。
前も読んだ気がするけど、見落としていた。
前はレンダーフィルタだったから、ソースフィルタの振る舞いの部分は意識していなかったのだろう。

投稿者 Takenori : 20:47 | トラックバック

2005年10月09日

使用するインターフェイスの変更

何とか画像が出るようになったけど、IWMReaderを使ったフィルタではいろいろと不都合が出そうだ。

ソースフィルタはひたすらデータを処理してレンダーへ送る。
Receive または GetBuffer 内でブロックされたら、そこで待つ必要がある。
と言うような動作を行うが、IWMReaderは内部でスレッドを持ち、それ自身で同期を取りながら再生を行う。
だけど、GetBuffer 内でブロックされたら停止しなければならない。
現在、OnSampleで次のフィルタへデータを送っているわけだが、この中で勝手にブロックするのはまずいだろう。
かといって、ブロックされる時にPauseしてしまうと、OnSampleが呼ばれなくなり再生がとまってしまう。
CSourceStreamの実装を見ると、GetDeliveryBufferをポーリングするような作りになっている。
つまり、止まってしまうとまずい。
なら、ワーカースレッドを作ってそいつに……と出来なくはないが、ここはおとなしくIWMSyncReaderを使うように書き換えたほうが良さそうだ。
IWMSyncReaderであれば、非同期にデータの取得が可能なので、Pauseなどの心配がない。
ってことで、WMSyncReaderサンプルを見て、IWMSyncReaderの使い方を調べないと。

投稿者 Takenori : 00:29 | トラックバック

2005年10月10日

動画拡張の予定と吉里吉里3について

VMR9モードの追加とWMVファイルへの対応が終わったら、とりあえず機能追加の予定はない。
でも、必要になってさらに機能追加する可能性はあるけど。

としたら…… 本家にマージしてもらう時どうしよう?
吉里吉里3が出来るまで、2系のマイナーバージョンが上がっていくこともありうる?
公開しないと言う手もあるにはあるけど、マージしてもらおうと思った時にW.Deeさんが対応してくれるのなら、追加した機能を公開すると言うスタンスになるかな。

それならずっと、吉里吉里2で行くのかと言うと、やはり改良したい(して欲しい)点と言うのはある。
1番大きいのは、BCBじゃなくてVCでデバッグしたいと言うもの。
BCBのデバッガはやっぱり使い辛いっす。
後、現状の構造だと、VideoOverlayクラスにひたすらプロパティとメソッドが追加されて、モードによっては使えたり使えなかったりするのが増えていくのは何とかしたい。出来れば、クラスを分けたい。
内部的には、DSMovie - Layer、DSMovie - Overlay - VMRと言うような構造になっているので、それに近いクラス構造がいいが、OverlayやVMRを使い続けるのかどうかと言うのがある。

吉里吉里3でマルチプラットホームとなった場合、それらが使えるのかどうかと言うのがまず問題になる。
ただ、SDL_Overlayと言うのがあるようなので、Overlayは他のプラットホームでも使えそうではある。(詳しくは知らない)
VMRは無理そうだが、そもそもそれほど必要性がない気がする。
今回追加してみて、思ったほどの効果が得られなかったので、意味なさげだ。
Direct3Dでテクスチャへのムービーレンダリングなどの用途で効果を発揮するもののような気がする。(ムービー再生中に他にいろいろとやることがあるから)

個人的な意見としては、レイヤー描画モードでムービーが再生できれば十分。
ただ、CPU負荷と言うことを考えるのなら、Overlayなどでの再生も必要になるかもしれない。
ま、その差がどの程度かによるが。
DirectShowでMPEGをオーバーレイで再生した時、ビデオカードのムービー再生支援機能などが使われていたとしたら、TheoraをCPUのみでデコードした後、オーバーレイ領域に転送するだけではそれほど恩恵がなさそうだ。
でも、拡大縮小をするのなら効果があるか。


一番の悩みどころは、吉里吉里3への対応をどうするかだな。
思い付くところとしては……
1. 吉里吉里3が出来た後、欲しい機能をポーティングする。
2. 吉里吉里3のムービー部分の開発に参加させて欲しいとお願いする。
3. ムービー再生用のライブラリを作って吉里吉里3に使って欲しいなぁと願う。
4. 独自のゲームエンジンを作る。
さてどれ?
しばらく、考えよっと。
『5. 欲しい機能だけ伝えて、寝て待つ』と言うのもあるにはあるか。

IRC以外に、吉里吉里3開発室ってな感じで、掲示板とメーリングリストみたいのがあるといいなぁ。
XOOPSで作れるようなコミュニティーサイトとか。
今度聞いてみよう。

投稿者 Takenori : 04:27 | コメント (4) | トラックバック

2005年10月12日

IWMSyncReaderでの出力フォーマットの指定

IWMReader、もしくはIWMSyncReaderで、出力させたいフォーマットの指定方法がわかった。

まずは、対象となる出力の番号を得る。
これはストリーム番号とは異なる。
ストリーム番号がわかっている場合は、IWMSyncReader::GetOutputNumberForStreamで出力番号が得られる。

IWMSyncReader::GetOutputFormatCountで出力できるフォーマットの種類の数を得る。
IWMSyncReader::GetOutputFormatでフォーマット番号を指定して、そのフォーマット番号のIWMOutputMediaPropsを得る。(フォーマット番号は、単なる0からフォーマットの種類の数までの値。forとかでIWMOutputMediaPropsを得ていく。)
IWMOutputMediaProps::GetMediaTypeで、メディアタイプを取得し、取得したメディアタイプが出力したいものであれば、IWMSyncReader::SetOutputPropsへIWMOutputMediaPropsを渡す。

このようにすれば、欲しい形式で出力させられる。

投稿者 Takenori : 00:43 | トラックバック

2005年10月16日

WMVからの読み出し

DirectShowには、フィルタを開発を行いやすくするためにいくつか基底クラスが用意されている。
ソースフィルタの開発用にはCSourceと言うフィルタ用の基底クラスとCSourceStreamと言う出力ピン用の基底クラスがある。
CSourceStreamのFillBufferをオーバーライドし、この中でダウンストリームに渡すIMediaSampleへ各フレームの画像などをコピーするようにすれば、楽にソースフィルタを作ることが出来る。(実際にはこれ以外にアロケーターの設定用と受け入れるメディアタイプの取得用のメソッドもオーバーライドする必要がある。後、CSourceの実装も)
また、CSourceStreamはワーカースレッドを持っており、内部で適切にFillBufferを呼び出すような実装になっている。

WMVファイルから各フレームの画像などを同期的に読み出すIWMSyncReaderインターフェイスには、GetNextSampleと言うメソッドがあり、これでサンプルを得ることができる。
このメソッドは指定したストリームのサンプルを得ることも出来るし、単純にファイルの中の並びでサンプルを取得することも出来る。
ムービーファイルの中にはビデオとオーディオがインターリーブされている(と思う)ので、ストリームを指定した呼び出しの場合、ファイルへのアクセスがシーケンシャルにならない場合も出てくる。(ビデオストリームかオーディオストリームの取得順がファイル中の並びと異なる場合)
これでは…… と書きかけて気付いた。(ちなみに、ここまでが説明のための長い前置き)
別にシークが発生してもそんなに大した負荷増にならないんじゃないかと。
内部でキャッシュするような構造になっていれば、関係ない話だし。
うーん……

フィルタにワーカースレッドを持たせて、各出力ピンへサンプルをプッシュするような構造にしようかと思っていたけど、CSource/CSourceStreamのように各出力ピンがワーカースレッド持ちプルするような構造の方がいい気がしてきた。
別にシーケンシャルじゃなくても問題なさそうと気付いた後、liboggVFAPIの仕様について確認してみた。
ビデオとオーディオがどういう順番で並んでいるかを得るのは、VFAPIでは無理っぽい。
また、liboggでも面倒くさそうだ。まあ、oggファイルフォーマットは仕様が公開されているので、自分で実装すれば何とでもなるが。

これを書く前にクラス構造などをある程度詰めていたが再考した方が良さそうだ。

追加説明
ソースフィルタは、画像を表示したり音を出したりするタイミングを考慮せずにひたすらサンプルをダウンストリームに送るような作りになっている。
では、どうやって同期を取るのかというと、それはレンダーフィルタに任されている。
レンダーフィルタは、適切なタイミングでサンプルをレンダリングする。
もし、レンダリング時間より早くサンプルが到達した場合は、それの受け取りを拒否する。(デコードの遅延などを考慮して、いくつかのサンプルをキャッシュしている場合もある)
ソースフィルタは、サンプルの受け取りが拒否されたらそこでレンダリングフィルタが受け取れるようになるまで待つ。(CSourceStreamではポーリングによる実装になっている)
このような作りになっているので、ビデオとオーディオの並びがどうなっているかわからなくても問題ない。
オーディオレンダラとビデオレンダラが暗にタイミングを調整してくれている。
つまり、各出力ピンがワーカースレッド持ちフィルタからサンプルをプルするような構造の方が望ましい。
と言うか、そうしないと並びがわからない場合、実装が難しそう。

投稿者 Takenori : 11:13 | トラックバック

2005年10月17日

DirectShowのデバッグ支援

DirectShowには、DbgSetModuleLevelでタイプとレベルを指定することで、デバッグ用の出力を表示することが出来ると今頃気付いた。

出力のタイプには次のようなものがある。
LOG_ERROR エラー通知。
LOG_LOCKING クリティカル セクションのロックとアンロック。
LOG_MEMORY メモリ割り当てと、オブジェクトの作成および破棄。
LOG_TIMING タイミングとパフォーマンスの測定。
LOG_TRACE 一般的な呼び出しトレース。
CUSTOM1 ~ CUSTOM5 カスタム デバッグ メッセージ用に使用可能。
レベルは大きくすればするほどいろいろと出力されるようだ。
ざっとソースを見たところ、5が使われている最大値だった。

これらの機能を使用するには、使用前にDbgInitialiseをコールし、使い終わったらDbgTerminateをコールする必要があるが、CBaseObjectを継承しているオブジェクトを使っていたら、それらは自動的にコールされるようになっている。
フィルタやピンなど多くの基底クラスはこのクラスを継承しているので、独自のフィルタなどを作った場合は、DbgInitialiseのコールについて気にする必要はなさそう。

デバッグ文字列の出力先はレジストリで指定されており、コンソールかデバッガウィンドウか指定ファイルになる。
CBaseObjectを継承したクラスを使っている場合、自動的にレジストリの"\HKEY_LOCAL_MACHINE\SOFTWARE\Debug\[Module Name]\LogToFile"へ登録されるようだ。(気付いたらkrkr.eXeなどが登録されていた)
ただし、これらはデバッグビルド時のみ有効なので、リリース版では出力や登録は行われないよう。

今後はこれを使っていく予定。
現行のソースは気が向いたら書き換えていくことにする。(OutputDebugStringを使っているので特に問題はない。統一感がないのが問題と言えば問題だけど)

投稿者 Takenori : 19:15 | トラックバック

DirectX Video Acceleration

ムービーのハードウェアデコード支援機能を使用するための機能としてDirectX Video Acceleration(DirectX VA or DXVAと略記されている)と言うものがある。
これ用のインターフェイスであるIAMVideoAcceleratorはVideo Mixing Renderer フィルタの入力ピン、オーバーレイ ミキサのピン 0でサポートされている。
これらはデコーダーを作る人以外は特に関係なさそうだが、WMF SDKを使ってWMV用のフィルタでハードウェアデコード支援機能使う場合は関係してくる。
とは言っても、細部は特に気にする必要はなく、単にWMF SDKとDirectX VAのネゴシエーションの手順だけわかれば良さそうだ。
でも、少し気になったので見てみた。

IAMVideoAcceleratorを使えば、動き補償や逆離散コサイン変換などをハードウェアに任せられるようになるようだ。
つまり、これを使えばTheoraのデコードが軽くなるかもしれない。(デコードプロセスによっては使えないかもしれないが)
動き補償や逆離散コサイン変換などのデコード処理をしなくていいし、圧縮データをビデオカードに送ることになるので転送量も減る。
問題はハードウェアがサポートしていないと使えないことだが、動き補償(Motion compensation)や逆離散コサイン変換(IDCT)程度であれば、数年前のビデオカードでもサポートしていたはず。(GeForce4 MXは確認した。もっと前のものでもサポートしていたはず)
GeForce 6600を見ると、デコード部がプログラミング可能とか。
プログラマブルシェーダーみたいなものだろうけど、いつの間にかそういう機能が積まれていたようだ。

にしても、DirectShowのフィルタを作るのでも情報が少なくマニアックなのにDirectX VAとか使い出したら余計に濃くなりそうだ。
後、Windows以外ではどうなのかと言うのも問題。
ハードウェア的には機能があるのでドライバがあれば何とかなる話だと思うけど、統一されたアクセス手段があるのかどうか。。。
まあ、Windows以外は考えない。CPUのみでやる。と言うのでも良いと思うが。

投稿者 Takenori : 19:45 | トラックバック

2005年10月19日

WMリーダーの構造

CSource/CSourceStreamを派生したクラスを使い、CSourceを継承したクラスにサンプルを読む機能を持たせたデマルチプレクサリーダークラスを持たせるような構造にすることにした。
このような構造にすれば、WMVのソースフィルタもOggTheoraもVFAPIも、フィルタとピンを使いまわせるので、他のフォーマットに対応させるのが楽になる。
ただ、DirectX VAをサポートしようとしたらこのままでは出来ないので、その場合はまた別の構造にする。
と言っても、そのような構造が必要になるのはWMVだけ。
VFAPIはDirectX VAを使えないし、Theoraの場合はDemuxとDecoderを別フィルタとする構造になるはず。
この3つ以外のソースフィルタ以外には、とりあえず欲しいものはないのでこれでいけそう。
ただ、ツールを作ることを考えると、QuickTime(MPEG4)とRealVideoのサポートがあったほうがいいけど、今はいらない。

投稿者 Takenori : 06:13 | トラックバック

2005年10月21日

WMソースフィルタの構造

初め出力ピンにはVideoとAudioが必要だろうと言うことで、次のような構造にした。

クラス図

ただし、一番初めはCDemuxOutputPinはなく、Video等の出力ピンはCSourceStreamを直接継承していた。
が、コーディングしていくうちに、ほとんどのメソッドが共通になると思い、CDemuxOutputPinを追加。
さらに書いていくと、VideoとAudioにほとんど違いがないことに気付く。
IDemuxReaderにはGetVideoMediaTypeやGetAudioMediaTypeなど、単にAudioやVideoの文字だけが違うメソッドを用意していたのだが、CDemuxVideoOutputPinとCDemuxAudioOutputPinの違いは、その呼ぶメソッドだけが違うだけだ。(各メソッドの引数にはCMediaTypeなどがあり、これによってAudioやVideoなどのメディアタイプは抽象化され、ソースフィルタとしてはその違いを意識する必要はなくなっている)
呼ぶメソッドが違うだけなら、CDemuxOutputPinだけ作り、CDemuxVideoOutputPinなどは必要なく出来る。
で、どのような実装にするか考える。
まず、GetMediaTypeなどの引数を増やし、ストリーム番号などをとるようにすることを思い付いたが、面倒な上にイマイチ。
もう少し考えて気付く、ストリーム番号ではなく出力ピンがインターフェイスを持つようにすればいいんじゃないかと。
つまり、次のようなクラス図になる。

クラス図

このような構造であればソースフィルタ側はVideoやAudioの違いを意識することはなくなるし、VideoやAudio以外にも楽に対応できる。
また、メディアタイプを知っているのはリーダーになるので、本来あるべき姿のようにも思う。
ってことで、このような設計で行こう。

投稿者 Takenori : 11:08 | トラックバック

2005年10月22日

WMVの再生が出来るように

WMソースフィルタの構造のように実装していたが、CVideoOutputとCAudioOutputが同じになるので、まとめて1つにした。
CWMReaderはAudioとVideoの違いを知っている必要があるが、IOutputStreamの方は単にストリーム番号さえわかっていれば良い。

で、すべて実装してビルドが通るようにし、再生も出来るようにした。
絵が出るようになったので、今度はDirectShound Filterをつなぐ。
フィルタはつながって音も出るのだが、途切れ途切れに再生される。
サンプルのプレゼンテーション時間がおかしいのかと思っていろいろとやるがうまくいかず。
で、ふとIMediaSample::SetActualDataLengthじゃないかと思って、コピーしたバイト数をセットしてやったらきれいに音がなるようになった。
これで、WMVの再生が出来るようになった。

再生が出来るようになったので、640*480のムービーでCPU負荷を見てみた。
50%…… んっ? このマシンでこの数字は……
AthlonXP 1600の方で見てみると、80~100%!
重っ!
現在、メモリの単純コピーしている部分は、カスタムアロケータを作ることによってなくせるはずなので、バッファレンダーの時を考えると、あと10%ぐらいは減りそうだが、それでも70%~90%。
これは…… レイヤー描画モードの場合の必要スペック1.5GHz、推奨2.0GHzとかになってしまうのだろうか。

もし、ハードウェア支援が受けられた場合はどうなるのだろうか?
単純にGraphEditへWMVファイルをドロップして再生してみたら、CPU負荷35%~50%。
低っ!
Media Playerで再生してみても同程度だった。
どうやら、ハードウェア支援があるかどうかで全然違うようだ。
ここにきてVMRの効果が発揮されそうな予感。
MPEG Iだと大して効果がなかったが、WMVでは明らかに違いが出そうだ。
ただ、VMRにつなげるにはDirectX VAのネゴシエーションとやらをしなければならない。

WMVの再生には、あとカスタムアロケータとDirectX VAか。

投稿者 Takenori : 15:27 | トラックバック

2005年10月23日

DirectX VAネゴシエーション ステップ訳

Windows Media Format SDKのヘルプのDirecX VAネゴシエーション ステップを訳してみた。
以下の訳は、ほとんど直訳の上に私の英語力のなさが加わって意味不明な文章になってます。
でも、大体何をすればいいかはわかります。(私だけかも)
現状単なるメモなので、コーディングして動くようになって理解したら、文章を修正します。

1. プレーヤーは、ソースフィルタとリーダーオブジェクトをインスタンス化します。リーダーは、ビデオデコーダーDMOの生成と(圧縮された)入力タイプの設定を行います。
これらはプレーヤーがフィルタグラフの構築を行うより前に行わなければなりません。
なぜなら、SDKとデコーダーDMOは、グラフのネゴシエーションプロセスにかかわらなければならないからです。
また、DMOはステップ9の間、入力フォーマットを知っていなければなりません。

2. プレーヤーは、ビデオソースフィルタの出力ピンを引数にしてIGraphBuilder::Renderをコールします。
DirectShowフィルタグラフマネージャはVMRをプレーヤーのビデオソースフィルタのこの出力ピンへ接続しようとします。

3. フィルタグラフマネージャは、プレーヤーのビデオソースフィルタの出力ピンのIPin::Connectをコールします。

ステップ 4 ~ 10は、IPin::Connect内で起こります。

4. ソースフィルタは、リーダーのIWMReaderAccelerator::GetCodecInterfaceでIWMCodecAMVideoAcceleratorを取得します。
CodecがDirectX VAをサポートしない場合、GetCodecInterfaceの呼び出しはたぶん失敗します。
この場合、ネゴシエーションプロセスはいつも通りの(DirectX VAをサポートしない)物になります。

5. ソースフィルタはIWMCodecAMVideoAccelerator::SetAcceleratorInterfaceを通して内部に接続されたピンからデコーダDMOへIAMVideoAcceleratorのポインタを渡します。

6. 次に、ソースフィルタはIPin::Connect オペレーションの残りをCBaseOutputPin::Connectメソッドへ任せます。
SDKとのフォーマットの列挙は今日するように続きます。???
Codecが接続されているコンテンツのためのDirectX VAをサポートしていたら、CodecのDMOはそれらのDirectX VAサブタイプをサポートするYUVやRGBサブタイプの前に最初に提供します。
DirectX VAサポートが利用できるのなら、ステップ7~11はDirectX VAサブタイプのコンテキスト中で試みられます。
以下のソースコードはDirectX VAメディアサブタイプを識別するためのものです。

ヘルプにソースコードがあります。

7. CBaseOutputPin::Connectの実装は、ステップ3の間にIPin::CompleteConnectを呼びます。
もし、DirectX VAサブタイプに確定していたなら、DirectX VAネゴシエーションが試みられます。
出力ピンは、現在の出力メディアタイプを渡してIWMCodecAMVideoAccelerator::NegotiateConnectionをコールします。

8. デコーダDMOはIAMVideoAcceleratorインタフェースを通してVMRとの必要なネゴシエーションを実行して、2が同意したビデオsubtype GUIDを返します。
出力ピンがこのプロセスの間にIAMVideoAcceleratorNotifyインタフェースで受信したすべての呼び出しは、デコーダDMOのIAMVideoAcceleratorNotifyインタフェースが代理します。(また、IWMReaderAccelerator::GetCodecInterface メソッドでそれを得ることができます)

9. IWMCodecAMVideoAccelerator::NegotiateConnectionが成功するなら、出力ピンはIWMPlayerTimestampHookインターフェイスを渡してIWMCodecAMVideoAccelerator::SetPlayerNotifyをコールする。
このHookで、それらがレンダラーに手渡される前にソースフィルタはサンプルの上のタイムスタンプをアップデートします。

10. ソースフィルタはIWMReaderAccelerator::Notifyにネゴシエートされたメディアタイプを渡してコールする。
これでリーダーは内部変数をアップデートし、DirectX VAを受け入れる。
これはCodecかリーダーが失敗できる最後の場所です。
上のステップのどれかが失敗するなら、ソースフィルタは、ステップ3に戻って、リーダーが列挙した次のタイプを試すべきです。

11. 再生は始まります。 リーダーは接続の出力タイプがDirectX VAであるならデコーダDMOからの出力バッファを無視します。

12. IPin::Disconnectが発生したら、ソースフィルタはNULLを引数にしてIWMCodecAMVideoAccelerator::SetAcceleratorInterfaceをコールします。
これはコーデックとレンダラーとのDirectX VA接続を解除します。

投稿者 Takenori : 22:44 | トラックバック

2005年10月24日

Async Readerでないと無理

パトラッシュ、僕はもう疲れたよ。

IWMSyncReaderとCSource/CSourceStreamの組み合わせで、ソースフィルタを作った。
再生も出来た。
DirectX VAのネゴシエーションも組んだ。
が、IWMReaderAcceleratorインターフェイスがもらえません。
IWMSyncReaderインターフェイスは、IWMReaderAcceleratorを実装していないと返ってくる。
ヘルプをよく読むと、IWMReaderを使わないとDirectX VAが使えないっぽい。
なんてことだ orz
OnSampleが非同期でコールされるスタイルでは、ソースフィルタを組みづらいためIWMSyncReaderにして作ったのに、それらが水泡に帰した。
ま、TheoraやVFAPIを実装する場合、現在の構造でやることになるので、まったくの無駄ってことはないのだが。

IWMReaderでOnSampleがコールバックされるスタイルで組むのなら、ソースフィルタにスレッドと、バッファのキューを持たせて、OnSample時にレンダーが受け取れなかったらキューにデータを入れて、イベントをスレッドに投げて起こして、起こされたスレッドはレンダーが受け取れるまでポーリングする。
もし、キューにデータが多くなりすぎたら、IWMReaderの再生を一時停止するか、データを捨てるかする。
と言った実装になるだろうか。

よし、1時間ぐらい現実逃避してからリトライだ。

投稿者 Takenori : 20:11 | トラックバック

インプレイスフィルタ with VMR

DirectX VAを使用する場合は、レンダーの入力ピンからIAMVideoAcceleratorを取得する必要がある。
だが、インプレイスフィルタをデコーダーとVMRの間に挿入した場合、デコーダーに渡される入力ピンはインプレイスフィルタのものになる。
つまり、インプレイスフィルタの入力ピンに対してQueryInterfaceしてIAMVideoAcceleratorを得ようとするわけだが、インプレイスフィルタの入力ピンはそんなもの持っていない。

VMRでパフォーマンスが下がってしまう原因はこれだろうか?
本来、インプレイスフィルタはデータの変換を行うので、DirectX VAの利用はできなくなってしまうが、現在使用しているインプレイスフィルタは単にイベントを投げるだけなので、DirectX VAの使用は問題ないはず。

現在の接続プロセスでは、一度デコーダーとVMRをつないで、その接続に使われているメディアタイプを取得し、取得したメディアタイプを使ってインプレイスフィルタをつなぐ。
もし、最初の接続でDirectX VAが使われていた場合、このメディアタイプはDirectX VAの物となる。
だけど、WMF SDKのヘルプには、IPin::Disconnectが発生したらソースフィルタはNULLを引数にしてIWMCodecAMVideoAccelerator::SetAcceleratorInterfaceをコールし、CodecとレンダラーとのDirectX VA接続を解除するように実装しろとある。
つまり、現在の実装では一度VMRの入力ピンのデコーダーとVMRをDisconnectしてしまっているので、DirectX VAの接続が切れてしまっている可能性がある。
ただ、Disconnectなどの実装はいい加減なことが多いようなので、DirectX VAの接続を解除していない可能性もある。
この場合、DirectX VAがたまたま使えているかもしれない。
でも、そのような手抜きのたまたまの処理に依存するのは好ましくない。
単にイベントを投げるだけのインプレイスフィルタでは、レンダーの入力ピンへのポインタを保持しておき、QueryInterfaceでIID_IAMVideoAcceleratorが指定された時に、保持しているレンダーの入力ピンへ処理を委譲した方が良さそうだ。

だけど、DirectX VAが使えないとしたら、以前の接続に使われていたメディアタイプで再接続できるのは変だな。。。
DirectX VAの接続の解除を行っていないのか?

後、良くわからないのはインプレイスフィルタのReceiveがコールされていること。
WMF SDKのヘルプとDirectXのヘルプによると、DirectX VAが使われている時は、IAMVideoAcceleratorを使ってダイレクトに表示の更新を行う。
もしインプレイスフィルタを使うなら、アップストリームからダウンストリームへ順次Receiveを呼び出していくような処理が必要だし、現にReceiveがコールされているので、そのような処理が組まれていると思われる。
これはどうやっているのか?
単純にWMF SDKのヘルプのDirectX VAのネゴシエーション方法を参照しただけではこの辺りがうまく実装できなさそうだ。
デコーダフィルタでIAMVideoAcceleratorを使う方法なども良く読んでおいたほうが良いな。

投稿者 Takenori : 22:36 | トラックバック

2005年10月25日

DirectShowとWMF SDKのメディアタイプ

メモるのを忘れていたのでメモ。

WM_MEDIA_TYPEとAM_MEDIA_TYPEは別に定義されているが、実体はまったく同じ。
ついでにDMO_MEDIA_TYPEも同じ。
WM_MEDIA_TYPEの定義を見るとコメントに、『名前の競合を避けるために別名で定義しているが、これはAM_MEDIA_TYPEとぴったり一致する。』と書かれている。
ヘルプにも同じことが書かれている。

また、MEDIATYPE_VideoとWMMEDIATYPE_VideoなどのメジャータイプやサブタイプのGUID値も同じ。
これはヘッダーを見て、実際の値を見ればわかる。

WM_MEDIA_TYPEからAM_MEDIA_TYPEへ変換する関数や、メジャータイプの変換を行うテーブルを作ったりしたけど、それはまったくの無用。
キャストすれば難なく使える。

投稿者 Takenori : 12:11 | トラックバック

2005年10月26日

IWMReaderを使うように組み直し

IWMReaderを使うように組み直した。
DirectX VAを使う前にまずは正常に再生されることを確認しようと、レイヤー描画で動作させてみる。
が、CBaseOutputPin::Deliverでブロックされる。
どうやら、レンダーでイベント待ちが発生しているようだ。

IWMSyncReaderを使った方で確認すると、イベントが発生している。
見ると別スレッドでCBaseRenderer::Runがコールされ、そこでイベントがセットされているようだ。

OnSampleでブロックされたスレッドとCBaseRenderer::Runをコールするスレッドは同じなのだろうか?
でも、最初にIWMReaderを使って実装した時は再生できたのだが……
もう少し追う必要がありそうだ。

投稿者 Takenori : 22:38 | トラックバック

IWMCodecAMVideoAcceleratorの取得

IWMReaderAdvanced2::OpenStreamでストリームを開いた後、いろいろとインターフェイスを取得していくようにしているのだが、IWMReaderAcceleratorは取得できたが、IWMReaderAccelerator::GetCodecInterfaceでIWMCodecAMVideoAcceleratorが取得出来ない。
E_UNEXPECTED : The WM Reader has no pointer to the codec.が返ってくる。
なぜ?
せっかくIWMReaderにしたのに、DirectX VA使えないのか?

他のデバッグをやっていると、OpenStreamよりも後に"wmvdmod.dllを読み込まれました。"のメッセージが表示されていることに気付いた。
もしかしてこれが原因か?
オープン直後ではなく、出力フォーマットが確定した後にGetCodecInterfaceを呼ばないとダメかもと思っていたが、どうやらその考えは近かったようだ。
実際に、wmvdmod.dllが読み込まれていたのは、IWMReader::GetOutputFormatCountがコールされた時だった。
GetOutputFormatCountは、サポートしているフォーマットを取得する時に、まずその数を取得するのに使っている。
これ以外でもwmvdmod.dllがロードされるメソッドはありそうだが、たぶんこれを最初にコールすることになると思う。

それはともかく、GetOutputFormatCountをコールした後にGetCodecInterfaceでIWMCodecAMVideoAcceleratorが取得できるかどうか確認したところS_OKが返ってきた。
どうやら読み通りだったようだ。
でも、こんなこと書かれていなかったと思うんだけど……

投稿者 Takenori : 22:52 | トラックバック

デッドロック

スレッドがイベント待ちのせいで再生されないのではなく、デッドロックが発生しているのではないか?と気付いた。
フィルタはさまざまな場所でクリティカルセクションでロックするような構造になっているが、そのどこかでデッドロックが起こっている可能性がある。
そこで、少し前に書いたIWMReaderを使うソースを見てみると…… OnSample内でCBaseOutputPin::Deliverをコールする部分だけわざとロックされないようにしていた。
はて? なぜそういう風に書いたのだろう?
たぶん、何かのソースを参考にしたからだと思うが…… と考えていて気付いた。(思い出したわけではない)
PushSourceサンプルを参考にしたのではないか? と。
見てみると、FillBuffer内のみロックしている。
FillBufferは、CSourceStreamのものをオーバーライドして使うようになっている。
で、FillBuffer後にDeliverがコールされるような構造になっている。
今回の場合、CSourceStreamは使っていないので、FillBufferはない。
そこで、前に書いた時はFillBufferの処理に相当する部分のみロックするように書いたのだろう。

ま、それはいいとして、Deliverをロックしないようにしたら再生が開始されるようになって絵も出た。
良かった、良かった。
と、終了させても終了しない。
他にもデッドロック?
でも、これはすぐに気付いた。
IMediaFilter::Stop内だろうと思って、前のソースを見るとそこはロックしておらず、今回のはロックしていた。
で、今回もロックしないようにしたらきちんと終了されるようになった。

最初に書いたものはサンプルを参考にきちんと書いていたんだな。
今回はだいたいわかっていたから、サンプルはほとんど参照せずに書いたせいかいろいろと不備があったようだ。
クリティカルセクションを使用している場所はもう一度確認しておいたほうが良いな。
また、流れを追ってきちんとロックすべき場所を特定しよう。

と言うことで、次はやっとDirectX VAのネゴシエーション処理だな。

投稿者 Takenori : 23:24 | トラックバック

2005年10月27日

インターフェイスの取得順

IWMReaderAcceleratorとIWMCodecAMVideoAcceleratorの取得をIStreamでWMファイルオープン時に行っていたが、CompleteConnectで接続に使われたメディアタイプがDirectX VA用のものではない場合、IWMCodecAMVideoAccelerator::SetAcceleratorInterfaceでNULLに設定した後、IWMReaderAcceleratorとIWMCodecAMVideoAcceleratorをリリースするようにしたので、IWMReaderAcceleratorとIWMCodecAMVideoAcceleratorはConnect時に取得するようにした。
これで、オープンしてConnectした後にDisconnectして、再度Connectしても問題なく動作するようになるはず。
と言っても、そんな使い方をする予定はないのだが。
毎回生成してオープンしてつないだほうが何かと安心だし。

にしても、インターフェイス名が長いので読みづらいな。
名前も似ているし。

投稿者 Takenori : 20:22 | トラックバック

DirecX VAが有効にならない?

DirecX VAのネゴシエーションを行うようにして、VMR9と接続してみた。
が、メディアタイプがDirecX VAのものではなく、YV12で接続されていた。
そのため、DirecX VAのネゴシエーションプロセスは行われず、通常の接続となった。
って、えーっ!
せっかくIWMReaderを使うように書き換えたのに意味ないじゃん。
人生ままならんものだな。

それはともかくとして、この状態でCPU負荷を見てみてると、Athlon XP 1600で40%~70%になった。
かなり軽くなっている。
YV12からRGB32への変換が思った以上に重いようだ。
もしかしたら何か変なことをしているのかもしれないが、カスタムアロケーターでメモリコピーをなくしたら、10%強軽くなったことを考えると妥当な数字かも。
WMVは内部的にYV12で保存されているのか?
IWMSyncReaderとIWMReaderの違いと言う可能性もあるが、それはまた後で確認しよう。
これで、カスタムアロケーターを作れば、MPEG Iと同程度の負荷になりそうだ。
まあ、WMVがVMRを使用した場合と言う条件でだが。

この結果から考えるとDirecX VAの処理は必要ないかもしれない。
それならIWMSyncReader版でやった方がいいかも。

投稿者 Takenori : 20:34 | トラックバック

WMのアロケーター

アロケーターの実装は、IWMReaderAllocatorExはIMemAllocatorをwrapし、INSSBufferはIMediaSampleをwrapするだけで事足りそうだ。
IWMSyncReaderへのアロケーターの設定は、CBaseOutputPin::DecideAllocator後にIWMSyncReader2::SetAllocateForOutputで設定するだけで良さそう。

問題はアロケーターの内部のバッファ数が1個より多い時。
この場合にいかにしてWMVのデコーダーの出力先と、これからダウンストリームへ渡そうとしているバッファを同じものにするか。。。
DirectShow側のIMemAllocatorもカスタムアロケーターとして実装すれば問題なさそうだが、面倒だな。
CBaseOutputPin::DecideBufferSizeでは、バッファ数が1個になるようにしているので、ダウンストリームの提示するアロケーターが無理にバッファ数を増やそうとしない限り、1個より多くなる事はないだろう。
もし、1個より多くなった場合は、IWMSyncReaderへカスタムアロケーターを提示しないようにしよう。

自前のアロケーターのバッファかどうか確認する時は、実際に内部のバッファへのポインタを取得して、そのポインタで比較する。
たぶん、これが一番確実な方法だろう。
もし、異なっていた場合は素直にmemcpyでコピーすることにする。

投稿者 Takenori : 21:57 | トラックバック

2005年10月28日

WM同期リーダーでの負荷

WM同期リーダー( IWMSyncReader )で、DirectX VA関連の処理を排除してから負荷を見てみると、WM非同期リーダー( IWMReader )と同程度だった。
やはり、YV12で出力されると軽いのか。

投稿者 Takenori : 12:02 | トラックバック

WM用カスタムアロケーター

まず、単純にIMemAllocatorをラップしたアロケーターを作ったが、それではまったく機能しなかった。
アロケーターがコールされるのはIWMSyncReader::GetNextSample内だが、このメソッドをコールする前にGetDeliveryBufferによってアロケーターからバッファを取得している。
アロケーターは、バッファのプールが空の時に確保しようとすると、バッファが使えるようになるまでブロックする。
つまり、GetDeliveryBufferで確保した後に、GetNextSample内で再度確保しようとしたらデッドロックする。
そこで、IMemAllocatorを使用してバッファを確保するのではなく、GetNextSample呼び出し前にカスタムアロケーターへバッファを設定し、カスタムアロケーターはそのバッファを返すようにした。
これでカスタムアロケーターを使用して再生できるようになった。

よくよく考えたら、GetDeliveryBufferを使わずに、GetNextSampleから返ってきたバッファからIMediaSampleを取り出して、そいつをDeliverでダウンストリームに送るような実装でも良かった。
こちらの方が構造的に良いかもしれない。
ただ、後述する問題を考えるとどちらがいいかは難しいところ。

カスタムアロケーターが出来たので、負荷を見てみようとAthlon XPのマシンで動かそうとしたが…… 最初のフレームだけを表示して動かない。
開発に使っているPen4では問題なく再生されるのだが。
さっぱり理由がわからないので、シークの処理を作ろうと四苦八苦していて気付いた。(シークの問題については後で書く予定)
GetNextSampleが1回しかコールされていないのにWM用カスタムアロケーターが2回コールされるタイミングがあるようだ。
2回コールされると、確保しようとした時にブロックされて止まってしまう。
そこで、内部にCMemAllocatorを持たせて、2回連続でコールされた時は、内部のCMemAllocatorからメモリを確保するようにした。
このようにしたら、Athlon XP 1600のマシンでも再生できるようになったが、音が出ない。
まだ何かあるようだ。
とりあえず、サウンドストリーム側は大したデータ量ではないだろうということで、カスタムアロケーターは使用しないことにしたら音が出るようになった。
で、CPU負荷はというと、40%~60%になった。
MPEG Iより少し重いが、これぐらいならいいかな。
ちなみにレイヤー描画の場合は、70%~90%。
レイヤー描画を使う場合、推奨 2.0GHz以上?

とりあえずは再生できるようになったが、なぜ2回連続でコールされるのか? など良くわからない部分がある。

投稿者 Takenori : 19:10 | トラックバック

2005年10月29日

シーク処理

シークの処理はレンダリングフィルタからアップストリームへ向かってシークを処理できるところまでやってくる。
ヘルプには、レンダリングフィルタが複数ある場合は、複数回コールされる可能性があるが、ソースフィルタは何度もコールされないように適切に処理する必要があると書かれている。
ソースフィルタでシーク処理を実装しやすくするために用意されている基底クラス( CSourceSeeking )では、複数の出力ピンを持ったものを考慮していない。

初期の実装では、シーク処理はソースフィルタでサポートし、複数回のコールについては何ら対処をしていなかった。( CSourceSeekingを使って実装していた )
そのため何度も同じメソッドがコールされていた。
ただ、何度コールされても問題ないはずなのだが、うまくシークされなかった。

また、フィルタグラフマネージャがどのような流れでシーク処理を行うのかがよくわからない。
ヘルプにはどのような順序でコールされていくのかといった記述は見当たらない。
デバッガで追ったところでは、まずフィルターのStopがコールされた後、実際のシーク処理であるSetPositionsがコールされ、フィルターのRunがコールされている。
『シーク処理後には、ストリーム タイムはゼロにリセットされる。』と言うのはこのためか。( Runがコールされたらストリーム タイムはゼロになる )
だとしたら、Stop、SetPositions、Runと言う流れだと言う認識はあながち間違いではなさそうだ。

他に、シーク直後にレンダリングフィルタへ送るサンプルのタイムスタンプは0にしないといけないようだ。
これはシーク後ストリーム タイムはゼロにリセットされるためだ。
レンダリングフィルタは、現在のストリーム タイムがサンプルのタイムスタンプの時間になったらレンダリングするようになっている。
WMリーダーから取得したデータには、そのサンプルのムービー内での時間が入っているため、この部分は変換を行う必要がある。
WM同期リーダー( IWMSyncReader)を使った場合、シーク処理はSetRangeを使って行う。
SetRangeは単純に再生範囲(開始時間と長さ)を指定するメソッドだ。
このメソッドをコール後、GetNextSampleを呼び出すとSetRangeで指定した範囲のサンプルが得られる。
つまり、SetRangeで指定した開始時間からサンプルが得られる。
ただし、ビデオのサンプルは指定した開始時間からではなく、指定した開始時間直前のキーフレームからサンプルが出てくる。
このため単純にGetNextSampleで得られたサンプルタイムスタンプから指定した開始時間を引いた場合は負の値となる。
現在、負の値になった場合は直ちにレンダリングされるように開始タイムと終了タイムに0を設定している。
すなわち、直前のキーフレームから指定した開始時間までのサンプルを出来るだけ速くレンダリングする。
言うまでもなくこれはかなり重い。
キーフレームと指定開始時間のギャップをうまく処理する必要がある。
とりあえず、負の値になった場合は指定開始時間を最初のサンプル時間へずらすようにした。
そのため、シーク処理は常にキーフレーム単位で行われることになる。
IMediaSeekingではシークをキーフレーム単位で行うかどうかの指定がある。
本来はこの指定に従ってシークするべきだろうが、現在はこの指定を見てない。


シーク処理はレンダリングフィルタからやってくるのは前述の通りだが、実際には出力ピンに対してシーク用インターフェイスの取得が行われ、そのインターフェイスに対して呼び出しが入る。(このインターフェイスの取得はレンダリングフィルタからやってくる)
出力ピンのシーク用インターフェイスは、ソースフィルタのシーク用インターフェイスに処理を委譲するような作りになっている。
このため、ビデオレンダリングフィルタとサウンドレンダリングフィルタをつないだ場合、2回同じメソッドがコールされるはずなのだが、なぜか3回もコールされていた。
デバッガで追いながら呼び出し履歴を見てみると、出力ピンからの呼び出しだけでなく、直接フィルタのシーク用インターフェイスへ呼び出しが行われていた。
つまり、出力ピンからの呼び出しでSet系のメソッドは無視してOKってことだろうか?
と言うか、シークの呼び出しはレンダリングフィルターからやってくるんじゃなかったのか?
ヘルプにはそのように書かれているが。
だけど、直接ソースフィルタのシーク用インターフェイスを呼び出したら、レンダリングフィルターからやってくる呼び出しの意味って……
ヘルプでCSourceSeeking::GetPositionsの解説を見ると、『フィルタグラフマネージャを使って現在位置の取得を行う場合はソースフィルタではなく、レンダリングフィルタから取得される』とある。
IMediaSeekingインターフェイスへの呼び出しすべてをソースフィルタが処理すると言うわけではないようだ。
推測の域をでないが、メソッドによってはダイレクトに呼び出す場合と、レンダリングフィルターからやってくる場合があるのだろう。
出力ピンが持つシーク用インターフェイスではSet系の呼び出しは無視することにした。


WMリーダーはシーク直後になぜかWM用アロケーターへサンプルサイズよりも大きいサイズのバッファを要求する時があるようだ。
IWMSyncReader::GetMaxOutputSampleSizeで最大サイズを取得すると、サンプルサイズの3倍のサイズが返ってきた。
サンプルサイズには、縦×横×1が格納されていた。
YV12フォーマットの場合、Yは画像サイズと同値で、UとVは1/4になるはず。
つまり、サイズとしては縦×横×1.5が格納されるはずだが、なぜか確定したメディアタイプのサイズは縦×横×1になっている。
よくわからない。
実際、アロケーターへのコールを見てみると、要求されているサイズは縦×横×1.5だった。
でも、普段は縦×横×1のサイズを返していたが、問題なく再生されていた。
が、シーク後は縦×横×1.5のサイズが必要なようで、その要求が通るまで情け容赦なくアロケートをコールするようだ。
で、そこで返って来なくなっていた。
そこで、GetMaxOutputSampleSizeで得られるサイズをアロケーターが返せるようにしたら、無限ループはなくなり再生が続けられるようになった。
ただし、GetNextSample 1回に対して、アロケーターを2回コールするのはなくならないようだ。
シーク後に動き補償などの関係で必要になることがあるのだろうか?
理由は良くわからないが、2回コールするのは間違いない。
もしかしたら、もっと多くコールされているかもしれないが、認識している範囲では2回だけだ。


まだいくつか不都合はあるが、ある程度シークが出来るようになった。
にしても、シーク処理はややこしいな。

投稿者 Takenori : 13:46 | トラックバック

標準ビデオレンダラの接続

VMRとバッファレンダーフィルタでは接続を試していたが、標準ビデオレンダラ(オーバーレイ)フィルタでは試していなかったので、試したら……
映像乱れまくり。
接続プロセスを追うとRGB32で接続されている。
YV12ではないのか?
ヘルプを見ると次のように記述されていた。

ビデオ レンダラは、ビデオ グラフィック カードが YUV オーバーレイ サーフェイスをサポートする場合は、YUV フォーマットを取り扱うことができる。ただし、最初にアップストリーム フィルタに接続する際、ビデオ レンダラは、現在のモニタ設定の色深度に合致する RGB フォーマットを必要とする。たとえば、現在のディスプレイ設定が 24 ビット カラーの場合は、アップストリーム フィルタ側で 24 ビット RGB ビデオを提供できなければならない。フィルタ グラフが実行状態に切り替わると、ビデオ レンダラは、適切な YUV 色空間への動的なフォーマット変更をネゴシエートする。

動的なフォーマット変更!?
何それ?
なんかまた面倒くさそうなやつが出てきた。
オーバーレイモードではWMVをサポートしなくてもいいかな。

投稿者 Takenori : 14:13 | トラックバック

2005年10月31日

再生開始シーケンスが違う?

レイヤー描画でMPEG Iを再生させようとしたらエラーが発生して再生できなかった。
原因は、WMVの画像が出るようになったで行ったBufferRendererへの改変のようだ。

この修正では、アロケーターが持つメディアサンプルにバックバッファのアドレスを設定しているが、この時にはまだアロケーターのメディアサンプルがNULLのためアクセス違反が発生していた。
なぜNULL?
と言うことで、少し追ってみた。(自分で書いたコードだけど……)
アロケーターのメディアサンプルは、Allocがコールされた時に設定される。
Allocは、Commit内でコールされる。
今回作っているWMソースフィルタでは、DecideBufferSize内でCommitをコールしていた。
つまり、接続が確立された時点でメディアサンプルには値が設定されていた。
が、MPEG IのデコーダーはDecideBufferSize内ではまだCommitしていないようだ。
CBaseOutputPinの実装を見るとActive内でCommitがコールされている。
Activeは再生が開始された瞬間にコールされる。
つまり、再生が開始される時までメモリの確保は行われないわけだ。
たぶん、MPEG Iのデコーダーもこのような実装になっているのだろう。
だから、顕在化しなかったのか。
そこで、WMソースフィルタでもこれに習い、DecideBufferSize内でのCommitはやめた。

DecideBufferSize内でCommitを呼んでいたのは、TheoraなどのDShowフィルタのソースを参考にしたからなんだけど…… あんまり当てにしないほうが良いかも。

投稿者 Takenori : 00:00 | トラックバック

動的フォーマット変更

動的フォーマット変更はサポートしないつもりだったが、そうも行かない様子。
DirectShowのヘルプには次のように書かれている。

DirectShow フィルタを作成する場合、動的フォーマット変更の機構を知っておく必要がある。フィルタがそのような変更をサポートしない場合でも、他のフィルタが新しいフォーマットを要求した場合、正しく応答しなければならない。

サポートしないと他の再生にも支障をきたすようだ。

動的フォーマット変更をサポートするには、IPin::QueryAcceptで変更可能なフォーマットがきた時にS_OKを返す。(不可の場合はS_FALSE)
ダウンストリームへサンプルを渡す時に、アロケーターから得たIMediaSampleでフォーマットが変更されているかどうか確認。
変更されていた場合は、出力するフォーマットを変更する。
と言ったことが必要。

じゃ、QueryAcceptで常にS_FALSE返せばと思い、そうしたらVMRでも再生できず。
以前、サンプルのサイズが縦×横×1などと言うことを書いたが、これが起因しているようだ。
接続確立時はサンプルのサイズは縦×横×1でもいいが、再生が開始された瞬間に縦×横×1.5を要求してくる。
ここでS_FALSEを返したら、メモリが足りませんと言うエラーで再生が出来ない。
Commitは再生が開始された瞬間に行って、そこでアロケーターを確定するのと同じように、メディアタイプも再生が開始された瞬間に確定するようだ。
じゃ、いったい何のために面倒な接続プロセスがあるんだろう? って話なんだが……
ま、そんなことを言っても仕方がないので、実装するしかない。

VMRでは、16の倍数でない幅を持ったムービーを渡すと16の倍数への変更を要求されるようだ。
オーバーレイの場合は、RGB32からYV12への変更を要求される。
メディアタイプの変更は、IWMSyncReader::SetOutputPropsで行うが、実際にやってみるとS_FALSEが返ってくる。
WMF SDKのヘルプにはいつでも変更できるとあるのだが……
ただし、FAILEDマクロでチェックするとS_FALSEは失敗ではないという扱いになっている。
そこで、S_FALSEでも気にしないことにしたら、再生は継続された。
S_FALSEでもいいようだ。

これで、16の倍数でない幅を持つムービーの再生やオーバーレイにつなぐことが出来た…… かと思いきや、Athlonマシンで再生できなくなった。
Athlonマシンは、Win2kで、Media Playerのバージョンは9なので、開発用のマシンとは少し環境が違う。
でも、再生できるはずなのだが……

投稿者 Takenori : 19:43 | トラックバック

Commitのタイミング

Win2kの方で動かなくなったのは、DecideBufferSize内でCommitしなくなったのが原因のようだ。
DecideBufferSize内のCommitを元に戻したら動くようになった。

だとしたら益々いつCommitするべきなのかなぞだ。
現状でもきちんと動いているがMPEG Iデコーダーなどとはタイミングが違うはず。
ヘルプのIMemAllocator::Commitの説明では、IMemAllocator::GetBuffer メソッドを呼び出す前に、必ずこのメソッドを呼び出すこととあるので、早くても問題はないと言えばないかもしれないが。。。
ま、接続したいレンダリングフィルタで問題が出なければいいか。

投稿者 Takenori : 19:57 | トラックバック

2005年11月01日

VMRとフルスクリーン化

VMRで再生中に他のアプリをフルスクリーンにして元に戻すとエラーが発生して落ちる。
自分自身をフルスクリーンにしても落ちる。
ただ、フルスクリーンからウィンドウへの切り替えは問題ないようだ。
これはWMVだけでなく、MPEGでも発生したのでVMRの設定の問題のようだ。
エラー内容を見ると、どうもフィルタの接続が解除されているようだ。
もしかしたら、ウィンドウモードではそのような事態を想定していないのかもしれない。

IVideoWindowインターフェイスを見ると、put_FullScreenModeというメソッドがある。
フルスクリーンモードにする場合は、このメソッドをコールするようだが、現在は使っていない。
説明を読むと、このメソッドをコールしたらDirectShowがフルスクリーンモードにしてくれるようだ。
ただ、吉里吉里は吉里吉里がフルスクリーンモードにするので、このメソッドを使ってもいいかどうかはわからない。

他のアプリをフルスクリーンにと言うのはレアなケースだと思うが、Win2kの場合はCtrl+Alt+Delでタスクマネージャなどのボタンがあるダイアログを表示して戻した場合でもエラーが発生する。
何が原因だろうか?
ウィンドウモードではなく、ウィンドウレスモードじゃないとうまくいかないのかなぁ。。。

投稿者 Takenori : 00:07 | トラックバック

ウィンドウレスモード

サンプルプログラムを見るが、ウィンドウモードで再生するものはない。
ウィンドウレスモードかレンダレスモードのものしかない。
ウィンドウモードは使わないほうが良いのだろうか?
現にフルスクリーン周りで問題が発生しているわけだが。
確認のため、ウィンドウレスモードを使っているサンプルでムービー再生中に他のアプリをフルスクリーンにしてみたが、特に問題なく再生が継続していた。
やはり、ウィンドウレスモードにするべきか。

ウィンドウレスモードは、IBasicVideo インターフェイスまたは IVideoWindow インターフェイスはサポートしない。とヘルプにある。
またはとか言っているけど、たぶん両方使えない。
少なくともIBasicVideo が使えないことは確認した。
IVideoWindow は思いっきりウィンドウに関連しているので使えない可能性大。
なので、これらのインターフェイスを使って何らかの操作をしている場所は代替手段を考えなければならない。

ビデオのサイズを取得するためにIBasicVideo::get_SourceWidthとIBasicVideo::get_SourceHeightを使っているが、これはIVMRWindowlessControl9::GetNativeVideoSizeを使えば大丈夫そう。

平均フレーム時間を取得するのにIBasicVideo::get_AvgTimePerFrameを使っている。
IVMRWindowlessControl9インターフェイスを見てもそれに相当するメソッドは見当たらない。
これはレンダリングフィルタの入力ピンが接続に使っているメディアタイプから取得することが可能なので、グラフ構築時にこれを保持しておけばなんとかなりそう。

ウィンドウの付け替えは…… 無理っぽい。
IVMRWindowlessControl9::SetVideoClippingWindowでクリッピングに使うするウィンドウを設定するのだが、再生途中にIVMRWindowlessControl9::SetVideoClippingWindowへNULLを渡してみたら、無効な引数と返ってきた。
すでに設定されているウィンドウが破棄されたらどうなるかわからないが、やばそう。

後、ウィンドウレスモードの用件としてアプリケーションがWM_DISPLAYCHANGE メッセージを受け取った時はIVMRWindowlessControl9::DisplayModeChangedをコールする必要がある。
registerMessageReceiverでWM_DISPLAYCHANGE を見張るか、本体に手を入れるかすれば、これは何とかなりそう。

再描画が必要な時は、IVMRWindowlessControl9::RepaintVideoをコールする必要がある。
吉里吉里本体のUpdateなどで画面が更新された後などでRepaintVideoをコールするようにすれば何とかなりそう。


ウィンドウの付け替えが何とかなればウィンドウレスモードで安定して動作させられそうだな。

投稿者 Takenori : 19:35 | トラックバック

2005年11月02日

ダミーウィンドウへ退避

IVMRWindowlessControl9::SetVideoClippingWindowへNULLが設定できないのなら、ダミーのウィンドウを作って、そいつを設定すればいいのではないか?
この仮定の下実験してみたら、予想通りうまくいった。

非表示のダミーウィンドウをSetVideoClippingWindowで設定した瞬間にビデオは非表示になり、元のウィンドウを再設定してやると元通りに表示されるようになった。

これでウィンドウの付け替えの問題は解消されそうだ。

投稿者 Takenori : 16:29 | トラックバック

2005年11月03日

何も描画されない

ウィンドウレスモードで実装してみたが、何も描画されない……
確か、一番初めにウィンドウレスモードで実装して何も描画されなかった気がしたが、ブログに書き忘れていた。
やっぱり、描画されなかったか。
で、自分で子ウィンドウを作って、そこへ描画するようにしても事態は変わらず。
サンプルを何度も見て、違いを追っていろいろ試して何とか解決。

RECT dest;
GetClientRect(hWnd, &dest);
IVMRWindowlessControl9::SetVideoPosition(NULL, &dest);

と言うように、クライアント領域をビデオの出力領域として設定してやらないといけないようだ。
これによって絵が出るようになった。
子ウィンドウかどうかも関係なし。
他のアプリでフルスクリーンに切り替えられたとしても、IVMRWindowlessControl9::DisplayModeChangedを呼んでいれば問題なし。

だがしかし、あんたちょっと奥さん。
吉里吉里でフルスクリーンにしたら絵が出なくなる。
ウィンドウモードに戻しても絵は出ないまま。
IVMRWindowlessControl9::GetCurrentImageと言うメソッドで現在表示中のイメージのコピーを取得しようとしてもエラーが返ってくる。
この時にレンダリングフィルタの接続状態を確認すると接続が解除されてしまっている。
フルスクリーンにした瞬間は問題ないようだが、少ししたら失敗するようになるようだ。

フルスクリーン状態で起動した場合は表示される。
その後、ウィンドウモードにしても表示されている。
が、再度フルスクリーンにしたら消える。
ウィンドウ状態で起動した場合は、フルスクリーンにしたら消える。

GetCurrentImageが失敗するのを使ってどこで失敗しているか見てみると、フルスクリーン化した後のSetVideoClippingWindowのウィンドウ設定時に失敗している。(GetCurrentImageはRepaintVideoをコールした後でないと失敗しないようだ)
フルスクリーン化した後、GetAncestor( OwnerWindow, GA_ROOTOWNER )でルートウィンドウを取得したら、ウィンドウモードの時と変わっている。
このルートウィンドウが変わった時SetVideoClippingWindowで別のウィンドウを設定し、ウィンドウモード戻ってきた時にOwnerWindowで再度ウィンドウを作って、それをSetVideoClippingWindowしたら表示が復活した。
フルスクリーン時に親とするべきウィンドウのハンドルが取得できれば何とかなりそうだが、うまくいかない。
と言うか、フルスクリーン時のウィンドウの構造がよくわからない。


ウィンドウモードからフルスクリーンにした時の、フルスクリーン状態の時のみ表示できないところまできたが、ここからまたなぞ。

投稿者 Takenori : 20:32 | トラックバック

落ちるのは別の原因

Win2kでCtrl+Alt+Delを押した後、元にに戻ってくるとアプリが落ちるのはWMVフィルタが原因のようだ。
VMR9でフルスクリーン切り替えで落ちなくなったのに、まだ落ちるからMPEG Iでやってみたら落ちなくなった。
WMVフィルタもまだ不具合を含んでいるようだな。

投稿者 Takenori : 20:38 | トラックバック

2005年11月04日

ウィンドウを列挙する

EnumWindowsでトップレベルウィンドウを列挙できる。
EnumChildWindowsで指定した親ウィンドウに属する子ウィンドウを列挙できる。
GetWindowで指定したウィンドウと指定した関係にあるウィンドウのハンドルを取得できる。

EnumChildWindowsで、VideoOverlayに渡されるOwnerWindowを調べてみると、フルスクリーンモードとウィンドウモードで子が変わっている。
ScrollBoxとPaintBoxかと思いきや、GetClassNameで調べると両方TScrollBoxだった。
ウィンドウ時 : TForm - TScrollBox - TPaintBox
フルスクリーン時 : TForm - TPaintBox
だと思っていたのだが……

GetWindowで調べると――
自分 : TTVPWindowForm
Ownerは常にTAplication
Childも常にTScrollBox

ウィンドウ時
Next : TTVPMainForm
Next Next : TAplication
その次は関係のないもの

フルスクリーン時
Next : 関係のないもの

となっている。

Delphiのウィドウの関係については、Owned Window を使いたいApplication 変数が詳しい。
C++Builderも同じVCLを使っているので、たぶん同じ構造を持つと思われる。(上述の関係を見るとそうなっている)
また、オーナーウィンドウなどの関係については、Win32 Window Hierarchy and Stylesに書かれている。

と、ここまでのことをいろいろと考えてみると、ウィンドウの関係などが原因ではない気がしてきた。

フルスクリーン前と後で渡されるOwnerWindowはいつも同じ。
Ownerは常にTAplication。
OwnerWindowの子はTScrollBoxだけど、ハンドルは変わる。
ウィンドウモード時に再生が開始され、フルスクリーン時にOwnerWindowで作った子ウィンドウをSetVideoClippingWindowに渡すとエラーになる。
フルスクリーンになった時に、非表示のウィンドウをSetVideoClippingWindowに渡し、ウィンドウモードに戻った時に子ウィンドウをSetVideoClippingWindowに渡すと復活する。
初めからフルスクリーンモードの時は、OwnerWindowで作った子ウィンドウをSetVideoClippingWindowに渡しても問題ない。また、その後ウィンドウモードになった時にOwnerWindowで作った子ウィンドウをSetVideoClippingWindowに渡しても問題ない。でも、その次は失敗する。
他のアプリケーションがフルスクリーンにするのは問題ない。

『DirectDraw 排他モード』が原因では?

VMRには、次のようなインターフェイスとメソッドがある。
《 IVMRSurface9 インターフェイスは、Video Mixing Renderer フィルタ 9 で使われるメディア サンプルに実装される。フィルタはこのインターフェイスを使って、メディア サンプルに必要な DirectDraw サーフェイスにアクセスできる。 》
《 IVMRSurfaceAllocatorNotify9::ChangeD3DDevice メソッドは、VMR に Direct3D デバイスが変更されたことを通知する。 》

VMR9は、Direct3D 9を使っている。
Direct3D 9を使っている状態で、DirectDrawを使い排他モードへ切り替えた場合、Direct3D デバイスはロストするのではないか?
もしくは、そのデバイスによって作られたサーフェイスは排他モード中使えなくなるのでは?
以前気まぐれで作った吉里吉里用のDirect3Dプラグインで実際に試してみた。
予想通り、フルスクリーンモードに切り替えた後のIDirect3DDevice9::Presentは失敗し、D3DERR_DEVICELOST と言うエラーを返す。
そこで、IDirect3DDevice9::TestCooperativeLevelをコールすると、ラッキーなことにD3DERR_DEVICENOTRESETが返ってきた。
これはヘルプによると《デバイスは、消失しているが、現在リセットできる。》 とある。
そこで、GetAdapterDisplayModeでディスプレイの解像度などを取得し、、IDirect3DDevice9::Resetをコールしてやると復活した。

つまり、IDirect3DDevice9を自前で作り、IVMRSurfaceAllocatorNotify9::SetD3DDeviceでVMRに設定し、フルスクリーン切り替わり時にResetをコールしてやると復活するのでは?
ただ、フォーマットの変更やサーフェイスの再生成が必要になるから失敗する可能性はある。
また今度試そう。


ちょっと関係ないけどメモ
古いWindowsへの対応
ウィンドウクラスを作るには

投稿者 Takenori : 00:25 | トラックバック

Direct3Dを自前で……

ムービーを再生したいだけなのに、なぜDirect3Dの初期化やリセットの処理を書いているのだろう? と疑問に思いつつも、大体はコピペで済むからいいかと実装。
これらの処理を書く前から一抹の不安があったが、実行して的中。
レンダリングレス再生モードでないとIVMRSurfaceAllocatorNotify9は使えない。
つまり、自前のDirect3Dを設定できない。

レンダリングレス再生モードを使う場合には、次のものを自前で実装する必要がある。
・再生ウィンドウを管理する。
・DirectDraw オブジェクトまたは Direct3D オブジェクト、および最終的なフレーム バッファを割り当てる。
・使われているオブジェクトを残りの再生システムに通知する。
・正しい時間にフレーム バッファを表示する。
・すべての解像度モードの変更、モニタ変更、およびサーフェイス損失を処理する。これらのイベントについて残りの再生システムにアドバイスする。

また、カスタム アロケータ プレゼンタなどを作る必要がある。


しかし、そこまでやる必要あるのかなぁ。
ビデオ再生中のフルスクリーン切替禁止か、現在のビデオの状態を保存した後、一度VideoOverlayを破棄し、フルスクリーンにした後に再生成すると言うどちらかのアプローチでいい気もする。
レンダリングレスモードでやればフルスクリーン切替が安定して動作するとは言い切れないし。
まあ、VMR9を使ってビデオをテクスチャに貼り付けると言ったことをやりたいとは思っているが、今すぐと言うわけではないし。
うーむぅ。
少し考えるか。

投稿者 Takenori : 23:33 | トラックバック

2005年11月12日

予定している残作業など

ビデオ再生中のフルスクリーン切り替えについて悩んでいたが、とりあえず今対応は行わず、3Dの方でVMR9を使ったレンダリングレス再生を実装したら、それをこっちに持って来ることにする。
状態の保存&再生成 or 禁止はKAG側で対応する。

イベントを投げるインプレイスフィルタの入力ピンのいくつかのメソッド呼び出しを、レンダリングフィルタの入力ピンのものにする。
これを行わないと接続がうまくいかない時があるよう。
また、インプレイスフィルタの出力ピンも適切に処理する必要がありそう。上述のように入力ピンの処理を代理して決定したメディアタイプを出力ピンが提示するようにしないとまずいと思われる。

WMVフィルタを使い、Win2kでCtrl+Alt+Delを押して『Windowsのセキュリティー』というダイアログを出した後、元に戻ってくると落ちる。これはリモートデバッグで確認する必要がありそう。
シーク後キーフレーム時間との誤差を基準タイムへ反映していない。

Theoraの再生を試したい。

Theora以外のものが出来たら、一端開発中版として公開するかな。

追記:
DirectX9がインストールされていない環境で動作確認をする必要がある。

投稿者 Takenori : 18:57 | トラックバック

2005年12月14日

再開

一区切り付いたので、VMRとWMVの開発を再開。
1ヶ月以上開いてた。

動画再生エンジン開発の方は、集中してやればそんなに時間かからなさそうだが、Theoraが重いのと吉里吉里3のリリースがだいぶ先なので、構造をあれこれ考えるだけにとどめて、しばらく置いておこう。
替わりに、動画拡張の方でTheoraの再生を試すのを検討している。
ま、Theora用のDirectShowフィルタだが。
そうすれば、試すことはすぐに出来るようになる。

でも、WMVが出来るとTheoraは微妙かも。
エンコードが速いのでホビー用に使うのはいいんだけど。

投稿者 Takenori : 19:03 | トラックバック

VMR9&WMV サンプル

とりあえず動かしてみたら直ぐに落ちてしまったので、ソースを見るとDirect3Dを自前でやろうとしていた途中だった。
そこでその部分をコメントアウトし、動的フォーマット変更のところでおかしかった部分を修正。
で、現状のものを一度公開することにした。

VMR9&WMV ムービープレイヤーサンプル

説明を書いていて、全然出来てないじゃないか!って気がしてきた。
ってか、公開していいのかこれ?

次は、リモートデバッグの設定をして、Win2kでおかしくなる原因をいろいろと調べないと。

投稿者 Takenori : 23:59 | トラックバック

2005年12月15日

DirectX VAを有効に

いろいろと思い出すために過去のエントリーを読んでいて、やはりDirectX VAなんとかならないかなぁと考えた。
で、ふとGraphEditでやったらどうなるのだろう? と見てみると、CPU負荷激減。
800*600 30fpsのWMVムービーをPen4 3GHz HTで再生すると10%~15%になった。
今までのYV12接続では30%~35%だった。
また、Windows Media Player 10でもほぼ同じ負荷。
WMPと同程度だったので、こんなものかと思っていたが、GraphEditの負荷を見ると明らかに異なる。
GraphEditでは、DirectX VAが使われている可能性が高い。
ちなみに、640*480 30fpsのWMVムービーで試すと10%程度だった。WMPでは20%~30%。
これはMPEG IをWMPで再生した時と同程度の負荷だ。
DirectX VAが使えたら、CPU負荷は半分以下になる可能性がある。
これはでかい。

GraphEditで接続情報を吐かせると、接続にはDXVA_ModeWMV9_Bが使われていた。
やはり、DirectX VAが使われている。
でも、どうやって?

動的フォーマット変更か?と思って試したが、違うようだ。

デバッガで追っていたら気になることがあった。
IPin::ReceiveConnectionの返り値がE_NOINTERFACEになっている。
普通、接続できない場合は「指定したメディア タイプは受け入れられない。(VFW_E_TYPE_NOT_ACCEPTED)」が返ってくる。
なのに、E_NOINTERFACEとはおかしい。
また、IAMVideoAccelerator でサポートしているGUIDを取得するとDXVA_ModeWMV9_Bが入っていた。
ネゴシエーションのプロセスが間違っているのではないか? と思いながら、VMRの入力ピンが要求しそうなインターフェイスを考える。
確か、IAMVideoAcceleratorはIAMVideoAcceleratorNotifyが必要になるはず。
IAMVideoAcceleratorの説明を見ると――
ビデオ デコーダ フィルタがこのインターフェイスのメソッドを呼び出す場合は、デコーダの出力ピンでIAMVideoAcceleratorNotifyインターフェイスをサポートしている必要がある
――出力ピンでIAMVideoAcceleratorNotifyインターフェイスをサポート?
ソースを見直すと、そんなことしていない。

そこで、IAMVideoAcceleratorNotifyが要求されたら、IWMReaderAcceleratorから取得して渡すようにした。
すると、今まで失敗していたIPin::ReceiveConnection呼び出しが成功した。
が、接続は失敗する。
CBaseOutputPin::CompleteConnect内で失敗しているようだ。
デバッガで追うとアロケーターのComitで失敗していた。
Commitのタイミングで、DecideBufferSize内でCommitするようにした。
が、やはりCommitのタイミングはここではないようだ。
とりあえず、ここのCommitをコメントアウトすると接続は完了するようになった。

でも、IWMReaderでは再生させる部分を完全には作っていないので再生しても何もでない。
また、Commitもきちんとしないといけない。
基底クラスのソースを見ると
CBaseRendererでは、PauseとRunでCommitをコールしていた。
CBaseOutputPinでは、Active内でCommitをコールしていた。
CBaseOutputPinは現在使用しているので、Commitは気にしなくても大丈夫だろうか?

ま、なんにしてもIWMReaderを使って再生できるようにしないと。
でも、そしたら今のやつとは全然違うものになってしまう。
今日公開したサンプルは意味なしかも・・・

投稿者 Takenori : 03:51 | トラックバック

2005年12月18日

再生が進まない

DirectX VAの前にIWMReaderを使ってOverlayの方で再生できるようにすることに。
が、再生できない。
Commitで失敗している。
デバッガで追うとビデオのサイズが0を返しているのがまずいらしい。
WM_MEDIA_TYPE::bFixedSizeSamplesをそのまま設定していたのだが、これが0になっていたようだ。
そこで、IWMReaderAdvanced::GetMaxOutputSampleSizeを使ってサンプルの最大サイズを取得するようにして、それを設定した。
そしたら、再生できるようになった。

で、いざDirectX VAで再生しようとするが・・・
最初のフレームは表示されるもののそれ以上進まない。
どうもレンダリングフィルタがポーズ状態のままなのが原因のような気がする。
DirectX VAを使わない場合は、OnSampleがコールされるので、この中でサンプルをダウンストリームへ送る。
確か、最初のサンプルが到達した時点でレンダリングフィルタがPause呼び出しから戻り、各フィルタのRunがコールされて再生が開始されていたはず。
DirectX VAでは、ネゴシエーション時にIWMPlayerTimestampHookインターフェイスを渡し、デコーダーはサンプルを表示する時間を得るためにIWMPlayerTimestampHook::MapTimestampをコールする。
MapTimestampでは、開始時間に従って、サンプルのタイムスタンプからプレゼンテーション時間を修正するようだ。
また、ヘルプには"When DirectX video acceleration is enabled, the OnSample method is never called."とあり、OnSampleはコールされなくなる。
じゃあどこでサンプルをダウンストリームへ送るのか?ってことになる。
MapTimestampしかチャンスがないように思うので、その中でダウンストリームへ送るようにしても再生は進まなかった。

そもそもサウンドはどうなるのだろうか?
サウンドはDirectX VAとは無縁のはずなので、サウンドサンプルをダウンストリームへ送るのにはどうするのだろうか?
よくわからない。
ヘルプとサンプルソースとデバッガで調べないと。
ググってもMSと自分のサイト以外はヒットしないし。。。

投稿者 Takenori : 20:48 | トラックバック

Win2kでのデバッグ

DirectX VAで再生がまったく進まない問題はとりあえず置いておいて、Win2kのCtrl+Alt+Delから戻ってきた時に落ちたりする問題について調べることにした。

まずリモートデバッグの設定をしてからリモートデバッグ開始。
Ctrl+Alt+Delした時は何も起こらず、解除するとなぜかStopがコールされて、その後OutputPinのDisconnectがコールされた後、再度接続が行われシークしてから再生が開始された。
まさかそのような動作が行われるとは。
接続が解除され、再度接続されてからまた再生が開始されると言うような動作には完全には対応していなかったような・・・
落ちたりしていたのはこれが原因か。
接続解除&再接続にもきちんと対応できるようにしておかないといけないな。
WinXPではこのような動作はなかったと思うんだけど、変更されたのかな。

後、開発マシンではアクセス違反の例外が出まくるが、Win2kの方では何も出なかった。
どうやら開発マシンのDLLか何かがおかしなことになっているようだ。

投稿者 Takenori : 23:31 | トラックバック

2005年12月19日

音なしで再生

オーディオレンダリングフィルタがPauseから返ってきていないだけかもしれないと思い、オーディオレンダリングフィルタを追加せずに再生してみた。
再生できた。
負荷もかなり軽い。
DirectX VAを使って再生できた。
少しうれしかったが、音がないのでまだ使えない。
いったいどうすればいいのだろう?

音が原因だとわかったので、その部分に絞って調べれば良いのだが、よくわからない。
音の出力側でもDirectX VAの設定をしてしまっているようだったので、しないようにしたが変化なし。(そもそも勘違い)

構成を変えるべきだろうか?
現状、ソースフィルタ内にDemuxとDecoderが入った構成になっているが、これをソースフィルタ内にはDemuxのみにし、Decoderは別フィルタにしてくっつけると言う方法もある。
WMF SDKのヘルプには、DemuxとDecoderがくっついたソースフィルタで作れと書かれている。(To enable DirectX VA for streamed content, you must create a custom source filter like the one in the top diagram. top diagramはDemuxとDecoderがくっついたソースフィルタの図を指している)
だけど、DirectShowに元々あるWMV用のソースフィルタ(WM ASF Reader)は、DemuxとDecoderが分離した形になっている。
GraphEditにドロップした時もこの形になり、DirectX VAが有効になる。
だから、どっちでもいいと言ったらどっちでもいいと思われる。
ただ、フィルタを分離すると少しオーバーヘッドがある。
カスタムアロケーターを作ればコピーが減らせるので、大差ないかもしれないが、現状の方が効率的だろう。

もう少し現在の構成でがんばってみるかな。
ヘルプにはWMV Decoder DMOとネゴシエートすると明確に書かれているし、図ではWMA Decoder DMOがAudio Rendererとつながっている。
だから出来そうではあるのだが・・・

投稿者 Takenori : 10:36 | トラックバック

2005年12月21日

分離することに

Demux+Decoderの構成でいろいろと試してみるが、どうもうまくいかない。
すっぱり諦めて、元々あるWM ASF ReaderのようにDemuxのみのソースフィルタにすることにした。
つまり、WM ASF ReaderのIStream読み込み対応版と言うことになる。

同期リーダーで圧縮されたままのサンプルをダウンストリームに送るように変更し、フィルタグラフにはWMV(WMA) Decoder DMO Wrapper Filterを追加。
再生してみる。
負荷を見ると、前回より数パーセント高くなっているようだ。
まあ、分離したので多少重くなっても仕方がない。
でも、元々あるWM ASF Readerよりも数パーセント高いのが気になる。
もっと効率的に出来る部分があるのだろうか?
良くわからないのはバッファの数。
初めは1個にしていたのだが、1個だけでは全然足りないので増やしていくがいくつぐらいにすれば良いかわからない。
とりあえず、30個あれば止まらないようなのでそうした。
これでとりあえずは出来たかと思ったが、Win2kマシンでは引きつる。
同期リーダーと言うのが原因だろうか?
デコード前のデータが表示時間に合わせて出力されるためにデコードの分遅延が発生してしまい、遅いマシンではそれが顕著にわかるのかもしれない。
そこで、同期リーダーの再生速度を2倍にしてみるが、再生できなくなってしまった。
他にもいろいろと試すが、どうも改善されない。
非同期リーダーを使わないと難しいのかも。

非同期リーダーで圧縮されたままのサンプルをダウンストリームに送るようにした。
引きつりはなくなりきれいに再生されるようになった。
負荷は特に変わらず。
バッファの数は1個あれば良さそう。
後はいらなくなった処理を省いていき、シークや再接続の対応をきっちりしていけば完成かな。

投稿者 Takenori : 01:03 | トラックバック

2005年12月22日

かなり安定した

キーフレーム以外のフレームへのシークは未対応だが、特に落ちたりすることもなく再生できるようになった。
現在、キーフレーム以外のフレームへのシークした場合、指定したフレームの前のキーフレームから画像が出てくる&指定フレームに追いつくまで最速で再生となっている。(音声は普通に再生される)
キーフレーム以外へのシークはしないと思うので、これでもいいと思うが、開始位置を強制的にキーフレーム位置へずらすなどした方が親切と言えば親切。

時々落ちていた原因は、CUnknownを継承した2つのクラスを多重継承していたために、やむなくAddRefなどの処理を別に定義していた辺りが原因のようだ。
後、参照カウンタ周りの処理がスレッドセーフではない可能性があったかもしれないので、この辺りの処理をCUnknownを使い、NonDelegatingQueryInterfaceやGetInterfaceを使うように統一した。
この2つの対策によって確認した限りでは落ちなくなった。

かなり安定したので、もうほぼ完成かな。
後は、シークのキーフレーム位置ずらしとVMRのフルスクリーン完全対応をやるかどうか。

投稿者 Takenori : 02:07 | トラックバック

LIBを使わないように

wmvcore.libをリンクしてしたのをやめて、wmvcore.dllを実行時にロードするようにした。
これでWMPがインストールされていない場合、突然終了するのではなく、wmvファイルのオープンに失敗するようになるはず。

シークについては現在のままにすることにした。
指定位置までのデータを捨てるようにしてみたが、そうすると次のキーフレームまで画像が表示されなくなる。
指定した位置から再生を開始する方法を模索したが、Decoderが分離しているのでよくわからなかった。
と言うことで、指定位置の前のキーフレームから画像が出てきて再生位置に追いつくまで出来るだけ早くデコードし表示していくのが比較的良さそうと言う事で、この仕様にした。

オーバーレイ、レイヤー描画、VMRすべてできちんと再生されるようにした。
再生時の負荷は、レイヤー描画 > オーバーレイ > VMRとなる。
たぶん、レイヤー描画でのWMV再生は現実的ではない。
AthlonXP 1600で640*480*30fpsのムービーを再生するとCPU負荷は80%ぐらいで、時々100%を超えてしまう。
レイヤー描画でWMV再生を行う場合は、2GHz程度のCPUが必要になりそう。
オーバーレイでは負荷は40%ぐらいなので、YUVからRGB32への変換とメモリコピーが40%程度占めていることになる。
MPEG Iの場合、その差は10%程度なので、単純に考えると30%程度がYUVからRGB32への変換に使われていることになる。
これだとバッファレンダーでYV12を受け付けるようにして、色空間変換を自前で実装した方が軽くなるかもしれない。
オーバーレイとVMRはDirectX VAが使えないと大差ない。
DirectX VAが使える場合は、かなり軽くなる。

投稿者 Takenori : 10:47 | トラックバック

2005年12月23日

YV12からRGB32への変換

バッファレンダーでYV12を受けつけるようにして、YV12からRGB32への変換を自前で実装してみた。
素で書いたら余計に遅くなった。
テーブルを使ったものにすると同じぐらいになり、MMXを使ったものにすると少し軽くなったような変わらないような。
やはり、そんなには変わらないようだ。

SSE2を使えば倍速になるかもと思ったが、調べるとPentium 4 で 7/10 程度だそうだ。
しかも、SSE2はMMX実行ユニットを2回呼び出すだけらしい。
メモリアクセス周りをもう少し改善できるかもしれないが、SSE2が使える環境では普通に再生できると思うのであんまり意味はないかも。


この結果から考えるとYV12からRGB32への変換を使う場合、Theoraはかなり辛そう。
ビデオとレイヤーを重ね合わせる時はDirectXを使わないと苦しい気がする。
DirectXを使ってもYUV系のフォーマットがサポートされていないビデオカードだとあまり意味ないが。
それよりも気になったのはMPEG Iデコーダー。
MPEG IデコーダーはRGB32で出力してくるが、MPEGもYUVで出て来るはず。
と言うことは、AhtlonXP 1600で50%ぐらいで再生されているけど、これをYUVのまま出したとするともしかして20%ぐらいになる?
そしたら、640*480 MPEG Iムービーを500 MHzでも普通に再生できるようになるかも。
なんか、こっちの方が魅力的な気がする。

投稿者 Takenori : 08:34 | トラックバック

レンダーレスモード

フルスクリーン切り替えに完全対応するべくVMRのレンダーレスモードを実装することにした。
改めてヘルプを見てみるとそんなに大変ではなさそう。
実装するインターフェイスは IVMRSurfaceAllocator9 と IVMRImagePresenter9 の2つでいいよう。
レンダリングするタイミングも IVMRImagePresenter9::PresentImage がコールされた時でいいようだ。
ただ、ヘルプでは良くわからない部分もある。
Direct3D ワールドでの DirectShow ムービーにサンプルソースの一部があったので、これでなんとなくわかった。

うまくいけばそんなに時間はかからずに実装できそうだな。
まあ、大体うまくいかないんだが。

投稿者 Takenori : 23:27 | トラックバック

2005年12月26日

メニューが出ない

ヘルプを見てレンダーレスモードを実装するも、書かれている通りに動かない。
違うメソッドがコールされて初期化が進まない。
いろいろと試すが良くわからず、ふとサンプルがあるのではないかと見てみたらあった。
するとDirect3Dの初期化のタイミングやネゴシエーション用のメソッドの呼び出すタイミングが違う・・・・・・
ヘルプでは
IVMRSurfaceAllocator9::InitializeDevice
InitializeDevice メソッドは、Direct3D デバイスを初期化する。
とか書いているのに、サンプルではこの中でDirect3D デバイスの初期化なんてやってない。
Direct3D デバイスの初期化はコンストラクタの中でやっていた。
それはともかく、サンプルを参考に実装したら動くようになったのだが絵が出ない。
いろいろと悩んで、ふとIDirect3DDevice9::Clearを呼ばないようにしたら絵が出るようになった。

最初は次のように呼び出していた。
IDirect3DDevice9::Clear
IDirect3DDevice9::BeginScene
IDirect3DDevice9::StretchRect VMRでレンダリングされた画像をバックバッファ絵コピー
IDirect3DDevice9::EndScene
IDirect3DDevice9::Present

Clearでバックバッファをクリアして、StretchRectでバックバッファへコピーしているはずなのに絵が出ない。
VMRのレンダリング対象がバックバッファになっているのかとも思ったが、そうでもないようだ。
なぜClearを呼ばなければ絵が出るのかあれこれ考えていて思い出した。
3D関連のコマンドはキューにためられて一気に実行される。
これはビデオカードとドライバの作りによるが、大体のビデオカードはそうなっていると聞いたことがある。
つまり、2Dのコマンドとは実行タイミングが違うために描画された後に消してしまっていた可能性がある。
まあ、これらは推測でしかないが。
でも、バックバッファの全面にビデオを描画するのでClearは必要ない。
と言うか、ない方が速い。

これで、うまく絵が出るようになり、リセットや初期化を直したりしてフルスクリーンとウィンドウを何度切り替えてもうまく動くようになった。
が、フルスクリーン時にメニューが出ない。
正確に言えばちらちらと出ている。
どうも、ビデオの画像に毎回消されているようだ。
Zオーダーをいじったりいろいろとやってみるが、よくわからず。
諦めかけていたが、ふと思った。
フルスクリーンって何? と。そしてそこからいろいろと考えていて思った。
レンダーレスモードのアロケーターとイメージプレゼンターでは、フルスクリーンモードの時は、DirectXをフルスクリーンモードで初期化している。
吉里吉里側もフルスクリーンモードに切り替えている。
競合するのではないか?
そもそもVMR側はフルスクリーンモードで初期化する必要があるのだろうか?
子ウィンドを使って、ウィンドウモードで初期化しても描画できるのではないか?
と思い至り、フルスクリーンモードをやめて、ウィンドウモードで初期化するようにした。
うまくいった。
フルスクリーン時でもメニューが表示される。

後はデバイスの能力を取得して出来るだけ多くの環境に対応できるようにするのと、エラー発生時にうまく対処するようにして、より安定するようにしていくのみ。
もう少しでVMR&WMVは出来そうだな。

投稿者 Takenori : 07:47 | コメント (2) | トラックバック

EAccessViolationに悩む

再生できるようになって、メニューの問題もクリアしたと思ったら、EAccessViolationに悩まされた。
ムービーを1個だけ再生した場合は発生せず、簡易ムービープレーヤーで2つ目のムービーファイルをドロップした時に発生する。

初めデストラクタが2回コールされている?と思ったが、そんなことはなく、どうも2回目の初期化が失敗してすぐに破棄されているようだった。
2回目の初期化が失敗しているとわかった後は比較的早く原因がわかった。
2回目に失敗していた直接の原因はRegisterClassExでのウィンドウクラスの登録だった。
VMRのアロケーター&プレゼンターは子ウィンドウを持っていて、最初に子ウィンドウを作る時にウィンドウクラスを登録し、アロケーター&プレゼンターが削除される時にウィンドウクラスの登録を解除していた。
ムービープレーヤーでは、破棄した直後に生成しているので、この作りでも問題ないはずだが、なぜか登録解除が間に合っていなかった。(同時再生を考えるとこの作りではまずい)
アロケーター&プレゼンターは、VideoOverlayの実体となるtTVPDSMixerVideoOverlayが保持しているCOMオブジェクトになっている。
初めtTVPDSMixerVideoOverlayが削除される時にReleaseを呼ぶようにしていたが、参照カウントが4も残っていて消えないので、deleteするようにしたが、それではまずいようなのでReleaseに戻した。
アロケーター&プレゼンターは、VMRにも保持されているので、削除時に参照カウントが残っていても不思議ではない。
そして、どうやらこの参照カウントが0になるタイミングがtTVPDSMixerVideoOverlayが削除された少し後のようだ。
DirectShowのグラフは複数のスレッドで構成されているのでRelese()がコールされた瞬間には消えないのだろう。

そこで、ウィンドウクラスを登録したアトムを持つ変数をクラス属性にして、実行時に1回だけ登録されるようにした。
こうすることでEAccessViolationはなくなり、初期化の失敗もなくなった。

フルスクリーンとウィンドウの切り替えは何度もやったが、生成&破棄などはあまりやっていない。
もっといろいろとテストしたほうが良さそうだ。

投稿者 Takenori : 22:33 | トラックバック

2005年12月28日

返ってこなくなる

IDirect3DDevice9が変更された時、IVMRSurfaceAllocatorNotify9::ChangeD3DDeviceを コールして、変更を通知する必要があるのだが、このメソッドをコールすると返ってこなくなることがある。
すでに設定されているIDirect3DDevice9と同じIDirect3DDevice9を使ってコールした場合、このメソッドを呼び出すと返ってこなくなる。
それ以外にも返ってこなくなるケースが存在するようだが、それが何かはわからない。
しかも、なぜだかMPEGを再生した時のみ発生する。
WMVの時は問題なく再生されているのにもかかわらずだ。
デコーダーがVMRに影響するとは考えづらいが、実際に発生しているので何かあるのだろう。
まあ、フィルタが違うので少しは接続方法が違ったりすると思うが。
後、デバッガでステップ実行していると起きない時もあるので、タイミングも影響してるのかもしれない。

どうも、MPEGの時は違うIDirect3DDevice9でも、描画対象ウィンドウのオーナウィンドウが同じ場合、発生するようだ。
オーナーが変わった時のChangeD3DDevice呼び出しでは問題ない。
ただし、常に発生するかと言うとそうではなく、1個目のムービーでは発生せず2個目のみ発生することもある。
バックバッファのサイズが大きいものから小さいものへ変更されるか、同じ場合、1個目のムービーでも返ってこなくなる。
どうも規則性があるようだが、完全にはわかっていない。

とりあえず、以下のような対処をすることで固まることはなくなった。
バックバッファのサイズはムービーサイズと同じサイズにする。
ただし、初回のDirect3D初期化時はサイズ不明なので、ウィンドウサイズと同じにしている。
ウィンドウ付け替えが行われている期間は描画を行わない。(この期間に描画を行い、デバイスロスト時に再構築をかけると返ってこなくなることがある)
Present時にデバイスロストして、再構築した時にエラーが発生しても通知しない。(タイミングによっては再構築が失敗することもある。ヘルプによるとこれはDirect3Dの仕様のようだ)

ただし、これらは吉里吉里のムービーに対する処理順序に強く依存している。
ウィンドウの付け替えと言う処理があるので、この辺りは仕方ない。

これでフルスクリーンとムービーの切り替えに対してはだいぶ安定してきたかな。
後テストしておいたほうが良さそうなのは・・・
・ムービー切り替え直後のフルスクリーン切り替えや、フルスクリーン切り替え直後のムービー切り替え。
・レイヤーとのミキシング。
・表示非表示の切り替え。
・ムービーサイズの変更。
・DirectX 9がインストールされていない環境での実行。
・Windows Media Player 9がインストールされていない環境での実行。
かな。
かなり頻繁に変更するようにしてテストしよう。
これらのテストがうまくいったらソースを整理してコメントをきちんと書いてからリリースかな。

異なるビデオカードによるマルチモニタ環境で、ウィンドウをモニタ間移動した場合はうまくいかないかもしれない。
ウィンドウが占めている領域が大きい方のモニタを使ってDirect3Dを初期化しているので、描画に失敗して再構築される時は違うモニタが使われるはずだから、大丈夫だとは思うが試していないので何ともいえない。
とりあえず、GeForce 6600 GTのTwinViewでの移動は問題なかった。
しかし、これはテスト環境作るのが面倒だなぁ。
まあ、気力があったらテストしよう。

環境依存のテスト以外が終わったらサンプルを公開するかな。

投稿者 Takenori : 12:30 | コメント (2) | トラックバック

2006年01月09日

いろいろとテスト

ムービーの再生を開始した瞬間にフルスクリーン化し、フルスクリーンになった瞬間にムービーを変更して再生し、すぐにウィンドウモードに戻すようなスクリプトでテスト。
特に問題なく再生できた。(誰もこんな使い方しないだろうけど)
表示/非表示切り替え、ムービーサイズ切り替え問題なし。
VMRでのセグメントループも問題なし。
ただ、セグメントループ時次のフレームが一瞬見えてしまうので、画像の表示を本体側で行うように変更した。
これで、次のフレームが一瞬見えてしまうことはなくなり、ほぼレイヤー再生と同じ動作のはず。
ミキシングを確認している時に、ムービーを縮小するとメッセージレイヤーも小さくなってしまうことに気付き、レイヤーとのミキシング時の表示位置を求めるサイズをムービーの実サイズではなく、表示サイズに変更した。
これによって、小さいムービーを拡大した時に本来ある位置にメッセージレイヤーが表示されるようになったが、元サイズの画像に対してミキシングされるため、文字などが汚くなってしまう。
ムービーを拡大してメッセージレイヤーとミキシングするのはイマイチかも。
VMR9のミキシングを使わずに、自前でミキシングするようにすれば解決出来なくはないが、アルファブレンドする時など面倒なことになりそうなのでこのままにする。
実サイズで再生すれば特に問題ないし。

WMP9やDX9がインストールされていない環境での動作確認はまだだけど、前回と同じ場所 吉里吉里2 VMR9&WMV ムービープレイヤーサンプル へ最新のものを上げた。
リリースするのはこれとほぼ同じものの予定。
動作は、前回よりもかなり安定しているはず。


後はソースのヘッダーコメントをキチンと書いて、ソースを見直した後、DX9などがインストールされていない環境で動作確認して、もう一度テストしたらリリース予定。

投稿者 Takenori : 00:19 | トラックバック

2006年01月11日

dxva_sig.txtが作られないように

WMVファイルを再生するとdxva_sig.txtが出来ていて、なんだろうこれ? と思っていたけど、特に実害はないので放置していた。
が、もしかしたら作られないようにする方法があるんじゃないかと思ってググったら、Windows Media Player 10のパッチで作られないようになるとか。
インストールして実行してみると確かに作られなくなった。
WMP側の問題だったのか。

投稿者 Takenori : 17:45 | トラックバック

DX9とWMP9がない環境

DX9とWMP9がインストールされていない環境で動作確認した。
オーバーレイ+MPEGで問題なく再生できた。
VMR9を使うようにしたら、例外が発生し"Failed to create device."と出た。
これは予定通り。
DX9が入っていないので、DirectX9の初期化に失敗する。
で、オーバーレイ+WMVにすると例外が発生し"EAccessViolation"と出た。
オープンに失敗したと言うような内容のメッセージが出るはずなのだが……
いろいろと調べるも良くわからず。
まあ、ビデオのオープンに失敗したらDX9とWMP9をインストールしてくれと表示すれば大丈夫と言えば大丈夫だが。

投稿者 Takenori : 19:56 | トラックバック

2006年01月12日

ドキュメント更新

VideoOverlayのドキュメントを更新した。
KAGにはまだ何も追加していない。
一通りKAGからも設定できるようにしておいた方が良いかな。

投稿者 Takenori : 23:56 | トラックバック

2006年02月15日

VMR9とWMV対応をコミット

VMR9とWMVに対応したムービー部をコミットした。
TJSから使える。(ドキュメント)
KAGは入れていない。
そもそもKAGから操作するものはほとんど作ってない。
確かモードの指定ぐらいだった気がする。
そのうちいろいろ入れたら入れよう。

他にMPEGのマルチビデオにも対応しているけど、使えないのでドキュメントには書いていない。
マルチオーディオはまだしも、マルチビデオはなぁ……
タイムラグあるし。


今回、WMVに対応したもののWMVはしばらく使わないことになりそう。
かなり苦労したけど。
VMR9は使う予定。

投稿者 Takenori : 23:25 | コメント (6) | トラックバック

2007年03月13日

VMR9とDirectXと

ごうさんのところでDirectXの話があったので、思い出しつつ少し。

VMR9モードを作っている時、レンダーレスモードでやらざるを得なくなって、仕方なく普通にDirect3Dを自分で初期化して描画するようになった。
IDirect3DSurface9に描かれたムービー画像をStretchRectでバックバッファに描き込んでいる。

レイヤーとのミキシングは、レイヤーをbitmapにしてから、VMR9に渡しているが、これが重い。
毎フレームミキシングするレイヤーを更新していたらかなり重い。
この重さから、レイヤーもDirectXで作れば……と結構思ったが、メッセージの処理などが面倒なのではないかと思って止めた。
でも、よくよく考えれば、KAG側でメッセージ表示のほとんどをやっているわけで、文字が描ければメッセージ表示出来る。
レイヤーをDirectX ( Direct3DSurface ) に持っていければ、VMR9モードはレイヤー描画モード並みと言うか、それ以上に柔軟性が増すはず。
そうなってくると描画モードは、3つもいらなくてVMR9に統一できるんじゃないかと思う。

描画周りがDirectXになるといろいろと面倒なこともあるけど、その分いろいろ出来るようになる。
かなり面白そうで実験したくなる。
7月まではなかなか時間が取れないのが口惜しい。

投稿者 Takenori : 11:47 | トラックバック

2007年03月31日

アルファ付きムービーの再生

無圧縮 AVI などでアルファ付きならアルファ付きで描画されるはずと思っていたが、やってみるとアルファ情報は消えていた。
レイヤーモード用のレンダーフィルタのソースを見ると、MEDIASUBTYPE_RGB32 のみ受け付けるようになっていた。
そこで、MEDIASUBTYPE_ARGB32 も受け付けるようにしてみた。
対応した krmovie.dll はこちら
指定するレイヤーのタイプは、videoLayer.type = ltAlpha のようにアルファを有効にする必要がある。
ltOpaque などになっていると透けない。

無圧縮だとファイルサイズがすごいことになるので、配布用に使うのは難しい。
アルファチャンネルをサポートしたフォーマットはいくつかあるようだけど、今すぐ使える形式では見当たらない。
Indeo Videoもアルファチャンネルをサポートしているっぽい記述を見かけたが、試していないのでわからない。
サポートするならアルファチャンネルのみを持ったムービーファイルを作って、それをアルファチャンネルへ書き込むのが確実だろうか?
レンダーフィルタの前にアルファを合成するインプレイスフィルタを作って、それを間に挟んで、入力ピンの片方にアルファチャンネルムービーを渡せば出来るはず。
でも、何に使うのが効果的だろうか?

効果的な使い方があったらサポートを考えてもいいけど、今のところ効果的な使い方が思い付かない。

投稿者 Takenori : 15:04 | トラックバック

2007年04月13日

krmovie.dll のビルド手順

・DirectShow は DirectX から Platforma SDK に移動したようです。 後日そちらで手順を書き換えます。参考1参考2 (2007/05/06)
・DirectX 9.0 SDK Extra のコピー位置がおかしかったので修正(2007/05/03)

PC を 替えてビルドできなくなっていたので、krmovie.dll をビルドするにあたって手順を整理。

krmovie.dll のビルドには次の4つが必要。

・Visual Studio.NET 2003
・DirectX 9.0 SDK
・DirectX 9.0 SDK Extra
・Windows Media Format 9 Series SDK (9.5)

Visual Studio は上位バージョンでも大丈夫だと思うが試していない。
下位バージョンの場合は、プロジェクトファイルを作る必要がある。
Visual C++ 2005 Express Edition は試していないが、ATLが付いていないので、たぶんビルドできないと思う。
でも、ATLはそんなに大したものは使っていないはずなので、移植しようと思えば出来るはず。

DirectX 9.0 は、リポジトリのプロジェクトでは、DirectX 9.0 SDK Update - (October 2004)DirectX 9.0 SDK Update (October 2004) Extras の組み合わせになっている(一番初めはこの組み合わせだったので)。
最新の DirectX SDK - February 2007DirectX 9.0 SDK Update (February 2005) Extras の組み合わせでもビルドできたので、こちらでも大丈夫だと思われる。

Windows Media Format 9 Series SDK は、たぶん9.5でも大丈夫なはず。
なぜか日本語のページはリンク切れだったので、ここからダウンロードする必要がある。

DirectX 9.0 SDK Extra は、展開して出来たDirectShowなどのフォルダを"C:\Program Files\Microsoft DirectX SDK (February 2007)\Samples\C++"にコピーする。
DirectX 9.0 SDK Extra は、展開して出来たDirectShowの中身を"C:\Program Files\Microsoft DirectX SDK (February 2007)\"にコピーする。

以上で環境は整う。


次にビルド。
まず、DirectShow の BaseClasses をビルドする必要がある。
"C:\Program Files\Microsoft DirectX SDK (February 2007)\Samples\C++\DirectShow\Samples\C++\DirectShow\BaseClasses\baseclasses.vcproj"
を開く。

"C:\Program Files\Microsoft DirectX SDK (February 2007)\Samples\C++\DirectShow\BaseClasses\baseclasses.vcproj"
を開く。
プロジェクトの設定の C/C++ の コード生成 のランタイムライブラリがマルチスレッドDLLになっているので、これをマルチスレッドにする。
こうしないと msvcrXX.dll などが必要になってしまう。
そして、krmovie.dll は マルチスレッド にしているので、リンク時にLIBCMT.LIB をはずしてくれとか良くわからないエラーが出て困る。
Debug 以外にも Release などすべてマルチスレッド DLL ではないほうに設定してから、ビルドする。
これで strmbasd.lib と strmbase.lib が出来る。
これができれば、以降はこれを使うことになるので、この辺りのビルドは不要になる。

次に krmovie.vcproj 。
DirectShow関係のパスが October 2004 になっているので、これをインストールしてあるものにあわせる。
後、Windows Media Format 9 Series SDK のインクルードパスも追加する。
今回ビルドしたら、なぜか wsprintfW でエラーになったので、これを swprintf に替えた。(参考)
後はビルドすれば出来上がり。

krmovie.dll は DirectX や WMV に 依存しているので少し面倒だけど、一度入れてしまえばおしまいなので面倒なのは最初だけ。

投稿者 Takenori : 12:46 | トラックバック

2007年05月11日

循環参照でメモリリーク

自動読み進みでひたすら回していると、メモリ不足に陥り、メモリ確保に失敗して落ちる問題が発覚。
メモリ使用量が増えていくのは画像キャッシュかと思っていたけど、メモリが枯渇するほどキャッシュするとは思えない。
メモリリークしているのだろうか?
画像キャッシュをOFFにして見ても使用量は増えている。
動作と使用量を見ていると、ムービーを再生するごとに増えていっている。
VideoOverlay の delete 時に開放出来ていないような。
使用しているのはWMV + レイヤー描画モード。

_CrtDumpMemoryLeaks を使えば、デバッグ出力に何度目に new されたメモリがリークしているかが表示される。
V2Unlink の最後に _CrtDumpMemoryLeaks を仕掛け、デバッグ出力で何度目に new されたものかを確認。
結構いっぱい出てる。
一番若い番号を _CrtSetBreakAlloc に指定して、もう一度動かす。
ブレークがかかった後は、呼び出し履歴(コールスタック)を見て、new 元を探る。
見てみると BufferRender だった。

だけど、BufferRender は CComPtr で自動的に Release されるはず。
また、BufferRender をフィルタグラフに追加した後は、フィルタグラフが Release するはず。
何かおかしい。
BufferRender の生成周りやフィルタグラフ構築周りでの、参照カウンタの増減を見ても特に不自然なところはない。
開放部分を追うがおかしなところは見当たらない。

さっぱりわからない。
ただ、IGraphBuilder の参照カウンタが1残っている。
どこかで開放し忘れているのだろうか?
確か、以前フィルタグラフが完全に停止しておらず、開放できないということがあった気がする。
今回もそうかと思い、追って見るも違う様子。
IGraphBuilder を2回 Release してみると、BufferRender のデストラクタが呼ばれ開放されていくが、当然落ちる。
どこかで開放できていないのは間違いないが、どこかわからない。

そこで、すべてのインターフェイスの開放時の参照カウンタ減少具合を見てみることにした。
すると IMediaSeeking や IMediaControl を Release するごとに参照カウンタが減っていっているように見える。
そして、最後に参照カウンタが1残る。
もしかして、これらの参照カウンタは共通なのか?
これらのインターフェイスは、IGraphBuilder に QueryInterface して取得しているが。

だとしたらあそこか!
以前、セグメントループのタイミングをより正確にする対応で、BufferRender 内でフィルタグラフからIMediaSeeking を取得し、IMediaSeeking から現在時間を取得するようにした。
そしてこの IMediaSeeking は、BufferRender のデストラクタで Release するようにした。
つまり、IGraphBuilder と BufferRender で循環参照が発生していたわけだ。
厳密には、IGraphBuilder と IMediaSeeking が参照カウンタを共有しており、BufferRender が IMediaSeeking を保持していたわけだ。
って言うか、そんなの知らないよ。

まあ、とにかく BufferRender で IMediaSeeking をデストラクタがコールされるまで持つのではなく、一時的に QueryInterface で取得してすぐに Release するようにすることで、このメモリリークは解消された。
ただ、レイヤー描画以外にもメモリリークは存在していた。

WMV のメモリリークに続く。

投稿者 Takenori : 22:57 | トラックバック

2007年05月12日

WMVソースフィルタ のメモリリーク

WMV ソースフィルタのメモリリークの原因も循環参照だった。

WMV ソースフィルタ では、シークを実現させるため IMediaSeeking を実装している。
そして、 IMediaSeeking はピンでも実装する必要がある。
単純にピンに送られた IMediaSeeking への操作をアップストリームへ渡して処理する場合は、CPosPassThru を使用すれば良いが、WMV ソースフィルタの場合は、そのピンを持っているソースフィルタに処理させたい。
そこで、フィルタへのポインタを持つ、CMediaSeekingProxy と言う単純に保持したポインタへ操作を流すクラスを作って対処した。
CMediaSeekingProxy では、フィルタへのポインタを CComPtr を使って保持していた。
CComPtr は代入した時にポインタの参照カウンタを増加させる。
CComPtr はデストラクタが呼ばれるか、明示的に Release をコールした時に、参照カウンタを減少させる。
つまり、CMediaSeekingProxy は、生存期間中渡されたフィルタの参照カウンタを増加させたままにする。

CMediaSeekingProxy は、前述の通り、出力ピンが持っている。
出力ピンは、WMV ソースフィルタが持っている。
以上で循環参照の出来上がり!と言うわけだ。

これを解消するには、CMediaSeekingProxy が CComPtr を使わずに、単純にポインタを持つようにすれば良さそうだ。
ただ、単純にポインタを持つと不正なポインタへのアクセスが発生してしまう可能性があるが、前述したような構造上生存期間は、CMediaSeekingProxy の持っているポインタよりも CMediaSeekingProxy の方が短い。
と言うことで、CMediaSeekingProxy では CComPtr を使わずに、ポインタとしてソースフィルタを持つことにした。

これで解決したかと思いきや、まだ開放できていないメモリがあった。
この問題はアロケーターだった。
WMV の SDK のアロケーターと DirectShow のアロケーターは少し違う。
そのため、この橋渡しをしてやるアロケーターを作る必要がある。
このアロケーターは、単純に WMV の SDK のアロケーターのアロケート要求を DirectShow のアロケーターを使って実現しているだけなのだが、この DirectShow のアロケーターでは同時に確保できる個数を2個に制限していた。
なぜ2個かと言うとレイヤー描画用のレンダーフィルタのためだ。
DirectShow のアロケーターは、ダウンストリームのフィルタ(ピン)から渡される。
レイヤー描画用のレンダーフィルタは、表示用のメモリに直接デコーダーにデコードさせるようなアロケーターを持っている。
そして、このアロケーターはダブルバッファリングのために2個だけ同時に確保できるようになっている。
WMV ソースフィルタは、初めは直接レンダーフィルタに接続するような形態になっていた。
そのため2個という制限が発生していた。
なぜ直接接続するようにしていたかと言うと、その構造の方がオーバーヘッドが少ないだろうという判断からだ。
しかし、その構造で DirectX VA を有効に使う方法を見出せなかったため、WMV Decoder DMO を間に挟む構造となった。
この時点で上記2個の制限はなくなったわけだが、とくに変更を行っていなかった。

しかし、WMV は2個以上のバッファを要求する。
ダウンストリームから渡されたアロケーターでは、上述のように2個しか確保できない。
そのため、WMV の SDK のアロケーターは、別にもう一つアロケーターを持っていた。
WMV がバッファを要求した時、ダウンストリームから渡されたアロケーターで確保に失敗すると、もう片方のアロケーターからメモリを確保する。
このようにすることで WMV からのアロケート要求にこたえる形となっていた。

DirectShow のアロケーターは、実際にメモリの確保を行う前に Commit をコールしておく必要がある。
また、確保する必要がなくなったとき、Decommit をコールする必要がある。
しかし、Decommit のコールを忘れていた。
調べると Commit は参照カウンタを増加させ、Decommit は参照カウンタを減じる。
橋渡し用に作った WMV の SDK のアロケーターのデストラクタで、Decommit をコールしてやると開放されるようになった。
他に DirectShow のアロケーターの Release を不用意にコールしてしまっている箇所があり、不正アクセスが発生してしまったが、これはその箇所を修正して解消した。

これで解決したと思っていたのだが、このエントリーを書いていて、上記2個の制限の経緯を思い出した。
既に2個の制限は必要ない。
この制限が必要ないのであれば、予備のアロケーターを別に作る必要もないし、Commit がどうとか言うことも関係ない。
と言うことで、2個の制限を撤廃し、アロケーターを決定する時にダウンストリームへ7個を要求するようにした。
これによって、予備のアロケーターは必要なくなり、構造はすっきりした。

以上で WMV ソースフィルタのメモリリークが解消され、すべてすっきりしたかと思いきや、VMR モードにするとなぜか不正アクセスが発生。
VMR のカスタムアロケーターの不正アクセス問題へ続く。

投稿者 Takenori : 19:48 | トラックバック

2007年05月15日

VMR のカスタムアロケーターの不正アクセス問題

VMR 関係はいじっていないのに、VMR モードで再生して終了すると不正アクセスが発生するようになった。
ソースを元に戻しても発生する。
が、前のマシンで動かすと発生しない。
発生するマシンで、デバッガを使わずに直接起動すると発生しない。
なんだろうこれは?

ドライバのバージョンを上げてみたり、いろいろとするが改善されない。
そこでふと、そういえば不正アクセスが発生しているアドレスにブレークを張ればいいことに気付き張る。
カスタムアロケーター&イメージプレゼンターのデストラクタで発生している。

メンバには、D3D 関係のものと IVMRSurfaceAllocatorNotify9 を保持している。
デストラクタでは、明示的に D3D 関係の破棄などを行っている。
IVMRSurfaceAllocatorNotify9 は CComPtr に任せて放置。
もしかして、IVMRSurfaceAllocatorNotify9 から Direct3D 関係のものへアクセスが発生しているのか?
そこで、IVMRSurfaceAllocatorNotify9 を明示的に D3D 関係の破棄前に Release するようにしたところ発生しなくなった。

なぜデバッガで動かした時のみ発生したのかはわからないが、Direct3D 関係のものよりも前にIVMRSurfaceAllocatorNotify9 を Release する必要があったようだ。

投稿者 Takenori : 23:41 | トラックバック

2007年07月08日

アルファ付きムービー2

下書きのまましばらく放置していたけど、吉里吉里掲示板の方で触れられたので少し追記して公開することに。

CRI Sofdecのページを教えてもらい、見るとその用途が見えてきた。
効果的な場面もありそうだ。

ごうさんの日記でも過去に触れられています。
レイヤでのαつきムービー再生 (吉里吉里)
最新版は、吉里吉里のSVN内のようです。
これはアルファチャンネルとカラーチャンネルを横にくっつけたムービーを準備して、それを再生時に画像幅を半分にしてアルファを合成するというアプローチで実現されています。


アルファ付きムービーに関しては、アルファ付きムービーの再生を書いていた頃、いろいろと検討していたのですが、今はアニメーションに興味が移っています。
それはいいとして、アルファ付きムービー用のフォーマットとして MotionJPEG が有効ではないかと考えていました。
アルファチャンネルをサポートしたムービーファイルフォーマットもあることはあるようですが、目的の用途に使えそうなものは見付かりませんでした。
そうすると、独自フォーマットかアルファチャンネルを別ムービー、もしくは別ストリームとしてMPEGなどで多重化する案が考えられます。
で、いろいろと考えて MotionJPEG を ogg コンテナに入れるのが、手軽ではないかと ( コンテナフォーマットは何でもいいですが )。
MotionJPEG であれば、すべてが I フレームで構成されるので、どこへでもすばやくシークできるため、演出用途には使いやすいと思います。
ただ、単純に背景を抜くような用途では、圧縮率がMPEGよりも劣るので不利ですね。
背景を抜く場合は、カラーキー方式をサポートすれば良さそうです。
非可逆なのである程度の幅を持たせてやる必要があるのと、境界付近が心配ですが。

投稿者 Takenori : 17:21 | トラックバック

2007年08月23日

Flashのレイヤーへの描画

書きかけだったので、少し加筆して公開。

Flash の WMode を Transparent にするソース発見。
今まで検索していて知っている人のところをぐるぐるしていた感じだったけど、今日はなぜか発見できた。
何で検索したかは忘れてしまったけど。

とりあえず、Flashの描画結果をBitmapファイルへ書き出せることは確認したので、これをレイヤーへ書き出せばレイヤーに書ける。
と思ったが、すんなりいかないことがわかった。
ウィンドウ周りがうまくいかない。
どうすればいいのか……


5月中頃にこのエントリーを書いたよう。
で、今は Flash への求心力は薄れているので放置。
アニメーション対応が出来れば、Flash の必要性は減るハズ。

投稿者 Takenori : 19:44 | トラックバック

2007年10月07日

VMR の落ちる問題への対処

吉里吉里本体が DirectDraw を使っていたり、Vista で動かしていたりすると、オーバーレイがうまく動かないようだ。
うまく動かないと言うのは、見れなくなるというわけではなく、ハードウェアオーバーレイが使われていないと言う事のよう。
そして、拡大が汚い。

と言う事で、VMR-9 ( mixer モード ) の重要性が増してきたんだけども、VMR モードではムービー再生中にウィンドウとフルスクリーンを何度も切り替えると落ちるという問題があった。
まあ、そんなことする人はいないと思うけど、それでも落ちるというのは問題と言う事で調査。

原因はどうやら IDirect3DDevice9::Reset の前に IDirect3DDevice9::TestCooperativeLevel をコールしてリセット可能な場合のみ IDirect3DDevice9::Reset をコールするようになっていないケースがあることのよう。
つまり、IDirect3DDevice9::Reset をいきなり呼んでいるからっぽい。
でも、IDirect3DDevice9::Reset をいきなり呼んだら常に落ちるというわけではなく、時々落ちるみたい。
毎回落ちればわかりやすいんだけど、そうではないようだ。
と言う事で、必ず IDirect3DDevice9::TestCooperativeLevel でチェックしてリセット可なら IDirect3DDevice9::Reset をコール。そうでないなら、Direct3DDevice を破棄して作り直すように ( Direct3DDevice の再生成は前からやってた )。
これでわかっている問題はすべて直したはず。
まあ、Direct3D や VMR-9 を使っているので、ハードウェア依存の問題は何かしらあるかもしれないけど、それは仕方ない。
発覚して直せそうなら直す。

それと、クリックが吉里吉里側に伝わっていなかったようだ。
これは、DrainWindow へメッセージを流して終わりと言う事で、そうした。
後、KAGでmode="mixer"がなかったので追加。

以上、対応を行ったものをコミットした。
これで、以前よりも mixer モード が安定した。
でも、誰も何も言わなかったて事は、mixer モードほとんど使われていなかったんだろうなぁ。

投稿者 Takenori : 15:26 | トラックバック

レイヤーモードの注意点

たぶん書いていなかったと思うので、書いておきます。
レイヤーモードでムービーを再生した場合、描画対象のレイヤーのモードが alpha などになっていると何も表示されない状態になります。
これは、アルファを持たないムービーがアルファチャンネルに書く値は不定で、たぶん0が入ってるため、完全に透明となり何も描画されないためです ( 推測 )。

このような状態になるのは、レイヤーに画像を読み込んだ後、そのレイヤーにムービーを再生しようとした場合などです。
このような場合は、一度そのレイヤーに対して [freeimage] するか、画像を読み込むときに [image storage="title.jpg" layer=0 mode="opaque"] として不透明にするなどする必要があります。
直接レイヤーのモードを opaque してもいいと思います。
ただ、描画対象レイヤーが base の時はこのようなことをしなくても正しく描画されます。

投稿者 Takenori : 15:43 | トラックバック

2007年10月16日

Vista で拡大補間が効かない対応

VMR9 でウィンドウ - フルスクリーン切り替えが安定して動くようなって良かったと思っていたら、Vista で拡大補間が効かない環境があるとか。
なんとまぁ。
StretchRect の最後の引数を D3DTEXF_NONE にしていて、その場合はドキュメントには「D3DTEXF_NONE を指定した場合、ドライバはフィルタリング アルゴリズムを選択します」と書かれている。
そのため、環境によっては拡大補間が効かないのかと思ったけど、ここを変えても直らないよう。
次に、バックバッファのサイズを疑ったが、そこも効果なく、仕方なく板ポリにムービーをテクスチャとして貼ることにした。
で、対応。
この対応で、ビデオカードによって作られるサーフェイスが異なるように。
最近のであれば、テクスチャサーフェイスに直接 VMR9 で描けるようで、VMR9 が描いたテクスチャサーフェイスをそのままテクスチャとして使う。
テクスチャサーフェイス に直接描けない場合は、オフスクリーンサーフェイスになる。
さらに、出力フォーマットが YUV 系ならテンポラリのテクスチャサーフェイスが作られ、描画時に一度そこにコピーされ、それがテクスチャとして使われる。
何が使われたかはコンソールに以下のどれかが出力されるので、それで判別できる。
krmovie : Use texture surface.
krmovie : Use offscreen surface.
krmovie : Use offscreen and YUV surface.

また、テクスチャサイズに二の累乗制限がある時は、コンソールに以下のメッセージが出る。
krmovie : Use power of two surface.

テクスチャサイズの上限に引っかかった場合、VMR は使えない。
たぶん、VMR が分割して描いてくれるとかないだろうから、そこは放置して、使えないとすることに ( でも、VMR9PresentationInfo を見ると転送元矩形や転送先矩形の情報が入っているから、やってくれるかも知れない。調べたほうがいいかも ) 。
その場合、以下のように表示された後、例外が発生してビデオが再生されない。
krmovie warning : Video size too large. May be cannot create texture.

VMR で再生できなくて例外が発生した時は、catch して、モードをオーバーレイモードやレイヤーモードに変更する必要がある。

ビデオメモリ使用量を抑えるため、ムービーを開いた後、サイズ変更不可のウィンドウでプログラム側からウィンドウサイズを変更 ( 大きいサイズに ) した場合に、ムービーのサイズが小さくなってしまいます。
これはサイズ変更不可のウィンドウの時は、バックバッファのサイズをウィンドウサイズと同じにしているためです。
まあ、ゲームではこのような状況はまずないと思うので、問題ないとは思いますが、一応書いておきます。
( これは、なんとか出来なくはないと思いますが、あまり害はないと思うのでそのままにしています )

投稿者 Takenori : 02:03 | トラックバック

2007年11月09日

Turbo C++ Explorer で吉里吉里2をビルド

C++ Builder 6 で作っていたツールを C++ Builder 2007 でビルドしたところ、動作が機敏になったことから、吉里吉里2 を C++ Builder 2007 でビルドしたら速くなるかも? ということで対応してみた。
コンパイラのエラーの出かたが変わっていて数箇所直す必要があったのと、boost で少し困ったぐらいでその日のうちにできた。
で、肝心の結果はと言うと、TJS2 の実行速度は遅くなってた。
聞くと C++ Builder 5 で最大の速度が出るようにチューニングしてあるらしい。
期待はずれな結果になってしまった。
もしかしたら、VCL 周りが速くなってたりするのかな?

C++ Builder 2007 では残念な結果になってしまったが、ついでに無料の Turbo C++ Explorer でビルドできるようにしといた。
これは既にメイントランクに取り込まれているので、リポジトリから取ってきた後、kirikiri2/src/core/environ/win32/bcb2006 にあるプロジェクトファイルを Turbo C++ Explorer で開いてメイクすれば、kirikiri2/bin/win32 に実行ファイルが出来上がる。
bcb2006 以下にある include や lib が大きいので削ると聞いていたから、その部分の準備方法を…… と思ったのだけれど、チェックアウトしてみたらそのまま入っていた。
なので、特に気にする必要はなく、Turbo C++ Explorer をインストールして、メイクすれば吉里吉里2の実行ファイルが出来てしまう。
これで、困ったことがあったり、動作を確認したい時は手軽に誰でも (?) メイク出来るようになった。

個人的には C++ Builder 6 のデバッガが時々おかしかったのが回避でき、C++ Builder 2007 の使いやすくなった GUI を利用できるのがありがたい。
まあ、最近はほとんど本体のほうはいじっていないんだけども。

投稿者 Takenori : 03:51 | トラックバック

2007年12月14日

VMR でウィンドウが大きくなっても正常に

Vista で拡大補間が効かない対応 の下の方に「ビデオメモリ使用量を抑えるため、ムービーを開いた後、サイズ変更不可のウィンドウでプログラム側からウィンドウサイズを変更 ( 大きいサイズに ) した場合に、ムービーのサイズが小さくなってしまいます。」と制限を書いていたが、これをなくした。
具体的には、バックバッファのサイズより大きいサイズにビデオが広げられた時、DirectX を再生成してバックバッファを大きくするようにした。
小さくなる時は何もしない。
つまり、大きくしてから小さくするとビデオメモリを無駄使いしていることになるが、再生成すると一瞬ちらついてしまうので、あまりやらない方がいいだろうということと、大きくして再生出来たのだからビデオメモリの心配はないだろうということでそうした。
当然、再生成してバックバッファを大きくするので、ビデオメモリが足りなくて生成に失敗する可能性がある。
失敗した時は例外がとぶので、catch しておいた方が安全。
まあ、catch してどうするのだと言う話もあるが ( ビデオ再生スキップ? ) 。

投稿者 Takenori : 02:39 | トラックバック

2008年01月03日

吉里吉里2/KAG3 機能仕様書

吉里吉里2/KAG3 機能仕様書を勝手に書き始めた (コミット許可などはもらってます)。
何か時々手間だなと感じるのはこういうドキュメントがなかったからじゃないかと気付いた。
これがあれば、吉里吉里で何が出来るのかわかりやすい。
吉里吉里で出来ることを説明すること過去数回。
このドキュメントがあれば、これ参照してくださいで済むはず。
他に機能一覧からリファレンスへリンクを貼ることで、機能からリファレンスが引けるようになるはず。

コーディングに疲れた時などに整備して行こうと思う。

投稿者 Takenori : 11:57 | トラックバック

2008年01月25日

WMV のライセンスまとめ

WMVのライセンスについて、開発当初と状況が変化しているため、ここにまとめておく。
状況が変化したのは、WMV9 を標準規格にしようと MS がソースコードを標準規格を決める機関に提出してから。
このソースコードの公開によって、MPEG 関連特許を利用していることが判明し、VC-1 の特許についても MPEG LA が管理するようになった。
VC-1 は、Windows Media Video 9 (オーディオは関係ない) をベースとしたもの。
ただ、完全に同じものかと言うとそうともいえず、プロファイルは異なっている模様。
これは、Q3: Xbox 360 が対応する WMV (VC-1) の詳細を教えてください。などで、WMV9(WMV3) と VC-1 が区別されていることや、以前 XBox360 で再生できたり出来なかったりと言うものがあったりしたことから、異なっていると考えられる。
とは言っても、WMV9 は MPEG 関連特許使っているだろうけど…… ( 本当に使っているかどうかはわからない? )

以上のようなことがあってから、MS の HP の内容はいろいろと書き換えられている。
まず、Windows Media のライセンス料金とロイヤリティ に関して、Windows Media Format SDK テクノロジを使用した場合のライセンス料又はロイヤリティについて、「ライセンスが有効な Windows オペレーティング システムのコピーを所有しているユーザーは、追加料金不要。」となっている。
以前は、このような書き方ではなかったと思うが、MPEG LA に特許が管理されるようになってから変わったものと思われる。
後、コーデックのライセンス料比較表のようなものがあって、WMV の優位性をアピールしていたはずだけど、それが見当たらない。

次に MPEG LA の VC-1関連特許のライセンスについて
これは以下ページにまとまっている。
MPEG LA、VC-1関連特許のライセンス料を公開-OS組み込みやコンテンツ配信などで利用へ
MPEG LAが動画符号化技術「VC-1」関連特許のライセンス費を公開
OSとそれ以外の機器、コンテンツで分かれている。
コンテンツについては、12分以下か、それを越えるかで有料/無料が変わる。

後、Windows Media Format SDK 9 の EULA について
これは、以前に WMF SDK 9 EULA でまとめている。

以上が前提条件。
で、吉里吉里に対してはどうかと言うと……
まず、EULA に対してだけど、これはこれを守って作っている。
DRM は対応していない。
なので、Windows Media Player や 再配布可能コンポーネントを同梱せずに、コンテンツを配布すれば問題ないはず。
別途、Windows Media Player 9 以降を MS のページからダウンロードしてインストールしてくださいと書いておけばいい。
次に、VC-1 について。
上記のように、VC-1 はプロファイルが WVC1 や WMVA となっている。
吉里吉里2の krmovie.dll プラグインでは、メディアのサブタイプが WMMEDIASUBTYPE_WVC1 や WMMEDIASUBTYPE_WMVA の場合、再生できない。
これは、Windows Media Format SDK 9 にはこのサブタイプの定義がないことと、実装時に厳密にメディアタイプをチェックしていたことが奏功している。
また、Windows Media Format SDK で OS のライセンス云々をわざわざ記載していることから、Windows 上で VC-1 を利用するアプリケーションの場合は、ライセンス料は必要ないのかもしれない。MS がばーんと払ってるから安心して使ってくれとか書いておいてくれたら安心なんだけど、そうは書いてない。

と言うことでまとめ。
・配布時には、Windows Media Player や 再配布可能コンポーネントを同梱しないこと。
・たぶん、吉里吉里での WMV のサポートは問題ない。
・12分を越えると少しやばいかも。

なんだかんだで結構グレーな雰囲気だけど、多分大丈夫だろうと言う見解。
でも、かなり安心なコーデックとして Theora もサポートした方がいいかなぁ。
誰か開発費出してくれたら組み込んでもいいよ(ぇ

投稿者 Takenori : 16:04 | トラックバック

2008年02月26日

アルファムービーをやや強引に入れるアイデア

Motion JPEG フォーマットにして、アルファ付きムービーを実現する方法を考えていたけど、Motion JPEG だと圧縮率が低いのが難点。
そこで他の動画フォーマットにアルファ情報を加える方法を考えていた。
ただ、動画のデコードはそこそこ重いので、別ストリームにして2つ同時再生は少し辛いのではないかと思う。
だから、アルファのストリームは簡易的な圧縮方法で格納しないと難しいだろうということで、Theora の圧縮方法を見ながら、そこからいくつか機能を削って軽くする方向で考えていて気付いた。

YUV 4:2:2 にして、その中にアルファ入れてしまえばいいんじゃないか?と。
MPEG I は YUV 4:2:0 のみだけど、Theora は YUV 4:2:2 も選択できる。

YUV 4:2:0 とは、輝度に対して、色差の Cr, Cb をそれぞれ 1/4 のサイズで格納する方式。
YUV 4:2:2 は、輝度に対して、色差の Cr, Cb をそれぞれ 1/2 のサイズで格納する方式。
図にすると以下のようなイメージ。

20080226_yuv_alpha_movie.png

図を見てもらえばすぐに気付くと思うが、YUV 4:2:2 で圧縮するけど、実データは YUV 4:2:0 と同じようにCr, Cb をそれぞれ 1/4 にすれば、輝度の 1/2 サイズの領域が残る。
ここにアルファ情報を入れればいいのではないかと思った。
こうすると アルファチャンネルの解像度が横 1/2 になってしまうけど、それぐらいなら大丈夫なんじゃないかなぁと考えている。

今度実験してみよう。

投稿者 Takenori : 16:15 | トラックバック

2008年05月02日

Motion JPEG でアルファ付きムービーを

Theora の YUV 4:2:2 に入れることを考えていたけど、やはり Motion JPEG にしようと作り始めた。
なぜ Motion JPEG かと言うと 再生時の負荷を下げて複数同時再生を行えるようにするため。
圧縮率よりも 複数再生して演出の幅を広げる方を優先するのが良いだろうと判断した。

基本的には IJG JPEG library 高速化版 を参考にするが、IJG JPEG library との互換性はなくして、より高速化できそうだと思う部分は高速化したいと思っている。
また、アルファ付きなので、色変換部分は新たに作る必要がある ( 後で混ぜてもいいけど遅くなりそうだから一気にやってしまう予定 )。

色変換やDCTなど部分的に作り始めたが、まずはアルファを不可逆にして問題なさそうか、どれぐらい削っても大丈夫そうか実験することにした。
後、量子化テーブルのチューニングは必要だろうから、それが出来るものもはじめに作っておく。

ファイルフォーマットは独自にする予定。
まあ、他のコンテナに入れてもいいけど、面倒だからそういうのは作らなさそう。

投稿者 Takenori : 19:11 | トラックバック

2008年05月03日

アップサンプリング、ダウンサンプリング

輝度に対して、色差は1/4サイズにする形式が一般的だけど、そこはどのようにするかと言う事で、IJG JPEG library のソースを見てみた。
ダウンサンプリング は予想通り単純に4画素平均四捨五入付きだった ( 自分の書いたものでは四捨五入していなかったので、やった方が良さそう。たいして差はでないと思うけど、圧縮は多少遅くてもいいので ) 。
より広範囲の画素を参照してぼやけさせる方法も用意されているようす。

アップサンプリングで補間を有効にすると以下のような加重平均が用いられるようだ。
1 3 | 3 1
1 9 | 9 3
---+---
3 9 | 9 3
1 3 | 3 1
これはトライアングルフィルタで、速度と見た目のクオリティのバランスが良いらしい。
( This is a good compromise between speed and visual quality. )

色差は別にアップサンプリング時に補間しなくてもいいんじゃないかなぁと思うんだけど、かなり違いが出るものなのだろうか?
まあ、試してみる予定なので、それで結果はわかるが。
アルファも1/4サイズにしても大丈夫じゃないかなと思っているので、1/4サイズで試す予定。
ただ、アルファの場合は補間を行わないとガタガタになってしまう可能性が高いので、アルファは補間を使おうと考えている。
で、考えていたデコード方法に問題があることに気付いた。

デコードは、ハフマン or ランレングス復号化 → 逆量子化 → IDCT → 色変換 という順で行われ、JPEG の場合 8x8 ブロック単位で処理される。
色差が 1/4 サイズということを考えると、色変換時は 16x16 ブロック単位で行うことになる。
ハフマン or ランレングス復号化を全データまとめて行い、次に 逆量子化 と IDCT、最後に画像全体を色変換するという方法もあるが、キャッシュの効率を考えると 16x16 ブロック単位でこの一連の復号化処理を行ったほうが効率が良いのではないかと思って、そのような処理にしようと考えていた。
しかし、補間処理をするとなると隣接するブロックの端の色情報が必要になる。
その理由からも色差は補間をしない方向で考えていたんだけど、アルファを補間するとなるとそうは行かない。
さてどうするかと考えてすぐに気付いた。
アルファは独立しているので、別でまとめて処理してしまっても問題ない。
ただ、テンポラリに一度書き出すのはもったいないので、最終的に出力するバッファに書き出すことにする。
で、前のエントリーに書いた「色変換部分は新たに作る必要がある」と言うのは、別にそうじゃなくなった。
ま、色差が補間ないと酷かったり、アルファの1/4がまずそうなら、またいろいろ考え直さないといけないんだけど。

投稿者 Takenori : 02:40 | トラックバック

アルファチャンネルの圧縮方法

アルファ付き動画の想定している用途は、立ち絵とエフェクトの2つ。
立ち絵は、立ち絵に限定せず、背景の上にのっかている抜き情報を持った画像全般と考えているが、とりあえずここでは立ち絵と書いておく。

簡単に確認できる方法でアルファチャンネルのみをいくつかの方法で圧縮してみた。
確認方法は、立ち絵のアルファチャンネルを抜き出し、グレースケール画像にして Portable Bit Map 形式で保存し、そのファイルを圧縮すると言うもの ( BMP にするとパレットになるので単純なグレースケール画像とはならない )。
JPEG および PNG、BMP(RLE)、GIF については、グレースケール画像をそのまま変換した。
JPEG は PhotoShop で 画質 8 にして保存している。
1/4 JPEGは、画像のサイズを 1/4 にして JPEG 保存したもの。

元の Portable Bit Map ファイルのサイズ 689 x 630 ( 434,085 Byte ) 。
ZIP圧縮 17,505 Byte ( 4% )
GIF 19,619 Byte ( 4.5% )
PNG 24,542 Byte ( 5.6% )
1/4 JPEG 26,001 Byte ( 6% )
BMP(RLE) 28,128 Byte ( 6.5% )
JPEG 35,073 Byte ( 8% )
LZSS圧縮 64,952 Byte ( 15% )

立ち絵 の場合、エッジ付近に中間階調があるのみで、他は 0 か 255 なのでスライド辞書やランレングスだけでも圧縮率が高い。
Flash のアルファ付き JPEG は、JPEG データ + アルファの ZLib 圧縮のようなので、立ち絵のような画像の場合最も圧縮率が高くなる組み合わせが使われていることになる。
まあ、当然か。
ちなみに色のほうを 画質 8 で JPEG 圧縮したものとアルファを ZIP 圧縮したもののサイズを合計すると 84,287 Byte となった。
これの PNG ファイルは 263,998 Byte なので、サイズは PNG の 32% 程度になる。
画質 8 の JPEG だと見た目はたいして変わらないので、サイズが 1/3 になるのはなかなか魅力的。


一方、アルファの中間階調が多く使われているエフェクト画像も同様に圧縮してみた。

元の Portable Bit Map ファイルのサイズ 421 x 410 ( 172,625 Byte ) 。
1/4 JPEG 17,019 Byte ( 9.9% )
JPEG 23,409 Byte ( 14.6% )
PNG 58,735 Byte ( 34% )
ZIP圧縮 67,079 Byte ( 38.9% )
GIF 77,631 Byte ( 45% )
LZSS圧縮 82,748 Byte ( 47.9% )
BMP(RLE) 94,136 Byte ( 54.5% )

やはり、中間階調が多い場合は、JPEG が強い。
これの色+アルファの PNG ファイルサイズは 162,768 Byte で、JPEG + アルファ 1/4 JPEG の合計は 40,428 Byte 。
つまり、25%。
1/4 ぐらいのサイズになる。
アルファが 1/4 サイズになっているけど、エフェクト用の画像ならそうわからないのではないか思う。

以上の結果からするとアルファは ZLib で圧縮するか JPEG で圧縮するかのどちらかを選択するのが良さそう。
立ち絵のような画像かエフェクトのような画像かで ZLib or JPEG を指定してやるか、自動的に圧縮率の高いものを選択するようにするのが良いかな。
自動の場合、JPEG + JPEG になるとエッジ付近がぼけると思うので、見た目が悪くなるかもしれない。
後、Zip の展開速度次第では別の方法を検討した方がいいかもしれない。
その時は、ランレングスでもかなり圧縮できているので、ランレングスでもいいかな。

投稿者 Takenori : 21:19 | トラックバック

2008年05月04日

JPEG の中にアルファチャンネルデータを詰め込む

JPEG には、アプリケーションデータセグメントと言うものがあり、ここにいろいろとデータを入れられ、その種類を示すマーカーが 0 ~ 15 番までの16種類使える。
ただ、JFIF が 0 番を、Exif が 1 番を、Adobe の何かが 14 番を使うようだ。
同じマーカー番号を使っても、その内容で見分けられるようにしてあれば、問題はないが分けていたほうが扱いやすいのは確か ( 実際、Exif は かぶってもわかるように"Exif\0\0"と言う識別子が入っている )。
と言うことで、2 番 ~ 13 番か 15 番を使うのが良さそう。
特に理由はないけど、7番 ( マーカーコード 0xFFE7 ) を使うことにした ( 最初は 5 番にしようかと思ったけど、マーカーコードの 0xE5 はなんとなく避けたいから止めた ) 。

マーカー内のデータサイズは 2 バイトなので、格納するデータサイズが 16 ビット であらわせる数を超える場合、複数に分けないといけない。
そのため、マーカーのデータサイズ以外に、実際のデータサイズを入れておいたほうが便利だろうと言うことで、データサイズは入れる。
後、"Alpha\0"などの識別子も入れておいたほうが良さそう。
圧縮データの格納方式も入れる必要がある。
展開後のサイズは、画像の幅 x 高さでわかるので入れない。

最初のセグメント
marker ( 0xFFE7 ) ( 2 byte )
segment size ( 2 byte )
"Alpha\0" ( 6 byte )
type ( 4 byte )
compressed data size ( 4 byte )
compressed data ……

16 ビットであらわせるより大きい場合の、2個目以降のセグメント
marker ( 0xFFE7 ) ( 2 byte )
segment size ( 2 byte )
"Alpha\0" ( 6 byte )
compressed data ……

こんな感じかな。
もし、拡張したくなったら type の種類を変えて、それ以降の意味が変わるようにするか。

type はとりあえず、以下の4つあればいいかな。
"RAW " : 無圧縮
"ZLIB" : Zlib圧縮
"JPEG" : JPEG圧縮
"JPG4" : 画像のサイズを 1/4 にして、JPEG圧縮したもの

以上のような形でアルファ付きJPEGを保存することにした。
このマーカーを扱えない ( 現状全ての ) アプリで見ると、アルファのない普通の JPEG ファイルとして見れるはず。
マーカーを無視しないアプリがあったら開けないだろうけど。

投稿者 Takenori : 22:47 | コメント (1) | トラックバック

2008年05月05日

アルファチャンネル付きJPEG

JPEG の中にアルファチャンネルデータを詰め込む で書いた形で、アルファチャンネルのデータを ZLIB で圧縮して格納し、静止画として書き出せるようになった。
一般のツールで見ると、予想通り普通のJPEGとして見える。
ただ、Add Alpha 形式にしているので、透明部分は黒になっている。

圧縮クオリティを 80% や 90% にすると、モスキートノイズが見えるようだ。
画像にもよると思うけど、95% まで上げると元の画像とほとんど見分けが付かない。
それで、ファイルサイズはだいたい 1/3 になる。
アルファチャンネルの圧縮方法 で書いた立ち絵の圧縮率と同じ。
劣化は絶対許さないと言われると難しいけど、そうでないのならかなり使えるんじゃないかと思う。
100MB あった画像データが 30MB になったりすると、ダウンロードで配布するのならかなり有用だろう。
アルファ付きムービーの副産物だけど、アルファ付きJPEGを使えるようにするのは結構いい気がする。

ただ、なぜか少し抵抗があるのは確か。
動画や音声については非可逆が当たり前だけど、静止画となると可逆にしたいと言う気持ちがある。
見ても違いがわからないんだったらいいんじゃないのかと思う気持ちもあるんだけど……
動画や音は元々サイズが大きくて、非可逆で劇的にサイズが小さくなるけど、静止画の場合はそんなに容量食ってるわけじゃないから可逆でもと思うせいか。

投稿者 Takenori : 20:49 | トラックバック

2008年05月06日

JPEG による画像の劣化

JPEG は非可逆なので画像は劣化するが、クオリティを上げればほとんど変わらないものが出来る。
モスキートノイズは 95% 程度にすればなくなった。
これなら元と変わらないかと思いきや、鮮やかさが落ちている。
特に赤が顕著。
なぜだろうと思って検索してみると、ダウンサンプリングが原因だと書いているサイトが幾つか見つかった。
ダウンサンプリング……と考えてすぐに思い当たるところが。

JPEG は、だいたい YUV 420 になっているはず。
つまり、輝度に対して、色差は 1/4 のサイズになっている。
これは人の目が輝度に比べて色差の変化には気付きにくいと言うのが理由らしい。
ただ、サイズを 1/4 にしたからと言って鮮やかさが落ちるわけではないはずだ。
問題は補間の方にあると思われる。
つまり、1/4 のサイズにする時のダウンサンプリング時と、元のサイズに戻す時のアップサンプリング時の補間によって色差の輝度が落ちているわけだ。

1/4 のサイズにする時、普通に考えると 4 ピクセルの値の平均値を取るだろう。
実際、IJG JPEG もそのようにするようだ ( より広範囲のピクセル値を参照する方法もある様子 ) 。
平均値を取ると言う事は、その 4 ピクセル内の最大輝度値よりは低い値に、最小輝度値よりは高い値になる。
つまり、これによって鮮やかさが失われると考えられる。

元のサイズに戻す時、単純に 1 ピクセルの値を 4 ピクセルにすれば、値は変わらないが アップサンプリング、ダウンサンプリング に書いたように、トライアングルフィルタが適用されると、ここでも最大輝度値よりも低い値になる。
トライアングルフィルタによって、色差の変化がなだらかになり、ぱっと見た時の綺麗さは向上するのかもしれないが、鮮やかさが失われるのは間違いないと思われる。

と言う事は、最大輝度値が低下しないようにダウンサンプリングとアップサンプリングを行えば、鮮やかさが失われないのではないだろうか?
つまり、4 ピクセル平均ではなく、4 ピクセル中最大輝度のものを使用し、アップサンプリングの補間を行わないようにすれば、いいのではないかと思う。
ただ、4 ピクセルの内、最大で 3 ピクセルは元のピクセル値よりも値が大きくなるため、鮮やかさが増してしまう懸念はある。
このような方法を取って符号化・復号化した場合、そうでない場合と比べてどの程度印象が変わるのかはやってみないとわからないが、くすんだようになってしまうことは避けられると思う。
また、別の方法としてダウンサンプリングとアップサンプリングが原因と言うことであれば、YUV 420 ではなく、YUV 444 を使えば補間は行わないので解決するはずだ。
まあ、その場合は圧縮率が少し犠牲になるわけだけど。

ま、このような実験をやるとしたら、アルファ付きムービーが動くようになった後だろうけど。

2008/05/08 追記
YUV 422 を YUV 420 に修正

投稿者 Takenori : 16:07 | トラックバック

2008年05月07日

アルファチャンネル付きJPEGがデコード出来るように

画質に関してはJPEGと同じ。
アルファは、可逆圧縮にしているので、元のままのものが再現される。
劣化不可でなければ、使えるレベルだと思う。

JPEGのデコードには SIMD 拡張版 IJG JPEG library を用いている。
450 x 400 のアルファ付きの立ち絵 5 枚をデコードする時間を計ったところ、以下のようになった。

Core2Duo E6750 で 14 ~ 15 ミリ秒
Athlon XP 1600+ で、60 ~ 70 ミリ秒

これを 動画の 30 フレームとして考えるのなら 6倍して
Core2Duo E6750 90 ミリ秒
Athlon XP 1600+ 390 ミリ秒
となる。

この他にアルファの合成負荷も加算される。
現状のままでは Athlon XP 1600+ では、3つ同時は厳しい。
2つはいけると思う。
動画はアルファ付きJPEG用に特化して最適化するかなぁ……

投稿者 Takenori : 21:40 | トラックバック

2008年05月08日

JPEG の赤のブロックノイズとぼやけ

アップサンプリング時の補間を OFF にした画像を注意深く見ていて気付いたが、ほぼ純色の赤に黒で縁取りがされていて、その黒の線が斜めになっている時、補間を OFF にするとブロックノイズのようなものが見える。
色差が輝度 1x1 に対して 2x2 であるので、そのサイズでブロックのようなものが見えている。
そのせいで斜めの黒線が途切れているように見えてしまう。
ただ、鮮やかさはそれほど落ちていない。
ダウンサンプリングの影響か少しは暗くなっているように見えるが。

そして、アップサンプリング時の補間を ON にするとブロックのようなものは消えるというか、その辺りがぼやける。
JPEG でぼやけていると言う印象は少なかったが、この部分に関しては良くわかる。
赤で塗った上に黒で何本か線を引いて JPEG で保存すれば、かなり酷いことが良くわかる。
ただ、PhotoShop で保存したところ 8 だと酷いが、9 にするとかなり軽減される。
もちろんそれ以上にするともっと軽減される。
ボケていると言う印象が消えるので、もしかしたら品質をかなり高く設定すると YUV 444 で保存されるのかもしれない。

補間を止めて、画像を鮮やかにと言うのは、画像の種類によっては NG だな。
やっぱり、YUV 444 が綺麗か。
圧縮率は犠牲になるが。
ただ、YUV 420 と YUV 444 では面積比 1 : 2 だが、ファイルサイズは倍違うと言うことにはならず、もう少し差は小さいようだ。
アルファチャンネルの圧縮方法で、アルファチャンネルを 1/4 サイズにしたのと等倍のでファイルサイズを見ると 4 倍違うと言うことにはなっていない。

投稿者 Takenori : 19:28 | トラックバック

2008年05月10日

静止画アルファ付きJPEGプラグインが動くように

静止画アルファ付きJPEGプラグインが動くようになり、アルファが入ったJPEGを読み込んで綺麗に抜けることが確認できた。
なお、このプラグインではアルファのない通常のJPEGファイルも読み込めるようになっている。
そこで、本体と読込み速度を比較してみた。

比較環境のマシンの CPU は Core2Duo E6750 となっている。
SIMD 拡張版 IJG JPEG library を用いているので、現状本体の読込み速度と最も差が大きくなる環境となる。
もう少し古い CPU では、差は縮まるはず。

まず、通常の JPEG ファイルの読込み。
普通に読み込むとコンソールに読込み時間が何ミリ秒か出るのでそれで比較した。
また、ディスクキャッシュなどの影響も考え、複数回起動し読込み速度が落ち着いた値を元に比較している。
それで、読込み時間は 2倍より少し速い程度になった。

アルファ付きの方は TLG5 と比較した。
すると、ほぼ同等か少し速いぐらいになった。

速い。
これは、SIMD 拡張版 IJG JPEG library によるものと、デコード後の RGB24 から RGB32 への変換を省いていることによる。
たぶん、SIMD 拡張版 IJG JPEG library の影響がかなり大きいと思う。
デコード後の変換は、吉里吉里2本体では一度テンポラリに展開後、変換しつつレイヤーのバッファへ書き込んでいる。
対して、このプラグインでは IJG JPEG library のデバッグオプションによって、最初から RGB32 で出力されるようにし、レイヤーのバッファへ直接デコード結果を書き込むようにしている。
アルファを混ぜるコードは、現状普通に書いているが、ここを SIMD 化すれば更なる高速化が期待できる。

他に画質に関するオプションも本体のものとは異なっている。
本体では以下の3つの内いずれかかを指定可能で、デフォルトでは normal となっている。
1. low はIDCTに固定小数点(整数)ANN、色差拡大補間なし。
2. normal はIDCTに固定小数点(整数)ANN、色差拡大補間あり。
3. high はIDCTに浮動小数点AAN、色差拡大補間あり。

このプラグインでは、IDCTに固定小数点(整数)LLM、色差拡大補間あり としている。
IDCTの固定小数点ANNアルゴリズムは、高速だけど比較的誤差の大きいアルゴリズムだと言われている。
普段吉里吉里2を使用していて特に大きく劣化しているとは感じないが、時々掲示板などで特定の画像でJPEGが汚いと指摘されていることがある。
その場合オプションで high を指定して回避すると良いと言う話だ。
また、過去に ANN は誤差が大きいから使えないというのを見たことがある ( 確か MPEG 関係で調べていた時 ) 。
まあ、普段の感覚からすると大体は問題ないけど、時々汚いと言う感じだろうか。
( JPEG が苦手なものだったり、圧縮率が高い場合に影響が大きい可能性もある )

それと、 IJG JPEG library では、IDCTに固定小数点(整数)LLM、色差拡大補間あり がデフォルトで推奨設定となっている。
そのため、このライブラリを用いて作られているアプリの多くはこの設定になっていると思われる。
このため他で表示したら綺麗なのに吉里吉里2で見たら汚いと言うのは、このプラグインでは回避できるのではないかと思われる。

なお、AAN と LLM の速度差は SIMD 化されているものではそれほどない。
そもそも、SIMD 化されたものでは、IDCT 自体の処理時間が全体的に見て少ないようだ。
これは IDCT の処理時間はほぼ純粋に計算にかかる時間で、最近の CPU では速度が出やすいものだからだと思われる。
対して、ハフマンデコードやビット単位での読込みは遅いと思われる。

以上のような理由から、IDCTに固定小数点(整数)LLM、色差拡大補間あり を使うのが良いと思われ、今回はそうした。

ただ、最近のいくつかのエントリーで書いたように色差拡大補間ありが常に良いかというと、これは検討の余地がある。
色差拡大補間ありでは、鮮やかさが落ちる場合があるからだ。
ただし、なしの場合にはブロックノイズのようなものが見えることもあるので、全体的に綺麗に見えるということであれば、色差拡大補間ありの方が良いと思う。
まあ、鮮やかさが落ちる前の画像を見てなかったらわからないと言えばわからないし。

今回、loadAlphaJpegImage という名前で Layer クラスにメソッドを追加したのだが、この場合ひとつ難点がある。
それは、本体とは別機構で読み込むため、本体のキャッシュ機能が効かないということ。
このためスキップ処理時に少し速度的にペナルティがある。
同じ画像 2 枚を交互に読んでいたりすると速度差はだいぶ大きいと考えられる。
スキップ処理を最速で早送りではなく、本当にジャンプするようなシステムにしているのなら問題はない。
とは言っても、私はそんなことしていないので、キャッシュが効くように出来ればそれに越したことはない。
で、気付いたが、本体のLayer.loadImages をプラグインでオーバーライドしてしまえばいいのではないかと思った。
て、待てよ…… キャッシュのチェックなどは本体の Layer.loadImages 内でやってる気がするから意味なしか?
マスクや領域画像のことを考えて、せっかくだから吉里吉里3のより高速化された TLG の読み込みとかもついでに移植するかーと思ったけど、キャッシュが意味なくなるのはなぁ……

すぐに思いつく方法は以下の3つ
1. 本体にこの機能を入れてしまう
2. 本体に画像読込み専用のプラグイン機構を入れる
3. キャッシュは諦める

3 が楽。1 は bcc でコンパイル出来るようにしないといけないので面倒。
後、遅くなる気がする。
出来るのなら 2 がいいかなぁ。
krmovie.dll のように krimage.dll とかあってそれで拡張とか……
ただまあ、組み込み方は相談しないことには始まらないか。
一度、キャッシュ周りの処理を見てみよう。

投稿者 Takenori : 15:26 | トラックバック

2008年05月11日

本体側の画像キャッシュとの連携を考える

前回のエントリーで以下の3つの方法を書いたが、キャッシュへ入れたり出したりするメソッドを追加するのが良いのではないかと思った。
1. 本体にこの機能を入れてしまう
2. 本体に画像読込み専用のプラグイン機構を入れる
3. キャッシュは諦める

つまり、Layer クラスに以下のような2つのメソッドを追加するのが手軽ではないかと。
bool loadImageFromCache( name )
bool storeImageToCache( name, metadata )

キャッシュにあったらレイヤーに読み込むメソッドと今レイヤーに入っている画像を指定した名前でキャッシュに入れるメソッド。

1.番を避けたい理由は前回書いたとおり、2番はわざわざ専用の機構を入れると言うのが少しためらわれるので避けたいと思った。
で、やりたいことはキャッシュから取り出したり、入れたりすることだから、その機能を外に出せばいいのではないかと。
で、プラグイン用の専用メソッドにするとヘッダーが変わってしまうので、Layer クラスのメソッドとして追加すれば、ヘッダーに変化がなく手軽。
また、本体のソース読んだら比較的楽に分離できそうだったというのもある。

追記:
Deeさんに組み込み方法などを相談した結果、キャッシュは使わないということに。
効率的に動かそうとすれば、スクリプトはキャッシュに頼らないような書き方になるから、自分が書くならなくてもいいよなぁと思ってたのもある。
後、アルファ付きJPEGはおまけで、アルファ付き動画が本命だし。
でも、アルファ付き動画どうするかなぁ……
出来るだけ少ない手間で高速化したいけど。
IJG JPEG Library に手を入れて、色空間変換時にアルファを混ぜ込むのが現実的なラインかな。

投稿者 Takenori : 23:53 | トラックバック

2008年05月13日

アルファ付き動画の高速化を考える

現在のまま実装すると 1.5 GHz 程度の CPU では、2個同時ぐらいが限界になりそうなので、もう少し高速化する方法を考える。
できれば、3個同時に再生したい。

SIMD 拡張版 IJG JPEG library では、数ラインずつデータを取得していくが、中を見るとそのラインに必要な分だけデコードしているっぽい。
必要な分だけやっているとしたら、キャッシュの効率は良さそうだ。
高速化するのなら他の部分や特性を考えないと難しそう。

IDCT のアセンブリソースを C に書き直していて気付いたけど、どうも一度 char サイズの YCrCb カラーに変換してから、RGB カラーに変換している ( IDCT の係数行列は short 型 )。
YCrCb カラーから RGB カラーに変換する時、また一度 short に戻している。
これは少し無駄に見える。
元の IJG JPEG library の構造からくる制約だろうか?
それか、一度 unsigned char にすることで飽和処理を用いて 255~0 の範囲に丸める必要があるのかもしれない。
この辺りは実験した方が良さそう。

また、YCrCb カラーから RGB カラーに変換する部分でも、精度を重視したような実装になっている。
四捨五入したり、2倍してから計算して半分に戻すなど。
少しの誤差は気にしない方針にしてしまえば、ここも少し速く出来るかもしれない。

これ以上はアルファに注目して、高速化するのが良さそう。
16x16 のブロックで全て透明のものは保持せず、ブロックが完全に透明かそれ以外かでフラグを保存しておけば、完全透明の時は単純にゼロフィルすればいいだけになる。
また、色空間変換時にアルファ値を参照して 0 ならその部分の変換はスキップしてゼロフィルする。
これが出来るだけでも、かなり速くなるのではないかと思う。
画像にもよると思うが半分以上は透明なのではないかと思う。
そうなると、その半分はほとんどの処理を飛ばせるのでかなり速くなるはず。
ただ、完全に透明な領域が少ないと効果は少ない。
そこは動画なのでフレームによって差があると思うので、先読み分である程度処理負荷を平均化出来るのではないかと考えている。

でも、これやるとなると IJG JPEG library にかなり手を入れるか一から作る必要があるのが大変。
と言いつつ、SIMD 拡張版 IJG JPEG library のアセンブリソースを少しずつ C に直しているんだけど。

投稿者 Takenori : 23:13 | トラックバック

2008年05月16日

α動画圧縮の手前まで

アルファ付き動画は、SIMD 拡張版 IJG JPEG library からいくつかの機能を切り出して、ある程度はそれを使い、残りは自分で組んで作ろうとしている。
「ブロック分割→色変換→DCT→量子化→①→逆量子化→IDCT→色変換→ブロック合成」の①を除く処理の流れは出来た。
①の部分は、ハフマン符号化とゼロランレングスによって圧縮し、伸張する処理。
①の部分は可逆なので、それ以外の部分によって劣化具合がわかる。
つまり、今作っている部分でどの程度劣化するかはわかる。
アルファもこの流れで符号化/復号化してみたが、気になるほどエッジがぼけるということはないようだ。
アルファは 1/4 サイズにするのは止めて等倍にした。
以前書いたように、立ち絵のようなほとんど不透明/透明で構成されている画像の場合は、ZLib で圧縮した方が圧縮率が良くなるので、そちらで圧縮する。

投稿者 Takenori : 01:54 | トラックバック

α動画の高速化を阻む特許

α動画関係の特許を調べておくかと思って調べた。
すぐに見つかったのは次の2つ。

1. αチャンネル映像のための符号化ブロックパターン生成装置及び方法とそれを利用したαチャンネル映像符号化/復号化装置及び方法

2. グレイアルファチャンネルを含んだ映像の符号化/復号化装置および方法

両方サムスンがとっているよう。
ぱっと見意味不明だが、2の方が今回考えていた高速化に引っかかる。
まず、1 の方は、αチャンネルと言っているが、1ビットのマスクを想定して書かれていて、ブロックが全て透明、全て不透明、混在の3つに分けて処理する方法が書かれている。
そして、JPEG などでは 16x16 のブロック を 8x8 に分けて圧縮するが、このブロックの透明/不透明の3パーンの情報を 8x8 に付与し、16x16 の方は 8x8 の透明情報の論理和などを取って判断することが書かれている。
つまり、8x8 単位で分類されているが、16x16 単位の段階でまず判断でき、そこで混在している場合は、8x8 単位で判断し処理できる。
なるほど、これはよく考えられている。
8x8 単位にすることは考えたが、そこで分けるよりも 16x16 単位にして処理してしまった方が速いだろうと思ってそうはしなかった。
ただ、8x8 単位で考えていた場合は、この特許に引っかかっていたかもしれない。

問題は2番。
ブロックのαチャンネル値が全て0の時、符号化処理を省く方法について書かれている。
また、αチャンネル値が全て0かどうかで処理を分けること自体が封じられているようだ。
なんてことだ。
こんな簡単に思いつく方法を特許にしないでくれよ。
1番はなるほどと思ったけど、2番はちょっと考えてすぐに思いついた方法と同じ。
特許がとられた年を考えると、既にこの技術を使った製品などあってもおかしくなさそうだけど、特許の取り消しとか個人で何とかなるのか良くわからないし、時間もかかるだろうからこの方法は諦めるしかないか。
何度か読んで回避方法も考えてみたが、請求項の範囲が広すぎてどうにもならないかと諦めることに。

仕方がない。
深い部分に踏み込むのは止めて、JPEG に α情報を付与したものを出来るだけ高速にデコードするように頑張るか。
1.5GHz で 3つ同時は辛いかもしれないなぁ。

2008/05/17 追記
これはまだ特許になっていないそうな。
審査請求されていないとか。
特許の審査未請求って?
特許のシステムをイマイチ理解していなかった。
もう少し勉強した方が良さそうだ。
公開されていたから、特許かと思ってた。

投稿者 Takenori : 19:52 | コメント (2) | トラックバック

2008年05月17日

α動画をどう実装するか

α動画の高速化を阻む特許で書いた特許は、審査請求されていないそうで、まだ特許になっていないんだとか。
イマイチ特許のシステムを理解していなかった。
まあ、それはいいとして、現状まだ特許になるかどうなるかわからないようだ。
請求項の範囲が広すぎるし、誰でも思い付きそうな方法だから特許にはならないんじゃないかなぁと思うんだけど、どうしよう。

ブロックが完全に透明かどうかで処理を分けるか否かに関わらず必要な処理は高速化しないといけないと思うので、まずは一通りの処理を書いて、速度を測ってみるか。
後、ブロックが完全に透明かどうかで処理を分けるのも、一度書いてみよう。
構造的に、ifdef で使うかどうか切り替えられると思うので、そのようにしておけばどっちになっても大丈夫か。
でも、特許として認められるよりも早く製品を出した場合はどうなるんだろう?
これはもっと特許について勉強しないといけないな。

それよりも何よりも、どの程度高速化するのかしないのかについて興味がある。
たいして速くならないのならなくしてしまった方が安心と言えば安心。

投稿者 Takenori : 02:46 | トラックバック

2008年05月22日

JPEGの圧縮方法とハフマン復号化の高速化

JPEG は、主に DCT、ハフマン符号、ゼロランレングスで出来ている。
JPEG でハフマン符号化を行うのは値そのものではなく、その値を表すのに必要なビット数。
ビット数のハフマン符号化行った後、その最低限必要なビット数で表した値を記録する。
値を直接符号化すると、パターンが多く符号が長くなってしまうが、ビット数であれば 10 までの値を符号化すればいいので短くて済む。

ハフマン符号化 ( エンコード ) は、ある値を別の値に置き換える作業。
これでなぜ圧縮できるかというと、出現頻度の高い値に短い符号を、低い値に長い符号を割り当てるため。
つまり、「あ」はいっぱいあるから 01 にしよう、「ん」はほとんどないから 00000000001 にしようという具合に値を置き換えていく。
こうすると、データの偏りが大きければ大きいほど圧縮される。

ランレングスは、同じ値が連続でつながっていた場合、その値が何個つながっているかを保存することで圧縮する方法。
つまり、「あああああああい」となっていたら、「あ 7 い 1 」というように保存する。
こうすれば元のデータより圧縮される。
ただ、連続していない場合は、データが増える。
JPEG では、ゼロの値についてのみランレングスで圧縮を行う。
なぜゼロかというと、離散コサイン変換 ( DCT ) を行い、量子化を行ったデータにゼロが多く含まれるから。

離散コサイン変換とは、周波数領域に変換する作業。
画像を離散コサイン変換し、高周波領域を削って元に戻しても、劣化があまりわからないことを利用して、圧縮する。
削るのは割り算で行う。
劣化がわかり辛い高周波領域ほど大きな値で割る。
割り算は整数で演算するので、小数点以下はなくなる。
圧縮率を大きくするということは、大きな値で割ることなので、ゼロが多くなる。
JPEG では、この割る作業を量子化と言っている。
元に戻す時は掛け算を行う。
当然小数点以下は失われているので劣化していて、元の値には戻らない。

ハフマン復号化 ( デコード ) は、ハフマン符号化で変更した値を元に戻す作業。
対応表に従って元に戻せばいいんだけど、符号値は可変長なので、まじめにやると1ビット読み込んで比較してなかったらもう1ビット読んで…… とちまちま比較していかないとならない。
圧縮率が高い = 短い符号が多いということなので、高圧縮のデータは数回読めば比較は終了する。
でも、そうでない場合は時間がかかる。
一般的な高速化方法はテーブルを使うこと。
まず最大長のハフマン符号ビット数分読み込んで、その値をインデックスとしてテーブルを引く。
テーブルには値とその符号のビット数を記録しておけば、すぐにわかる。
読み込みすぎた分は元に戻す。( 実際には、最初は参照し、ビット数がわかったらそのビット数分捨てるような作り。参照では読込み位置のポインタは進めない )
ただ、最大長のハフマン符号が大きい時はテーブルが大きくなってしまう。
JPEG では、16 ビットまであるので 65536 個のテーブルになってしまう。
64KB なのでメモリ容量的にはそれほど大きくはないが、テーブルが大きいと遅くなってしまう。
SIMD 拡張版 IJG JPEG library では、ある長さでテーブルを切っている。
つまり、先頭数ビット分のテーブルのみ持ち、持っている長さ分読み込んでテーブルを引く。
テーブルを引くと、その長さ以下の符号なのか、その長さでは足りない符号かわかる。
もし、足りなければそれ以降は1ビットずつ読み込んで比較していく。
ただ、長い符号はあまり出てこないはずなので、結果的にはほとんどテーブルを引くだけで済ませられると思われる。
ハフマン復号化はこの方法を使おうと思う。
もっと高速化できる方法があればいいんだけど……

投稿者 Takenori : 12:27 | トラックバック

2008年06月06日

α動画圧縮の一連の流れ

「ブロック分割→色変換→DCT→量子化→ハフマン符号化 ( ゼロランレングス符号化 ) →ハフマン復号化 ( ゼロランレングス符号化 ) →逆量子化→IDCT→色変換→ブロック合成」が出来るようになった
この一連の流れが出来れば、後はそれほど難しい作業ではない。
動画のファイルフォーマットは完全独自にする予定で、フォーマットの仕様は考えて簡単にまとめた。
ただ、開発途中で問題に気付いて変更する可能性はある。

と言うことで、エンコーダーの開発に取り掛かろうと思う。
エンコーダーは、連番 PNG か無圧縮 32bit AVI から、専用フォーマットの α付きモーション JPEG にエンコードする形になる予定。
連番 PNG は、別に連番でなくても良くて、指定フォルダ内にある PNG ファイルを名前でソートして、それを動画としてひとつにまとめる形にするつもり。
最初のバージョンでは、ハフマン符号化テーブルは固定にする。
その後に 2パス圧縮をサポートしてテーブルを最適化し、圧縮率を高めるバージョンを作るかもしれない。
出来上がったものの圧縮率や再生負荷を見て、後の改良は考える。

投稿者 Takenori : 23:05 | トラックバック

2008年06月19日

α動画基本動作OK

α動画のエンコードとデコード(+再生)ができるようになった。
640x480 80フレーム(2.6秒)の立ち絵動画をクオリティ95で圧縮すると、約 3.1 MBになった。
立ち絵なので、αチャンネルは ZLib で圧縮されている。
クオリティ80だと 1.8 MBになる。
元の無圧縮 AVI ファイルは 96MB。
ちなみに同じ動画を WMV 95% にすると 844KB。
許せる範囲かな?
ただ、640x480 とは言っても、立ち絵は半分くらいの領域を使っているのみ。
α動画では、完全に透明ではないピクセルを含む最小矩形領域のみ保存しているので、占める矩形領域が小さいとかなり小さい動画を圧縮しているのと同じになる。
また、この矩形サイズはデコード時も関係している。
吉里吉里側のα合成の負荷を下げるため、レイヤーサイズもこの大きさに変更し、レイヤーにデコード画像を描いている。
ただ、矩形領域なので、大の字のようなポーズだったりすると無駄が多い。

で、違う立ち絵の動画3つを Core2Duo E6750 で再生すると、CPU 負荷 10% ~ 15% 程度。
まだ、MMX 化までしかしていないので、SSE2 まで対応するともっと下がると思う。
ただ、SSE2 が使えない CPU では意味がない。
1.5 GHz程度のマシンでどの程度の負荷かはまだ確かめていない。
ほかに出来る MMX のみでの更なる高速化は、完全に透明なブロックの処理を省けるようにすることくらいかなぁ。
ただ、あの特許申請中のものがなぁ……
完全に透明かどうかではなく、ブロックが単一色かどうかは IDCT の前段階でわかる。
AC 係数がすべて0であれば、そのブロックは単一色となる。
単一色であれば、色変換は1ピクセルだけ行い、後はその色で塗りつぶせばいい。
αチャンネルを ZLib で圧縮したバージョンのほうは、ブロックのαチャンネルをすべて調べるのと気にせず合成してしまうのでどちらが速いかは微妙。
この方法で単一色ならブロックを塗りつぶすようにすれば、完全に透明などは関係なく、べたで塗られているところは高速化される。

今回作っているα動画は、krmovie.dll とは独立した機構になっている。
元々は、高速化のためにレイヤーのサイズをフレームによって変更する必要があったので、別のプラグインとして作ることにした。
それ以外にも制御の多くを TJS 側に持っていく形にした方が、いろいろと都合が良いだろうということもある。
このプラグインでは、バックグラウンドで動画を順にデコードしてキューにためていくことしかしない。
キューから取り出して描画を指示するのは TJS スクリプト側の仕事。
つまり、再生速度のコントロールは TJS 側で行うことになっている。
このような仕組みのため、プラグインを変更することなく、スクリプト側でより柔軟な制御が出来るようになるはず。
ただ、現在デコード順は順方向のみなので、この辺りの指定をもっといろいろ出来るようにすると、さらにいろいろな再生が出来るようになると思う。
また、ファイル終端までデコードが終了したら、指定した別ファイルを続けてデコードすることを可能にしたので、複数のムービーを滑らかにつなげるようになった。
従来セグメントループなどの形で実現していたことを、別々のファイルで出来るので、動画制作時の取り扱いなどが楽になるはず。

投稿者 Takenori : 16:13 | トラックバック

2008年06月22日

完全透明ブロックの劣化を回避する

エフェクト用の動画は圧縮率を上げても ( クオリティーを低くしても ) 気付きにくいということで、60% にして試してみたら、うっすらと枠のようなものが見えた。
どうも、完全に透明な部分が劣化して、透明ではなくなってしまっているようだ。
初め αチャンネルを非可逆圧縮にして圧縮率を上げたのが原因と考えて、αチャンネルと色を独立してクオリティーを指定できるようにしようと考えたが、よく考えるとそうではない気がした。
吉里吉里側の演算負荷を下げるために、αチャンネルつき動画は画像を AddAlpha 形式で保持しているが、この形式の場合色に誤差があった場合でも、透明部分が透明でなくなってしまうはず。
という事で、あんまりクオリティーを下げるのはまずいかと思ったが、よく考えれば完全に透明なブロックが劣化しないようにすればいい。
一見、非可逆圧縮で劣化させないようにすることは矛盾しているようだが、完全に透明なブロックのみに限定するのであれば、これは可能だと考えられる。

あるブロックを DCT 変換すると、最初の係数にはそのブロックの平均色が入る。
この係数は特別扱いし、DC (直流) 係数 と呼ぶ。
もし、ブロックが1色のみの場合は、その色を表した係数がこの DC 係数に入る ( と思っているけど、間違ってるかも ) 。
JPEG 圧縮で劣化する主な原因は、以前書いたと思うけど、この DCT 変換後の係数行列を量子化テーブルの値で割るためで、そこで小数点以下の値が失われてしまう。
逆に言えば、小数点以下の値が出ないような値で割れば、劣化しないことになる。
つまり、ブロックが完全に透明な色のみだった場合、その時の DC 係数値の約数を量子化テーブルの内の DC 係数の箇所に持ってくれば、完全に透明な色のみのブロックは劣化することなく復元できることになる。
という事で、量子化テーブルの最初の値は、完全に透明なブロックを DCT 変換した時の各要素の値の約数になるようにしようと思う。
クオリティーを指定して計算された量子化テーブルの最初の値を、その値より小さな約数値になるようにすることになるので、少し圧縮率は低下すると思うが、まあうっすらと枠が見えてしまうのは避けたいので仕方ない。

実際に試してみると、800x600 149フレーム のパーティクルの動画で 2.86MB が 2.96MB になったが、枠のようなものは見えなくなった。
この程度の圧縮率の低下であればいいかな。
まあ、エンコーダのオプションとして、有効/無効選べるようにしてもいいし。

2008/06/22 修正。
DC 係数を AC と間違って書いていました。

投稿者 Takenori : 01:53 | トラックバック

2008年07月22日

α動画 SSE2対応など

色変換と IDCT の SSE2 版を作った。
Core2Duo で MMX に比べると 18% 程度速くなった。

IDCT は LLM を使っているけど、 ANN も試してみた。
2%ぐらい速くなるが、誤差が大きいせいか汚い。
補間があればもう少しましだろうか?
ANN は使わないことにする。

ブロックが単色かどうかの判定は、AC 係数すべてを調べる必要があると思っていたが、よく考えればハフマン復号化の時にわかることに気付いた。
JPEG では、係数行列の残りが全て 0 の時終端記号が入る。
つまり、デコーダ内部の AC 係数の書き込み終端位置を覚えておけば、AC係数の要素数がわかる。
AC 係数がなくて DC 係数のみの場合、DC 係数値を 8 で割った値が色になる。
これを利用して輝度と色差の両方が DC 係数のみの場合、1度だけ色変換の計算を行って、後は全てその色をコピーすればよい。
1ブロックは 64 要素なので、単色のブロックがある場合だいぶ高速化されるはず。
単色のブロックは完全に透明なブロック以外にもところどころある様子。
単色判定を入れて立ち絵の動画で試したところ 2% 程度速くなった。
期待したほど速くならなかったが、IDCT や 色変換部分の処理が大幅に削減されるので、SSE2 が使えなくて MMX になるような環境であれば、もう少し効果があるはず。

他に考えられる高速化には、ジグザグ行列を転置済みのものにしておくというのがある。
転置済みにしておけば、IDCT 時に1回転置が減る。
SSE2 版で試したところほとんど速度差がない。
MMX 版であればもう少し効果があるかもしれないが、かなり書き換える必要があるので、転置はいいか。

非 SIMD 版も計ろうと動かしたらバグってた。
いつの間に。
まず使うことはないだろうけど、一応修正。
MMX と SSE では、IDCT 内でレベルシフトしていたけど、非 SIMD 版はそれをしていなかったのが原因で色がおかしくなってた様子。
速度的には、SSE2 版より2倍遅い。
非 SIMD 版はどちらかというと動作確認用みたいなものなので、それほど高速化などは考慮していない。

デコーダー側はこれで一通り完成かな。
後は高速化の残骸などを消してソースコードを整理してテスト。


エンコーダーはGUI版を作った。
1個1個エンコードする時はGUI版の方が楽。
エンコードのコア部分は DLL にしているので、コマンドライン版もその DLL を使うように書き換える予定。
でも、こまごまデータもらいながら作るのなら、コマンドライン版はあんまりいらないかも。

投稿者 Takenori : 16:34 | トラックバック

2009年03月21日

アルファチャンネル付き動画公開

アルファチャンネル付き動画 を公開しました。
エンコーダー、プラグイン、サンプル、マニュアルが入っています。

サンプルの動画が大きいので、少しサイズが大きくなってます。
もう少しサンプルの動画を増やせたら、サンプル動画のみを分離して、別途配布という形にする予定です。

投稿者 Takenori : 18:29 | トラックバック

2010年05月25日

WebM / VP8

週末に組み込もうと予定していたけど、よく見ると「DirectShow Filters Source Coming soon.」と書かれていて、DirectShow フィルタのソースがまだ公開されていないため断念。
実装方法は、DirectShow フィルタをインストールしなくても再生できるように、krmovie.dll 内にフィルタを持つ形にする予定 ( そのためソースコードが必要 )。
インストール前提であれば、フィルタ繋ぐだけなのでたいした作業は必要ないが、現状でマイナーなコーデックなので再生できない人も多く出るだろうからそれは避ける。
DirectShow フィルタのソースが公開され次第再生出来るようにするべく組み込み作業をする予定だけど、あまりに公開が遅いようだとフィルタは作ってしまうかもしれない。

そこでとりあえずクオリティーや再生負荷を見るべく動画再生エンジンで作っていたプレイヤーに組み込んで再生してみることに。
libvpx は、yasm を入れる必要があるけど、VS2008 で問題なくビルドできる。
とりあえずは、サンプルの IVF コンテナを再生出来るようにしてエンコードしようと思ったら、添付の ivfenc.exe は YV12 か I420 形式の画像をつなげたバイナリの固まりを必要としていたので、AVI から YV12 に変換して一つのバイナリに固めるツールを作って、変換して、ivfenc.exe に食わせてエンコード。
エンコードはそれなりに重い。
作ったツールの YUV の色変換が少しおかしくて手間取ったものの再生出来るように。
( libvpx はデコード画像を I420 で出力するんだけど、YV12 に出来ないのかな? 謎。まあプレーンの並びが入れ替わるだけなので大して変わらないが )

動画は普通に綺麗で圧縮率も良い。
CPU 負荷は、Theora の1.6倍程度。
ポストプロセスを入れると 2.4倍くらいになる。
※ここの倍率測定は、デコード時間で見ているもので、CPU負荷を目視したものではありません。
重い。
Core i7 で見て1~3%のCPU負荷 ( 640x480で ) 。
ポストプロセスは、補間を入れることで品質を上げるものだと思われます。

Core 2 Duo E6750 2.66GHz で CPU 負荷を見てみると……
Theora : 5~8 %
VP8 : 6~10 %
VP8 + post process : 10~15 %

640x480 の再生に 1GHz くらい必要かな? ( NetBurst じゃないもので )
Atom D510 で今週末にでも負荷を見てみる予定。
PenIII 866MHz のノートPCでも再生テストしようとしたけど、YV12 のサーフェイスがサポートされていないのかプレイヤーが落ちてしまったので未確認。
上記テストは D3D で表示しているので、これを Overlay にすると少し負荷が軽くなるのと、ノートでも動くかもしれないので、後で確認してみる予定。
CPU での YV12 → RGB24 の変換を入れるとかなり重くなるので、吉里吉里のレイヤーモードで再生すると要求する CPU はさらに上がる。

About のページ のNoteに、「サンプルのブラウザサポートはかなり重いけど、それはまだ最適化していないから。現在リリースされている VP8 SDK を使うことでより正確な負荷がわかります。最適化版ブラウザは準備中です。」的なことが書かれている。
つまり、上の libvpx をビルドして組み込んで試したものが、現状で最適化されたものってことかな。
何とかもう少し Google が頑張って最適化してくれるといいな。

VP8 の圧縮率は確かにいいので、他で要求する CPU が底上げされているのなら、使っても良いと思う。
そうでない場合は、普通に MPEG I や WMV を選択すると思うけど。
WMV は現状である程度のハードウェア支援も期待できるし。
VP8 はハードウェア支援が浸透してくると、WMV から置き換えてもいいと思う。
要求する CPU はある程度高くなってもいいから圧縮率が高いのがいいってことなら VP8 は全然あり。

使うかどうかは別にして、吉里吉里2でのサポートはする。

投稿者 Takenori : 19:40 | コメント (1) | トラックバック

 
Total : Today : Yesterday :