link もっと前
   2018年 7月 7日 -
      2018年 6月 28日  
link もっと後

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


link permalink

Android と MPEG2-TS その 3

その 1その 2その 3その 4


デスクランブル処理 Java 側(たぶん)


    public final int descramble(
            @NonNull ByteBuffer srcBuf, @NonNull ByteBuffer dstBuf,
            @NonNull MediaCodec.CryptoInfo cryptoInfo) {
        try {
            return native_descramble(
                    srcBuf, srcBuf.position(), srcBuf.limit(),
                    dstBuf, dstBuf.position(), dstBuf.limit());
        } catch (ServiceSpecificException e) {
            MediaCasStateException.throwExceptionIfNeeded(e.errorCode, e.getMessage());
        } catch (RemoteException e) {
        return -1;

ここで呼び出している native_descramble() は native であると宣言されています。つまり JNI 経由で呼び出します。メディアフレームワーク(の JNI 実装は frameworks/base/media/jni に配置されているようです。

デスクランブル処理 JNI 側


static jint android_media_MediaDescrambler_native_descramble(
        JNIEnv *env, jobject thiz, jbyte key, jint numSubSamples,
        jintArray numBytesOfClearDataObj, jintArray numBytesOfEncryptedDataObj,
        jobject srcBuf, jint srcOffset, jint srcLimit,
        jobject dstBuf, jint dstOffset, jint dstLimit) {
    sp<JDescrambler> descrambler = getDescrambler(env, thiz); //★★★★ descrambler は JDescrambler 型
    if (descrambler == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException",
                "Invalid descrambler object!");
        return -1;

    hidl_vec<SubSample> subSamples;
    ssize_t totalLength = getSubSampleInfo(
            env, numSubSamples, numBytesOfClearDataObj,
            numBytesOfEncryptedDataObj, &subSamples);
    if (totalLength < 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                "Invalid subsample info!");
        return -1;
    err = descrambler->descramble(
            key, totalLength, subSamples,
            srcPtr, srcOffset, dstPtr, dstOffset,
            &status, &bytesWritten, &detailedError); //★★★★

status_t JDescrambler::descramble(
        jbyte key,
        ssize_t totalLength,
        const hidl_vec<SubSample>& subSamples,
        const void *srcPtr,
        jint srcOffset,
        void *dstPtr,
        jint dstOffset,
        Status *status,
        uint32_t *bytesWritten,
        hidl_string *detailedError) {
    auto err = mDescrambler->descramble(
            (ScramblingControl) key,
            [&status, &bytesWritten, &detailedError] (
                    Status _status, uint32_t _bytesWritten,
                    const hidl_string& _detailedError) {
                *status = _status;
                *bytesWritten = _bytesWritten;
                *detailedError = _detailedError;
            }); //★★★★

ここで出てくる mDescrambler は IDescrambler 型のポインタです。パッと見では、何がセットされているのか良くわかりませんが、このインタフェースを実装しているのは下記しかなさそうです。



class DescramblerImpl : public IDescrambler {


Return<void> DescramblerImpl::descramble(
        ScramblingControl scramblingControl,
        const hidl_vec<SubSample>& subSamples,
        const SharedBuffer& srcBuffer,
        uint64_t srcOffset,
        const DestinationBuffer& dstBuffer,
        uint64_t dstOffset,
        descramble_cb _hidl_cb) {
    ALOGV("%s", __FUNCTION__);

    // Get a local copy of the shared_ptr for the plugin. Note that before
    // calling the HIDL callback, this shared_ptr must be manually reset,
    // since the client side could proceed as soon as the callback is called
    // without waiting for this method to go out of scope.
    std::shared_ptr<DescramblerPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★ holder = mPluginHolder
    if (holder.get() == nullptr) {
        _hidl_cb(toStatus(INVALID_OPERATION), 0, NULL);
        return Void();


    // Casting hidl SubSample to DescramblerPlugin::SubSample, but need
    // to ensure structs are actually idential

    int32_t result = holder->descramble(
            dstBuffer.type != BufferType::SHARED_MEMORY,

この mPluginHolder は DescramblerPlugin 型です。DescramblerImpl が生成される時に設定されます。DescramblerImpl を生成するのは MediaCasService::createDescrambler() のみ?のように見えます。

次に出てくる holder には DescramblerPlugin のポインタが入ります。Plugin の実装を探してみると ClearKey にそれらしき処理があります。

Descrambler plugin の実装


ssize_t ClearKeyDescramblerPlugin::descramble(
        bool secure,
        ScramblingControl scramblingControl,
        size_t numSubSamples,
        const SubSample *subSamples,
        const void *srcPtr,
        int32_t srcOffset,
        void *dstPtr,
        int32_t dstOffset,
        AString *errorDetailMsg) {

    ALOGV("descramble: secure=%d, sctrl=%d, subSamples=%s, "
            "srcPtr=%p, dstPtr=%p, srcOffset=%d, dstOffset=%d",
          (int)secure, (int)scramblingControl,
          subSamplesToString(subSamples, numSubSamples).string(),
          srcPtr, dstPtr, srcOffset, dstOffset);

    if (mCASSession == NULL) {
        ALOGE("Uninitialized CAS session!");

    return mCASSession->decrypt(
            secure, scramblingControl,
            numSubSamples, subSamples,
            (uint8_t*)srcPtr + srcOffset,
            dstPtr == NULL ? NULL : ((uint8_t*)dstPtr + dstOffset),

ここで出てくる mCASSession は ClearKeyCasSession を指すようです。setMediaCasSession() にて設定されています。これは後で追ってみます。

ClearKey によるデスクランブル


// Decryption of a set of sub-samples
ssize_t ClearKeyCasSession::decrypt(
        bool secure, DescramblerPlugin::ScramblingControl scramblingControl,
        size_t numSubSamples, const DescramblerPlugin::SubSample *subSamples,
        const void *srcPtr, void *dstPtr, AString * /* errorDetailMsg */) {

    AES_KEY contentKey;

        // Hold lock to get the key only to avoid contention for decryption
        Mutex::Autolock _lock(mKeyLock);

        int32_t keyIndex = (scramblingControl & 1);
            ALOGE("decrypt: key %d is invalid", keyIndex);
            return ERROR_CAS_DECRYPT;
        contentKey = mKeyInfo[keyIndex].contentKey; //★★★★ ClearKeyCasSession::updateECM() で設定する鍵だと思う
        // Don't decrypt if len < AES_BLOCK_SIZE.
        // The last chunk shorter than AES_BLOCK_SIZE is not encrypted.
        if (scramblingControl != DescramblerPlugin::kScrambling_Unscrambled
                && subSamples[i].mNumBytesOfEncryptedData >= AES_BLOCK_SIZE) {
            err = decryptPayload(
                    (char *)dst);

// Decryption of a TS payload
status_t ClearKeyCasSession::decryptPayload(
        const AES_KEY& key, size_t length, size_t offset, char* buffer) const {

    // Invariant: only call decryptPayload with TS packets with at least 16
    // bytes of payload (AES_BLOCK_SIZE).

    CHECK(length >= offset + AES_BLOCK_SIZE);

    return TpBlockCtsDecrypt(key, length - offset, buffer + offset);

// AES-128 CBC-CTS decrypt optimized for Transport Packets. |key| is the AES
// key (odd key or even key), |length| is the data size, and |buffer| is the
// ciphertext to be decrypted in place.
status_t TpBlockCtsDecrypt(const AES_KEY& key, size_t length, char* buffer) {

CBC モードで AES 暗号を復号しているようです。

まとめると、MediaDescrambler(ドキュメントはここ)の descrambler() によって、スクランブルされたデータのデスクランブルができそう、ということがわかりました。


[編集者: すずき]
[更新: 2018年 7月 17日 22:46]
link 編集する


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

link permalink

Android と MPEG2-TS その 2

その 1その 2その 3その 4

昨日の続き。スクランブルの掛かったストリームは mParser->mCasManager に任せていました。mCasManager は ATSParser::CasManager でしたので、実装を見てみます。



bool ATSParser::CasManager::parsePID(ABitReader *br, unsigned pid) {
    ssize_t index = mCAPidToSessionIdMap.indexOfKey(pid);
    if (index < 0) {
        return false;
    hidl_vec<uint8_t> ecm;
    ecm.setToExternal((uint8_t*)br->data(), br->numBitsLeft() / 8);
    auto returnStatus = mICas->processEcm(mCAPidToSessionIdMap[index], ecm); //★★★★ processEcm()
    if (!returnStatus.isOk() || (Status) returnStatus != Status::OK) {
        ALOGE("Failed to process ECM: trans=%s, status=%d",
                returnStatus.description().c_str(), (Status) returnStatus);
    return true; // handled

謎の mICas がどこから来るかは、後で調べるとして、関数名 processEcm() で探してみると、HAL の方にコードがあります。

processEcm() の実装


Return<Status> CasImpl::processEcm(
        const HidlCasSessionId &sessionId, const HidlCasData& ecm) {
    ALOGV("%s: sessionId=%s", __FUNCTION__,
    std::shared_ptr<CasPlugin> holder = std::atomic_load(&mPluginHolder); //★★★★ CasPlugin
    if (holder.get() == nullptr) {
        return toStatus(INVALID_OPERATION);

    return toStatus(holder->processEcm(sessionId, ecm));

想像するに CasPlugin というクラスを派生させて処理を実装するのでしょう。探してみると frameworks/av/drm/mediacas/plugins 以下に clearkey と mock という実装があります。

ClearKey の実装


class ClearKeyCasPlugin : public CasPlugin {


status_t ClearKeyCasPlugin::processEcm(
        const CasSessionId &sessionId, const CasEcm& ecm) {
    ALOGV("processEcm: sessionId=%s", sessionIdToString(sessionId).string());
    sp<ClearKeyCasSession> session =
    if (session == NULL) {

    Mutex::Autolock lock(mKeyFetcherLock);

    return session->updateECM(mKeyFetcher.get(), (void*), ecm.size()); //★★★★ mKeyFetcher

status_t ClearKeyCasSession::updateECM(
        KeyFetcher *keyFetcher, void *ecm, size_t size) {
    uint64_t asset_id;
    std::vector<KeyFetcher::KeyInfo> keys;
    status_t err = keyFetcher->ObtainKey(mEcmBuffer, &asset_id, &keys); //★★★★ keyFetcher
    if (err != OK) {
        ALOGE("updateECM: failed to obtain key (err=%d)", err);
        return err;

    ALOGV("updateECM: %zu key(s) found", keys.size());
    for (size_t keyIndex = 0; keyIndex < keys.size(); keyIndex++) {
        String8 str;

        const sp<ABuffer>& keyBytes = keys[keyIndex].key_bytes;
        CHECK(keyBytes->size() == kUserKeyLength);

        int result = AES_set_decrypt_key(
                reinterpret_cast<const uint8_t*>(keyBytes->data()),
                AES_BLOCK_SIZE * 8, &mKeyInfo[keyIndex].contentKey); //★★★★ libssl の関数に渡して鍵を生成している?ようだ


status_t ClearKeyFetcher::ObtainKey(const sp<ABuffer>& buffer,
        uint64_t* asset_id, std::vector<KeyInfo>* keys) {

引数に渡している mKeyFetcher(と get() が返す keyFetcher も同様に)は KeyFetcher 型のポインタでした。KeyFetcher を継承した ClearKeyFetcher 型のオブジェクトが格納されていました。

ClearKey の仕組みは詳しく知りませんが、ClearKeyCasSession::updateECM() で AES の復号などをしていることと、AES の復号鍵は ClearKeyFetcher::ObtainKey() が ECM を読んで復号鍵を取得してくれるように見えました。

Android で ECM の解読を行っている箇所が見つけられました。エレメンタリストリームのデスクランブルはどこで行っているのでしょうね…??

[編集者: すずき]
[更新: 2018年 7月 17日 22:45]
link 編集する


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

link permalink

Android と MPEG2-TS その 1

その 1その 2その 3その 4

Android 8 が MPEG2-TS の PSI(Program Specific Information)をどのように処理しているのか、気になったので調べてみました。調査に使ったコードは AOSP のタグ android-8.1.0_r33 です。

PSI のことをセクションと呼ぶ人もいますね。MPEG2 System の規格書 ISO13818-1/ITU-T H.222.0 によれば、PSI は xxx Table という名前(PAT なら Program Association Table)で、テーブルは 1 つないし、複数のセクション(xxx_section という名前で定義されている、PAT なら program_association_section)から構成されるからだと思います。

さておき TS を処理しているところは、下記のようになっています。

Extractor TS 受付部分


status_t MPEG2TSExtractor::feedMore(bool isInit) {
    Mutex::Autolock autoLock(mLock);

    uint8_t packet[kTSPacketSize];
    ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize);

    if (n < (ssize_t)kTSPacketSize) {
        if (n >= 0) {
        return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM;

    ATSParser::SyncEvent event(mOffset);
    mOffset += n;
    status_t err = mParser->feedTSPacket(packet, kTSPacketSize, &event); //★★★★
    if (event.hasReturnedData()) {
        if (isInit) {
            mLastSyncEvent = event;
        } else {
    return err;

ここで出てくる mParser は ATSParser のポインタなので、ATSParser の実装を見てみます。

TS 受付部分


status_t ATSParser::feedTSPacket(const void *data, size_t size,
        SyncEvent *event) {
    if (size != kTSPacketSize) {
        ALOGE("Wrong TS packet size");
        return BAD_VALUE;

    ABitReader br((const uint8_t *)data, kTSPacketSize);
    return parseTS(&br, event); //★★★★

status_t ATSParser::parseTS(ABitReader *br, SyncEvent *event) {
    status_t err = OK;

    unsigned random_access_indicator = 0;
    if (adaptation_field_control == 2 || adaptation_field_control == 3) {
        err = parseAdaptationField(br, PID, &random_access_indicator);
    if (err == OK) {
        if (adaptation_field_control == 1 || adaptation_field_control == 3) {
            err = parsePID(br, PID, continuity_counter,
                    event); //★★★★

status_t ATSParser::parsePID(
        ABitReader *br, unsigned PID,
        unsigned continuity_counter,
        unsigned payload_unit_start_indicator,
        unsigned transport_scrambling_control,
        unsigned random_access_indicator,
        SyncEvent *event) {
    ssize_t sectionIndex = mPSISections.indexOfKey(PID);
    if (sectionIndex >= 0) { //★★★★ PAT か PMT の PID ならこの条件が成り立つ
        sp<PSISection> section = mPSISections.valueAt(sectionIndex);

ここで出てくる mPSISection は unsigned をキー、sp<PSISection> を値とする KeyedVector です。キー 0 に PAT を持っていて、それ以外のキーは PMT の PID(PAT が一覧を持っている)です。PMT の PID は PAT を受信したときに ATSParser::parseProgramAssociationTable() が追加するようです。



status_t ATSParser::parsePID(
        ABitReader *br, unsigned PID,
        unsigned continuity_counter,
        unsigned payload_unit_start_indicator,
        unsigned transport_scrambling_control,
        unsigned random_access_indicator,
        SyncEvent *event) {
    ssize_t sectionIndex = mPSISections.indexOfKey(PID);
    if (sectionIndex >= 0) { //★★★★ PAT か PMT の PID ならこの条件が成り立つ
        sp<PSISection> section = mPSISections.valueAt(sectionIndex);
        if (PID == 0) {
            parseProgramAssociationTable(&sectionBits); //★★★★ PID 0 なら PAT の解析
        } else {
            bool handled = false;
            for (size_t i = 0; i < mPrograms.size(); ++i) { //★★★★ それ以外は PMT かどうか見る
                status_t err;
                if (!mPrograms.editItemAt(i)->parsePSISection( //★★★★ PMT か?
                            PID, &sectionBits, &err)) {

bool ATSParser::Program::parsePSISection(
        unsigned pid, ABitReader *br, status_t *err) {
    *err = OK;

    if (pid != mProgramMapPID) {
        return false;

    *err = parseProgramMap(br); //★★★★ PMT だったので PMT の解析

    return true;

status_t ATSParser::Program::parseProgramMap(ABitReader *br) {
    unsigned table_id = br->getBits(8);
    ALOGV("  table_id = %u", table_id);
    // descriptors
    CADescriptor programCA;
    bool hasProgramCA = findCADescriptor(br, program_info_length, &programCA); //★★★★ PMT の持っている descriptor を見ている
    if (hasProgramCA && !mParser->mCasManager->addProgram(
            mProgramNumber, programCA)) { //★★★★ CA descriptor の指す PID つまり ECM の PID を追加
        return ERROR_MALFORMED;
    size_t infoBytesRemaining = section_length - 9 - program_info_length - 4;

    while (infoBytesRemaining >= 5) { //★★★★ エレメンタリストリームの PID と一緒に付いている descriptor を見ている
        CADescriptor streamCA;
        bool hasStreamCA = findCADescriptor(br, ES_info_length, &streamCA);
        if (hasStreamCA && !mParser->mCasManager->addStream(
                mProgramNumber, elementaryPID, streamCA)) { //★★★★ CA descriptor の指す PID つまり ECM の PID を追加
            return ERROR_MALFORMED;
    for (size_t i = 0; i < infos.size(); ++i) {
        StreamInfo &info = infos.editItemAt(i);

        if (mParser->mCasManager->isCAPid(info.mPID)) { //★★★★ CA descriptor に記載のあったストリーム
            // skip CA streams (EMM/ECM)
        ssize_t index = mStreams.indexOfKey(info.mPID);

        if (index < 0) {
            sp<Stream> stream = new Stream(
                    this, info.mPID, info.mType, PCR_PID, info.mCASystemId);

            if (mSampleAesKeyItem != NULL) {

            isAddingScrambledStream |= info.mCASystemId >= 0; //★★★★ CA descriptor に記載が無いのにスクランブルされている??
            mStreams.add(info.mPID, stream);

ざっくり言うと、スクランブルの掛かったストリームは mParser->mCasManager に任せ、スクランブルの掛かっていないストリームは mStreams に任せるようです。

CA descriptor に載っていないのにスクランブルの掛かった変なストリームがあると警告が出るようになっています。

[編集者: すずき]
[更新: 2018年 7月 17日 22:45]
link 編集する


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

link permalink


先日、作った(2018年 6月 1日の日記参照)ISDB というか ARIB のデスクランブラの続きです。

DVB API で制御可能なチューナー(私は PT2 で確認しています)を使っている方であれば、下記のようにチューニング(コードは GitHub にあります)できます。チューニングに成功して放送が受信されると、/dev/dvb/adapter0/dvr0 からスクランブルの掛かった MPEG2-TS が出力されます。

PT2 のチューニングのテスト
例: BS プレミアム(衛星はアダプタ 0 か 2、地デジはアダプタ 1 か 3 を使います)

$ ./sample_dvb 0 S BS 3 0x4031


$ ./sample_dvb 0 S BS 3 0x4031 > /dev/null

スマートカードリーダーを PC に接続し、B-CAS カードをリーダーに挿入した上で、下記のようにデスクランブル(コードは GitHub にあります)できます。デスクランブルした MPEG2-TS は UDP で送るか、ファイルに保存できます。

例: 自分自身に UDP で送る

$ ./arib_descramble /dev/dvb/adapter0/dvr0 localhost 1234

VLC を起動し、udp://@:1234 を再生すると、受信中の放送が映るはずです。


自身の規格理解のためもあって、かなり手抜き実装していて、異常に重いため、いくつか改良してみました。まずプロファイラで見てみると、MULTI2 復号と、どこかにある無駄なコピーに、時間がかかっているようです。

MULTI2 復号の高速化には SSE2 を使ってみました。MULTI2 の復号は 8バイトずつですが、SSE2 を活用するには 32バイトの方が都合が良いです。ですので 4単位まとめて(4 x 8バイト = 32バイト = 128bit = SSE2 のレジスタ幅)処理して、残った 32バイトに満たないデータは従来どおり 8バイトずつ処理します。

残念ながら、結果から言うとあまり最適化が効きませんでした。SIMD で高速化できないロード/ストアの割合が高いのか、計算が占める割合が低いのか、いまいちわからなかったのですが、あまり高速化できませんでした。CPU 利用率でいうと 12% が 11% になるか、ならないか…程度です。

無駄なコピーは 2箇所見つけたのでガッツリ消しました。これは効果があったようで、CPU 利用率でいうと 11% が 10% くらいまで削減できました。

無駄なコピーはもう 1つありましたが、単純に消すわけにいかなくてやや難しそうだったので、また今度にします。

ARM でも実行してみた

PC だと、CPU 利用率 10% 程度だったので、最近のマルチコア CPU なら割と余裕の負荷です。ではショボい CPU で実行するとどうなるか、試してみました。

手持ちの Raspberry Pi 3(ARM Cortex A53 x 4/1.4GHz)で実行してみたところ、CPU 利用率 25〜27% 程度でした。動かないかもしれないと思っていたので、正直意外でした。かなり健闘していると思います。

ARM には NEON という SIMD 命令がありますが、NEON を使った復号の高速化にはまだ手を出していません。今度やってみますが、SSE2 の結果を見た限りでは、絶大な効果は見込めないでしょう。きっと。

[編集者: すずき]
[更新: 2018年 7月 5日 00:44]
link 編集する


  • すずき 
    NEON にも対応してみましたが、やはり MULTI2 の復号はボトルネックではないらしく、CPU 負荷はほとんど変わりませんでした……。 
    (2018年07月11日 21:26:36)
open/close この記事にコメントする

link permalink

libtool の謎のエラー

Raspberry Pi 3 を持っているのですが、あまり速くない(当たり前ですけど)こともあり、ほとんどコンパイルには使っていませんでした。今日、久しぶりにコードのビルドに使ってみたら、変な症状にハマりました。

autoreconf は正常終了?
$ autoconf --version
autoconf (GNU Autoconf) 2.69

$ automake --version
automake (GNU automake) 1.15

$  autoreconf -fi
aclocal: warning: couldn't open directory 'm4': No such file or directory installing 'conf/compile' installing 'conf/install-sh' installing 'conf/missing'
src/ installing 'conf/depcomp'

更地からのビルドなので autoreconf -fi を実行しています。この時点では特にエラーも出ずに終わったように見えます。

configure がエラーで落ちる
$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking whether make supports nested variables... (cached) yes
./configure: line 2505: syntax error near unexpected token `ac_ext=c'
./configure: line 2505: `ac_ext=c'

しかし configure が謎のエラーで終了してしまいます。しかも config.log にエラーの内容が記録されておらず、怪しいです。

しばし をいじってみてわかったことは、以下の条件を満たしていると、このエラーが発生するようです。

  • に LT_INIT() を記述、つまり libtool を使うように記述する
  • システムに libtool がインストールされていない
  • 更地(autoconf, automake 関連のファイルが全く無い状態)からビルドする

解決策は libtool をインストールするか、libtool を使っていないなら から LT_INIT() を削除しても良いです。

この辺の仕組みは詳しくありませんが、libtool が無いなら無いと言ってくれれば、もう少しわかりやすいのにな…と思います。

[編集者: すずき]
[更新: 2018年 7月 4日 23:12]
link 編集する


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

link permalink

Android チューナー周りの調査メモ

Android 8 のチューナー周りについて、家でも少し調べていたので、わかったことのメモを貼っておきます。環境は下記の通りです。

  • タグ: android-8.1.0_r33
  • ディレクトリ: android/packages/apps/TV

チューナーの動作は大きく分けると、チャンネルスキャン、視聴の 2つです。


チャンネルスキャンは全てのチャンネルに対してチューニングを行って、受信できるかどうか試す動作を指します。Android 8 の場合、フレームワーク内部にチャンネルのリストを持っていて、リストの先頭から試す仕組みになっているようです。


// @ src/com/android/tv/tuner/setup/
TunerSetupActivity::executeAction(String category, int actionId, Bundle params)
    switch (category)
      case ConnectionTypeFragment.ACTION_CATEGORY:
        mLastScanFragment = new ScanFragment();
        Bundle args1 = new Bundle();
        // actionId は GUI 画面の選択肢(Antenna, Cable, Not sure)の
        // 上から何番目か?に相当する、一番上が actionId = 0
        // ★★ CHANNEL_MAP_SCAN_FILE は後述
        // ...snip...
        // ScanFragment を実行、表示する

ScanFragment::onCreateView() @ src/com/android/tv/tuner/setup/
    new ChannelScanTask()

ScanFragment::ChannelScanTask::doInBackground() @ src/com/android/tv/tuner/setup/
  ChannelScanFileParser::parseScanFile() @ src/com/android/tv/tuner/setup/
    TunerTsStreamer::startStream(ChannelScanFileParser.ScanChannel) @ src/com/android/tv/tuner/source/
      TunerHal::tune() @ src/com/android/tv/tuner/
        Java_com_android_tv_tuner_TunerHal_nativeTune() @ jni/tunertvinput_jni.cpp
          DvbManager::tune() @ jni/DvbManager.cpp
            //DVB API v5 でのチューニング方法
            ioctl(FE_SET_PROPERTY) // /dev/dvb0.fe0 などに対して実施

DvbManager::tune() は ATSC 決め打ちの場所があって、DVB や ISDB など他の放送規格には対応していないように見えます。

CHANNEL_MAP_SCAN_FILE はスキャンするチャンネルの一覧が書いてあるファイルのリソース ID が並んでいます。

チャンネルリストのリソース ID
    private static final int CHANNEL_MAP_SCAN_FILE[] = {

ちなみにファイルは packages/apps/TV/usbtuner-res/raw の下にあります。もし新しいファイル file_name を追加したいときは、ディレクトリの下にファイルを追加し、CHANNEL_MAP_SCAN_FILE に R.raw.file_name のように、追加すれば良いみたいです。


TV 視聴の際は、チューニングと MPEG2-TS データを受け取って、セクション解析する処理が行われるようです。

チューニングについては、チャネルスキャンで紹介した TunerHal::tune() が使われますので割愛して、TS データを受け取るところを調べます。どうも下記のようになっているようです。

DVB API デバイスから TS を受け取って、PSI パーサーに渡すまで
TunerTsStreamer::StreamingThread::run() @ src/com/android/tv/tuner/source/
  TunerHal::readTsStream() @ src/com/android/tv/tuner/
      Java_com_android_tv_tuner_TunerHal_nativeWriteInBuffer() @ jni/tunertvinput_jni.cpp
        DvbManager::readTsStream() @ jni/DvbManager.cpp
          read() // /dev/dvb0.dvr0 など
  EventDetector::feedTSStream() @ src/com/android/tv/tuner/tvinput/
    TsParser::feedTSData() @ src/com/android/tv/tuner/ts/
            SectionParser::parseSections() @ src/com/android/tv/tuner/ts/

詳細に調べ切れていませんが、動画や音声データは ではあまり触らないようです。ExoPlayer に丸投げですかね?

[編集者: すずき]
[更新: 2018年 6月 29日 21:37]
link 編集する


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

link permalink

Namazu 延命

Namazu の延命策として、検索文字列に UTF-8 文字列を投げられるようにしました。

といっても、オリジナルの CGI を namazu.cgi → namazu2.cgi にリネームして、下記のラッパー CGI を用意しただけです。

Sakura は Perl が 2種類(/usr/bin/perl = perl 5.8, /usr/bin/perl5 = perl 5.14)入っていて、perl 5.14 だけ Text::Iconv がインストールされています。しかしなぜか @INC にパスが通っていません。仕方ないので、かなりダサいですが、強引にパスを通しています。

UTF-8 の検索文字列を EUC-JP に変換するラッパー


use lib '/usr/local/lib/perl5/site_perl/5.14/mach';

use CGI;
use Text::Iconv;

my $cv = Text::Iconv->new("UTF-8", "EUC-JP");
my $cgi = CGI->new;
my $q_cv = $cv->convert(scalar $cgi->param('query'));
$cgi->param('query', $q_cv);
$q_str = $cgi->query_string();
print "Location: namazu2.cgi?" . $q_str . "\n\n";

今回スクリプトを書いていて初めて知ったのですが CGI の param 関数を読んでスカラ値を受けたい場合 scalar $cgi->param('query') という書き方をした方が良いそうです。

理由の詳細はこのブログ(New Class of Vulnerability in Perl Web Applications)に載っていますが、ハッシュで受けると意図せず他の値を上書きしてしまう可能性があるので、明示的にスカラとして受け取るべき、ということらしいです。


Perl 初心者の私がなぜ気づいたかというと、動作テストをしたときに Apache の動作ログに、下記の Warning が記録されたからです。

Apache のエラーログ
AH01215: CGI::param called in list context from /home/katsuhiro/public_html/namazu/aaa.cgi line 8, this can lead to vulnerabilities.
See the warning in "Fetching the value or values of a single named parameter" at /usr/share/perl5/ line 412.

初心者にはエラーメッセージだけだと意味がわかりませんが、Warning メッセージに言われるがまま /usr/share/perl5/ 412行目を見ると、

	# list context can be dangerous so warn:
	if ( wantarray && $LIST_CONTEXT_WARN == 1 ) {
		my ( $package, $filename, $line ) = caller;
		if ( $package ne 'CGI' ) {
			$LIST_CONTEXT_WARN++; # only warn once
			warn "CGI::param called in list context from $filename line $line, this can lead to vulnerabilities. "
				. 'See the warning in "Fetching the value or values of a single named parameter"';

危険な理由として、先ほど紹介したブログの URL を書いてくれていました。親切ですね。ブログを読んだので何となくわかりましたが、説明なしでは若干難解なコードかもしれません…。

[編集者: すずき]
[更新: 2018年 6月 29日 00:12]
link 編集する


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

link もっと前
   2018年 7月 7日 -
      2018年 6月 28日  
link もっと後


link 記事を新規作成

合計:  counter total
本日:  counter today

link About
RDF ファイル RSS 1.0
QR コード QR コード

最終更新: 2/21 00:46



最近のコメント 5件

  • link 18年12月11日
    すずき 「確認いただいてありがとうございます。直っ...」
    (更新:12/27 00:22)
  • link 18年12月11日
    T4 「再現しませんね\n\n確かに、過去にそう...」
    (更新:12/25 12:45)
  • link 18年12月11日
    すずき 「失礼しました。Rock64 の方は、\n...」
    (更新:12/22 02:10)
  • link 18年12月11日
    T4 「私は、Tinker Board を持って...」
    (更新:12/21 10:14)
  • link 18年12月11日
    すずき 「> 但し、これもう一年以上前の話で...」
    (更新:12/19 11:54)

最近の記事 20件

link もっとみる
  • link 19年02月19日
    すずき 「[RISC-V のコンパイラ] Crosstool-NG は RI...」
    (更新:02/21 00:46)
  • link 19年02月18日
    すずき 「[RockPro64 のシリアル出力] RockPro64 のシリ...」
    (更新:02/21 00:43)
  • link 19年02月20日
    すずき 「[RockPro64 の HDMI Audio] RK3399 の...」
    (更新:02/21 00:33)
  • link 19年02月17日
    すずき 「[RockPro64 のシリアル文字化け] 以前(2018年 12...」
    (更新:02/19 00:28)
  • link 19年01月05日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:27)
  • link 18年12月23日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:26)
  • link 18年11月29日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:26)
  • link 18年11月28日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:25)
  • link 18年11月11日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:25)
  • link 18年11月10日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:25)
  • link 18年10月14日
    すずき 「[linux-next で ROCK64 のオーディオが動かな] ...」
    (更新:02/13 00:24)
  • link 19年02月11日
    すずき 「[クロック分周器] Linux のクロックフレームワークで実装でき...」
    (更新:02/13 00:23)
  • link 19年02月04日
    すずき 「[Rock64 HDMI オーディオ] 先日購入した HDMI モ...」
    (更新:02/05 00:59)
  • link 19年02月03日
    すずき 「[続編が楽しみなマンガ 10作品] 最近読んだ、これからも続編が楽...」
    (更新:02/04 22:38)
  • link 19年02月02日
    すずき 「[小型 HDMI ディスプレイ購入] 小型の HDMI ディスプレ...」
    (更新:02/04 22:23)
  • link 19年01月31日
    すずき 「[ハコヅメ] Facebook で教えてもらったマンガ「ハコヅメ」...」
    (更新:02/04 22:09)
  • link 19年01月26日
    すずき 「[生物は遺伝子の運び屋] 弱者を抹殺する。 不謹慎な質問ですが、疑...」
    (更新:01/28 23:26)
  • link 19年01月18日
    すずき 「[Cocos2d-x 再び] 以前 Cocos2d-x をビルドし...」
    (更新:01/20 00:16)
  • link 19年01月14日
    すずき 「[家の PC 性能表、更新] 昔(2017年 5月 2日の日記参照...」
    (更新:01/14 02:08)
  • link 19年01月13日
    すずき 「[ROCK64 と Tinker Board の U-Boot] ...」
    (更新:01/13 21:44)


open/close wiki
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 過去日記について


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