昨日(2017年11月5日の日記参照)の続きです。
OpenMAXの解説をしていると日が暮れるのでやめます。とにかくデコードされた画素データはFillBufferDoneで返ってくることがわかっていれば、コードを追いかけられるはずです。
見ているコードはAndroid 7.1です。タグで言えばandroid-7.1.2_r33辺りです。
FillBufferDoneはコールバックであることは説明しました。OpenMAXの規格では、コンポーネントがコールバックする関数は、コンポーネントを生成する際に指定します。コールバックされる関数を探すには、コンポーネントを生成していそうな個所を探せばわかるはずです。
//android/frameworks/av/media/libstagefright/omx/OMX.cpp
status_t OMX::allocateNode(
const char *name, const sp<IOMXObserver> &observer,
sp<IBinder> *nodeBinder, node_id *node) {
...
OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name); //★★1番目の引数がownerなので、thisつまりこのオブジェクトが指定される★★
OMX_COMPONENTTYPE *handle;
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name, &OMXNodeInstance::kCallbacks,
instance, &handle); //★★2番目の引数kCallbacksがコールバック関数の指定。3番目の引数instanceがFillBufferDoneのpAppDataに渡される★★
//android/frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp
// static
OMX_CALLBACKTYPE OMXNodeInstance::kCallbacks = {
&OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
};
かなり端折ってますが、FillBufferDoneのコールバック関数にはOMXNodeInstance::OnFillBufferDoneを指定しているようです。従ってデコードが終わると、画素データが入ったバッファがOMXNodeInstance::OnFillBufferDone関数に渡されます。
//android/frameworks/av/media/libstagefright/omx/OMXNodeInstance.cpp
// static
OMX_ERRORTYPE OMXNodeInstance::OnFillBufferDone(
OMX_IN OMX_HANDLETYPE /* hComponent */,
OMX_IN OMX_PTR pAppData,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
...
OMXNodeInstance *instance = static_cast<OMXNodeInstance *>(pAppData); //★★makeComponentInstanceの3番目の引数に渡した値★★
if (instance->mDying) {
return OMX_ErrorNone;
}
int fenceFd = instance->retrieveFenceFromMeta_l(pBuffer, kPortIndexOutput);
return instance->owner()->OnFillBufferDone(instance->nodeID(),
instance->findBufferID(pBuffer), pBuffer, fenceFd); //★★ownerはOMX型のオブジェクトなのでOMX::OnFillBufferを見る★★
}
//android/frameworks/av/media/libstagefright/omx/OMX.cpp
OMX_ERRORTYPE OMX::OnFillBufferDone(
node_id node, buffer_id buffer, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer, int fenceFd) {
ALOGV("OnFillBufferDone buffer=%p", pBuffer);
omx_message msg;
msg.type = omx_message::FILL_BUFFER_DONE;
msg.node = node;
msg.fenceFd = fenceFd;
msg.u.extended_buffer_data.buffer = buffer;
msg.u.extended_buffer_data.range_offset = pBuffer->nOffset;
msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen;
msg.u.extended_buffer_data.flags = pBuffer->nFlags;
msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
findDispatcher(node)->post(msg); //★★post() とは何だろうか??★★
return OMX_ErrorNone;
}
sp<OMX::CallbackDispatcher> OMX::findDispatcher(node_id node) {
Mutex::Autolock autoLock(mLock);
ssize_t index = mDispatchers.indexOfKey(node);
return index < 0 ? NULL : mDispatchers.valueAt(index); //★★mDispatchersとは?★★
}
謎の関数CallbackDispatcher::post() が出てきました。名前からするとメッセージパッシングを行うための関数ではないかと予想されます。この場所に限らずstagefrightではあらゆる場所でメッセージパッシングが使用されており、とても読みづらいです……。
CallbackDispatcherというクラスが出てきましたので、見てみます。
//android/frameworks/av/media/libstagefright/include/OMX.h
class OMX : public BnOMX,
public IBinder::DeathRecipient {
...
KeyedVector<node_id, sp<CallbackDispatcher> > mDispatchers; //★★mDispatchersの定義★★
//android/frameworks/av/media/libstagefright/omx/OMX.cpp
struct OMX::CallbackDispatcher : public RefBase {
CallbackDispatcher(OMXNodeInstance *owner);
// Posts |msg| to the listener's queue. If |realTime| is true, the listener thread is notified
// that a new message is available on the queue. Otherwise, the message stays on the queue, but
// the listener is not notified of it. It will process this message when a subsequent message
// is posted with |realTime| set to true.
void post(const omx_message &msg, bool realTime = true);
...
private:
...
std::list<omx_message> mQueue;
void OMX::CallbackDispatcher::post(const omx_message &msg, bool realTime) {
Mutex::Autolock autoLock(mLock);
mQueue.push_back(msg); //★★メッセージをキューに追加★★
if (realTime) {
mQueueChanged.signal();
}
}
引数のnode_id nodeから、適切なCallbackDispatcherを探して、内部キューmQueueにメッセージを追加しています。mQueueを手掛かりにメッセージを処理する側を探すと、どうやらCallbackDispatcherThreadが処理しているようです。
//android/frameworks/av/media/libstagefright/omx/OMX.cpp
bool OMX::CallbackDispatcherThread::threadLoop() {
return mDispatcher->loop();
}
bool OMX::CallbackDispatcher::loop() {
for (;;) {
std::list<omx_message> messages;
{
Mutex::Autolock autoLock(mLock);
while (!mDone && mQueue.empty()) {
mQueueChanged.wait(mLock);
}
if (mDone) {
break;
}
messages.swap(mQueue); //★★mQueueのロック時間を短くするため、別のリストに全てのメッセージを移動させる★★
}
dispatch(messages); //★★メッセージ処理★★
}
return false;
}
void OMX::CallbackDispatcher::dispatch(std::list<omx_message> &messages) {
if (mOwner == NULL) {
ALOGV("Would have dispatched a message to a node that's already gone.");
return;
}
mOwner->onMessages(messages); //★★メッセージ送信先のmOwnerとは?★★
}
OMX::CallbackDispatcher::CallbackDispatcher(OMXNodeInstance *owner)
: mOwner(owner), //★★CallbackDispatcherの生成時に渡された引数で初期化されている★★
mDone(false) {
mThread = new CallbackDispatcherThread(this);
mThread->run("OMXCallbackDisp", ANDROID_PRIORITY_FOREGROUND);
}
ここまででわかったことは、
困ったことに、肝心のメッセージがどこに行くか?がいまだに不明です。mOwnerとはどこで指定されているのでしょう?
OMX::mDispatchersを操作している箇所を探すと、1箇所見つかります。先程も出てきたOMX::allocateNode() です。
//android/frameworks/av/media/libstagefright/omx/OMX.cpp
status_t OMX::allocateNode(
const char *name, const sp<IOMXObserver> &observer,
sp<IBinder> *nodeBinder, node_id *node) {
...
OMXNodeInstance *instance = new OMXNodeInstance(this, observer, name);
OMX_COMPONENTTYPE *handle;
OMX_ERRORTYPE err = mMaster->makeComponentInstance(
name, &OMXNodeInstance::kCallbacks,
instance, &handle); //★★3番目の引数、つまりinstanceがFillBufferDoneのpAppDataに渡される★★
if (err != OMX_ErrorNone) {
ALOGE("FAILED to allocate omx component '%s' err=%s(%#x)", name, asString(err), err);
instance->onGetHandleFailed();
return StatusFromOMXError(err);
}
*node = makeNodeID_l(instance);
mDispatchers.add(*node, new CallbackDispatcher(instance)); //★★メッセージの送信先を登録する★★
どうやらOMXNodeInstanceにメッセージを送っているようです。従ってmOwner->onMessages(messages) はここに辿り着きます。
void OMXNodeInstance::onMessages(std::list<omx_message> &messages) {
for (std::list<omx_message>::iterator it = messages.begin(); it != messages.end(); ) {
if (handleMessage(*it)) {
messages.erase(it++); //★★デコードに付随する情報をメッセージに載せる★★
} else {
++it;
}
}
if (!messages.empty()) {
mObserver->onMessages(messages); //★★mObserverとは?★★
}
}
ここで終わりかと思いきや、まだです。mObserverとは何者でしょうか?メッセージの冒険は続きます。
たまにはAndroidの話でも。Androidのメディア再生のデコード完了から出画までを見てみました。
Media | Android Open Source Project 辺りにあるように、Androidはlibstagefrightにメディアの処理を任せています。
図からはちょっと読み取りづらいですが、libstagefrightは動画、音声のデコードにOpenMAXというAPIを用います。図だとOMX Coreと書かれている部分です。
OpenMAXの各種デコーダ(※)は「コンポーネント」と呼ばれる部品になっています。
OpenMAXではデータの入力はEmptyThisBufferと呼びます。データの入力は非同期に行うことができます。コンポーネントは入力を処理し終えたら完了通知をコールバック(EmptyBufferDone)する仕組みになっています。
データの出力はFillThisBufferと呼びます。出力も非同期に行うことができます。コンポーネントはデータ出力の完了通知(FillBufferDone)をする仕組みになっています。
libstagefrightはOpenMAXのコンポーネントに対して、下記の処理を行います。他にも設定、フラッシュ、などややこしい処理がありますが、省略。
入力側はこんな感じです。
出力側もほぼ同じです。
バッファが2つある場合も基本的には同じです。OpenMAXの特徴はバッファ1とバッファ2がお互いを気にしなくて良いことです。バッファ2が返ってきていようが返ってきていまいが、バッファ1はコンポーネントに渡して構いません。
例えばバッファ2にすごく時間が掛かって、こんな順になっても構いません(コロンの右側はコンポーネントに渡したが返ってきていないバッファの一覧)。
特にデコーダの場合は、この例のように渡した順番と返ってくる順番が違う場合がほとんどです。
(※)OpenMAXの規格が定義するコンポーネントの機能は、デコーダだけではありません。しかしAndroidはデコーダコンポーネントしか使いません。
目次: Kindle
Kindleは書籍をAmazonから本体にダウンロードしてから読みます。漫画や雑誌のように図画の多い書籍は1冊100MB近いサイズとなるため、ダウンロードに時間がかかります。
時間帯やネットワーク環境にもよりますが、DTIに乗り換えてからはネットワーク環境が改善したので、早くて2, 3分、遅くても10分程度の短いものですが、ただボーっと待っているには長い時間です。
幸いにもKindle Fireはダウンロードしながら読むことができる機能があります。
しかし、この機能はまともに動かなくて、本当は何か書いてあるはずのページなのに、真っ白なページが表示されることが多いです。一度ページが白くなってしまうと、何度見ても白いままです。
ページが白くなってしまった場合は、書籍を本体から削除し、ダウンロードしなおすと直ります。元から白いページもあるのでややこしいですけど、その辺は本を読んでいる人ならわかりますよね。
きちんと動作すれば便利な機能だと思うのですが、今は使い物にならないです。それどころか、間違えてこの機能を発動させると本が歯抜けになってしまって迷惑なので、ダウンロード中は絶対に本を開かないようにしています。
Kindle Fireは色々残念なところが多いですね……。
< | 2017 | > | ||||
<< | < | 11 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | 1 | 2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.15.
using GD bundled (2.1.0 compatible)(png support.)