コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2017年11月6日 >>> 2017年10月28日
link もっと後

2017年11月6日

Androidメディア処理

昨日(2017年11月5日の日記参照)の続きです。

OpenMAXの解説をしていると日が暮れるのでやめます。とにかくデコードされた画素データはFillBufferDoneで返ってくることがわかっていれば、コードを追いかけられるはずです。

見ているコードはAndroid 7.1です。タグで言えばandroid-7.1.2_r33辺りです。

デコード完了のお知らせ

FillBufferDoneはコールバックであることは説明しました。OpenMAXの規格では、コンポーネントがコールバックする関数は、コンポーネントを生成する際に指定します。コールバックされる関数を探すには、コンポーネントを生成していそうな個所を探せばわかるはずです。

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ではあらゆる場所でメッセージパッシングが使用されており、とても読みづらいです……。

メッセージfrom OpenMAX

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);
}

ここまででわかったことは、

  • コンポーネントがOMXNodeInstance::OnFillBufferDoneをコールバックする
  • OMX::OnFillBufferDoneがメッセージを送信する
  • メッセージの行き先はOMX::mDispatchersに登録されているCallbackDispatcherをnewするときに渡した引数(mOwner)

困ったことに、肝心のメッセージがどこに行くか?がいまだに不明です。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とは何者でしょうか?メッセージの冒険は続きます。

編集者:すずき(2017/11/23 20:55)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年11月5日

Androidのメディア再生処理

たまにはAndroidの話でも。Androidのメディア再生のデコード完了から出画までを見てみました。

Media | Android Open Source Project 辺りにあるように、Androidはlibstagefrightにメディアの処理を任せています。

図からはちょっと読み取りづらいですが、libstagefrightは動画、音声のデコードにOpenMAXというAPIを用います。図だとOMX Coreと書かれている部分です。

OpenMAXざっくり紹介

OpenMAXの各種デコーダ(※)は「コンポーネント」と呼ばれる部品になっています。

OpenMAXではデータの入力はEmptyThisBufferと呼びます。データの入力は非同期に行うことができます。コンポーネントは入力を処理し終えたら完了通知をコールバック(EmptyBufferDone)する仕組みになっています。

データの出力はFillThisBufferと呼びます。出力も非同期に行うことができます。コンポーネントはデータ出力の完了通知(FillBufferDone)をする仕組みになっています。

libstagefrightはOpenMAXのコンポーネントに対して、下記の処理を行います。他にも設定、フラッシュ、などややこしい処理がありますが、省略。

入力側はこんな感じです。

  • 圧縮データが入ったバッファをEmptyThisBufferでコンポーネントに渡す
  • (コンポーネント内でバッファのデータが処理される)
  • 空になったバッファがEmptyBufferDoneで返ってくる

出力側もほぼ同じです。

  • 空のバッファをFillThisBufferでコンポーネントに渡す
  • (コンポーネント内でバッファにデコード済みデータが詰め込まれる)
  • デコード済みデータが入ったバッファがFillBufferDoneで返ってくる

バッファが2つある場合も基本的には同じです。OpenMAXの特徴はバッファ1とバッファ2がお互いを気にしなくて良いことです。バッファ2が返ってきていようが返ってきていまいが、バッファ1はコンポーネントに渡して構いません。

例えばバッファ2にすごく時間が掛かって、こんな順になっても構いません(コロンの右側はコンポーネントに渡したが返ってきていないバッファの一覧)。

  • start: なし
  • FillThisBuffer 1: 1
  • FillThisBuffer 2: 1, 2
  • FillBufferDone 1: 2
  • FillThisBuffer 1: 1, 2
  • FillBufferDone 1: 2
  • FillThisBuffer 1: 1, 2
  • FillBufferDone 2: 1
  • FillBufferDone 1: なし

特にデコーダの場合は、この例のように渡した順番と返ってくる順番が違う場合がほとんどです。

(※)OpenMAXの規格が定義するコンポーネントの機能は、デコーダだけではありません。しかしAndroidはデコーダコンポーネントしか使いません

編集者:すずき(2017/11/23 19:45)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2017年10月28日

Kindle Fireの書籍をダウンロードしながら読む機能

目次: Kindle

Kindleは書籍をAmazonから本体にダウンロードしてから読みます。漫画や雑誌のように図画の多い書籍は1冊100MB近いサイズとなるため、ダウンロードに時間がかかります。

時間帯やネットワーク環境にもよりますが、DTIに乗り換えてからはネットワーク環境が改善したので、早くて2, 3分、遅くても10分程度の短いものですが、ただボーっと待っているには長い時間です。

幸いにもKindle Fireはダウンロードしながら読むことができる機能があります。

しかし、この機能はまともに動かなくて、本当は何か書いてあるはずのページなのに、真っ白なページが表示されることが多いです。一度ページが白くなってしまうと、何度見ても白いままです。

ページが白くなってしまった場合は、書籍を本体から削除し、ダウンロードしなおすと直ります。元から白いページもあるのでややこしいですけど、その辺は本を読んでいる人ならわかりますよね。

きちんと動作すれば便利な機能だと思うのですが、今は使い物にならないです。それどころか、間違えてこの機能を発動させると本が歯抜けになってしまって迷惑なので、ダウンロード中は絶対に本を開かないようにしています。

Kindle Fireは色々残念なところが多いですね……。

編集者:すずき(2021/12/08 03:59)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2017年11月6日 >>> 2017年10月28日
link もっと後

管理用メニュー

link 記事を新規作成

<2017>
<<<11>>>
---1234
567891011
12131415161718
19202122232425
2627282930--

最近のコメント5件

  • link 21年3月13日
    すずきさん (03/05 15:13)
    「あー、このプログラムがまずいんですね。ご...」
  • link 21年3月13日
    emkさん (03/05 12:44)
    「キャストでvolatileを外してアクセ...」
  • link 24年1月24日
    すずきさん (02/19 18:37)
    「簡単にできる方法はPowerShellの...」
  • link 24年1月24日
    KKKさん (02/19 02:30)
    「追伸です。\nネットで調べたらマイクロソ...」
  • link 24年1月24日
    KKKさん (02/19 02:25)
    「私もエラーで困ってます\n手動での回復パ...」

最近の記事3件

  • link 24年3月25日
    すずき (03/26 03:20)
    「[Might and Magic Book One TASのその後] 目次: Might and Magicファミコン版以前(...」
  • link 21年10月4日
    すずき (03/26 03:14)
    「[Might and Magicファミコン版 - まとめリンク] 目次: Might and Magicファミコン版TASに挑...」
  • link 24年3月19日
    すずき (03/20 02:52)
    「[モジュラージャックの規格] 古くは電話線で、今だとEthernetで良く見かけるモジュラージャックというコネクタとレセプタク...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 03/26 03:20