はじめに従来のMac OS(System 6.0.8~Mac OS 9)においてQuickTimeが残したものが意味していることは、オリジナルAPIの多くがシングルスレッドから呼び出すように設計されていたということです。しかし、Mac OS XはMacintoshデベロッパに真のマルチプロセス処理のパワー、すなわち、一度に複数のアプリケーションで複数の処理を実行する能力をもたらしました。Mac OS Xはさらに、単一のアプリケーションの中から複数のスレッドで実行できるタスクの種類に関する従来のMac OSの制限を取り除きました。 QuickTimeアプリケーションを開発する場合、なぜこれが重要なのでしょうか? この疑問に答えるために、あらゆる演算処理が一瞬で終わると仮定しましょう。これが真ならば、理論上は、すべての処理を同じスレッドから実行できることになります。しかし現実には、多くの一般的な処理は実際に時間がかかります。このようなプロセッサを集中的に使用する処理が、ユーザインターフェイスをブロックしたり、同時に実行できるタスクの数を制限したりすることにより、アプリケーションの応答を遅らせていると、ユーザエクスペリエンスが損害を受けることになります。ユーザとの直接的な対話を必要としないQuickTimeの処理を実行でき、メインスレッド上で行われていることに関係なく実行できれば、アプリケーションにとって有利です。 たとえば、ムービー圧縮について考えます。これは多少時間のかかる処理です。おそらく、アプリケーションで2つのムービーを同時に圧縮したり、1つのムービーを再生しながら別のムービーを圧縮したくなることがあります。Mac OS X 10.3以前は、このようなタイプのQuickTime処理は、容認できないほど処理を遅らせたり、アプリケーションが応答しなくなる場合がありました。 さて、この処理をメインスレッド(ユーザインターフェイスをブロックするスレッド)からバックグラウンドスレッド、または複数のバックグラウンドスレッドに移行することを考えてください。アプリケーションは、さまざまなフォーマットの複数のムービーを同時に圧縮してエクスポートしながら、ユーザインターフェイスをブロックせずに別のムービーを再生したり、他のタスクをすべて実行することができるようになります。 Mac OS X 10.3およびそれ以降におけるQuickTimeマルチスレッドサポートの設計目標は、特にこのような問題を対象にしています。現在、QuickTimeは、プロセッサを集中的に使用するタスクをバックグラウンドスレッドに移行してメインスレッドを解放し、それによってアプリケーションの速度、応答、および全体的なパフォーマンスを改善できるようになっています。 スレッドセーフ処理Mac OS X 10.3以降では、QuickTimeの次の処理をバックグラウンドスレッドから実行できるようになりました。
このようなタイプの処理は、処理に非常に時間がかかり、CPUのボトルネックやユーザインターフェイスの応答の問題につながる可能性があります。このような処理はバックグラウンドスレッドに移行するのが理想的です。 注:ムービーの再生は常にユーザインターフェイスのブロックを回避してきました。この方法はQuickTime 1.0以来、Mac OS X 10.3でも継続しています。 スレッドセーフにできるものについて述べる前に、QuickTimeムービー、ムービーファイル、QuickTimeオブジェクト、相互非関連(Disjoint)など、いくつかの用語を定義する必要があります。
注: QuickTimeムービー(メモリ内のデータ構造)とムービーファイル(ディスク上のファイル)の間で、上記の違いを理解することは重要です。 スレッドセーフにできるもの以下のツールボックスマネージャと連携するQuickTimeアプリケーションはスレッドセーフにできます。
アプリケーション中の各スレッドは、複数のスレッドからの論理的に相互非関連であるQuickTimeオブジェクトのセットを安全に使用することができます。あるスレッドが独自のQuickTimeオブジェクトのセットを持っており、別のスレッドが独自のセットを持っている場合、これらのオブジェクトに対して実行される処理はスレッドセーフと見なすことができます。2つのスレッドに共通のQuickTimeオブジェクトがあってはなりません。図1を参照してください。 たとえば、2つのバックグラウンドスレッドは .DVストリームをインポートし、 図1:相互非関連アクセスはスレッドセーフ
これに対して、2つのスレッドが同じQuickTimeオブジェクトに同時にアクセスする場合、それらのオブジェクトに対して実行される処理はスレッドセーフと見なされません。これらのオブジェクトを操作するスレッドは、いずれかのオブジェクトアクセスを中心にして独自の同期(またはロック)を実行する必要があります。これは、複数のスレッドからアクセスする必要がある保護されていない(すなわち、グローバルな)データ構造と違いはありません。ある種のスレッド保護は、同時アクセスを許可する前に実施する必要があります。図2を参照してください。 図2:同時アクセスはスレッドセーフではない
注:POSIXのpthread APIは2つのスレッド同期プリミティブ、つまりミューテックス(mutex)と条件変数を提供しています。 ミューテックスは単純なロックで、2つのバックグラウンドスレッド間で共有するGraphics Importer InstanceまたはDecompression SequenceのようなQuickTimeオブジェクトへのアクセスを制御するのに使用できます。 ミューテックスには、ロックとアンロックの2つの状態しかありません。POSIXの条件変数はミューテックスと一緒に使用し、スレッドをブロックして別のスレッドからの信号を待たせることができます。信号が届いたら、ブロックされていたスレッドは関連するミューテックスでロックをグラブしようと試みることができます。 マルチプロセッシングサービスも、スレッド同期とシグナリングメカニズムを提供します。 pthreadとマルチプロセッシングサービスに関する詳細は、このテクニカルノートの最後にある「参考資料」のセクションを参照してください。 ファイルシステムを扱う場合は、例外が発生します。ファイルへのアクセスを実装するオペレーティングシステムルーチンは、すべてスレッドセーフです。ファイルシステムがマルチスレッドアクセスに合わせて設計されているため、独立したスレッドで2つのQuickTimeオブジェクトに同じファイルを参照させることができます。2つのスレッドに読み取りのために同じムービーファイルを共有するのはまったく問題ありません。2つのスレッドでは、それぞれのQuickTimeムービーが同じムービーファイルを参照し、読み取り処理を実行することができます。たとえば、これらのスレッドでは何の問題なく、ムービーの異なる部分を同時にレンダリングまたは表示することができます。しかし、一方のスレッドでムービーファイルに書き込みながら、他方のスレッドでロックしないで読み取るのは安全でない可能性があります。図3を参照してください。 図3:読み取りのために同じムービーファイルを共有するのはスレッドセーフになります
この議論の要点は、スレッドセーフであることを保証したい場合は、アプリケーションで複数のスレッドが同じQuickTimeオブジェクトを同時に操作しないようにするということです。このことを確実にするのは呼び出し側の責任です。 スレッドセーフでないもの以下のツールボックスマネージャはスレッドセーフでないため、QuickTimeアプリケーションではそれらのサービスをバックグラウンドスレッドから使用できません。
さらに、以下のMovie Toolbox機能もバックグラウンドスレッドから使用できません。
Human Interface Toolboxは、アプリケーションにユーザインターフェイス要素を提供します。HIToolboxを使った作業はスレッドセーフではありません。さらに、ユーザが生成するすべてのイベントを通常受け取るのは、メインスレッドの実行ループです。 CarbonとCocoaのどちらの環境でQuickTimeを扱うかに関係なく、ユーザインターフェイス要素はメインスレッド上になければならず、バックグラウンドスレッド上でダイアログやウインドウを作成または表示しないようにするのは呼び出し側の責任です。QuickTimeユーザインターフェイス要素には、Standard Compression Dialog、 図4:すべてのユーザインターフェイスをメインスレッドに保持
Resource Managerの使用も、そのAPIにリソースチェーンというグローバル状態の使用が伴うため、スレッドセーフではありません。リソースチェーンは、開いているリソースファイルのリストです。これは事実上、大部分のResource Manager APIに渡される間接的なパラメータです。つまり、バックグラウンドスレッドから、 Movie Toolboxは時間ベースのデータをインポート、再生、レンダリング、作成、編集、保存できる機能を提供します。ムービーはバックグラウンドスレッドでは再生できないため、 Movie Controller(よく知られているHIToolbox Controlに関して考える場合はCarbon Movie Control)は、QuickTimeムービーを処理する際に完全な機能スイート(再生、編集、ユーザインターフェイス、対話型イベントディスパッチなど)を提供します。しかし、Movie Controllerはスレッドセーフではなく、メインスレッドからのみ作成・使用する必要があります。 アプリケーションデベロッパへのアドバイスムービーファイルを開いたり、画像のフォーマットを変換したりするいくつかの高レベル処理の実行には、Movie Importers、Data Handlers、Image Decompressorsなど、いくつかの低レベルコンポーネントが必要になることがあります。これらのコンポーネントは、スレッドセーフかどうかが分からず、多くの場合、アプリケーションは処理が開始するまで、どの低レベルコンポーネントが呼び出されるかを知る方法がありません。 つまり、バックグラウンドスレッドからは、いくつかのメディアファイルを開くことができず、一定のメディア変換を安全に実行することができないということです。アプリケーションでは、このような可能性に対処するための計画を取り入れる必要があります。 Mac OS X 10.3には、スレッドセーフおよび非スレッドセーフコンポーネントがいくつか含まれています。たとえば、JPEG、PNG、およびTIFFなど、最も広く利用されている静止画フォーマットはスレッドセーフですが、MacPaintはスレッドセーフではありません。この例に示したケースは、スレッドセーフでないコンポーネントを必要とするメディアを自分のコードがどれだけきちんと扱えるかをテストするための厳密に定義されたケースとして利用できます。「ほとんどの場合、QuickTimeオブジェクトをメインスレッドに移行して、処理を継続する必要があります。このテクニックについては、この記事の「シナリオとサンプルコード」のセクションにあるスレッドインポートとエクスポートのサンプルで示してあります。 EnterMoviesOnThreadの呼び出しバックグラウンドスレッドでQuickTimeを使用するアプリケーションは、バックグラウンドスレッドで他のQuickTime APIを呼び出す前に、各バックグラウンドスレッドで
アプリケーションは、バックグラウンドスレッドから呼び出されたQuickTime APIから リスト1:EnterMoviesOnThread OSErr EnterMoviesOnThread(UInt32 inFlags) EnterMoviesOnThreadは、アプリケーションが現在のスレッド上でQuickTime APIを使用することを QuickTimeに示すために使用します。 inFlags - 既存のスレッドがどのようにQuickTimeを使用するかを指示するフラグ。デフォルトの オプションとして0を渡します。 フラグ: kQTEnterMoviesFlagDontSetComponentsThreadMode = 1L << 0 ディスカッション: アプリケーションは、作成したスレッドでEnterMoviesOnThreadを呼び出します。QuickTime APIを 呼び出す際に、作成したプリエンプティブスレッド上でEnterMoviesOnThreadを呼び出さないと、 グローバルなQuickTime状態がメインスレッドと共有されます。 デフォルトでは、EnterMoviesOnThreadは、QuickTimeのスレッド共有ポリシーに応じて、現在の Component Managerスレッドモードを設定します。QuickTimeの状態がスレッドに対して非公開の場合は、 モードがkCSAcceptThreadSafeComponentsOnlyMode に設定されます。QuickTimeの状態が EnterMoviesOnThreadを呼び出すスレッドとメインスレッド間で共有されている場合は、モードが kCSAcceptAllComponentsModeに設定されます。 kQTEnterMoviesFlagDontSetComponentThreadModeフラグをEnterMoviesOnThreadの呼び出しに インクルードすると、スレッドモードは変更されず、呼び出し前の状態のままです。スレッドモードの 設定は、EnterMoviesOnThreadによって便宜を図るために提供されており、CSSetComponentsThreadModeを 使用して直接行うことができます。また、CSGetComponentsThreadModeを呼び出して、現在の スレッドモードを取得することもできます。 EnterMoviesOnThreadまたはCSSetComponentsThreadModeを呼び出して設定したスレッドモードは プロセスグローバルなものではなく、スレッドローカルなものです。 EnterMoviesOnThreadの最初の呼び出しは、kQTEnterMoviesFlagDontSetComponentsThreadModeフラグを 渡さない限り、Component Managerのスレッドモードを変更します。それに続く呼び出しはすべて、 それに続く呼び出しはすべて、Component Managerのスレッドモードをそのままにします。 EnterMoviesOnThreadの複数の呼び出しは、単一スレッド上で行うことができます。スレッドを作成し、 EnterMoviesOnThreadを呼び出すアプリケーションはその一例です。その場合、アプリケーションは QuickTimeを使用するライブラリコードも呼び出します。ライブラリコードは、呼び出し側が QuickTimeを初期化したかどうかを予測できないため、EnterMoviesOnThreadも呼び出します。 これは、ライブラリによる現在一般的なEnterMoviesの用法に合っています。 リスト2:ExitMoviesOnThread OSErr ExitMoviesOnThread(void) ExitMoviesOnThreadは、アプリケーションが現在のスレッド上でQuickTime APIを使用しないことを QuickTimeに示すために使用します。 ディスカッション: ExitMoviesOnThreadは、処理を完了できなかった場合に、適切なオペレーティングシステムまたは QuickTimeエラーを返します。このエラーは、事前にEnterMoviesOnThread呼び出しをしなかった ために発生する可能性があります。 ExitMoviesOnThreadは、QuickTimeを使用して、EnterMoviesOnThreadで実行した設定を取り消す バックグラウンドスレッドを終了する前に呼び出してください。 それぞれのEnterMoviesOnThread呼び出しは、ExitMoviesOnThreadと対応させる必要があります。 対応するEnterMoviesOnThreadを事前に呼び出さずに、ExitMoviesOnThreadを呼び出すことはできません。 バックグラウンドスレッドで最後のExitMoviesOnThreadを呼び出した後、最初に EnterMoviesOnThreadを呼び出さないでQuickTime APIを呼び出すと、アプリケーションはあたかも EnterMoviesOnThreadとExitMoviesOnThreadのペアを使用しなかったかのように、メインスレッドの 状態を共有するスレッドになります。これは互換性のためです。 ExitMoviesOnThreadを呼び出さないことは、致命的ではありませんが、場合によってはリソースリークに つながることがあります。そのため、呼び出し側は、最初のEnterMoviesOnThreadと最後の ExitMoviesOnThreadの間にあるすべてのQuickTime呼び出しを、二次スレッドで囲む必要があります。 スレッド間でのQuickTimeオブジェクトの移動場合によっては、QuickTimeオブジェクトをスレッド間で移動する必要があります。 Graphics ImportersおよびGraphics Exportersのインスタンスを処理する場合は、独自のロックメカニズムを実装して、QuickTimeオブジェクト(この場合はコンポーネントインスタンス)を一度に1つのスレッドからのみ呼び出すようにすることで、これを管理する必要があります。 他方で、QuickTimeムービーは、いつでもそれらが属しているスレッドを認識する必要があります。QuickTimeムービーをスレッド間で移動する際に呼び出す必要がある2つのAPIがあります。まず古いスレッドからムービーをデタッチし、新しいスレッドにアタッチします。 QuickTimeムービーをスレッド間で受け渡すときには、古いスレッドで 図5:スレッド間でのQuickTimeオブジェクトの移動
QuickTimeムービーを含んでいる安全でないメディア(非スレッドセーフコンポーネントのレンダリングを必要とするメディア)をメインスレッドからデタッチする リスト3:AttachMovieToCurrentThread OSErr AttachMovieToCurrentThread(Movie m) m - この処理の対象となるムービー。アプリケーションは、NewMovie、NewMovieFromFile、 およびNewMovieFromHandleなどの関数を使ってこのムービーの識別子を取得します。 エラーがない場合はnoErrを、ムービーを現在のスレッドにアタッチできない場合は componentNotThreadSafeErrを返します。 ディスカッション: ムービーを現在のスレッドにアタッチします。 リスト4:DetachMovieFromCurrentThread OSErr DetachMovieFromCurrentThread(Movie m) m - この処理の対象となるムービー。アプリケーションは、NewMovie、NewMovieFromFile、 およびNewMovieFromHandleなどの関数を使ってこのムービーの識別子を取得します。 エラーがない場合はnoErrを返します。 ディスカッション: ムービーを現在のスレッドからデタッチします。 コールバックの処理多くのQuickTime APIによりコールバックルーチンのインストールが可能で、最も一般的なのは非同期完了コールバックとプログレスコールバックです。 非同期完了コールバックは必ず特別なスレッドから呼び出され、スレッドセーフでなければなりません。これらのスレッドは、処理を実行するスレッドと同じではありません。この動作はMac OS X 10.3でも変更されていません。 しかし、それ以外QuickTimeコールバックは、処理を実行する同じスレッドから呼び出されます。 これが特に重要なのは、いくつかのユーザインターフェイス要素を含む独自のプログレスコールバックを実装する場合です。バックグラウンドスレッドから呼び出されるプログレスコールバックは、そのスレッドで実行しています。このコードがアプリケーションのどこにあるか、または他のどの機能がそれを使用するかに関係なく、コールバックをバックグラウンドスレッドから呼び出す場合はスレッドセーフでなければなりません。 そのような場合には、プログレスコールバックを見直して、スレッドセーフにする必要があります。大変役に立つテクニックは、バックグラウンドスレッドのコールバックからメインスレッドのcarbonイベントへ送信されるカスタムcarbonイベントを使用することです。これは、バックグラウンドスレッドからスレッドセーフAPIの API 注:Mac OS X 10.3.xでは、 Mac OS X 10.4以降では 図6:PostEventToQueueを使用してカスタムのCarbonイベントをメインスレッドに送信
重要:Movie Toolboxのデフォルトのプログレス関数の代わりにMovieProgressUPPを-1に設定して、バックグラウンドスレッドで ムービープログレス関数を使いたくない場合は、NULL; Resource Managerの処理Resource Managerはスレッドセーフではありません。Resource Managerにはリソース、リソースチェーン、またはリソースマップを操作するAPIが含まれているということを覚えておくことが非常に重要です。 しかし、コンポーネントリソースを返す1回限りのComponent Manager呼び出しはスレッドセーフであるため、アプリケーションがコンポーネントからパブリックリソースを取得する必要がある場合は使用してください。 これらには以下のAPIが含まれています。
リスト5:GetComponentPublicResourceの使用
ComponentDescription cd;
ResourceHandle resource = NULL;
Component c = 0;
cd.componentType = MovieExportType;
cd.componentSubType = kAComponentSubType;
cd.componentManufacturer = kAManufacturer;
cd.componentFlags = 0;
cd.componentFlagsMask = 0;
c = FindNextComponent(c, &cd)
if (c) {
err = GetComponentPublicResource(c, 'PICT', 1, &resource);
if (noErr == err) {
// リソースへの何らかの処理
...
DisposeHandle(resource);
}
}
これらのAPIに関する詳細は、「流氷通信 21」 を参照してください。 シナリオとサンプルコード(a) 静止画のインポート - ThreadsImporterサンプルコード ThreadImporterでは、静止画を別々のスレッドにインポートして表示する方法を示しています。 (b) 静止画のエクスポート - ThreadsExporterサンプルコード ThreadsExporterでは、異なるフォーマットの静止画を別々のスレッドにインポートしてエクスポートする方法を示しています。 (c) QuickTimeムービーのインポート - ThreadsImportMovieサンプルコード ThreadsImportMovieでは、QuickTimeムービーを別々のスレッドにインポートして表示する方法を示しています。 (d) QuickTimeムービーのエクスポート - ThreadsExportMovieサンプルコード ThreadsExportMovieでは、QuickTime Movie Export Componentを使用して、ムービーを別々のスレッドにエクスポートする方法をデモしています。 (e) ムービーをメインスレッドで開き、ムービーのコピーをバックグラウンドスレッドに移動します。 リスト8には、何かの処理をするために、ムービーをメインスレッドからバックグラウンドスレッドに移動する方法をデモする一般的なコードの断片が含まれています。これは、リスト6および7に示すworker関数と一緒に使用します。関数 (f) 移動したムービーをバックグラウンドスレッドからエクスポートします。 リスト6では、QuickTime Movie Export Componentを開き、DV Codecを使用してビデオを圧縮するように設定し、次に リスト6:バックグラウンドスレッドのムービーをエクスポート
// このworker関数はバックグラウンドスレッドでムービーのエクスポートを実行する
void *DoSomeWorkFunction(void *inWorkerThreadRef)
{
QTAtomContainer exportSettings;
Handle theDataRef;
OSType theDataRefType;
EventRef theEventRef = NULL;
OSStatus status;
WorkerThreadRef worker = (WorkerThreadRef)inWorkerThreadRef;
EnterMoviesOnThread(0);
status = AttachMovieToCurrentThread(worker->theMovie);
require_noerr(status, CantAttachToCurrentThread);
CFStringRef path = CFSTR("/Users/fasteddie/Desktop/testoutput.mov");
status = QTNewDataReferenceFromFullPathCFString(path, kQTNativeDefaultPathStyle,
0, &theDataRef, &theDataRefType);
require_noerr(status, Done);
// QuickTime Movie Exportコンポーネントを開いて設定する
ComponentInstance ci = OpenDefaultComponent(MovieExportType, kQTFileTypeMovie);
if (ci) {
SCSpatialSettings ss;
SCTemporalSettings ts;
UInt8 falseSetting = false;
QTAtom videAtom = 0;
QTAtom sptlAtom = 0;
QTAtom tprlAtom = 0;
QTAtom ensoAtom = 0;
QTAtom saveAtom = 0;
QTAtom fastAtom = 0;
ss.codecType = kDVCNTSCCodecType;
ss.codec = NULL;
ss.depth = 0;
ss.spatialQuality = codecHighQuality;
ts.temporalQuality = 0;
ts.frameRate = FixRatio(30, 1); //30L<<16;
ts.keyFrameRate = 0;
// デフォルト値を取得して変更する - 必要に応じてこれらを保持する
status = MovieExportGetSettingsAsAtomContainer(ci, &exportSettings);
require_noerr(status, Done);
// ビデオオプション
videAtom = QTFindChildByID(exportSettings, kParentAtomIsContainer,
kQTSettingsVideo, 1, NULL);
if (videAtom) {
// 空間オプション
sptlAtom = QTFindChildByID(exportSettings, videAtom, scSpatialSettingsType,
1, NULL);
if (sptlAtom) {
status = QTSetAtomData(exportSettings, sptlAtom,
sizeof(SCSpatialSettings), &ss);
}
// 一時オプション
tprlAtom = QTFindChildByID(exportSettings, videAtom, scTemporalSettingsType,
1, NULL);
if (tprlAtom) {
status = QTSetAtomData(exportSettings, tprlAtom,
sizeof(SCTemporalSettings), &ts);
}
}
// ビデオのみ注意する
// サウンドのエクスポートを使用不可にする。
ensoAtom = QTFindChildByID(exportSettings, kParentAtomIsContainer,
kQTSettingsMovieExportEnableSound, 1, NULL);
if (ensoAtom) {
status = QTSetAtomData(exportSettings, ensoAtom, sizeof(falseSetting),
&falseSetting);
}
// インターネット向け、つまりファストスタート用に保存をオフにする
saveAtom = QTFindChildByID(exportSettings, kParentAtomIsContainer,
kQTSettingsMovieExportSaveOptions, 1, NULL);
if (saveAtom) {
fastAtom = QTFindChildByID(exportSettings, saveAtom,
kQTSettingsMovieExportSaveForInternet, 1, NULL);
if (fastAtom) {
status = QTSetAtomData(exportSettings, fastAtom, sizeof(falseSetting),
&falseSetting);
}
}
// これらを設定する
status = MovieExportSetSettingsFromAtomContainer(ci, exportSettings);
require_noerr(status, Done);
// プログレスプロシージャなし - カスタムのプログレスプロシージャを使用する場合は、
// このスレッドで呼び出される。また、ユーザインターフェイスは使用できない。
// ユーザインターフェイスが必要な場合に使用できる方法は、
// カスタムのcarbonイベントを作成し、メインスレッドにインストールしたハンドラに送ること。
SetMovieProgressProc(worker->theMovie, NULL, NULL);
// ムービーをエクスポートする
ConvertMovieToDataRef(worker->theMovie, 0, theDataRef, theDataRefType,
kQTFileTypeMovie, FOUR_CHAR_CODE('TVOD'),
createMovieFileDeleteCurFile |
createMovieFileDontCreateResFile,
ci);
}
Done:
DetachMovieFromCurrentThread(worker->theMovie);
if (ci) CloseComponent(ci);
if (exportSettings) QTDisposeAtomContainer(exportSettings);
if (theDataRef) DisposeHandle(theDataRef);
CantAttachToCurrentThread:
ExitMoviesOnThread();
worker->threadStatus = status;
CreateEvent(NULL, kEventClassQTThreading, kEventAppCleanUpThreadDroppings, 0,
kEventAttributeNone, &theEventRef);
SetEventParameter(theEventRef, kEventParamThreadData, typeWorkerThreadRef,
sizeof(worker), &worker);
if (theEventRef) {
PostEventToQueue(worker->mainEventQueue, theEventRef,
kEventPriorityStandard);
ReleaseEvent(theEventRef);
}
pthread_exit(NULL);
}
(g) ビデオをGWorldにレンダリングし、バックグラウンドスレッドでゼロからムービーを作成します。 リスト7では、新しいムービーとムービーファイルを作成して、移動したムービーを リスト7:バックグラウンドスレッドでムービーを作成してレンダリング
// このworker関数はバックグラウンドスレッドで新しいムービーを作成してレンダリングする
void *DoSomeWorkFunction(void *inWorkerThreadRef)
{
Handle theDataRef;
OSType theDataRefType;
DataHandler theDataHandler = 0;
Movie theNewMovie = NULL;
ImageDescriptionHandle id;
GWorldPtr theGWorld;
PixMapHandle theGWorldPixMap;
unsigned long theGWorldRowBytes;
Ptr baseAddr;
long theDataSize;
Track theTrack;
Media theMedia;
Rect srcRect;
short flags;
OSType whichMediaType = VideoMediaType;
TimeValue movieTime = 0;
TimeValue duration;
EventRef theEventRef = NULL;
OSStatus status;
WorkerThreadRef worker = (WorkerThreadRef)inWorkerThreadRef;
EnterMoviesOnThread(0);
status = AttachMovieToCurrentThread(worker->theMovie);
require_noerr(status, CantAttachToCurrentThread);
CFStringRef path = CFSTR("/Users/fasteddie/Desktop/testoutput.mov");
status = QTNewDataReferenceFromFullPathCFString(path,
kQTNativeDefaultPathStyle,
0,
&theDataRef,
&theDataRefType);
require_noerr(status, Done);
status = CreateMovieStorage(theDataRef, theDataRefType,
FOUR_CHAR_CODE('TVOD'), smSystemScript,
createMovieFileDeleteCurFile |
createMovieFileDontCreateResFile,
&theDataHandler, &theNewMovie);
require_noerr(status, Done);
// ムービーのサイズを取得する
GetMovieBox(worker->theMovie, &srcRect);
MacOffsetRect(&srcRect, -srcRect.left, -srcRect.top);
// フレームをレンダリングするGWorldを作成する
status = QTNewGWorld(&theGWorld,
k2vuyPixelFormat,
&srcRect,
NULL,
NULL,
0);
require_noerr(status, Done);
// GWorldを設定する
SetGWorld(theGWorld, NULL);
SetMovieGWorld(worker->theMovie, theGWorld, NULL);
theGWorldPixMap = GetGWorldPixMap(theGWorld);
LockPixels(theGWorldPixMap);
baseAddr = GetPixBaseAddr(theGWorldPixMap);
theGWorldRowBytes = QTGetPixMapHandleRowBytes(theGWorldPixMap);
// 画像記述とデータサイズを取得する
// 注記:「流氷通信19」に従って、
// 非圧縮のY'CbCrビデオデータを含むQuickTimeムービーファイルの画像記述は
// バージョン2にして、必要な画像記述の拡張をいくつかのインクルードする。
// シンプルにするために、これらは追加しない。
// http://developer.apple.com/quicktime/icefloe/dispatch019.html
status = MakeImageDescriptionForPixMap(theGWorldPixMap, &id);
theDataSize = (**id).dataSize;
// 新しいムービートラックとメディアを作成する
theTrack = NewMovieTrack(theNewMovie,
FixRatio((srcRect.right - srcRect.left), 1),
FixRatio((srcRect.bottom - srcRect.top), 1),
kNoVolume);
status = GetMoviesError();
require_noerr(status, Done);
theMedia = NewTrackMedia(theTrack, VideoMediaType,
kQTSDefaultMediaTimeScale, NULL, 0);
status = GetMoviesError();
require_noerr(status, Done);
// サンプルデータをファイルに書き出すための編集セッションを開始する
status = BeginMediaEdits(theMedia);
require_noerr(status, Done);
// 最初のフレームに、現在のフレームをインクルードする
flags = nextTimeMediaSample | nextTimeEdgeOK;
whichMediaType = VideoMediaType;
Handle theSampleData = NewHandle(theDataSize);
if (MemError() || NULL == theSampleData) goto Done;
while (1) {
// ソースムービーの次のフレームを取得する
// 次に対象とする時間にスキップする
GetMovieNextInterestingTime(worker->theMovie,
flags,
1,
&whichMediaType,
movieTime,
0,
&movieTime,
&duration);
status = GetMoviesError();
require_noerr(status, Done);
if (-1 == movieTime) break;
// フレームの時間を設定する
SetMovieTimeValue(worker->theMovie, movieTime);
// フレームをGWorldに描画する
MoviesTask(worker->theMovie, 0);
status = GetMoviesError();
require_noerr(status, Done);
// lumaを調整して、それぞれのY値から25を引き、
// 最小値を16に固定する(25+16 = 41)
// CbYCrY 8ビット、各コンポーネントのピクセル 0-1
// 「流氷通信19」を参照
// http://developer.apple.com/quicktime/icefloe/dispatch019.html
UInt32 height = srcRect.bottom;
Ptr nextScanLine = baseAddr;
while (height--) {
UInt32 width = theGWorldRowBytes >> 1;
UInt8 *thePixPtr = (UInt8 *)nextScanLine;
while (width--) {
UInt8 Y = thePixPtr[1];
Y = ((Y <= 41) * 16) | ((Y - 25) * !(Y <= 41));
thePixPtr[1] = Y;
thePixPtr += 2;
}
nextScanLine += theGWorldRowBytes;
}
// メディアサンプルをムービーに追加する
PtrToXHand(baseAddr, theSampleData, theDataSize);
status = AddMediaSample(theMedia,
theSampleData, // the video sample
0, // no offset into data
theDataSize,
60, // frame duration
(SampleDescriptionHandle)id,
1, // one sample
0, // self-contained samples
NULL);
require_noerr(status, Done);
flags = nextTimeMediaSample;
}
// メディア編集セッションを終了する
status = EndMediaEdits(theMedia);
require_noerr(status, Done);
// メディアをトラックに追加する
status = InsertMediaIntoTrack(theTrack, 0, 0, GetMediaDuration(theMedia),
fixed1);
require_noerr(status, Done);
status = AddMovieToStorage(theNewMovie, theDataHandler);
Done:
SetMovieGWorld(worker->theMovie, NULL, NULL);
// 何かが混乱しているため、ファイルを削除する
if (status && theDataHandler) {
DataHDeleteFile(theDataHandler);
}
if (theDataRef) DisposeHandle(theDataRef);
if (theDataHandler) CloseMovieStorage(theDataHandler);
if (id) DisposeHandle((Handle)id);
if (theSampleData) DisposeHandle(theSampleData);
if (theNewMovie) DisposeMovie(theNewMovie);
if (theGWorld) DisposeGWorld(theGWorld);
DetachMovieFromCurrentThread(worker->theMovie);
CantAttachToCurrentThread:
worker->threadStatus = status;
ExitMoviesOnThread();
CreateEvent(NULL, kEventClassQTThreading, kEventAppCleanUpThreadDroppings,
0, kEventAttributeNone, &theEventRef);
SetEventParameter(theEventRef, kEventParamThreadData, typeWorkerThreadRef,
sizeof(worker), &worker);
if (theEventRef) {
PostEventToQueue(worker->mainEventQueue, theEventRef,
kEventPriorityStandard);
ReleaseEvent(theEventRef);
}
pthread_exit(NULL);
}
リスト8:Do_SomeWorkOnSeparateThread
// お勧めのWorkerスレッド構造
typedef struct {
SInt32 refCount;
pthread_t workerThread;
Movie theMovie;
EventQueueRef mainEventQueue;
OSStatus threadStatus;
} WorkerThread, *WorkerThreadRef;
// カスタムのCarbonイベントタイプ
enum {
kEventClassQTThreading = 'QTTH',
kEventAppCleanUpThreadDroppings = 'clup',
kEventParamThreadData = 'thrd',
typeWorkerThreadRef = 'thrd' // WorkerThreadRef
};
// Carbonイベントハンドラをインストールするのに必要なコード
// このコードは、場合によりRunApplicationEventLoop()を呼び出す前に、
// 最小セットのCarbonイベントハンドラをインストールする
// RunApplicationEventLoop()
...
EventTypeSpec eventType[] = {{kEventClassQTThreading,
kEventAppCleanUpThreadDroppings}};
status = InstallApplicationEventHandler(Handle_CleanUpThreadData,
GetEventTypeCount(eventType),
eventType, NULL, NULL);
...
// カスタムのクリーンアップイベントのCarbonイベントハンドラ
static pascal OSStatus Handle_CleanUpThreadData(EventHandlerCallRef inHandlerCallRef,
EventRef inEvent, void *inUserData)
{
WorkerThreadRef worker = NULL;
GetEventParameter(inEvent, kEventParamThreadData, typeWorkerThreadRef,
NULL, sizeof(worker), NULL, &worker);
if (NULL == worker) return eventNotHandledErr;
if (1 == DecrementAtomic(&worker->refCount)) {
DisposeMovie(worker->theMovie);
free(worker);
}
return noErr;
}
// コンポーネントにcmpThreadSafeフラグがあるかどうかチェックする
Boolean IsComponentThreadSafe(OSType inComponentType, OSType inComponentSubType)
{
Component comp = 0;
ComponentDescription cd = { inComponentType, inComponentSubType,
kAnyComponentManufacturer, 0, cmpIsMissing };
ComponentDescription compDesc;
comp = FindNextComponent(0, &cd);
while (comp != NULL) {
GetComponentInfo(comp, &compDesc, NULL, NULL, NULL);
if (compDesc.componentFlags & cmpThreadSafe) return true;
comp = FindNextComponent(comp, &cd);
}
return false;
}
/*****************************************************
*
* Do_SomeWorkOnSeparateThread(WindowRef aWindow)
*
* 目的:渡されたウインドウで再生されているムービーをタスク化し、
* コピーして、バックグラウンドスレッドに移動し、
* 上記のDoSomeWorkFunctionsのいずれかを呼び出して作業を実行する
*
* 入力:ウインドウ参照
*
* 戻り値:なし
*/
static OSStatus Do_SomeWorkOnSeparateThread(WindowRef aWindow)
{
WorkerThreadRef worker;
pthread_attr_t attr;
OSStatus status = paramErr;
if (NULL == aWindow) return status;
WindowDataPtr wdr = (WindowDataPtr)GetWRefCon(aWindowRef);
if (NULL == wdr) return status;
Handle cloneHandle = NewHandle(0);
if (NULL == cloneHandle || status = MemError()) return status;
// workerスレッドデータにメモリを割り当てる
worker = calloc(1, sizeof(WorkerThread));
if (NULL == worker) { status = memFullErr; goto Failure; }
// バックグラウンドスレッドからPostEventToQueueを呼び出せるように
// メインイベントキューが必要だが、
// GetMainEventQueueは10.3.xではスレッドセーフでないため、
// ここでこれを設定する
// ただし、GetMainEventQueueは10.4以降では
// スレッドセーフであるため、新しいシステム上では
// あとでこれを柔軟に設定できる
worker->mainEventQueue = GetMainEventQueue();
// オリジナルのムービーをコピーして、その新しいムービーをメインスレッドに
// アタッチし、間違いなくデタッチする
status = PutMovieIntoHandle(wdr->fMovie, cloneHandle);
require_noerr(status, Failure);
status = NewMovieFromHandle(&worker->theMovie, cloneHandle, newMovieActive, NULL);
require_noerr(status, Failure);
status = DetachMovieFromCurrentThread(worker->theMovie);
if (componentNotThreadSafeErr == status) {
// しまった!このムービーは別のスレッドにあるので、エクスポートできない
// まず、安全でないメディアを含んでいるトラックを削除する必要がある
long trackCount = GetMovieTrackCount(worker->theMovie);
long count;
// 安全でないタイプのトラックをすべて削除する
for (count = 1; count <= trackCount; count++) {
Track track = GetMovieIndTrack(worker->theMovie, count);
Media media = GetTrackMedia(track);
SampleDescriptionHandle desc;
OSType theMediaType;
OSType theCodecType;
if (track) {
GetMediaHandlerDescription(media, &theMediaType, NULL, NULL);
desc = (SampleDescriptionHandle)NewHandle(0);
GetMediaSampleDescription(media, 1, desc);
theCodecType = (**desc).dataFormat;
DisposeHandle((Handle)desc);
if (!IsComponentThreadSafe(MediaHandlerType, theMediaType) ||
!IsComponentThreadSafe(decompressorComponentType, theCodecType)) {
DisposeMovieTrack(track);
count--;
}
}
}
// トラックカウントを再度取得する - トラックが残っていない場合は、
// エクスポートを試みてもあまり意味はない
trackCount = GetMovieTrackCount(worker->theMovie);
if (trackCount == 0) goto Failure;
// 再試行する
status = DetachMovieFromCurrentThread(worker->theMovie);
require_noerr(status, Failure);
}
// スレッドをデタッチする
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
IncrementAtomic(&worker->refCount);
// workerスレッドを作成し、何か作業をする
status = pthread_create(&worker->workerThread, &attr, DoSomeWorkFunction, worker);
pthread_attr_destroy(&attr);
require_noerr(status, Failure);
DisposeHandle(cloneHandle);
return noErr;
Failure:
if (cloneHandle) DisposeHandle(cloneHandle);
if (worker->theMovie) DisposeMovie(worker->theMovie);
if (worker) free(worker);
return status;
}
コンポーネントデベロッパへのアドバイスQuickTimeの高度なモジュール設計では、新しいサービスや特定のサービスを提供する新しいコンポーネントを追加することで拡張が可能です。 コンポーネントをアップデートする開発者の方は、次のバージョンをスレッドセーフにしてください。そのために必要な労力は、各コンポーネントのタイプによって異なります。しかし、すべてのコンポーネントタイプに当てはまる一般的なガイドラインがいくつかあります。 1回限りのComponent ManagerリソースAPIの使用前述のように、Resource Managerはスレッドセーフではありませんが、コンポーネントリソースを返す1回限りのComponent Manager呼び出しはスレッドセーフです。 リスト9:悪い例 - Resource Managerを使用しない
// 下記の例は、コンポーネントがリソースの1つを取得するのに使用できない典型的な
// 古いコードである。これは比較的コード量が多く、スレッドセーフではない。
// コンポーネントにこのような古いコードが含まれている場合は、
// コードをアップデートしたほうがよい。
OSErr err;
Handle resource = NULL;
short saveResFile;
short resRef;
saveResFile = CurResFile();
err = OpenAComponentResFile((Component)store->self, &resRef);
if (err == noErr) {
resource = Get1Resource('PICT', 128);
if (resource) {
LoadResource(resource);
DetachResource(resource);
} else {
err = ResError();
if (err == noErr)
err = resNotFound;
}
CloseComponentResFile(resRef);
UseResFile(saveResFile);
}
リスト10:正しい例 - GetComponentResourceを使用 // Component Managerの1回限りのスレッドセーフ機能を使用する。 // この1行は、上記のリストとまったく同じ機能を実行する。 OSErr err; Handle resource = NULL; err = GetComponentResource((Component)store->self, 'PICT', 128, &resource); リスト11:正しい例 - ImageCodecGetInfoを実装
// 'cdci' リソースの例
#define kMyCodecFormatName "My Cool Codec"
// これらのフラグは、コンポーネントの機能に関する情報を指定する
#define kMyDecoFlags (codecInfoDoes32 | codecInfoDoes8)
// これらのフラグは、コンポーネントが生成できる圧縮データのフォーマットと
// コンポーネントが解凍時に処理できる圧縮ファイルのフォーマットを指定する
#define kMyFormatFlags (codecInfoDepth32 | codecInfoDepth40)
// コンポーネントの説明
resource 'cdci' (129) {
kMyCodecFormatName, // Type
1, // Version
0, // Revision level
'MINE', // Manufacturer
kMyDecoFlags, // Decompression Flags
0, // Compression Flags
kMyFormatFlags, // Format Flags
0, // Compression Accuracy
128, // Decomression Accuracy
0, // Compression Speed
200, // Decompression Speed
0, // Compression Level
0, // Reserved
8, // Minimum Height
8, // Minimum Width
0, // Decompression Pipeline Latency
0, // Compression Pipeline Latency
0 // Private Data
};
// ImageCodecGetCodecInfo
// アプリケーションがImage Compression ManagerのGetCodecInfo関数を呼び出すときは
// いつでも、コーデックがImageCodecGetCodecInfo要求を受け取る。
// コンポーネントは、その機能を定義するフォーマット済みの
// CodecInfo構造を返す。
// compressorとdecompressorはこの呼び出しを受け取ることができる。
pascal ComponentResult MyDeco_GetCodecInfo(MyCodecGlobals glob, CodecInfo *info)
{
CodecInfo **tempCodecInfo;
OSErr err = noErr;
if (NULL == info) return paramErr;
err = GetComponentResource((Component)glob->self, codecInfoResourceType,
129, (Handle *)&tempCodecInfo);
if (noErr == err) {
*info = **tempCodecInfo;
DisposeHandle((Handle)tempCodecInfo);
}
return err;
}
これらのAPIに関する詳細は、「流氷通信 21」 を参照してください。 グローバル状態を保存するのにComponent RefConを使用しない一部のコンポーネントは、コンポーネントインスタンス間で共有されているグローバル状態を維持しており、そのためにComponent RefConを使用しています。これは、複数のスレッドからコンポーネントを呼び出すときには機能しなくなります。 コンポーネントが コンポーネント間の動的状態を共有する必要がある場合は、標準的なロック手法を利用して、このグローバル状態へのアクセスを通常どおり保護する必要があります。グローバル状態の初期化を防ぐには、 リスト12:pthread_once関数 int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)) pthread_onceは、初期化コードが一度だけ実行されたことを確認して、1回限りの初期化を実行する。 once_control - PTHREAD_ONCE_INITに初期化する静的変数のポインタ。 init_routine - 次のプロトタイプに対応するCの初期化関数、 void initRoutine(void); ディスカッション: pthread_onceを所定のonce_control引数で最初に呼び出すと、この関数はinit_routineを呼び出し、 初期化が成功したことを記録するonce_controlの値を設定する。成功した初期化が何も実行 しないうちに、同じonce_control引数でpthread_onceを呼び出す。 リスト13:pthread_onceを初期化に使用
#include <pthread.h>
typedef struct {
Handle someGlobalHandle;
long someDynamicValues[ARRAY_SIZE];
...
} SharedGlobals;
typedef struct {
ComponentInstance self;
ComponentInstance delegateComponent;
ComponentInstance target;
OSType codecType;
SharedGlobals *sharedGlob;
...
} ComponentGlobals, *ComponentGlobalsPtr;
static SharedGlobals componentSharedGlobals;
static void InitSharedGlobals(void)
{
componentSharedGlobals.someGlobalHandle = NewHandle(HANDLE_SIZE * sizeof(long));
InitSomeGlobalHandle(&componentSharedGlobals);
InitSomeDynamicValues(&componentSharedGlobals);
}
static SharedGlobals* GetSharedGlobals(void)
{
static pthread_once_t control = PTHREAD_ONCE_INIT;
/* pthread_onceは1回のプロセスで一度だけルーチンを実行する。
このプロセスで任意のスレッドがこのサブルーチンを初めて呼び出すと
パラメータなしで所定のルーチンを実行する。
その後の呼び出しは効果がない。
これが役に立つのは、固有の初期化を1つのスレッドで実行する必要があり、
同期要件を緩和する場合である。*/
pthread_once(&control, InitSharedGlobals);
return &componentSharedGlobals;
}
pascal ComponentResult MyCodec_ImageCodecOpen(ComponentGlobalsPtr glob,
ComponentInstance self)
{
ComponentResult result = noErr;
ComponentDescription cd;
result = GetComponentInfo((Component)self, &cd, NULL, NULL, NULL);
if (result) return result;
if ((glob = (ComponentGlobalsPtr)calloc(1, sizeof(ComponentGlobals))) == NULL)
return memFullErr;
SetComponentInstanceStorage(self,(Handle)glob);
glob->self = self;
glob->codecType = cd.componentSubType;
...
glob->sharedGlob = GetSharedGlobals();
...
return result;
}
定数テーブルを実行可能形式に置くコンポーネントがいくつかの定数テーブルの共有グローバルを必要とする場合(たとえば共有のグローバルを一度割り当て、後で使用するために保存する必要がある場合)は、テーブルを別々に作成し、事前に作成したデータを これは有用な手法で、いくつかのメリットがあります。
リスト14:事前に作成したテーブルを定数としてマーク
static const gReverseBitsTable[256] = {
0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
[..]
};
ターミナルから nm -m を実行すると、これらのテーブルが確かに
TEXTセグメントに置かれている様子が表示される。
% nm -m whatever.o
00000000 (__TEXT,__const) non-external _gReverseBitsTable
テーブル定義に"const"キーワードを使用しなかった場合は、
テーブルがDATAセグメントに置かれ、各プロセスが
それぞれの書き込み可能コピーを取得する。
00000000 (__DATA,__data) non-external _gNonConstReverseBitsTable
コンポーネントスレッドセーフフラグの設定コンポーネントをスレッドセーフにしたら、グローバルコンポーネントフラグでコンポーネントスレッドセーフフラグを必ず設定してください。これはQuickTime(またはコンポーネント情報を要求するアプリケーション)に、コンポーネントがバックグラウンドスレッドから安全に使用できることを示します。 リスト15:コンポーネントスレッドセーフフラグ cmpThreadSafe = 1L << 28 // コンポーネントはスレッドセーフ リスト16:cmpThreadSafeフラグの追加
// 拡張'thng'テンプレート
#define thng_RezTemplateVersion 1
#include <Carbon/Carbon.r>
#include <QuickTime/QuickTime.r>
resource 'thng' (256) {
kSomeQTComponentType, // タイプ
'DEMO', // サブタイプ
'DEMO', // メーカー
0,
0,
0, // - componentHasMultiplePlatformsを使用
0,
'STR ', // 名前タイプ
128, // 名前 ID
'STR ', // 情報タイプ
129, // 情報 ID
0, // アイコンタイプ
0, // アイコン ID
kMyComponentVersion, // バージョン
// 登録フラグ
componentHasMultiplePlatforms |
kOtherComponentRegistrationFlags,
0, // アイコンファミリのリソースID
{
// スレッドセーフフラグを追加
kMyComponentFlags | cmpThreadSafe,
'dlle', // コードリソースタイプ - 発見されたエントリポイント
// シンボル名'dlle'リソース
512, // 'dlle'リソースのID
platformPowerPCNativeEntryPoint,
// プラットフォームタイプ(gestaltComponentPlatform
// からの応答、あるいは、この応答がない場合は
// gestaltSysArchitecture)
};
};
QuickTimeスレッドセーフコンポーネントQuickTime 6.5.2がインストールされているMac OS X 10.3以降では、以下に示すQuickTimeコンポーネントがスレッドセーフです。QuickTimeのバージョンが新しいほど、スレッドセーフなコンポーネントもより多く提供されています。Fiendishthngs(このドキュメントの「ツール」セクション参照)を使って、より新しいバージョンのQuickTimeでの特定のコンポーネントの機能を調べることができます。 重要:DVオーディオデコーダを除いて、Sound Managerのオーディオエンコーダおよびデコーダはすべて、バージョン6.4~6.5.2のQuickTimeではスレッドセーフではありません。バックグラウンドスレッドへ移行する前にムービーのサウンドトラックを削除し、バックグラウンドスレッド上にオーディオを含むムービーファイルをインポートしないようにする必要があります。 最新バージョンのQuickTimeではCore Audioフレームワークを使い、これらの制限の多くを取り除いています。 表1:
表2:
表3:
表4:
表5:
表6:
QuickTime for Windows比較的安全なWindows上では1つのバックグラウンドスレッドからQuickTimeを呼び出すことができますが、複数のバックグラウンドスレッドからQTML (QuickTime Media Layer)を呼び出すのに必要なシステムレベルのサポートはWindowsプラットフォームにはありません。たとえば、QuickDrawのような重要なフレームワークはWindowsではスレッドセーフではありません。 さらに、Windows上ではQuickTimeは完全にシリアライズされており、QuickTimeライブラリへの呼び出しができるのは、一度に1つのスレッドに限られています。あるスレッドがQuickTime処理を実行している場合、QuickTimeのAPIを呼び出しているほかのスレッドは、前の処理が戻ってくるまでブロックされます。 そのため、Windowsで複数のバックグラウンドスレッドからQuickTimeを呼び出すのはお勧めしません。 注:これは、QuickTime for Windows 3.0のリリース以来、 QuickTime for Windowの現在のバージョンまで変わらず残っている問題です。 重要:このテクニカルノートで説明しているスレッドセーフなAPIは、QuickTime for Windows SDKを使用する場合には当てはまりません。 参考資料技術書一覧Mac OS X
PThreadsとマルチスレッディング
ツールFiendishthngs(Mac OS Xユニバーサルバイナリ) - このユーティリティを使うと、システム上にあるすべてのコンポーネントを一覧表示し、ほとんどのタイプのQuickTimeコンポーネントの詳細情報を照会できます。 ドキュメント改訂履歴
掲載日: 2006-02-14 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|