酒井 法雄 SAKAI,Norio norio@int21.co.jp
| MCIとは |
|---|
今やノートパソコンですらWaveやMIDIなどの音が出るのは当たり前だ.ましてやデジタルビデオすら再生できて,それがスゴいという人もいないご時世だ.このようなサウンドやビデオはマルチメディアと呼ばれ,一時はとても新しいモノとして騒がれたこともあった.
Windows上でのマルチメディアは,いろいろなメディアに対応できるような形になっているMCI(Multimedia Control Interface)として実現されている.つまり,サウンドやビデオ,アニメーションなどのデバイスドライバと,それらを共通に扱うコマンドセットにわけられる.たとえば,“Play”というコマンドはどのデバイスに対しても再生を意味する命令であり,実際にそのデバイスを使うためには,デバイスドライバが別途インストールされている必要があるという形である.
しかし,Visual Basicでは相変わらず標準ではこのマルチメディアをサポートしていない.もちろん,Professional Edition以上であれば,MCIコントロールを使ってマルチメディアを扱うことはできる.しかし,MCIコントロールでは細かい処理をしにくく,それゆえにメディアによっては機能が不足していることも確かだ.たとえば,ビデオ系の再生をするウィンドウは指定できるが,そのウィンドウサイズに合わせて表示するといった指定はすることができない.
また,カスタムコントロールは便利なものではあるが,バージョンの競合やらの問題もあり,最近では敬遠される場面も少なくない.
それならば,直接MCIのインターフェイスを使ってしまえばよい.もちろん,MCIはWindows APIセットとして用意されているから,宣言さえ書いてしまえばVisual Basicから呼び出すことができる.さらにいうならば,通常のAPIを使うよりずっとカンタンに扱うことができる高レベルMCIというものが用意されており,これは文字列だけで制御ができてしまうものだ.Windows APIの例に漏れず,MCIのAPIを使えば,大量のわけのわからないプロパティやメソッドのあるコントロールを使うより,ずっとスマートにコーディングできる.
ただし,それはMCIについてそれなりに理解をしておく必要がある.といっても,難しいことはない.逆にMCIを理解しておけば,MCIコントロールの難解なプロパティなどの意味もわかってくるというものだ.
また,MCIの基本セットコマンドはデバイスに依存しない命令セットである.したがって,ひとつのデバイスについて理解すれば,他のデバイスを扱うのもカンタンだ.ただし,MCIのコマンドにはデバイスに依存しているものもあるから,デバイスごとのクセを知る必要があり,それにはそれなりに時間がかかる.そこで,ここではすべてのデバイスについて述べないことを,あらかじめ申しあげておく.
Visual Basicのバグや仕様をなんとかするためにAPIを使うのとちがって,MCIは楽しい.何といっても音が出る,絵が動く,CD-ROMドライブのドアが開く.それだけでもやってみようという気になるではないか.
| Waveサウンドを扱うAPI |
|---|
高レベルMCIを扱う前に,Waveサウンドを扱うカンタンなAPIを紹介しておこう.というのも,高レベルMCIは非常に柔軟性の高いものではあるが,それなりに「おおごと」になってしまう.単にWaveサウンドを鳴らしたいだけならば,もっとお手軽なものがあるのだ.
それがsndPlaySoundというAPIだ.次に概要を示す(表1).
表1:sndPlaySound APIの概要
| 構文 |
|---|
| BOOL sndPlaySound(lpszSoundName, fuOptions) |
| LPCTSTR lpszSoundName; /* 再生するサウンド名 */ |
| UINT fuOptions; /* オプションフラグ */ |
| 解説 |
| sndPlaySound関数はファイル名やレジストリの[sounds]セクションのエントリで指定されたウェーブフォームサウンドを再生する.サウンドが見つからない場合は レジストリの[sounds]セクションのSystemDefaultエントリで指定されているデフォルトのサウンドを再生する.SystemDefaultエントリがない場合やデフォルトのサウンドが見つからない場合には関数はサウンドを再生せずにFALSEを返す |
| パラメータ | 説明 |
|---|---|
| lpszSoundName | 再生するサウンド名を指定する.関数はこの名前をもつエントリをレジストリの[sounds]セクションで検索し,対応するウェーブフォームファイルを再生する.この名前を持つエントリが存在しないときには名前がウェーブフォームのファイル名であると仮定される.このパラメータがNULLならば現在再生中のサウンドがすべて停止する |
| fuOptions | 次のフラグをひとつ以上使用してサウンド再生のオプションを指定する |
| 値 | 意味 |
| SND_SYNC | 同期的にサウンドを再生する.関数はサウンド再生が終了するまで制御を返さない |
| SND_ASYNC | 非同期的にサウンドを再生する.関数はサウンド再生の開始直後に制御を返す.非同期的に再生されているサウンドを終了させるにはlpszSoundNameをNULLに設定してsndPlaySoundを呼び出す |
| SND_NODEFAULT | サウンドが見つからないとき,関数はデフォルトのサウンドを再生せずに制御を返す |
| SND_MEMORY | lpszSoundNameで指定したパラメータがウェーブフォームサウンドのメモリ内データイメージを指すポインタであることを示す |
| SND_LOOP | lpszSoundNameパラメータをNULLに設定してsndPlaySoundを再び呼び出すまでサウンドを繰り返し再生する.サウンドを繰り返すにはSND_ASYNCフラグも指定しなければならない |
| SND_NOSTOP | サウンドが現在再生中ならば,関数は要求されたサウンドを再生せずに,すぐにFALSEを返す |
| 戻り値 |
|---|
| サウンドが再生された場合はTRUEを,それ以外の場合はFALSEを返す |
| 説明からもわかるように,指定された文字列は,レジストリの[sounds]セクションにあるサウンド名あるいは,ファイル名ということになっている.このsoundセクションというのは,実際には“HKEY_USERS\.DEFAULT\AppEvents\Schemes\Apps\.Default\”(Windows NTのとき.Windows95では違うかもしれない)以下にある“Open”,”Close”,“Maximize”といった動作に合わせて出る音のことである.レジストリを見ればわかるが,ここには「ジャングル」とか「ロボット」とかいうスキーマによって音が決まっている.現在選択しているスキーマに対応するWaveファイルが鳴るということだ.たとえば,「ジャングルサウンド」スキーマになっていて,「最大化」されたときにはWINNT\mediaディレクトリ下にある「ジャングル -最大化.WAV」が鳴るわけだ. |
図1:レジストリで見たサウンドスキーマ (16KB) |
つまり,「ジャングルサウンド」スキーマになっているときにsndPlaySoundで“Maximize”を指定すれば,同じ「ジャングル -最大化.WAV」が鳴るという次第だ.また,直接.WAVファイル名を指定すれば,そのファイルが鳴る.
また,第2引数にSND_SYNCを指定すれば,同期してサウンドが鳴る.つまり,サウンドが終了するまで次のサウンドは鳴らず,サウンドが終了するまでアプリケーションに制御は戻らない.
SND_ASYNCのときには,非同期,つまり再生が開始されると同時に制御はアプリケーションに戻り,再生途中であっても次のサウンドの再生を開始できる.もちろん,このときには最初に鳴っていたサウンドは途中で終わってしまう.
SND_LOOPのときには,サウンドを繰り返し再生したりすることができる.このときには,サウンド名を指定しないでsndPlaySoundを呼び出すか,別のサウンドの再生を開始するまでループが継続される.
また,再生中なら新しいサウンドを鳴らさないSND_NOSTOPもある.
sndPlaySound関数をVisual Basicから呼び出すには,次のように宣言する(リスト1).
リスト1:sndPlaySound関数の宣言
Declare Function sndPlaySound _ Lib "winmm.dll" Alias "sndPlaySoundA" _ (ByVal lpszSoundName As String, ByVal uFlags As Long) As Long Public Const SND_SYNC = &H0 Public Const SND_ASYNC = &H1 Public Const SND_NODEFAULT = &H2 Public Const SND_MEMORY = &H4 Public Const SND_LOOP = &H8 Public Const SND_NOSTOP = &H10 |
実際にこの関数を使った例を次に示す(図2,リスト2).
コンボボックスから選ばれたスキーマの音や,WAVEファイルの音がそれぞれ再生される.また,いくつかのパラメータによって動きの違いを確認できる.
| 高レベルMCIとは |
|---|
単純にWAVEサウンドを鳴らすだけなら,sndPlaySoundで十分だし,使い方もカンタンだ.しかし,MIDIやAVI,MPEGなどを使ってみたいときには,こういったカンタンな関数は必ずしも用意されていない.また,非常に大きなファイルのときには再生までに時間がかかってしまうこともある.それよりも,より細かい制御,たとえば一時停止したいとか,特定の位置から再生したいといったことや,録音などはこういったおおざっぱな関数では制御できない.
そこで,汎用のMCI制御関数の登場である.すでに述べたように,MCIはマルチメディアを使うための汎用のインターフェイスである.たとえば,DATでもビデオでも,Play, Stop, Pause, 早送り,巻き戻し,カウンターによる位置表示などは共通である.つまり,MCIはこういった共通のメソッドやステータスを管理するための共通インターフェイスなのである.
しかし,細かい違いもある.たとえば,CDオーディオであれば,何曲目かという概念があるが,Waveファイルにはない.また,位置を表わすにしても,開始位置からの時間で表わすこともあるだろうし,ビデオであればフレームで表わすこともあるだろう.このような違いは,タイムフォーマットの種類を変更して対処できるようになっている.つまり,基本的にはほとんど共通のインターフェイスで実現されているわけだ.
こういったインターフェイスをMCIと呼んでいる.MCIには,高レベルと低レベルがある.低レベルMCIは,WAVEサウンドやMIDIなどを扱うための固有な細かい関数の集まりであり,使い勝手は必ずしもよくない.より細かい制御がしたいときには,これらの低レベル関数を使った方がよい場面もあるが,通常は高レベルのみでもかなりのことができる.
| 高レベルMCIのAPI |
|---|
高レベルMCIには,大きくわけてmciSendCommandとmciSendStringのふたつの系列がある.
mciSendCommandは,たとえばファイルをオープンするならMCI_OPENといった定数,さらに付随するフラグや構造体などを組み合わせて使うものである.いわゆるAPIらしいという意味では,低レベルMCI関数群やmciSendCommandの方がそれっぽい.しかし,細かい定数を使わなくてはならないため,Visual Basicから使うためにはそれぞれ適切に定数を定義するなどしてやる必要があり,結構面倒である.
一方,mciSendStringは,このような面倒なことをしないで,すべてを文字列で処理してしまおうというものである.たとえば,
open g:\winnt\media\tada.wav type waveaudio alias x1というような文字列を送ってやれば,ファイルをx1というエイリアスでオープンする.
play x1とすれば,再生が開始されるといったものだ.実にわかりやすい.面倒な定数定義も必要ない.また,そのときのステータス情報も文字列として得ることができる.さらには,mciGetErrorString関数を使えば,エラー情報さえも文字列として得ることができるのである.
表2:mciSendString関数
| 構文 |
|---|
| MCIERROR mciSendString(lpszCommand, lpszReturnString, cchReturn, hwndCallback) |
| LPCTSTR lpszCommand; /* MCIコマンド文字列のアドレス */ |
| LPTSTR lpszReturnString; /* 返されるバッファのアドレス */ |
| UINT cchReturn; /* 返されるバッファの文字単位のサイズ */ |
| HANDLE hwndCallback; /* コールバック ウィンドウ ハンドル */ |
| 解説 |
| mciSendString関数はメディアコントロールインターフェイス(MCI)デバイスにコマンド文字列を送る.コマンドの送り先のデバイスもコマンド文字列である |
| パラメータ | 説明 |
|---|---|
| lpszCommand | Null文字で終わる文字列を指すポインタ.この文字列には次の形式でMCIコマンド文字列を指定する [command] [device] [parameters] |
| lpszReturnString | 情報が返されるバッファを指定する.返される情報が不要のときはこのパラメータにNULLを指定する |
| cchReturn | lpszReturnStringパラメータで指定された,返される情報のためのバッファのサイズを文字単位で指定する |
| hwndCallback | コマンド文字列の中で“notify”(通知)が指定されているときに,コールバックするウィンドウを識別する |
| 戻り値 |
|---|
| 関数が正常に終了した場合は0を返す.それ以外の場合は,エラーコードを返す.返されたダブルワードの下位ワードは,エラー戻り値を示す.エラー値の意味について詳しくはWin32 SDKを参照 |
| mciSendStringの戻り値のテキスト記述を取得するには,戻り値をmciGetErrorStringに渡す |
表3:mciGetErrorString関数
| 構文 |
|---|
| BOOL mciGetErrorString(fdwError, lpszErrorText, cchErrorText) |
| DWORD fdwError; /* エラーコード */ |
| LPTSTR lpszErrorText; /* エラー文字列を返すバッファのアドレス */ |
| UINT cchErrorText; /* 文字単位のバッファ長さ */ |
| 解説 |
| mciGetErrorString関数は指定されたメディアコントロールインターフェイス(MCI)エラーコードを記述した文字列を取得する |
| パラメータ | 説明 |
| fdwError | mciSendCommandまたはmciSendStringが返したエラーコードを指定する |
| lpszErrorText | 指定されたエラーを記述するNull文字で終わる文字列を取得するバッファを指すポインタ |
| cchErrorText | lpstrBufferが指すバッファの長さを文字単位で指定する |
| 戻り値 |
|---|
| 関数が正常に終了した場合はTRUEを返す.指定されたエラーコードが不明の場合はFALSEを返す |
それぞれのAPIのVisual Basicでの宣言は,次の通りである(リスト3).
■mciSendString
Declare Function mciSendString _ Lib "winmm.dll" Alias "mciSendStringA" _ (ByVal lpstrCommand As String, _ ByVal lpstrReturnString As String, _ ByVal uReturnLength As Long, _ ByVal hwndCallback As Long) As Long |
■mciGetErrorString
Declare Function mciGetErrorString _ Lib "winmm.dll" Alias "mciGetErrorStringA" _ (ByVal dwError As Long, _ ByVal lpstrBuffer As String, _ ByVal uLength As Long) As Long |
| MCI文字列の送信とステータス情報の取得 |
|---|
MCIを扱うためには,たったふたつのAPIだけで済む.
しかし,mciSendStringには,実はふたつの側面がある.ひとつは,文字どおりコマンド文字列を送って再生などの処理を行なわせることだ.もうひとつは,現在のステータスやデバイスのCapabilityなどの情報を得ることだ.いずれも文字列を送ることに変わりはないのだが,送りっぱなしでおしまいのものと,送った結果を得るものとではコーディングも異なってくる.
もっとも,送られた文字列に誤りがあったり,すでにオープンしているものと同じエイリアスを使ってしまったりしたとき,オープンしていないデバイスに対して再生などのコマンドを送ったときには,エラーになってしまう.こういったエラーハンドリングもしておく必要がある.
そこで,こういった処理をひとまとめにしてひとつのプロシージャを作ってしまうことにしよう.コマンドの送信と文字列の取得,そしてエラーハンドリングがカプセル化されてしまえば,あとは文字どおりコマンド文字列を送るルーチンワークで済んでしまうからである.
ここでは,mSendStringという名前の関数を,次のような仕様で作ることにしよう(表4).
表4:mSendString関数の仕様
| 引数 |
|---|
| s : コマンド文字列 |
| ErrString : エラー時の説明文字列 or 正常終了時にはStatus取得 |
| 戻り値 |
| エラー時にはエラー番号を返す (0は正常終了, -1 はコマンド文字列が指定されていない) |
mciSendStringでは,ステータス情報は第2パラメータに文字列として返ってくる.また,その文字列の最大数は第3パラメータで決定される.そこで,次のような定数と変数を定義,宣言しておこう.
Private Const LENCMDS = 512 Private szCmds As String * LENCMDS同様にして,mciGetErrorStringでは,エラーの内容を示す文字列およびそのサイズが,第2パラメータと第3パラメータに返ってくる.そこで,次のような定数と変数を定義,宣言しておこう.
Private Const LENERROR = 256 Private szError As String * LENERRORしたがって,mciSendStringは次の行のようにして呼び出すことができる.
r = mciSendString(s, szCmds, LENCMDS, 0&)この結果がエラーでないときには,ErrStringにはステータスを示す文字列を代入し,この関数の戻り値は0にする.
ErrString = _ Left(szCmds, InStr(szCmds, Chr$(0)) - 1) mSendString = 0エラーが発生したときには,mciGetErrorStringを呼び出す.このとき,この関数自体の実行が失敗して文字列が取れない可能性もある.このときには,「文字列が取れません」というエラーにしておく.そうでないときには,エラー文字列をErrStringに代入する.いずれの場合にも,エラー番号を戻り値としてやればよい.
If mciGetErrorString(r, szError, LENERROR) = _ False Then ' エラー文字列が取得できなかった ErrString = "Error (文字列が取れません)" Else ' エラー文字列が取得できた ErrString = _ Left(szError, InStr(szError, Chr$(0)) - 1) End If mSendString = rこの他にも,コマンド文字列自体が送られなかったというエラーも考えられるので,これは予防線を張って,API関数を呼び出す前にエラーとして処理してしまおう. こうしてできたのが,次の関数mcitestである(リスト4).
リスト4:mcitest.bas
Option Explicit
Declare Function mciSendString _
Lib "winmm.dll" Alias "mciSendStringA" _
(ByVal lpstrCommand As String, _
ByVal lpstrReturnString As String, _
ByVal uReturnLength As Long, _
ByVal hwndCallback As Long) As Long
Declare Function mciGetErrorString _
Lib "winmm.dll" Alias "mciGetErrorStringA" _
(ByVal dwError As Long, _
ByVal lpstrBuffer As String, _
ByVal uLength As Long) As Long
Private Const LENCMDS = 512
Private Const LENERROR = 256
Private szCmds As String * LENCMDS
Private szError As String * LENERROR
' mciSendString を呼び出し,ステータスを得る
' 引数
' s : コマンド文字列
' ErrString : エラー時の説明文字列 or
' 正常終了時にはStatus取得
' 戻り値
' エラー時にはエラー番号
' (0は正常終了, -1 はコマンド文字列が指定されていない)
Public Function mSendString(s As String, ErrString As String) As Long
Dim r As Long
If s <> "" Then ' コマンド文字列が指定されている
r = mciSendString(s, szCmds, LENCMDS, 0&)
If r <> 0 Then ' エラー
If mciGetErrorString(r, szError, LENERROR) = False Then
' エラー文字列が取得できなかった
ErrString = "Error (文字列が取れません)"
Else
' エラー文字列が取得できた
ErrString = Left(szError, InStr(szError, Chr$(0)) - 1)
End If
mSendString = r
Else ' 正常終了時にはステータスを得る
ErrString = Left(szCmds, InStr(szCmds, Chr$(0)) - 1)
mSendString = 0
End If
Else ' コマンド文字列が指定されていない
ErrString = "コマンド文字列が指定されていません"
mSendString = -1
End If
End Function
|
| 高レベルMCI文字列の使い方 |
|---|
たったふたつのWindows APIで,マルチメディアを制御できるというのは,とても嬉しいことだ.それもカプセル化できてしまったから,もう面倒なことは考えなくてもよい.
しかし,逆にいえば,制御するための文字列が反比例してたいへんたくさんあるということになる.もちろん,文字列は英語に準ずるものであってわかりやすいのではあるが,しょせんはコンピュータを制御するためのものであるから,適当なことを書いてやってもそれなりに処理してくれるというわけではない.
そこで,その文字列を知らなくてはならないのだが,これが実に膨大にあり,またデバイスによって使い方が異なっていたりする.したがって,いきなり全部を知ろうというのには無理がある.
ここでは,まずは基本的な文字列を紹介し,その後デバイスごとの構文を示すことにしよう(表5).
表5:高レベルMCIの基本コマンド−基本的なコマンド文字列には,次のようなものがある
| コマンド | 説明 | 引数 |
|---|---|---|
| open | 使用するデバイスをオープンし,初期化する | open device_name [alias device_alias] [shareable] [type device_type] |
| close | 使用後のデバイスをクローズする | close device_name |
| play | デバイスの再生を開始する | play device_name [from position] [to position] |
| record | デバイスの録音を開始する | record device_name [from position] [to position] [insert|overwrite] |
| stop | デバイスの再生や録音を停止する | stop device_name |
| pause | デバイスの再生や録音を一時停止する | pause device_name |
| resume | 一時停止中のデバイスの再生や録音を再開する | resume device_name |
| seek | 現在のメディア内での位置を変更する | seek device_name {to position|to start|to end} |
| set | 使用中の時刻形式などのデバイスのコントロール設定を変更する | set device_name [audio all off|audio all on|audio left off|audio left on|audio right off|audio right on|video off|video on] [door closed|door open] [time format milliseconds|time format ms] |
| status | 再生中や一時停止などのデバイスの状態に関する情報を要求する | status device_name [mode|ready] status device_name {current track|length|length track track_number|mode|number of tracks|position|position track track_number|ready|start position|time format} |
| capability | デバイスの能力に関する情報を要求する | capability device_name {can eject|can play|can record|can save|compound device|device type|has audio|has video|uses files} |
| info | デバイスに関連するハードウェアの説明などの,デバイスに関する情報を要求する | info device_name [product] |
| load | ファイルを読み込む | load device_name {file_name} |
| save | ファイルを保存する | save device_name [file_name] |
使い方
一般的な使い方としては,次のようになる.
x1を再生する:play x1
x1をクローズする:close x1
デバイス名
ここで使われるデバイス名は,次のようになっている.
| animation | アニメーションデバイス |
| cdaudio | CDオーディオプレーヤー |
| dat | デジタルオーディオテーププレーヤー |
| digitalvideo | ウィンドウ内デジタルビデオ(GDIは使用しません) |
| other | 未定義のMCIデバイス |
| overlay | オーバーレイデバイス(ウィンドウ内アナログビデオ) |
| scanner | イメージスキャナ |
| sequencer | MIDIシーケンサ |
| videodisc | ビデオディスクプレーヤー |
| waveaudio | デジタライズされたウェーブフォームファイルを再生するオーディオデバイス |
| avivideo | Video for Windows |
| mpegvideo | MPEGビデオ |
ただし,ファイルのときには,その拡張子から適切なデバイスが自動的に認識される.したがって,表6のような記述が可能である.なお,ファイル名の間にスペースが入ったり,日本語のファイル名などのときには,"(ダブルクォート)でファイル名をくくればよい.
表6:デバイスの自動認識による記述の省略例
open g:\winnt\media\tada.wav type waveaudio alias x1 notify ↓ open g:\winnt\media\tada.wav alias x1 notify |
open F:\TEMP\voyager.avi type avivideo alias x1 ↓ open F:\TEMP\voyager.avi alias x1 |
open "g:\winnt\media\こんぺいとうの踊り (「くるみ割り人形」より).RMI" type sequencer alias x1 ↓ open "g:\winnt\media\こんぺいとうの踊り (「くるみ割り人形」より).RMI" alias x1 |
waitオプション
コマンド文字列にwaitオプションをつけると,同期モードで実行される.すなわち,sndPlaySoundのときと同様に,同期モードではコマンド文字列で指定された処理が終了するまでアプリケーションに処理が戻らない.通常は非同期モードとして実行されるので,すぐに次のコマンドを送信することができる.
play x1 from 0 ' asyncで実行 play x1 from 0 wait ' syncで実行するので, ’再生が終了するまで ’次の処理はできない
notifyオプション
コマンド文字列にnotifyオプションをつけると,処理の終了が通知される.たとえば,次の行に記述してあるようなCDオーディオのオープン時には,最初にCD-ROMが読み込まれてスキャンされ,トラックの内容などが読み取られ,再生が可能になるまで時間がかかる.ドアが開いたり閉じたりしたときにも同様に時間がかかる.一般的な再生などのコマンド終了だけではなく,こういった処理が終わったことを通知するのが,notifyオプションである.
open cdaudio alias x1 notifyただし,notifyで通知されるのは,特殊なメッセージであるため,Visual Basicからは知ることができない.これを実現するには,別稿で述べるサブクラス化の手法を使う必要がある.詳しくは「Visual Basicの限界を広げるサブクラス化の手法」をご覧いただきたい.
| 高レベルMCIをテストする |
|---|
これで高レベルMCIを使う準備は整った.では,実際にMCIコマンド文字列を送信して,マルチメディアデバイスを制御したり,ステータスを得たりできるプログラムを作ってみよう.
といっても,難しいことはない.すでに重要な部分は完成してしまっているのだ.あとはこのmSendString関数を呼び出してやればよいだけである.
ここでは,フォームにMCIコマンド文字列を入力するテキストボックスtxtSendStringと,送信するためのボタンcmdSendString,そしてステータス情報を表示するテキストボックスtxtGetStringを配置した.
cmdSendStringがクリックされたときには,次のようなコードが走るようにした(リスト5).これで,ステータスも得られ,エラー時にはメッセージボックスでエラーを示す文字列が表示される.
実行例は図3・4に示すとおりである.
いろいろなコマンド文字列をその場でためすことができるので,とても便利だ.
リスト5:mcitest.frm
Option Explicit
Private ErrorString As String
Private r As Long
Private Sub cmdSendString_Click()
r = mSendString(txtSendString.Text, ErrorString)
If r <> 0 Then
MsgBox ErrorString _
& " (" & CStr(r) & ")", vbCritical, "Error"
Else
txtGetString.Text = ErrorString
End If
End Sub
|
図3:mcitestの実行例.サウンドファイルの位置を表示している
|
図4:mcitestの実行例.エラー発生時には,エラーを示す文字列が表示される
|
| MCIプレイヤーを作る |
|---|
ここまでできてしまえば,あとは読者諸氏の目的に合わせて,適当なコマンド文字列をテストしていただければ,何でもできてしまうだろう.
まずは,汎用のコマンドを作ろう.ここではオープンやプレイなどをカンタンにできるように,次のようなコマンドを作った.
ファイル名とエイリアス名を指定してオープンする.以後はエイリアス名だけで制御が可能になる.
プレイは,スタート位置stと終了位置enをOptionalとして指定できるようにした.指定がないときには,適切な判断するようなコードが入っている(リスト6).
リスト6:プレイ
クローズ,停止,一時停止,再開はエイリアスのみでOKだ.
PauseとResumeを組み合わせて,DATデッキやビデオデッキっぽいポーズボタンを,次のようなコードで実現した.
任意の位置への移動は,seekコマンドを使う.指定すべきなのは移動先の位置とエイリアスである.
メディアがあるかの確認は,status〜media presentコマンドで行なう.
デバイスによってタイムフォーマットの既定値は異なる.処理によっては,一時的に別のタイムフォーマットにする必要がある.現在のタイムフォーマットの取得と設定は次のようにstatus〜time formatとset〜time format〜で行なう.
メディアやファイルの長さは,status〜lengthで得られる.単位はタイムフォーマットによる.
現在の位置は,status〜positionで得られる.単位はタイムフォーマットによる.
現在位置の取得ができれば,変移量を指定して早送りや巻き戻しをすることができる.現在のタイムフォーマットを保存してから,タイムフォーマットmillisecondsにし,現在の位置を得る.そこから移動量を加えてseekコマンドを送る.元のタイムフォーマットに戻しておしまいだ.
CDなどのデバイスでは,トラックの概念がある.全トラック数は,status〜number of tracksで得られる.
現在のトラック位置は,status〜current trackで得られる.
指定されたトラック番号の位置をstatus〜position trackの後に指定してやれば,そのトラックの開始位置を得ることができる.
指定されたトラックからのプレイは,指定されたトラック番号の開始位置をGetTrackPositionで得て,PlayのFromに指定してやればよい.
次/前のトラックからの再生は,現在のトラックを得,そのトラックに+1または-1したトラックの開始位置を得,PlayのFromに指定してやればよい.
スクロールバーを使って任意位置へ移動するには,あらかじめスクロールバーに次の要素を指定しておく.トラックのときには,トラック数と現在のトラック.それ以外のときには,長さと現在の位置.
ウィンドウハンドルを指定して,表示するウィンドウを指定することができる.
表示するウィンドウのどの位置に表示するかを指定できる.この値によっては,アスペクト比が正しくならないので注意が必要だ.
元々のビデオサイズは,where〜sourceで得ることができる.これを元に,表示位置のアスペクト比を正しくするなどの計算をすることもできる.
CDドライブなどドアがあるデバイスのときには,set〜door open/closedコマンドでドアの開閉ができる.ただし,ドアが完全に開く/閉じるまでには,一般のコマンドに比較してタイムラグがある.この問題を解決するには,waitあるいはnotifyオプションを指定する必要がある.
いわゆるマルチメディアのために直接使うことは少ないだろうが,便利なものにtimeGetTime関数がある.
このように,高レベルMCI APIは,マルチメディアを扱うには実に有効な手段である.
付録:システムコマンドセットおよびデバイスに固有なコマンドセット
たとえば,
r = mSendString _
("play demo from " & StartPos, ErrorString)
このように,MCIの文字列をハードコーディングしたり,一部をVisual Basicの変数として組み合わせれば,ある程度決まりきったアプリケーションならば,ほとんど問題なく作ることができるだろう.
が,汎用なものを作ろうとすると,なかなかたいへんである.それは,デバイス固有のコマンドがあったり,デバイスによってタイムフォーマットの既定値が異なっていたりするからだ.
そこで,ここでは比較的汎用なMCIプレイヤーを作り,いくつかのポイントを説明しておこう.
汎用コマンドを作る
オープン
Public Function mOpen _
(s As String, atype As String) As Long
mOpen = mSendString _
("open """ & s & """ alias " & atype, _
ErrorString)
End Function
プレイ
Public Function mPlay _
(atype As String, Optional st As String, _
Optional en As String) As Long
If st = "" Then
If en = "" Then
mPlay = mSendString _
("play " & atype, ErrorString)
Else
mPlay = mSendString _
("play " & atype & " to " & en, ErrorString)
End If
Else
If en = "" Then
mPlay = mSendString _
("play " & atype & " from " & st, ErrorString)
Else
mPlay = mSendString _
("play " & atype & " from " & st & " to " & en, _
ErrorString)
End If
End If
End Function
クローズ,停止,一時停止,再開
Public Function mClose(atype As String) As Long
mClose = mSendString("close " & atype, ErrorString)
End Function
Public Function mStop(atype As String) As Long
mStop = mSendString("stop " & atype, ErrorString)
End Function
Public Function mPause(atype As String) As Long
mPause = mSendString("pause " & atype, ErrorString)
End Function
Public Function mResume(atype As String) As Long
mResume = mSendString("resume " & atype, ErrorString)
End Function
ポーズボタンの実現
Private Sub cmdPause_Click()
Static pause As Boolean
If pause Then
cmdPause.Caption = "||"
mResume CStr(cboFile.ListIndex)
Else
cmdPause.Caption = "|>"
mPause CStr(cboFile.ListIndex)
End If
pause = Not pause
End Sub
任意の位置への移動
Public Function mSeek _
(pos As Long, atype As String) As Long
mSeek = mSendString _
("seek " & atype & " to " & CStr(pos), _
ErrorString)
End Function
メディアの確認
Public Function mGetMediaPresent _
(atype As String) As String
mGetMediaPresent = mGetString _
("status " & atype & " media present")
End Function
タイムフォーマットの取得と設定
Public Function mGetTimeFormat _
(atype As String) As String
mGetTimeFormat = mGetString _
("status " & atype & " time format")
End Function
Public Function mSetTimeFormat _
(fmt As String, atype As String) As Long
mSetTimeFormat = mSendString _
("set " & atype & " time format " & CStr(fmt), _
ErrorString)
End Function
長さ
Public Function mGetLength _
(atype As String) As String
mGetLength = mGetString _
("status " & atype & " length")
End Function
現在の位置
Public Function mGetPosition _
(atype As String) As String
mGetPosition = mGetString _
("status " & atype & " position")
End Function
早送り/巻き戻し
Public Function mFF _
(atype As String, SKIP As Long) As Long
Dim tmpTF As String
Dim tmpPtr As Long
tmpTF = mGetTimeFormat(atype)
mSetTimeFormat "milliseconds", atype
tmpPtr = mGetPosition(atype)
mFF = mSeek(CStr(Val(tmpPtr) + SKIP), atype)
mSetTimeFormat tmpTF, atype
End Function
全トラック数の取得
Public Function mGetTracks _
(atype As String) As String
mGetTracks = mGetString _
("status " & atype & " number of tracks")
End Function
現在のトラック位置の取得
Public Function mGetTrack _
(atype As String) As String
mGetTrack = mGetString _
("status " & atype & " current track")
End Function
指定されたトラック番号の開始位置
Public Function mGetTrackPosition _
(atype As String, num As String) As String
mGetTrackPosition = mGetString _
("status " & atype & " position track " & num)
End Function
トラックの移動
Public Function mTrack _
(pos As Long, atype As String) As Long
mTrack = mPlay _
(atype, mGetTrackPosition(atype, CStr(pos)))
End Function
次/前のトラックへの移動
Private Sub cmdNext_Click()
Dim ct As Integer
Dim ctpos As String
ct = Val(mGetTrack(CStr(cboFile.ListIndex)))
ctpos = mGetTrackPosition _
(CStr(cboFile.ListIndex), CStr(ct + 1))
mPlay CStr(cboFile.ListIndex), ctpos
End Sub
Private Sub cmdPrev_Click()
Dim ct As Integer
Dim ctpos As String
ct = Val(mGetTrack(CStr(cboFile.ListIndex)))
ctpos = mGetTrackPosition _
(CStr(cboFile.ListIndex), CStr(ct - 1))
mPlay CStr(cboFile.ListIndex), ctpos
End Sub
スクロールバーを使った指定位置への移動
hsbPos.Min = 1
If InStr(lblTotal.Caption, ":") Then
hsbPos.Max =mGetTracks _
(CStr(cboFile.ListIndex))
hsbPos.Value = mGetTrack _
(CStr(cboFile.ListIndex))
dispmode = TRACK
Else
hsbPos.Max = mGetLength _
(CStr(cboFile.ListIndex))
hsbPos.Value = mGetPosition _
(CStr(cboFile.ListIndex))
dispmode = MILSEC
End If
スクロールバーが移動されたとき,Valueプロパティが移動位置である.トラックのときにはトラック位置,それ以外のときには現在位置を移動する.
Private Sub hsbPos_Scroll()
If dispmode = TRACK Then
mTrack hsbPos.Value, _
CStr(cboFile.ListIndex)
Else
mPlay CStr(cboFile.ListIndex), _
hsbPos.Value
End If
End Sub
Video関係に固有の機能
表示先ウィンドウの指定
Public Function mDispTargethWnd _
(atype As String, hWndTgt As Long) As Long
mDispTargethWnd = mSendString _
("window " & atype & " handle " & CStr(hWndTgt), _
ErrorString)
End Function
表示位置の指定
Public Function mDispTargetRect _
(atype As String, X As Long, Y As Long, _
w As Long, h As Long) As Long
mDispTargetRect = mSendString _
("put " & atype & " destination at " _
& CStr(X) & " " & CStr(Y) & " " & CStr(w) _
& " " & CStr(h), ErrorString)
End Function
元のビデオサイズの取得
ドア関係(CDドライブなど)
ドアのオープン/クローズ
Public Function mOpenCDDoor _
(atype As String) As Long
mOpenCDDoor = mSendString _
("set " & atype & " door open", _
ErrorString)
End Function
Public Function mCloseCDDoor _
(atype As String) As Long
mCloseCDDoor = mSendString _
("set " & atype & " door closed", _
ErrorString)
End Function
以上のノウハウを使って作ったのが,サンプルプログラムMCIPLAYである.実行画面とリストを示す(記事末参照.図5,リスト7・8).
実行画面は,Video for Windowsの画面がウィンドウ内のピクチャーボックスに縮小表示されて,一時停止中である.トラックはひとつしかない.タイムフォーマットはframesであり,649フレームが表示されている.元の画面は320x240のサイズであることがわかる.
1/1000秒単位での時刻取得
この関数は,高精度マルチメディアタイマーを使い,システム時刻を1/1000秒単位で取得できるものだ.通常のハードウェアのクロックからはここまで細かい制度で取得することはできない.
mciplay.basの最後に何気なく入っているwait関数は,この関数を使って1/1000秒単位でのウェイトを実現するものである.
Public Sub wait(w As Long)
Dim t As Long
t = timeGetTime()
Do
DoEvents
Loop Until timeGetTime() - t > w
End Sub
この関数の応用例としては,ベンチマークがある.つまり,ある処理を開始する直前にtimeGetTimeで得た時刻と,処理終了直後に得た時刻の差が,実行にかかった時刻となるわけだ.クエリーや実行速度などの最適化を図る上では,結構有用な関数である.
独自のMCIコントロールも作成できる
もちろん,MCIコントロールを使った方がカンタンなこともある.しかし,その先でちょっとだけ高レベルMCIを組み合わせて使うという手法もある.実は,MCIコントロールでも内部的には高レベルMCIを使っており,そのエイリアスはファイル名になっているのだ.したがって,MCIコントロールでほとんどの作業はして,表示先ウィンドウへの表示位置の調整などは高レベルMCIを使うといった手もある.
また,ここで紹介したようなMCIプレイヤーの基本的な部分を,ActiveXコントロールのメソッドして用意すれば,自分だけの便利なMCIコントロールを作ることも可能である.
実は,このMCIプレイヤーはTDK株式会社から発売されている「ActiveXがわか〜る」の中で私が作ったプログラムのサブセットである.「ActiveXがわか〜る」では,こうした手順を踏んだ上で,高レベルMCIを使うActiveXコントロールを作り,ゲームなどに応用する例を学習できるようになっている.興味のある方は,お買い求めいただけると幸いである.
最後に,システムコマンドセットおよび,デバイスに固有なコマンドセットの一覧を示す.デバイスに固有な機能を積極的に使いたいときに参照してほしい.ここでは紹介しきれなかった,さまざまな機能があることが分かるはずだ.
また,文中でも述べた通り,notifyを使った処理終了通知の方法については,「Visual Basicの限界を広げるサブクラス化の手法」に述べるので,ご覧いただきたい.
サンプルプログラムのダウンロード(8KB)
(ダウンロード後ディレクトリ付きで解凍してください)
int21 ホームページ | PCDN ホームページ
Copyright (c) 1998 int21 Corporation All Rights Reserved.
For questions or comments, please send mail to: pcdn@int21.co.jp