TheoraLib - TheoraLib.DLL
目次
・概要
・ライセンス、著作権
・DLLの呼び出し規約、構造体メンバの配置
・必要なコールバック関数
・使用方法
・OggTheora(*.ogv)の作り方
・MovieDecoderとWaveDecoderを同時使用した場合の注意点
・ループ再生や逆再生を行う場合
・theoralib_dll_create
・theoralib_dll_free
OggTheora形式の動画「*.ogv」の再生を行うためのものです。
Oggパケット形式の拡張である「*.ogm」ではないので注意してください。
「TheoraLib」はOggTheora動画を取り扱う「TheoraLib.DLL」と、DLLを扱うためのソースファイル「uTheoraLib_DLL.pas」の2つから成ります。
「TheoraLib.DLL」はOggTheora動画を扱いやすいようにラッピングしたものです。
Xiph.org公式のライブラリの関数は内包され、独自のものとなっています。
公式のライブラリをMMXオプションでコンパイルされているため、MMXが使えるCPUでないと動きません。
また、ファイルサイズが数ギガになるものや、ストリーミングは想定されておりません。
主にゲームで使う動画レベルを想定して設計されています。
「uTheoraLib_DLL.pas」はDelphiでDLLを扱うためのソースファイルです。
Delphi形式で提供されていますが、単純なコードと定義だけですので、C++や他の言語への移植も比較的簡単に行えるでしょう。
・著作権
Copyright (C) 2004-2009 Ko-Ta.
・ライセンス
OggTheora(FreeBSDライセンス)に準じます。
私に対しての報告や著作権の表記の義務は一切ありません。
でもTheoraのライセンスには従ってください。
DLL側から提供する関数、ならびにDLLより発行するコールバック関数全てにおいて、「cdecl」を使用します。
構造体のメンバは「1Byte単位(#progma (1))(packed record)」で配置されたものが使用されます。
読み込みやデコード処理にはコールバック関数が必要になります。
データを読み込みに関する「read,size」、映像データの展開用に「scanline,resize」が必要です。
映像データのYUV変換を自前で行いたい場合はこれに「decode」コールバック関数を定義する必要がありますが、普通いりません。
DLLとの呼び出し規約の関係で、最後に「cdecl」が付きます。お忘れなく。
・Ttheoralib_cb_read
Loadで指定したデータソースアドレスからの読み込みを行うコールバック関数。
普通のread関数とは異なりoffsetが指定されるので、チェックして異なるアドレスならシークさせてください。
最後に読み込んだBYTE数を返してください。EOFに達した場合は0を返してください。
function cb_read(datasource:Pointer; destptr:Pointer; offset:Integer; size:Integer):Integer;
cdecl; begin TmemoryStream(datasource).Position := offset; //BYTE単位です RESULT := TmemoryStream(datasource).Read(destptr^,size); end; |
・Ttheoralib_cb_size
データソースの全体サイズをBYTE数で返すコールバック関数です。
容量を偽ることはできません。よってストリーミングなどは行えません。
function cb_size(datasource:Pointer):Integer; cdecl; begin RESULT := TMemoryStream(datasource).Size; end; |
・Ttheoralib_cb_scanline
Loadで指定したビットマップソースのY座標の先頭アドレスを返すコールバック関数です。
TBitmapを使う場合はScanLine[]と同じ機能なので、そのまま使用できます。
尚、転送先のPixelFormatはARGB32Bitである必要があります。
function cb_scanline(bmpsource:Pointer; y:Integer):Pointer; cdecl; begin RESULT := TBitmap(bmpsource).ScanLine[y]; end; |
・Ttheoralib_cb_resize
転送が行われる前に、必要なサイズにを要求するコールバック関数です。
毎フレーム(毎デコード)呼ばれるので、同じサイズならスキップする処理を必ず入れてください。
ゼロクリアする必要はありません。ゴミがあってもかまいません。
procedure cb_resize(bmpsource:Pointer; w,h:Integer); cdecl; var b : Tbitmap; begin b := bmpsource; if not((w=b.Width)and(h=b.Height))then begin b.Width := w; b.Height:= h; end; end; |
・Ttheoralib_cb_decode
YUVからの変換を自前で行いたい場合に使用するもので、普通は使われません。
デコードされた映像データのYUV情報を直接取得できます。
俺はRGB24Bitに転送したいんじゃ!とかYUVまんまの情報がほしい!と言う場合に使用してください。
返り値を0にすると、通常通りの展開転送処理が行われるので、使えそうなら使ってください。
function cb_decode(bmpsource:Pointer; sender:Pointer; yuvbuf:PTheoralib_yuv_buffer;
vrev:Integer):Integer; cdecl; var b : Tbitmap; //info : Ptheoralib_theorainfo; begin b := bmpsource; //senderはMovieDecoderクラスポインタです //info := theoralib_m_info(sender); //このように取得できます //・返値 //(0...変換処理をDLLに委ねます / 1...DLLの変換処理を無効化します) //通常は常に1を返す仕様で問題有りません。 //もし、コメントに特定の文字が有る場合のみ例外的に処理したい場合などに0/1を使い分けてください。 RESULT := 1; end; |
1.「uTheoraLib_DLL.pas」をインクルードします。
interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, uTheoraLib_DLL; |
3.必要なコールバック関数を用意します。呼び出し規約「cdecl」が付きます。
詳しい説明は必要なコールバック関数をごらんください。
function cb_read(datasource:Pointer; destptr:Pointer; offset:Integer; size:Integer):Integer;
cdecl; begin if (TmemoryStream(datasource).Position<>offset)then //BYTE単位です TmemoryStream(datasource).Position := offset; RESULT := TmemoryStream(datasource).Read(destptr^,size); end; function cb_size(datasource:Pointer):Integer; cdecl; begin RESULT := TMemoryStream(datasource).Size; end; function cb_scanline(bmpsource:Pointer; y:Integer):Pointer; cdecl; begin RESULT := TBitmap(bmpsource).ScanLine[y]; end; procedure cb_resize(bmpsource:Pointer; w,h:Integer); cdecl; var b : Tbitmap; begin b := bmpsource; //毎フレーム呼ばれるのでサイズが同じならスキップしてやってください //内容をクリアする必要はありません if not((w=b.Width)and(h=b.Height))then begin b.Width := w; b.Height:= h; end; end; |
3.「TheoraLib.DLL」を読み込み、関数を取得します。(DLLはコピーして用意しておいてくださいね)
theoralib_dll_cretae('theoralib.dll'); |
4.ストリームにTheora(*.ogv)ファイルを読み込みます。(コールバックで指定したトリームを使用してください)
stream := TMemoryStream.Create; stream.LoadFromFile('sample01.ogv'); |
5.フレーム画像展開先となるARGB32Bitビットマップを作成します。(面倒なのでTBitmapつかいますね)
bmp := TBitmap.Create; bmp.HandleType := bmDIB; //DIBSection bmp.PixelFormat:= pf32bit;//ARGB32Bit bmp.Width:=8; //てきとー bmp.Height:=8; |
6.動画デコードに必要な「MovieDecoder」クラスを生成し、ロードします。
このとき、先ほど宣言したコールバック関数が必ず必要です。
//movieはPointer型です。 movie := theoralib_m_create; theoralib_m_load( movie, stream,cb_read,cb_size, //データストリームポインタとコールバック関数 bmp,cb_scanline,cb_resize, //ビットマップポインタとコールバック関数 nil //デコードコールバック関数。使用しないのでNULL ); |
7.準備はそろいました。「DecodeFrame」命令で任意のフレーム画像を取得します。
命令を発行するとコールバック関数が呼ばれて、指定のビットマップに映像がYUV→ARGB32Bitに変換、転送されます。
(YUVの変換も自前で行いたい場合はデコードコールバック関数を使用してください)
theoralib_m_decodeframe(movie,ScrollBar1.Position); |
8.最後は解放してください。MovieDecoder→ストリーム、ビットマップ→DLLの順で行ってください。
theoralib_m_free(movie); bmp.Free; stream.Free; theoralib_dll_free; |
その他、詳しい説明については、MovieDecoder、WaveDecoderの項目をご覧ください。
OggTheora動画を作る場合は、まずAVIかmpg形式で動画を作り、「ffmpeg2theora」を使用するのがいいでしょう。
音声がある場合も、そのまま変換してくれます。
・ffmpeg2theora
http://v2v.cc/~j/ffmpeg2theora/
.example >ffmpeg2theora sample.mpg >ffmpeg2theora -v 7 -a 3 sample.mpg |
ループ処理や逆再生はシークが発生するため、通常のエンコード設定(サイズ優先)では処理負荷が高く使用に耐えません。
まず動画をシーク処理が軽いものにエンコードし直す必要があります。
OggTheoraでシークに強くするには、キーフレームの挿入間隔を短くします。
デフォルトは64か32ぐらいなので、4〜8ぐらいに設定するといいでしょう。
解像度が小さいものなら多少間隔が広くてもかまいませんが、大きな動画の場合は狭くする必要があります。
>ffmpeg2theora -K 6 sample.mpg |
キーフレームの間隔を狭くする(キーフレームを増やす)と、サイズ容量と処理負荷が増加します。
むやみやたらにキーフレームを入れれば良いというわけではないので、適材適所、用途に合わせてデコードしてください。
ただし、例外もあります。
もし先頭にジャンプする場合はキーフレームを増やす必要はありません。
と言うのも、最初のフレームは必ずキーフレームになるため、増やしても減らしても関係ありません。
この場合はデフォルト設定の64ぐらいでエンコードするといいでしょう。
MovieDecoderとWaveDecoderを同時使用した場合の注意点
MovieDecoderとWaveDecoderを使用して、映像と音声を再生させた場合の注意点です。
メモリ上やHDDなどのシークが早い媒体の場合は問題ありませんが、CD/DVDメディアなどの場合は処理が間に合わなくなる可能性があるというお話です。
映像と音声は見かけ上動機していますが、音声はストリーミング再生の都合上映像より先のデータを読み込んで処理が行われます。
つまり、映像と音声の位置は同期しておらず、異なる位置で平行して処理が進みます。
位置が異なると言うことは、ストリーミングの更新おき(映像⇔音声に処理が切り替わる時)にシーク処理が発生します。
普通、CD/DVDを読み込んでもOSが読み込んだ内容をメモリキャッシュするため、このときの論理シークは最適化され、物理的シークを省略されるとは思いますが、それが期待できない場合や、OSに頼らない処理で安全を確保したい場合は、ちょっとデータソースの読み込みに工夫が必要です。
DVD/CD上でのシークを避ける場合は、OSのメモリキャッシュと同じく、「アプリケーション側でメモリキャッシュを用意」してやることです。
リングバッファを用意し、読み込み処理の際に、ついでに1〜2Mほどのデータを逐次キャッシュしておき、シークが発生したらキャッシュを確認し、そこから取り出せば回転系メディアの物理シークを回避できるでしょう。
簡単に済ませたいなら、それほど大きなファイルでなければ、いっそのことメモリ上に全部読む込んでしまうのも手かもしれません。
function theoralib_dll_create(const filename:String):boolean; filename...「theoralib.dll」ファイルパス |
DLLを読み込んで関数を取得します。
まずはじめはこれから。
procedure theoralib_dll_free; |
DLLを解放します。
後始末はお忘れなく。