The Media Kit: A BMediaEventLooper Example
The Media Kit Table of Contents     The Media Kit Index

A BMediaEventLooper Example

 スケジュールの発行を扱うためにBMediaEventLooperクラスを使用してnodeの実装を行うのは、非常に簡単な作業です。事実、Be社はBMediaEventLooperをバイパスしないことを推奨しています。この節では、制御ポート及びキューの処理を扱うために、BMediaEventLooperを使用するnodeを生成する例を提示します。

 BMediaEventLooperを使用するnodeを実装する際には、複数のクラスからnodeを派生させなければなりません。BMediaEventLooperはもちろんですが、ユーザがそのnodeを設定できるようにしたいなら、BControllableからも派生させる必要があります。それから、バッファを送受信を行うか否かによってBBufferConsumerBBufferProducerのいずれか或いは両方から派生する必要もあります。そして、もしnodeがtime sourceを提供できるなら、BTimeSourceから派生する必要もあります。

 BMediaEventLooperを使用しても、あなたのnodeが派生する全てのクラスの純粋仮想関数を実装する責任がなくなるわけではありません。同様に、あなたは他のいくつか或いは全ての仮想関数を実装しなければならないでしょう。あなたが得るBMediaEventは、受け取ったメッセージを自動的にキューに入れ、制御ポートを自動的に処理します。これは沢山の退屈で繰り返しばかりの作業を減らし、あなたの作業をとても楽にします。

 例として挙げられたコードを見てみましょう。このnodeはBBufferConsumerBControllable及びBMediaEventLooperから派生し、ディスクファイルにメディアイベントのログを書込みます。他のparameterの間にあるこのnodeのlatencyはユーザによって設定可能であるため、あなたはこのnodeをシステムに異った負荷をかけるシミュレーションに使用することができます。

 この節は、サンプルコードの鍵となる部分のみ示します。もし完全なソースコードを使用したいなら、Beのウェブサイトの ftp://ftp.be.com/pub/samples/media_kit/LoggingConsumer.zip からダウンロードすることができます。


The Constructor and Destructor

 それでは、本当の最初から(始めるのにとても良いところから)始めましょう。コンストラクタは、全てのサブクラスの初期化を行わなければなりません :


Constructor

   LoggingConsumer::LoggingConsumer(const entry_ref &logFile)
      :   BMediaNode("LoggingConsumer"),
         BBufferConsumer(B_MEDIA_UNKNOWN_TYPE),
         BControllable(),
         BMediaEventLooper(),
         mLogRef(logFile), mWeb(NULL),
         mLateBuffers(0),
         mLatency(50 * 1000),      // default to 50 milliseconds
         mSpinPercentage(0.10),      // default to spinning 10% of total latency
         mPriority(B_REAL_TIME_PRIORITY),
         mLastLatencyChange(0),
         mLastSpinChange(0),
         mLastPrioChange(0) {
      mLogger = new LogWriter(logFile);
   }

 BMediaNodeBBufferConsumerBControllable及びBMediaEventLooperのコンストラクタは、それぞれのサブクラスを標準的な方法で初期化するために、ここで全て呼び出されます。加えて、一組の局所変数も初期化されます。

 最後に、ログファイルへの書き込みを実際に扱うスレッドが開始されます。これは、LogWriterクラスのコンストラクタによって行われます。このクラスは、ログファイルにアクセスするために使用されます。LogWriterクラスが動作する方法の詳細について、これ以上は立ち入りません。LogWriterはファイルにログを書込むメッセージに影響するいくつかのpublicな変数と、ログファイルに新しいentryを実際に書込むLog()関数を持っている、とだけ言っておきます。


Destructor

   LoggingConsumer::~LoggingConsumer() {
      BMediaEventLooper::Quit();
      SetParameterWeb(NULL);
      mWeb = NULL;
      delete mLogger;
   }

 上記のデストラクタは、BMediaEventLooperの制御ループが停止することによって開始されます。これはBMediaEventLooper::Quit()の呼び出しによって行われます。次に、BControllable::SetParameterWeb()を呼び出すことでparameter webを削除します。私たちは、parameter webを私たち自身で削除する代りに、それにNULLをセットします。なぜなら、これによってnodeがクリーンアップを扱えるようになるからです—この場合、nodeがwebを削除します。

 全てが終了した後、ログを取るためのスレッドを削除します。もしログへの書き込みを起こすようなイベントが到着した場合に起こりうる競合状態を避けるために、これは最後に行われます。BMediaEventLooperが終了されるまで待つことで、この潜在的な問題を回避できます。


BMediaNode Functions

 私達は、BMediaNodeクラスからのいくつかの関数を実装する必要もあります。BMediaNode::AddOn()以外に、BMediaEventLooperもこれらの関数のデフォルト実装を提供します。多くの場合、デフォルトの実装が使用できます。ひとつのカスタム実装を見れば、どう実装するか理解することができます。


AddOn()

   BMediaAddOn* LoggingConsumer::AddOn(int32 *) const {
      return NULL;
   }

 BMediaNode::AddOn()関数の仕事は、nodeのインスタンスを生成するBMediaAddOnオブジェクトへのポインタを返すことです。この場合、私達はアプリケーションに内包されているため、NULLを返します。しかし、もしnodeがmedia node add-onによって生成されるなら、そのオブジェクトへのポインタをここで返します。


Other BMediaNode Functions

   void  LoggingConsumer::SetRunMode(run_mode mode) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      logMsg.runmode.mode = mode;
      mLogger->Log(LOG_SET_RUN_MODE, logMsg);
   
      BMediaEventLooper::SetRunMode(mode);
   }

 BMediaNode::SetRunMode()関数は、nodeの実行モードの変更を扱います。BMediaEventLooperクラスはこれを自動的に扱いますが、必要があればこの実装を強化することができます。

 実装した他のBMediaNode関数の全てに於ても同じですが、この場合、私達は単純に呼び出しをログに落とし、その後BMediaEventLooperの実装に従います。

 現在の時刻を得るためにTimeSource()->Now()を呼び出します。これは、発生したイベントのタイプの記述とともにログに挿入されます。

 同様に動作するよう実装した他のBMediaNode関数は、要求をログに書込んだ後、BMediaEventLooperの実装に従います。


BControllable Functions

 私達の実装するBControllable関数は、ユーザによるnodeの設定を可能にします。これらの関数は、誰かがparameterのうち一つの現在の値を知る必要がある時や、parameterの値が変更を必要とする時に呼び出されます。


GetParameterValue()

   status_t LoggingConsumer::GetParameterValue(int32 id,
            bigtime_t* last_change, void* value, size_t* ioSize) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      logMsg.param.id = id;
      mLogger->Log(LOG_GET_PARAM_VALUE, logMsg);
   
      if (*ioSize < sizeof(float)) return B_ERROR;
   
      switch (id) {
      case LATENCY_PARAM:
         *last_change = mLastLatencyChange;
         *((float*) value) = mLatency/1000;      // the BParameter reads milliseconds, not microseconds
         *ioSize = sizeof(float);
         break;
   
      case CPU_SPIN_PARAM:
         *last_change = mLastSpinChange;
         *((float*) value) = mSpinPercentage;
         *ioSize = sizeof(float);
         break;
   
      case PRIORITY_PARAM:
         *last_change = mLastPrioChange;
         *((int32*) value) = mPriority;
         *ioSize = sizeof(int32);
         break;
   
      default:
         return B_ERROR;
      }
   
      return B_OK;
   }

 BControllable::GetParameterValue()関数は、nodeの設定可能なparameterのうち一つの現在の値を得るために呼び出されます。このparameterは、引数idのID番号によって指定されます。valueは、値が保存されているメモリバッファのポインタを示し、ioSizeはバッファの大きさを示します。私達の仕事は、parameterが最後に変更された時間をlast_changeに、新しい値をvalueポインタによって示されるバッファに、そして返された値の実際の大きさをioSizeに保存することです。

 ログを取るためのnodeは、ログファイルに要求を書込むことから始まります。

 その後、関数を呼び出した者によって提供される空間が結果に対して十分大きいことを確認するためにチェックすることで、現実の実装が始まります。このnodeでは全ての値が4バイトであるためチェックが簡単ですが、あなたがnodeを書く場合はparameter毎のチェックをしなければならないでしょう。

 それから、idに基づいて、(webが値が変化したことを示す適切なメッセージを受け取った時に設定される)parameterの値の最後の変化をキャッシュした値が、結果に格納されます。私達は、BParameterWebの機能を間もなく見ることになります。


SetParameterValue()

   void  LoggingConsumer::SetParameterValue(int32 id,
            bigtime_t performance_time, const void* value, size_t size) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      logMsg.param.id = id;
      mLogger->Log(LOG_SET_PARAM_VALUE, logMsg);
   
      // if it>s one of our parameters, enqueue a "set parameter" event for handling at the appropriate time
      switch (id) {
      case LATENCY_PARAM:
      case CPU_SPIN_PARAM:
      case PRIORITY_PARAM:
         {
            media_timed_event event(performance_time,
                  BTimedEventQueue::B_PARAMETER, (void*) value,
                  BTimedEventQueue::B_NO_CLEANUP, size, id, NULL);
            EventQueue()->AddEvent(event);
         }
         break;
   
      default:      // do nothing for other parameter IDs
         break;
      }
      return;
   }

 BControllable::SetParameterValue()は、parameterの値を変更する要求が生成されるときに呼び出されます。通常、まず要求をログに取ります。

 この関数の要点は、switch構文です。これは、要求に対応するmedia_timed_eventをエンキュー(enqueue)します。私達は、変化が生じたperformance time及びparameterの値が変更される時に必要とされる他の全てのparameterによって、新しいmedia_timed_eventのインスタンスを生成します。

 それから、BTimedEventQueue::AddEvent()を呼び出すことでBMediaEventLooperのキューにこれを挿入します。このキューは、BMediaEventLooper::EventQueue()によって返されます。performance_timeによって指定された時間が来たら、これは自動的にキューから削除され、BMediaEventLooper::HandleEvent()に従って送られます。

 このイベントは、BTimedEventQueue::B_PARAMETERのタイプを与えられます。このタイプは、parameterの変更イベントに使用されます。


BBufferConsumer Functions

 次に、私達が実装しなければならないBBufferConsumerに来ました。これらの関数は実際に、バッファの到着と、producerとの交渉を扱います。


HandleMessage()

   status_t LoggingConsumer::HandleMessage(int32 message, const void *data,
            size_t size) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_HANDLE_MESSAGE, logMsg);
   
      return B_ERROR;
   }

 HandleMessage()は、もしBMediaEventLooperを使用している場合は決して呼び出してはいけません(もし呼び出したら、この継承レベルでは扱えないメッセージを受け取ります)。したがって、私達はそれをログに書き込み、エラーを返します。


AcceptFormat()

   status_t LoggingConsumer::AcceptFormat(const media_destination &dest,
            media_format* format) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_ACCEPT_FORMAT, logMsg);
   
      if (dest != mInput.destination) return B_MEDIA_BAD_DESTINATION;
   
      return B_OK;
   }

 AcceptFormat()は、与えられたdestinationにとって指定されたフォーマットが受け入れられるものかどうかを確認するために呼び出されます。あなたは、フォーマットを見てこれを判断するよう実装する必要があります。もしそのフォーマットに対応していなければ、エラーを報告します。

 この実装例では、呼び出しをログに書込んだ後、実際にそのdestinationが有効かどうかを確認するためにチェックを行います。このnodeの例では、ただ一つの入力だけが許されているため、これは簡単にチェックできます。もし入力の配列やリンクされたリストを持つなら、それらを全てチェックする必要があるでしょう。もしdestinationが認識されなければ、B_MEDIA_BAD_DESTINATIONを返して下さい。

 もしフォーマットが受け入れられるなら、B_OKを返して下さい。この場合、全てのフォーマットが受け入れ可能です。


GetNextInput()

   status_t LoggingConsumer::GetNextInput(int32* cookie,
            media_input* out_input) {
      if (0 == *cookie) {
         mInput.format.type = B_MEDIA_UNKNOWN_TYPE;      // accept any format
         *out_input = mInput;
         *cookie = 1;
         return B_OK;
      }
      else return B_BAD_INDEX;
   }

 GetNextInput()関数は、あなたのnodeが提供する全ての入力を繰り返し処理するために使用されます。out_inputに、要求された入力のmedia_input構造体のコピーを渡して下さい。cookieは、返される入力を指定するために使用されます。呼び出し側は、最初にGetNextInput()を呼び出した際に、0へのポインタを指定します。そしてこの値を、入力のリストのどこをスキャンしているか追跡するために適切に設定することができます。

 このケースでは使用できる入力が一つだけのため、もし*cookieが(リストの終端に到達したことを示す)0ではない値であればB_BAD_INDEXを返します。そうでなければ、out_inputにデータが格納され、cookieが最初の入力がスキャンされたことを示す1に変更されます。


DisposeInputCookie()

   void LoggingConsumer::DisposeInputCookie(int32 /*cookie*/ ) {
      /* handle disposing of your cookie here, if necessary */
   }

 もしcookieが単純な整数の値でないが、しかし実際のポインタであれば、DisposeInputCookie()の実装の中でそれを破棄して下さい。このケースではcookieが整数であるため、なにもすることはありません。


BufferReceived()

   void LoggingConsumer::BufferReceived(BBuffer* buffer) {
      bigtime_t bufferStart = buffer->Header()->start_time;
      bigtime_t now = TimeSource()->Now();
      bigtime_t how_early=bufferStart-EventLatency()-SchedulingLatency()-now;
   
      log_message logMsg;
      logMsg.now = now;
      logMsg.buffer_data.start_time = bufferStart;
      logMsg.buffer_data.offset = how_early;
      mLogger->Log(LOG_BUFFER_RECEIVED, logMsg);
   
      if (B_MEDIA_PARAMETERS == buffer->Header()->type) {
         ApplyParameterData(buffer->Data(), buffer->SizeUsed());
         buffer->Recycle();
      }
      else {
         status_t err;
         media_timed_event event(buffer->Header()->start_time,
                  BTimedEventQueue::B_HANDLE_BUFFER,
                  buffer, BTimedEventQueue::B_RECYCLE_BUFFER);
         err = EventQueue()->AddEvent(event);
   
         if (err) buffer->Recycle();
      }
   }

 BufferReceived()は、アクティブな接続にバッファが到着する際に呼び出されます。最初の仕事は、適切な時間にそれらが処理されるようキューに入れることです。到着時刻と到着までの時間に関する情報を含め、バッファの到着をログに取ることから始めます。

 B_MEDIA_PARAMETERSタイプのバッファは、特別なものとして扱われなければなりません(バッファ内の各々のparameter変化は、リスト化された自分自身のperformance timeを持っています)。そのため、私達はバッファがB_MEDIA_PARAMETERSバッファではないかを調べるために、バッファのヘッダをチェックします。もしバッファがB_MEDIA_PARAMETERSであれば、parameter変化を利用するためにApplyParameterData()を呼び出します。これによって、バッファは直ちにリサイクルされ、再使用可能となります。

 もし他のタイプのバッファであれば、新しいバッファを参照する新しいmedia_timed_eventが生成されます。イベントのタイプはBTimedEventQueue::B_HANDLE_BUFFER、クリーンアップモードはBTimedEventQueue::B_RECYCLE_BUFFERとなるので、バッファはイベントが処理された後に自動的にリサイクルされます。その後、新しいイベントがキューに追加されます。

 もしバッファをキューに入れようとした際にエラーが生じたら、バッファはキューにありませんので、BBuffer::Recycle()を呼び出して私達自身がそれをリサイクルする必要があります。もしこれを行わなければ、バッファはリークします。メモリのリークは治療法の知られていない厄介な問題ですので、適切な予防を行って下さい。


ProducerDataStatus()

   void LoggingConsumer::ProducerDataStatus(
            const media_destination & for_whom, int32 status,
            bigtime_t at_performance_time) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      logMsg.data_status.status = status;
      mLogger->Log(LOG_PRODUCER_DATA_STATUS, logMsg);
   
      if (for_whom == mInput.destination) {
         media_timed_event event(at_performance_time,
               BTimedEventQueue::B_DATA_STATUS, &mInput,
               BTimedEventQueue::B_NO_CLEANUP, status, 0, NULL);
         EventQueue()->AddEvent(event);
      }
   }

 ProducerDataStatus()は、上流のproducerがバッファの送出を開始・停止するといった変化を生じた際に呼び出されます。これによってあなたのnodeは、バッファが予期されるか否かに基づいて、その動作効率を最適化することができます。呼び出しをログに書込んだ後、この要求はキューに入れられます。指定されたdestinationであるfor_whomがnodeの実際の入力に一致する場合のみ、要求がキューに追加されるということに留意して下さい。もし一致しなければ、要求は無視されます。

 イベントはBTimedEventQueue:B_DATA_STATUSタイプを与えられ、クリーンアップはしないよう要求されます。


GetLatencyFor()

   status_t LoggingConsumer::GetLatencyFor(const media_destination &for_whom,
            bigtime_t* out_latency, media_node_id* out_timesource) {
      if (for_whom != mInput.destination) return B_MEDIA_BAD_DESTINATION;
   
      *out_latency = mLatency;
      *out_timesource = TimeSource()->ID();
      return B_OK;
   }

 GetLatencyFor()の仕事は、特定のdestinationについてのlatencyを報告することです。入力がそのdestinationを持つかどうかを見ることで全ての入力をチェックし、destinationが有効であるということを確認して下さい。もし全く一致しなければ、B_MEDIA_BAD_DESTINATIONを返して下さい。

 そうでなければ、B_OKを返す前に、入力に対する内部的で下流のlatency(しかしscheduling latencyではない)を追加し、out_latencyにその値を保存し、out_timesourceにnodeのtime sourceを保存して下さい。このnodeの例では、latencyはmLatencyであり、これはユーザが設定できるparameterです。

 このケースでは、このnodeには(producerではないため)下流のlatencyがないので、カウントされるlatencyは内部のlatency、つまりmLatencyだけです。これは、このnodeに於けるユーザが設定可能なオプションです。


Connected()

   status_t LoggingConsumer::Connected(
         const media_source& producer,
         const media_destination& where,
         const media_format& with_format,
         media_input* out_input) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_CONNECTED, logMsg);
   
      if (where != mInput.destination) return B_MEDIA_BAD_DESTINATION;
   
      // calculate my latency here, because it may depend on buffer sizes/durations, then
      // tell the BMediaEventLooper how early we need to get the buffers
      SetEventLatency(mLatency);
   
      // record useful information about the connection, and return success
      mInput.source = producer;
      *out_input = mInput;
      return B_OK;
   }

 BBufferConsumer::Connected()は、指定されたproducerと、nodeのdestinationであるwhereとの間の接続が確立される際に呼び出されます。この接続は、ログファイルに書込まれます。

 もしdestinationが有効でなければ、B_MEDIA_BAD_DESTINATIONが返されます。そうでなければ、接続のlatencyが計算され、latencyをBMediaEventLooperに伝えるためにBMediaEventLooper::SetEventLatency()が呼び出されます。これは重要なステップです。なぜなら、BMediaEventLooperクラスがスケジュールを扱いますが、それにはlatencyを知る必要があるからです!

 このケースでは、latencyの計算に関して大したことはしません。現実世界のnodeには既知のユーザ設定可能な値であるmLatencyが存在し、あなたは意味のある範囲で可能な限り正確に、これを計算する必要があります。

 最後に、接続についての重要な情報が(例えばmedia_sourceproducerといった)入力に記録されます。そして入力へのポインタがout_inputに詰め込まれます。


Disconnected()

   void LoggingConsumer::Disconnected(
            const media_source &producer,
            const media_destination &where) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_DISCONNECTED, logMsg);
   
      ::memset(&mInput, 0, sizeof(mInput));
   }

 BBufferConsumer::Disconnected()は、sourceであるproducerと、destinationであるwhereとの間の接続が終了される際に呼び出されます。この呼び出しはログファイルに書込まれ、その後、nodeの接続を切断するために必要なことを全て行います。

 このケースでは、接続によって使用されるmedia_inputレコードを消去します。実装によっては、nodeは他の作業を行う必要があるかも知れません。


FormatChanged()

   status_t LoggingConsumer::FormatChanged(
            const media_source &producer,
            const media_destination &consumer,
            int32 change_tag,
            const media_format& format) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_FORMAT_CHANGED, logMsg);
   
      return B_OK;
   }

 BBufferConsumer::FormatChanged()は、与えられたproducerとconsumerとの間の接続によって伝達されるバッファに対して使用されるmedia_formatが変化した際に、必ず呼び出されます。これは、nodeがBBufferConsumer::RequestFormatChange()を呼び出したことに対応して呼び出されます。つまり、一旦要求された変化が生じると、これはそのことをあなたに知らせるために呼び出されるのです。change_tagは、RequestFormatChange()によって返される指定された変更タグに一致します。また、formatは接続に対して交渉された新しいmedia_formatを示します。

 一旦あなたのFormatChanged()関数が返ると、これ以後のバッファは新しいフォーマットになり、この時点であなたはこの変更に対して準備をすべきです。このケースでは、実際には受け取ったバッファの内容について心配しなくてよいため、私達は生じた変更をログに書き込んでB_OKを返す以外、なにもすることはありません。


SeekTagRequested()

   status_t LoggingConsumer::SeekTagRequested(
            const media_destination& destination,
            bigtime_t in_target_time,
            uint32 in_flags,
            media_seek_tag* out_seek_tag,
            bigtime_t* out_tagged_time,
            uint32* out_flags) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_SEEK_TAG, logMsg);
   
      return B_OK;
   }

 BBufferConsumer::SeekTagRequested()は、指定されたtarget_timeに対応するseekタグを得るために、Media Serverから呼び出されます。あなたのnodeは、受け取ったバッファに組み込まれたこれらのタグをキャッシュする必要がありますので、この関数が呼び出された際にはこれらのタグを返すことができます。

 この例ではseekタグをサポートしないため、要求はログに書込まれてB_OKが返されます。更に詳しい情報は、BBufferConsumer::SeekTagRequested()をご覧下さい。


BMediaEventLooper Functions

 nodeの核となるのは、BMediaEventLooperの多様な仮想関数を実装することです。


NodeRegistered()

   void LoggingConsumer::NodeRegistered() {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_REGISTERED, logMsg);
   
      SetPriority(mPriority);
      Run();
   
      mInput.destination.port = ControlPort();
      mInput.destination.id = 0;
      mInput.node = Node();
      ::strcpy(mInput.name, "Logged input");
   
      mWeb = build_parameter_web();
      SetParameterWeb(mWeb);
   }

 BMediaRoster::RegisterNode()が呼び出される際に呼び出されるBMediaEventLooper::NodeRegistered()の仕事は、新たに登録されたnodeのセットアップを行うことです。

 この例では、呼び出しをログに書込むことから始め、BMediaEventLooper::SetPriority()の呼び出しによってスレッドの優先度を設定し、その後BMediaEventLooper::Run()を呼び出すことで制御ポートを動作させます。このスレッドはイベントを処理し、適切な時間にそれらをBMediaEventLooper::HandleEvent()に渡します。

 一旦これが終了すると、私達は入力を初期化できます。このケースでは入力は一つだけですが、あなたのnodeは複数の入力を持つかも知れません。入力のmedia_destinationのポートは、BMediaEventLooper::ControlPort()に設定されます。これはMedia Kitに対して、入力と更新するためにメッセージを送るポートがどれかを伝えます。destinationのIDは、nodeによって指定された値—このケースでは0に設定されますが、あなたのnodeはサポートする入力それぞれに対して異なった値を使用して下さい。そして、入力のnodeにはNode()の結果が設定されます。私達はまた、入力の名前を設定します。この名前は、各々の入力ごとにユニークであるべきです。

 最後に、nodeのparameter webがbuild_parameter_web()の呼び出しによってコンストラクトされ、私達はwebを確立するためにBControllable::SetParameterWeb()を呼び出します。


Start(), Stop(), Seek(), TimeWarp()

 これらの関数は、対応するイベントが発生した際に呼び出されます。イベントを扱う時間が来た際に、HandleEvent()は適切なメッセージを受け取るよう実装されているため、普通はこれらを実装する必要はありません。事実上、これらの関数を実装するのは、これらのイベントのうちの一つがいつキューに挿入されたかを知りたい場合のみです。

 もしこれらの実装を敢えて選択するなら、基底関数を呼び出して下さい。例として、Start()の実装を見てみましょう :

   void LoggingConsumer::Start(bigtime_t performance_time) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_START, logMsg);
   
      BMediaEventLooper::Start(performance_time);
   }

 このコードは結果をログに書き込み、その後にBMediaEventLooper::Start()を呼び出します。これは要求をキューに入れます。これがHandleEvent()でどのように扱われるか、下記に見ることができます。


HandleEvent()

 これはnodeの核心部分です。BMediaEventLooperがキューに入れられたイベントが処理される時刻になったことを検知すると、すぐにHandleEvent()が呼び出されます。あなたはキューから削除されるようにこれらのイベントを扱うよう、この関数を実装するべきです。あなたの実装の特性は、あなたのnodeが何をするかに依存します。

   void LoggingConsumer::HandleEvent(const media_timed_event *event,
            bigtime_t /* lateness */, bool /* realTimeEvent */) {
      log_message logMsg;
      logMsg.now = TimeSource()->Now();
      mLogger->Log(LOG_HANDLE_EVENT, logMsg);

 最初に、私達の例としてはお決まりの処理として、呼び出しがログに書込まれます。次に、下記に見られるように、私達のnodeがサポートする多様なイベントタイプをチェックして扱うための長いswitch命令がやってきます。

      switch (event->type) {
      case BTimedEventQueue::B_HANDLE_BUFFER:
         {
            BBuffer* buffer=const_cast<BBuffer*>((BBuffer*) event->pointer);
            if (buffer) {
               media_header* hdr = buffer->Header();
               if (hdr->destination == mInput.destination.id) {
                  bigtime_t now = TimeSource()->Now();
                  bigtime_t perf_time = hdr->start_time;
   
                  bigtime_t how_early = perf_time - mLatency - now;
   
                  logMsg.buffer_data.start_time = perf_time;
                  logMsg.buffer_data.offset = how_early;
                  logMsg.buffer_data.mode = RunMode();
                  mLogger->Log(LOG_BUFFER_HANDLED, logMsg);
   
                  if ((RunMode() != B_OFFLINE) &&            // lateness doesn>t matter in offline mode...
                     (RunMode() != B_RECORDING) &&      // ...or in recording mode
                     (how_early < 0)) {
                     mLateBuffers++;
                     NotifyLateProducer(mInput.source, -how_early, perf_time);
                  }
                  else {
                     // replace this with appropriate code for your node
                     bigtime_t spin_start = ::system_time();
                     bigtime_t spin_now = spin_start;
                     bigtime_t usecToSpin = bigtime_t(mSpinPercentage / 100.0 * mLatency);
                     while (spin_now - spin_start < usecToSpin) {
                        for (long k = 0; k < 1000000; k++) { /* intentionally blank */ }
                        spin_now = ::system_time();
                     }
                  }
   
                  if ((B_OFFLINE == RunMode()) && (B_DATA_AVAILABLE == mProducerDataStatus)) {
                     status_t err = RequestAdditionalBuffer(mInput.source, buffer);
                     if (err) {
                        logMsg.error.error = err;
                        mLogger->Log(LOG_ERROR, logMsg);
                     }
                  }
               }
               else {
                  /* wrong destination! */
               }
   
               buffer->Recycle();
            }
         }
         break;

 最初のcaseは、BTimedEventQueue::B_HANDLE_BUFFERイベントです。このイベントは、入ってきたバッファを扱う時間になる際に受け取られます。

 バッファは、イベントのポインタフィールドをBBufferのポインタにキャストすることで得られます。もしそれがNULLであれば、なにも行われません。そうでなければ、バッファのヘッダが掴まれてhdrにしまい込まれます。もしdestinationが不正であれば、なにもしません(これはなにも生じませんが、用心に越したことはありません)。

 もしdestinationが適正であれば、バッファを処理し始める時となります。バッファのperformance timeからlatencyと現在のperformance timeを引くことで、バッファがいつ到着するかを計算します。

 それから、バッファの動作がログに書込まれます(このステップは、もちろんこの特定のnode例に限定されたものです)。

 もしバッファが遅れ(how_earlyが負の値であり)、B_OFFLINEモードでもB_RECORDINGモードでもなければ、私達はバッファを無視し、producerにバッファが遅れることをBBufferConsumer::NotifyLateProducer()呼び出しによって通知します。これによってproducerは、以後のバッファの遅れを避けるためにその動作を調整することができます。B_OFFLINE及びB_RECORDINGモードに於ける遅れは容認されます。なぜならこれらのケースでは、遅れは問題にならないからです。

 もしバッファが予定通りであれば、私達はバッファを処理します。この例では、おおよそlatencyに対応する処理時間を無駄にします。実際のnodeでは、このコードをバッファを扱うコード(スクリーンへの表示、スピーカーでの演奏、或いは適切なこと全て)に置き換えて下さい。

 もしnodeがB_OFFLINEモードであり、キャッシュされたproducerのdata statusが(producerが我々に送るバッファが更に存在することを意味する)B_DATA_AVAILABLEであれば、私達はproducerに対して更にバッファを扱う準備ができていることを伝えるために、BBufferConsumer::RequestAdditionalBuffer()を呼び出さなければなりません。もしこの関数の呼び出しに失敗すれば、演奏は非常にひどいものになるでしょう。もしRequestAdditionalBuffer()が失敗した場合、エラーがログに記録されます。

 一度バッファの処理が終了したら、また将来使用できるようにBBuffer::Recycle()を呼び出します。

 次に生じうるイベントは、BTimedEventQueue::B_PARAMETERです。これはparameterの値が変更されたことを示します。

      case BTimedEventQueue::B_PARAMETER:
         {
            size_t dataSize = size_t(event->data);
            int32 param = int32(event->bigdata);
            logMsg.param.id = param;
   
            if (dataSize >= sizeof(float)) switch (param) {
            case LATENCY_PARAM:
               {
                  float value = *((float*) event->user_data);
                  mLatency = bigtime_t(value* 1000);
                  mLastLatencyChange = logMsg.now;
   
                  SetEventLatency(mLatency);
   
                  SendLatencyChange(mInput.source, mInput.destination, EventLatency() + SchedulingLatency());
                  BroadcastNewParameterValue(logMsg.now, param, &value, sizeof(value));
   
                  logMsg.param.value = value;
                  mLogger->Log(LOG_SET_PARAM_HANDLED, logMsg);
               }
               break;

 もしlatencyのparameterが変更になったら、私達は新しいlatencyをmLatencyに、変更が生じた時間をmLastLatencyChangeに記録し、制御スレッドにlatencyの変更を知らせるためにBMediaEventLooper::SetEventLatency()を呼び出します。

 加えて、新しいlatencyはBBufferConsumer::SendLatencyChange()の呼び出しによってproducerに送られます。これによって、producerは処理が荒れ狂うこと(或いは新しいlatencyのせいで脱落すること)を避けるために、自らの動作を適切に変更することができます。また、変更を監視しているもの全てとparameterの値の変化を共有するために、BControllable::BroadcastNewParameterValue()が呼び出されます。

 最後に、変化をログとしてディスクに書込みます。

            case CPU_SPIN_PARAM:
               {
                  float value = *((float*) event->user_data);
                  mSpinPercentage = value;
                  mLastSpinChange = logMsg.now;
                  BroadcastNewParameterValue(logMsg.now, param, &value, sizeof(value));
                  logMsg.param.value = value;
                  mLogger->Log(LOG_SET_PARAM_HANDLED, logMsg);
               }
               break;

 同様に、もしCPU spin parameterが変更されたら、私達は新しい値と変更の生じた時間を記録し、それからその変更を世界に広く伝え、変更をログに取ります。これはBMediaEventLooperが関心を持つことに全く対応していないため、追加すべき処理はありません。

            case PRIORITY_PARAM:
               {
                  mPriority = *((int32*) event->user_data);
                  SetPriority(mPriority);
   
                  mLastPrioChange = logMsg.now;
                  BroadcastNewParameterValue(logMsg.now, param, &mPriority, sizeof(mPriority));
                  logMsg.param.value = (float) mPriority;
                  mLogger->Log(LOG_SET_PARAM_HANDLED, logMsg);
               }
               break;

 優先度のparameterが変更された際は、制御スレッドに新しい優先度を伝えるためにBMediaEventLooper::SetPriority()を呼び出します。決してスレッドの優先度を直接変更してはいけません。なぜなら、優先度はnodeの機能に影響しますし、この変更に基づいて行われなければならない他の変更があるからです。

 新しい値は変更時間とともに保存され、その値は広く伝達され、そしてログのentryはディスクに記録されます。

            default:
               mLogger->Log(LOG_INVALID_PARAM_HANDLED, logMsg);
               break;
            }
         }
         break;

 ここで、存在しないparameterが変更された場合に対処しましょう。このエラー状態は、ディスクにログとして記録されます。もしそれが決して起こらないとしても、あなたはこの可能性を丁重に扱って下さい。

 扱われる必要のある次のイベントタイプは、BTimedEventQueue::B_STARTBTimedEventQueue::B_STARTです。これはバッファの処理を開始する時間になると受け取られます。

      case BTimedEventQueue::B_START:
         mLogger->Log(LOG_START_HANDLED, logMsg);
         break;

 この例のnodeでは、開始の要求を単にログとして記録します。あなたのnodeでは、バッファを処理するための準備に必要なコードを挿入して下さい。もしnodeがproducerであれば、この時点でバッファの送信を始めて下さい。consumerにバッファを送信していることを知らせるために、かならずBBufferProducer::SendDataStatus()を呼び出して下さい。

 次のイベントタイプはBTimedEventQueue::B_STOPBTimedEventQueue::B_STOPです。これは、バッファの処理を停止する時が来たら受け取られます :

      case BTimedEventQueue::B_STOP:
         mLogger->Log(LOG_STOP_HANDLED, logMsg);
         EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true,
               BTimedEventQueue::B_HANDLE_BUFFER);
         break;

 nodeを停止することは、現在までに受け取ったがまだ処理されていない全てのバッファが無視されることを意味します。このため、イベントキューから全てのBTimedEventQueue::B_HANDLE_BUFFERイベントが消去されます。もしnodeが停止する際に行う必要のある他の処理があれば、これはそれらを扱う場となります。

 もしnodeがproducerなら、もう到着するバッファがないということをconsumerに知らせるために、ここでBBufferProducer::SendDataStatus()を呼び出すべきだということに留意して下さい。

 BTimedEventQueue::B_SEEK要求は、nodeのメディア上でseek処理が行われる際に受け取られます。

      case BTimedEventQueue::B_SEEK:
         mLogger->Log(LOG_SEEK_HANDLED, logMsg);
         break;

 ここでseek処理を扱って下さい。このnodeでは、seek要求をログに取ります。

 BTimedEventQueue::B_WARP要求は、nodeでtime warp処理を行う際に受け取られます。

      case BTimedEventQueue::B_WARP:
         mLogger->Log(LOG_WARP_HANDLED, logMsg);
         break;

 この例では、要求をログに取ります。

 BTimedEventQueue::B_DATA_STATUSイベントは、producerのBBufferProducer::SendDataStatus()関数が呼び出される際に受け取られます。当然ながら、このイベントを扱う必要があるのはnodeがconsumerの時だけです。

      case BTimedEventQueue::B_DATA_STATUS:
         mProducerDataStatus = event->data;
         logMsg.data_status.status = event->data;
         mLogger->Log(LOG_DATA_STATUS_HANDLED, logMsg);
         break;

 このnodeでは、producerの現在のdata status(メンバ変数 [データメンバ] のmProducerDataStatusに格納されている)を記録します。この情報は、バッファを待つかどうかを決定するために使用されます。あなたのnodeは、もしproducerがバッファの送信を停止したら、自分の優先度を変更するか他の最適化を行いたいでしょうし、送信が再開されたら優先度を上げたいと願うでしょう。私達はこの変更もログに取ります。

      default:
         logMsg.unknown.what = event->type;
         mLogger->Log(LOG_HANDLE_UNKNOWN, logMsg);
         break;
      }
   }

 最後に、私達は受け取った理解できないメッセージ全てをログに取ります。一般的に、nodeが扱うよう準備されていないメッセージは無視することができます。


Creating a Parameter Web

 parameter webはMedia Kitに対して、Media Kitが利害関係を持つユーザ設定可能な多種のオプションを記述し、またそれらがどうやってユーザインターフェイスに現れるかを記述します。この情報は、BMediaRoster::StartControlPanel()によってインスタンスを生成された初期設定パネルを経由してnodeを設定する際に、ユーザが見るインターフェイスを描画するためにmedia themeによって使用されます。

 BParameterWebの生成は単純です。parameterに関連する論理的なグループであるBParameterGroupを生成することから始め、その後に生成可能なparameterの様々なタイプのために適切なBParameterGroupの関数を呼び出してそれぞれのグループにparameterを挿入します。

 BParameterGroup::MakeNullParameter()は、インターフェイスに現れるラベルやその他の設定できないcontrolを生成するために使用することができます。

 BParameterGroup::MakeContinuousParameter()は、浮動小数点の値でparameterを生成します。通常、浮動小数点の値はスライダのcontrolを使用するためのセットです。

 BParameterGroup::MakeDiscreteParameter()は、不連続な値を取りうるセットとともにparameterを生成します。いくつかのthemeはラジオボタンやリストを使用しますが、通常これらはポップアップメニューとして表示されます。

   static BParameterWeb* build_parameter_web() {
      BParameterWeb* web = new BParameterWeb;
   
      BParameterGroup* mainGroup = web->MakeGroup("LoggingConsumer Parameters");
      BParameterGroup* group = mainGroup->MakeGroup("Latency Control");
      BParameter* nullParam = group->MakeNullParameter(INPUT_NULL_PARAM, B_MEDIA_NO_TYPE, "Latency", B_GENERIC);
      BParameter* latencyParam = group->MakeContinuousParameter(LATENCY_PARAM, B_MEDIA_NO_TYPE, "",
         B_GAIN, "msec", 5, 100, 5);
      nullParam->AddOutput(latencyParam);
      latencyParam->AddInput(nullParam);
   
      group = mainGroup->MakeGroup("CPU Percentage");
      nullParam = group->MakeNullParameter(CPU_NULL_PARAM, B_MEDIA_NO_TYPE, "CPU Spin Percentage", B_GENERIC);
      BContinuousParameter* cpuParam = group->MakeContinuousParameter(CPU_SPIN_PARAM, B_MEDIA_NO_TYPE, "",
         B_GAIN, "percent", 5, 80, 5);
      nullParam->AddOutput(cpuParam);
      cpuParam->AddInput(nullParam);
   
      group = mainGroup->MakeGroup("Priority");
      nullParam = group->MakeNullParameter(PRIO_NULL_PARAM, B_MEDIA_NO_TYPE, "Thread Priority", B_GENERIC);
      BDiscreteParameter* prioParam = group->MakeDiscreteParameter(PRIORITY_PARAM, B_MEDIA_NO_TYPE, "", B_GENERIC);
      prioParam->AddItem(5, "B_LOW_PRIORITY");
      prioParam->AddItem(10, "B_NORMAL_PRIORITY");
      prioParam->AddItem(15, "B_DISPLAY_PRIORITY");
      prioParam->AddItem(20, "B_URGENT_DISPLAY_PRIORITY");
      prioParam->AddItem(100, "B_REAL_TIME_DISPLAY_PRIORITY");
      prioParam->AddItem(110, "B_URGENT_PRIORITY");
      prioParam->AddItem(120, "B_REAL_TIME_PRIORITY");
   
      return web;
   }

 優先度のparameterに不連続な値を追加するためにBDiscreteParameter::AddItem()関数を使用することについては、注意して下さい。それぞれのアイテムは、ユーザインターフェイスで表示される値とラベルを持ちます。この例では、これらは多様なスレッドの優先度と名前に対応します。

 このnodeによって提供されるユーザインターフェイスによって、スレッドの優先度、バッファが"processed"の間のCPUの占有率、そしてバッファの処理のlatencyを含むnodeの振る舞いをユーザが設定できます。

 当然ながら、あなたのnodeは異ったparameterを持つでしょう(または持たないかも知れません。このケースでは、nodeはBControllableから派生することさえなく、BParameterWebは全く必要ありません)。

 この関数は、上記のBMediaEventLooper::NodeRegistered()の実装から呼び出されます。NodeRegistered()は、その後にwebを使用するStartControlPanel()またはその他の関数によって使用されるwebを確立するためにSetParameterWeb()を呼び出します。

 Release 4.5のデフォルトのsystem themeによって解釈されるこのnodeのwebは、このように見えます :


Producer-specific Issues

 nodeがBBufferProducerから派生するなら、実装する必要のある仮想関数が明らかにいくつか残っています。

Connect()

 BBufferProducer::Connect()の実装では、latencyの合計を確立するためにBMediaEventLooper::SetEventLatency()を呼び出して下さい。内部的なlatencyに下流のlatencyを加えたこの値は、処理されるイベントをいつキューからポップするか決定するためにBMediaEventLooperによって使用されます。この値を最新の状態に保つことによって、動作効率を改善することができます。このコードは下記に似ているでしょう :

      /* calculate processing latency */
   
      bigtime_t latency = calculate_buffer_latency();
      latency += estimate_max_scheduling_latency();
   
      /* calculate downstream latency */
   
      bigtime_t downstream;
      media_node_id timesource;
      FindLatencyFor(output.destination, &downstream, &timesource);
   
      bigtime_t totalLatency = latency + downstream;
      SetEventLatency(totalLatency);


The Rest of the Story...

 この例では、LogWriterクラスの実装は重要ではありませんし、スペースを節約するという名目で上手に無視されています。もしあなたが好奇心旺盛なら、LoggingApplicationの完全なサンプルコードを、ftp://ftp.be.com/pub/samples/media_kit/LoggingConsumer.zipに参照することができます。


The Media Kit Table of Contents     The Media Kit Index


The Be Book,
...in lovely HTML...
for BeOS Release 5.

Copyright © 2000 Be, Inc. All rights reserved..