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を処理しているところは、下記のようになっています。
//frameworks/av/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
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) {
mParser->signalEOS(ERROR_END_OF_STREAM);
}
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 {
addSyncPoint_l(event);
}
}
return err;
}
ここで出てくるmParserはATSParserのポインタなので、ATSParserの実装を見てみます。
//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
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) {
ALOGV("---");
//...
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,
payload_unit_start_indicator,
transport_scrambling_control,
random_access_indicator,
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() が追加するようです。
//frameworks/av/media/libstagefright/mpeg2ts/ATSParser.cpp
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(§ionBits); //★★★★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, §ionBits, &err)) {
continue;
}
//...
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)
continue;
}
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) {
stream->signalNewSampleAesKey(mSampleAesKeyItem);
}
isAddingScrambledStream |= info.mCASystemId >= 0; //★★★★CA descriptorに記載が無いのにスクランブルされている??
mStreams.add(info.mPID, stream);
}
}
ざっくり言うと、スクランブルの掛かったストリームはmParser->mCasManagerに任せ、スクランブルの掛かっていないストリームはmStreamsに任せるようです。
CA descriptorに載っていないのにスクランブルの掛かった変なストリームがあると警告が出るようになっています。
< | 2018 | > | ||||
<< | < | 07 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
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 | 31 | - | - | - | - |
合計:
本日:
管理者: Katsuhiro Suzuki(katsuhiro( a t )katsuster.net)
This is Simple Diary 1.0
Copyright(C) Katsuhiro Suzuki 2006-2023.
Powered by PHP 8.2.18.
using GD 2.3.3(png support.)