/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "MatroskaExtractor" #include <utils/Log.h> #include "FLACDecoder.h" #include "MatroskaExtractor.h" #include "common/webmids.h" #include <media/DataSourceBase.h> #include <media/ExtractorUtils.h> #include <media/stagefright/foundation/ADebug.h> #include <media/stagefright/foundation/AUtils.h> #include <media/stagefright/foundation/ABuffer.h> #include <media/stagefright/foundation/ByteUtils.h> #include <media/stagefright/foundation/ColorUtils.h> #include <media/stagefright/foundation/hexdump.h> #include <media/stagefright/MediaDefs.h> #include <media/stagefright/MediaErrors.h> #include <media/stagefright/MetaDataUtils.h> #include <media/stagefright/foundation/avc_utils.h> #include <utils/String8.h> #include <arpa/inet.h> #include <inttypes.h> #include <vector> namespace android { struct DataSourceBaseReader : public mkvparser::IMkvReader { explicit DataSourceBaseReader(DataSourceHelper *source) : mSource(source) { } virtual int Read(long long position, long length, unsigned char* buffer) { CHECK(position >= 0); CHECK(length >= 0); if (length == 0) { return 0; } ssize_t n = mSource->readAt(position, buffer, length); if (n <= 0) { return -1; } return 0; } virtual int Length(long long* total, long long* available) { off64_t size; if (mSource->getSize(&size) != OK) { if (total) { *total = -1; } if (available) { *available = (long long)((1ull << 63) - 1); } return 0; } if (total) { *total = size; } if (available) { *available = size; } return 0; } private: DataSourceHelper *mSource; DataSourceBaseReader(const DataSourceBaseReader &); DataSourceBaseReader &operator=(const DataSourceBaseReader &); }; //////////////////////////////////////////////////////////////////////////////// struct BlockIterator { BlockIterator(MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index); bool eos() const; void advance(); void reset(); void seek( int64_t seekTimeUs, bool isAudio, int64_t *actualFrameTimeUs); const mkvparser::Block *block() const; int64_t blockTimeUs() const; private: MatroskaExtractor *mExtractor; long long mTrackNum; unsigned long mIndex; const mkvparser::Cluster *mCluster; const mkvparser::BlockEntry *mBlockEntry; long mBlockEntryIndex; unsigned long mTrackType; void seekwithoutcue_l(int64_t seekTimeUs, int64_t *actualFrameTimeUs); void advance_l(); BlockIterator(const BlockIterator &); BlockIterator &operator=(const BlockIterator &); }; struct MatroskaSource : public MediaTrackHelper { MatroskaSource(MatroskaExtractor *extractor, size_t index); virtual media_status_t start(); virtual media_status_t stop(); virtual media_status_t getFormat(AMediaFormat *); virtual media_status_t read( MediaBufferHelper **buffer, const ReadOptions *options); protected: virtual ~MatroskaSource(); private: enum Type { AVC, AAC, HEVC, MP3, PCM, VORBIS, OTHER }; MatroskaExtractor *mExtractor; size_t mTrackIndex; Type mType; bool mIsAudio; BlockIterator mBlockIter; ssize_t mNALSizeLen; // for type AVC or HEVC List<MediaBufferHelper *> mPendingFrames; int64_t mCurrentTS; // add for mp3 uint32_t mMP3Header; media_status_t findMP3Header(uint32_t * header, const uint8_t *dataSource, int length, int *outStartPos); media_status_t mp3FrameRead( MediaBufferHelper **out, const ReadOptions *options, int64_t targetSampleTimeUs); status_t advance(); status_t setWebmBlockCryptoInfo(MediaBufferHelper *mbuf); media_status_t readBlock(); void clearPendingFrames(); MatroskaSource(const MatroskaSource &); MatroskaSource &operator=(const MatroskaSource &); }; const mkvparser::Track* MatroskaExtractor::TrackInfo::getTrack() const { return mExtractor->mSegment->GetTracks()->GetTrackByNumber(mTrackNum); } // This function does exactly the same as mkvparser::Cues::Find, except that it // searches in our own track based vectors. We should not need this once mkvparser // adds the same functionality. const mkvparser::CuePoint::TrackPosition *MatroskaExtractor::TrackInfo::find( long long timeNs) const { ALOGV("mCuePoints.size %zu", mCuePoints.size()); if (mCuePoints.empty()) { return NULL; } const mkvparser::CuePoint* cp = mCuePoints.itemAt(0); const mkvparser::Track* track = getTrack(); if (timeNs <= cp->GetTime(mExtractor->mSegment)) { return cp->Find(track); } // Binary searches through relevant cues; assumes cues are ordered by timecode. // If we do detect out-of-order cues, return NULL. size_t lo = 0; size_t hi = mCuePoints.size(); while (lo < hi) { const size_t mid = lo + (hi - lo) / 2; const mkvparser::CuePoint* const midCp = mCuePoints.itemAt(mid); const long long cueTimeNs = midCp->GetTime(mExtractor->mSegment); if (cueTimeNs <= timeNs) { lo = mid + 1; } else { hi = mid; } } if (lo == 0) { return NULL; } cp = mCuePoints.itemAt(lo - 1); if (cp->GetTime(mExtractor->mSegment) > timeNs) { return NULL; } return cp->Find(track); } MatroskaSource::MatroskaSource( MatroskaExtractor *extractor, size_t index) : mExtractor(extractor), mTrackIndex(index), mType(OTHER), mIsAudio(false), mBlockIter(mExtractor, mExtractor->mTracks.itemAt(index).mTrackNum, index), mNALSizeLen(-1), mCurrentTS(0), mMP3Header(0) { MatroskaExtractor::TrackInfo &trackInfo = mExtractor->mTracks.editItemAt(index); AMediaFormat *meta = trackInfo.mMeta; const char *mime; CHECK(AMediaFormat_getString(meta, AMEDIAFORMAT_KEY_MIME, &mime)); mIsAudio = !strncasecmp("audio/", mime, 6); if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { mType = AVC; int32_t nalSizeLen = trackInfo.mNalLengthSize; if (nalSizeLen >= 0 && nalSizeLen <= 4) { mNALSizeLen = nalSizeLen; } else { ALOGE("No AVC mNALSizeLen"); } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_HEVC)) { mType = HEVC; int32_t nalSizeLen = trackInfo.mNalLengthSize; if (nalSizeLen >= 0 && nalSizeLen <= 4) { mNALSizeLen = nalSizeLen; } else { ALOGE("No HEVC mNALSizeLen"); } } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) { mType = AAC; } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) { mType = MP3; } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) { mType = PCM; } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) { mType = VORBIS; } } MatroskaSource::~MatroskaSource() { clearPendingFrames(); } media_status_t MatroskaSource::start() { if (mType == AVC && mNALSizeLen < 0) { return AMEDIA_ERROR_MALFORMED; } // allocate one small initial buffer, but leave plenty of room to grow mBufferGroup->init(1 /* number of buffers */, 1024 /* buffer size */, 64 /* growth limit */); mBlockIter.reset(); if (mType == MP3 && mMP3Header == 0) { int start = -1; media_status_t err = findMP3Header(&mMP3Header, NULL, 0, &start); if (err != OK) { ALOGE("No mp3 header found"); clearPendingFrames(); return err; } } return AMEDIA_OK; } media_status_t MatroskaSource::stop() { clearPendingFrames(); return AMEDIA_OK; } media_status_t MatroskaSource::getFormat(AMediaFormat *meta) { return AMediaFormat_copy(meta, mExtractor->mTracks.itemAt(mTrackIndex).mMeta); } //////////////////////////////////////////////////////////////////////////////// BlockIterator::BlockIterator( MatroskaExtractor *extractor, unsigned long trackNum, unsigned long index) : mExtractor(extractor), mTrackNum(trackNum), mIndex(index), mCluster(NULL), mBlockEntry(NULL), mBlockEntryIndex(0) { mTrackType = mExtractor->mSegment->GetTracks()->GetTrackByNumber(trackNum)->GetType(); reset(); } bool BlockIterator::eos() const { return mCluster == NULL || mCluster->EOS(); } void BlockIterator::advance() { Mutex::Autolock autoLock(mExtractor->mLock); advance_l(); } void BlockIterator::advance_l() { for (;;) { long res = mCluster->GetEntry(mBlockEntryIndex, mBlockEntry); ALOGV("GetEntry returned %ld", res); long long pos; long len; if (res < 0) { // Need to parse this cluster some more CHECK_EQ(res, mkvparser::E_BUFFER_NOT_FULL); res = mCluster->Parse(pos, len); ALOGV("Parse returned %ld", res); if (res < 0) { // I/O error ALOGE("Cluster::Parse returned result %ld", res); mCluster = NULL; break; } continue; } else if (res == 0) { // We're done with this cluster const mkvparser::Cluster *nextCluster; res = mExtractor->mSegment->ParseNext( mCluster, nextCluster, pos, len); ALOGV("ParseNext returned %ld", res); if (res != 0) { // EOF or error mCluster = NULL; break; } CHECK_EQ(res, 0); CHECK(nextCluster != NULL); CHECK(!nextCluster->EOS()); mCluster = nextCluster; res = mCluster->Parse(pos, len); ALOGV("Parse (2) returned %ld", res); if (res < 0) { // I/O error ALOGE("Cluster::Parse returned result %ld", res); mCluster = NULL; break; } mBlockEntryIndex = 0; continue; } CHECK(mBlockEntry != NULL); CHECK(mBlockEntry->GetBlock() != NULL); ++mBlockEntryIndex; if (mBlockEntry->GetBlock()->GetTrackNumber() == mTrackNum) { break; } } } void BlockIterator::reset() { Mutex::Autolock autoLock(mExtractor->mLock); mCluster = mExtractor->mSegment->GetFirst(); mBlockEntry = NULL; mBlockEntryIndex = 0; do { advance_l(); } while (!eos() && block()->GetTrackNumber() != mTrackNum); } void BlockIterator::seek( int64_t seekTimeUs, bool isAudio, int64_t *actualFrameTimeUs) { Mutex::Autolock autoLock(mExtractor->mLock); *actualFrameTimeUs = -1ll; if (seekTimeUs > INT64_MAX / 1000ll || seekTimeUs < INT64_MIN / 1000ll || (mExtractor->mSeekPreRollNs > 0 && (seekTimeUs * 1000ll) < INT64_MIN + mExtractor->mSeekPreRollNs) || (mExtractor->mSeekPreRollNs < 0 && (seekTimeUs * 1000ll) > INT64_MAX + mExtractor->mSeekPreRollNs)) { ALOGE("cannot seek to %lld", (long long) seekTimeUs); return; } const int64_t seekTimeNs = seekTimeUs * 1000ll - mExtractor->mSeekPreRollNs; mkvparser::Segment* const pSegment = mExtractor->mSegment; // Special case the 0 seek to avoid loading Cues when the application // extraneously seeks to 0 before playing. if (seekTimeNs <= 0) { ALOGV("Seek to beginning: %" PRId64, seekTimeUs); mCluster = pSegment->GetFirst(); mBlockEntryIndex = 0; do { advance_l(); } while (!eos() && block()->GetTrackNumber() != mTrackNum); return; } ALOGV("Seeking to: %" PRId64, seekTimeUs); // If the Cues have not been located then find them. const mkvparser::Cues* pCues = pSegment->GetCues(); const mkvparser::SeekHead* pSH = pSegment->GetSeekHead(); if (!pCues && pSH) { const size_t count = pSH->GetCount(); const mkvparser::SeekHead::Entry* pEntry; ALOGV("No Cues yet"); for (size_t index = 0; index < count; index++) { pEntry = pSH->GetEntry(index); if (pEntry->id == libwebm::kMkvCues) { // Cues ID long len; long long pos; pSegment->ParseCues(pEntry->pos, pos, len); pCues = pSegment->GetCues(); ALOGV("Cues found"); break; } } if (!pCues) { ALOGV("No Cues in file,seek without cue data"); seekwithoutcue_l(seekTimeUs, actualFrameTimeUs); return; } } else if (!pSH) { ALOGV("No SeekHead, seek without cue data"); seekwithoutcue_l(seekTimeUs, actualFrameTimeUs); return; } const mkvparser::CuePoint* pCP; mkvparser::Tracks const *pTracks = pSegment->GetTracks(); while (!pCues->DoneParsing()) { pCues->LoadCuePoint(); pCP = pCues->GetLast(); ALOGV("pCP = %s", pCP == NULL ? "NULL" : "not NULL"); if (pCP == NULL) continue; size_t trackCount = mExtractor->mTracks.size(); for (size_t index = 0; index < trackCount; ++index) { MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(index); const mkvparser::Track *pTrack = pTracks->GetTrackByNumber(track.mTrackNum); if (pTrack && pTrack->GetType() == 1 && pCP->Find(pTrack)) { // VIDEO_TRACK track.mCuePoints.push_back(pCP); } } if (pCP->GetTime(pSegment) >= seekTimeNs) { ALOGV("Parsed past relevant Cue"); break; } } const mkvparser::CuePoint::TrackPosition *pTP = NULL; const mkvparser::Track *thisTrack = pTracks->GetTrackByNumber(mTrackNum); if (thisTrack->GetType() == 1) { // video MatroskaExtractor::TrackInfo& track = mExtractor->mTracks.editItemAt(mIndex); pTP = track.find(seekTimeNs); } else { // The Cue index is built around video keyframes unsigned long int trackCount = pTracks->GetTracksCount(); for (size_t index = 0; index < trackCount; ++index) { const mkvparser::Track *pTrack = pTracks->GetTrackByIndex(index); if (pTrack && pTrack->GetType() == 1 && pCues->Find(seekTimeNs, pTrack, pCP, pTP)) { ALOGV("Video track located at %zu", index); break; } } } // Always *search* based on the video track, but finalize based on mTrackNum if (!pTP) { ALOGE("Did not locate the video track for seeking"); seekwithoutcue_l(seekTimeUs, actualFrameTimeUs); return; } mCluster = pSegment->FindOrPreloadCluster(pTP->m_pos); CHECK(mCluster); CHECK(!mCluster->EOS()); // mBlockEntryIndex starts at 0 but m_block starts at 1 CHECK_GT(pTP->m_block, 0); mBlockEntryIndex = pTP->m_block - 1; for (;;) { advance_l(); if (eos()) break; if (isAudio || block()->IsKey()) { // Accept the first key frame int64_t frameTimeUs = (block()->GetTime(mCluster) + 500LL) / 1000LL; if (thisTrack->GetType() == 1 || frameTimeUs >= seekTimeUs) { *actualFrameTimeUs = frameTimeUs; ALOGV("Requested seek point: %" PRId64 " actual: %" PRId64, seekTimeUs, *actualFrameTimeUs); break; } } } } const mkvparser::Block *BlockIterator::block() const { CHECK(!eos()); return mBlockEntry->GetBlock(); } int64_t BlockIterator::blockTimeUs() const { if (mCluster == NULL || mBlockEntry == NULL) { return -1; } return (mBlockEntry->GetBlock()->GetTime(mCluster) + 500ll) / 1000ll; } void BlockIterator::seekwithoutcue_l(int64_t seekTimeUs, int64_t *actualFrameTimeUs) { mCluster = mExtractor->mSegment->FindCluster(seekTimeUs * 1000ll); const long status = mCluster->GetFirst(mBlockEntry); if (status < 0) { // error ALOGE("get last blockenry failed!"); mCluster = NULL; return; } mBlockEntryIndex = 0; while (!eos() && ((block()->GetTrackNumber() != mTrackNum) || (blockTimeUs() < seekTimeUs))) { advance_l(); } // video track will seek to the next key frame. if (mTrackType == 1) { while (!eos() && ((block()->GetTrackNumber() != mTrackNum) || !mBlockEntry->GetBlock()->IsKey())) { advance_l(); } } *actualFrameTimeUs = blockTimeUs(); ALOGV("seekTimeUs:%lld, actualFrameTimeUs:%lld, tracknum:%lld", (long long)seekTimeUs, (long long)*actualFrameTimeUs, (long long)mTrackNum); } //////////////////////////////////////////////////////////////////////////////// static unsigned U24_AT(const uint8_t *ptr) { return ptr[0] << 16 | ptr[1] << 8 | ptr[2]; } static AString uriDebugString(const char *uri) { // find scheme AString scheme; for (size_t i = 0; i < strlen(uri); i++) { const char c = uri[i]; if (!isascii(c)) { break; } else if (isalpha(c)) { continue; } else if (i == 0) { // first character must be a letter break; } else if (isdigit(c) || c == '+' || c == '.' || c =='-') { continue; } else if (c != ':') { break; } scheme = AString(uri, 0, i); scheme.append("://<suppressed>"); return scheme; } return AString("<no-scheme URI suppressed>"); } void MatroskaSource::clearPendingFrames() { while (!mPendingFrames.empty()) { MediaBufferHelper *frame = *mPendingFrames.begin(); mPendingFrames.erase(mPendingFrames.begin()); frame->release(); frame = NULL; } } status_t MatroskaSource::setWebmBlockCryptoInfo(MediaBufferHelper *mbuf) { if (mbuf->range_length() < 1 || mbuf->range_length() - 1 > INT32_MAX) { // 1-byte signal return ERROR_MALFORMED; } const uint8_t *data = (const uint8_t *)mbuf->data() + mbuf->range_offset(); bool encrypted = data[0] & 0x1; bool partitioned = data[0] & 0x2; if (encrypted && mbuf->range_length() < 9) { // 1-byte signal + 8-byte IV return ERROR_MALFORMED; } AMediaFormat *meta = mbuf->meta_data(); if (encrypted) { uint8_t ctrCounter[16] = { 0 }; const uint8_t *keyId; size_t keyIdSize; AMediaFormat *trackMeta = mExtractor->mTracks.itemAt(mTrackIndex).mMeta; AMediaFormat_getBuffer(trackMeta, AMEDIAFORMAT_KEY_CRYPTO_KEY, (void**)&keyId, &keyIdSize); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_KEY, keyId, keyIdSize); memcpy(ctrCounter, data + 1, 8); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_IV, ctrCounter, 16); if (partitioned) { /* 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Signal Byte | | * +-+-+-+-+-+-+-+-+ IV | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | num_partition | Partition 0 offset -> | * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | -> Partition 0 offset | ... | * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | ... | Partition n-1 offset -> | * |-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | -> Partition n-1 offset | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | * | Clear/encrypted sample data | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ if (mbuf->range_length() < 10) { return ERROR_MALFORMED; } uint8_t numPartitions = data[9]; if (mbuf->range_length() - 10 < numPartitions * sizeof(uint32_t)) { return ERROR_MALFORMED; } std::vector<uint32_t> plainSizes, encryptedSizes; uint32_t prev = 0; uint32_t frameOffset = 10 + numPartitions * sizeof(uint32_t); const uint32_t *partitions = reinterpret_cast<const uint32_t*>(data + 10); for (uint32_t i = 0; i <= numPartitions; ++i) { uint32_t p_i = i < numPartitions ? ntohl(partitions[i]) : (mbuf->range_length() - frameOffset); if (p_i < prev) { return ERROR_MALFORMED; } uint32_t size = p_i - prev; prev = p_i; if (i % 2) { encryptedSizes.push_back(size); } else { plainSizes.push_back(size); } } if (plainSizes.size() > encryptedSizes.size()) { encryptedSizes.push_back(0); } uint32_t sizeofPlainSizes = sizeof(uint32_t) * plainSizes.size(); uint32_t sizeofEncryptedSizes = sizeof(uint32_t) * encryptedSizes.size(); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_PLAIN_SIZES, plainSizes.data(), sizeofPlainSizes); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_ENCRYPTED_SIZES, encryptedSizes.data(), sizeofEncryptedSizes); mbuf->set_range(frameOffset, mbuf->range_length() - frameOffset); } else { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Signal Byte | | * +-+-+-+-+-+-+-+-+ IV | * | | * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | | * |-+-+-+-+-+-+-+-+ | * : Bytes 1..N of encrypted frame : * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ int32_t plainSizes[] = { 0 }; int32_t encryptedSizes[] = { static_cast<int32_t>(mbuf->range_length() - 9) }; AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_PLAIN_SIZES, plainSizes, sizeof(plainSizes)); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_ENCRYPTED_SIZES, encryptedSizes, sizeof(encryptedSizes)); mbuf->set_range(9, mbuf->range_length() - 9); } } else { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Signal Byte | | * +-+-+-+-+-+-+-+-+ | * : Bytes 1..N of unencrypted frame : * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ int32_t plainSizes[] = { static_cast<int32_t>(mbuf->range_length() - 1) }; int32_t encryptedSizes[] = { 0 }; AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_PLAIN_SIZES, plainSizes, sizeof(plainSizes)); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CRYPTO_ENCRYPTED_SIZES, encryptedSizes, sizeof(encryptedSizes)); mbuf->set_range(1, mbuf->range_length() - 1); } return OK; } media_status_t MatroskaSource::readBlock() { CHECK(mPendingFrames.empty()); if (mBlockIter.eos()) { return AMEDIA_ERROR_END_OF_STREAM; } const mkvparser::Block *block = mBlockIter.block(); int64_t timeUs = mBlockIter.blockTimeUs(); for (int i = 0; i < block->GetFrameCount(); ++i) { MatroskaExtractor::TrackInfo *trackInfo = &mExtractor->mTracks.editItemAt(mTrackIndex); const mkvparser::Block::Frame &frame = block->GetFrame(i); size_t len = frame.len; if (SIZE_MAX - len < trackInfo->mHeaderLen) { return AMEDIA_ERROR_MALFORMED; } len += trackInfo->mHeaderLen; MediaBufferHelper *mbuf; mBufferGroup->acquire_buffer(&mbuf, false /* nonblocking */, len /* requested size */); mbuf->set_range(0, len); uint8_t *data = static_cast<uint8_t *>(mbuf->data()); if (trackInfo->mHeader) { memcpy(data, trackInfo->mHeader, trackInfo->mHeaderLen); } AMediaFormat *meta = mbuf->meta_data(); AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_TIME_US, timeUs); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, block->IsKey()); if (mType == VORBIS) { int32_t sampleRate; if (!AMediaFormat_getInt32(trackInfo->mMeta, AMEDIAFORMAT_KEY_SAMPLE_RATE, &sampleRate)) { return AMEDIA_ERROR_MALFORMED; } int64_t durationUs; if (!AMediaFormat_getInt64(trackInfo->mMeta, AMEDIAFORMAT_KEY_DURATION, &durationUs)) { return AMEDIA_ERROR_MALFORMED; } // TODO: Explore if this can be handled similar to MPEG4 extractor where padding is // signalled instead of VALID_SAMPLES // Remaining valid samples in Vorbis track if (durationUs > timeUs) { int32_t validSamples = ((durationUs - timeUs) * sampleRate) / 1000000ll; AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_VALID_SAMPLES, validSamples); } } status_t err = frame.Read(mExtractor->mReader, data + trackInfo->mHeaderLen); if (err == OK && mExtractor->mIsWebm && trackInfo->mEncrypted) { err = setWebmBlockCryptoInfo(mbuf); } if (err != OK) { mPendingFrames.clear(); mBlockIter.advance(); mbuf->release(); return AMEDIA_ERROR_UNKNOWN; } mPendingFrames.push_back(mbuf); } mBlockIter.advance(); return AMEDIA_OK; } //the value of kMP3HeaderMask is from MP3Extractor static const uint32_t kMP3HeaderMask = 0xfffe0c00; media_status_t MatroskaSource::findMP3Header(uint32_t * header, const uint8_t *dataSource, int length, int *outStartPos) { if (NULL == header) { ALOGE("header is null!"); return AMEDIA_ERROR_END_OF_STREAM; } //to find header start position if (0 != *header) { if (NULL == dataSource) { *outStartPos = -1; return AMEDIA_OK; } uint32_t tmpCode = 0; for (int i = 0; i < length; i++) { tmpCode = (tmpCode << 8) + dataSource[i]; if ((tmpCode & kMP3HeaderMask) == (*header & kMP3HeaderMask)) { *outStartPos = i - 3; return AMEDIA_OK; } } *outStartPos = -1; return AMEDIA_OK; } //to find mp3 header uint32_t code = 0; while (0 == *header) { while (mPendingFrames.empty()) { media_status_t err = readBlock(); if (err != OK) { clearPendingFrames(); return err; } } MediaBufferHelper *frame = *mPendingFrames.begin(); size_t size = frame->range_length(); size_t offset = frame->range_offset(); size_t i; size_t frame_size; for (i = 0; i < size; i++) { ALOGV("data[%zu]=%x", i, *((uint8_t*)frame->data() + offset + i)); code = (code << 8) + *((uint8_t*)frame->data() + offset + i); if (GetMPEGAudioFrameSize(code, &frame_size, NULL, NULL, NULL)) { *header = code; mBlockIter.reset(); clearPendingFrames(); return AMEDIA_OK; } } } return AMEDIA_ERROR_END_OF_STREAM; } media_status_t MatroskaSource::mp3FrameRead( MediaBufferHelper **out, const ReadOptions *options, int64_t targetSampleTimeUs) { MediaBufferHelper *frame = *mPendingFrames.begin(); int64_t seekTimeUs; ReadOptions::SeekMode mode; if (options && options->getSeekTo(&seekTimeUs, &mode)) { CHECK(AMediaFormat_getInt64(frame->meta_data(), AMEDIAFORMAT_KEY_TIME_US, &mCurrentTS)); if (mCurrentTS < 0) { mCurrentTS = 0; AMediaFormat_setInt64(frame->meta_data(), AMEDIAFORMAT_KEY_TIME_US, mCurrentTS); } } int32_t start = -1; while (start < 0) { //find header start position findMP3Header(&mMP3Header, (const uint8_t*)frame->data() + frame->range_offset(), frame->range_length(), &start); ALOGV("start=%d, frame->range_length() = %zu, frame->range_offset() =%zu", start, frame->range_length(), frame->range_offset()); if (start >= 0) break; frame->release(); mPendingFrames.erase(mPendingFrames.begin()); while (mPendingFrames.empty()) { media_status_t err = readBlock(); if (err != OK) { clearPendingFrames(); return err; } } frame = *mPendingFrames.begin(); } frame->set_range(frame->range_offset() + start, frame->range_length() - start); uint32_t header = *(uint32_t*)((uint8_t*)frame->data() + frame->range_offset()); header = ((header >> 24) & 0xff) | ((header >> 8) & 0xff00) | ((header << 8) & 0xff0000) | ((header << 24) & 0xff000000); size_t frame_size; int out_sampling_rate; int out_channels; int out_bitrate; if (!GetMPEGAudioFrameSize(header, &frame_size, &out_sampling_rate, &out_channels, &out_bitrate)) { ALOGE("MP3 Header read fail!!"); return AMEDIA_ERROR_UNSUPPORTED; } MediaBufferHelper *buffer; mBufferGroup->acquire_buffer(&buffer, false /* nonblocking */, frame_size /* requested size */); buffer->set_range(0, frame_size); uint8_t *data = static_cast<uint8_t *>(buffer->data()); ALOGV("MP3 frame %zu frame->range_length() %zu", frame_size, frame->range_length()); if (frame_size > frame->range_length()) { memcpy(data, (uint8_t*)(frame->data()) + frame->range_offset(), frame->range_length()); size_t sumSize = 0; sumSize += frame->range_length(); size_t needSize = frame_size - frame->range_length(); frame->release(); mPendingFrames.erase(mPendingFrames.begin()); while (mPendingFrames.empty()) { media_status_t err = readBlock(); if (err != OK) { clearPendingFrames(); return err; } } frame = *mPendingFrames.begin(); size_t offset = frame->range_offset(); size_t size = frame->range_length(); // the next buffer frame is not enough to fullfill mp3 frame, // we have to read until mp3 frame is completed. while (size < needSize) { memcpy(data + sumSize, (uint8_t*)(frame->data()) + offset, size); needSize -= size; sumSize += size; frame->release(); mPendingFrames.erase(mPendingFrames.begin()); while (mPendingFrames.empty()) { media_status_t err = readBlock(); if (err != OK) { clearPendingFrames(); return err; } } frame = *mPendingFrames.begin(); offset = frame->range_offset(); size = frame->range_length(); } memcpy(data + sumSize, (uint8_t*)(frame->data()) + offset, needSize); frame->set_range(offset + needSize, size - needSize); } else { size_t offset = frame->range_offset(); size_t size = frame->range_length(); memcpy(data, (uint8_t*)(frame->data()) + offset, frame_size); frame->set_range(offset + frame_size, size - frame_size); } if (frame->range_length() < 4) { frame->release(); frame = NULL; mPendingFrames.erase(mPendingFrames.begin()); } ALOGV("MatroskaSource::read MP3 frame kKeyTime=%lld,kKeyTargetTime=%lld", (long long)mCurrentTS, (long long)targetSampleTimeUs); AMediaFormat_setInt64(buffer->meta_data(), AMEDIAFORMAT_KEY_TIME_US, mCurrentTS); mCurrentTS += (int64_t)frame_size * 8000ll / out_bitrate; if (targetSampleTimeUs >= 0ll) AMediaFormat_setInt64(buffer->meta_data(), AMEDIAFORMAT_KEY_TARGET_TIME, targetSampleTimeUs); *out = buffer; ALOGV("MatroskaSource::read MP3, keyTime=%lld for next frame", (long long)mCurrentTS); return AMEDIA_OK; } media_status_t MatroskaSource::read( MediaBufferHelper **out, const ReadOptions *options) { *out = NULL; int64_t targetSampleTimeUs = -1ll; int64_t seekTimeUs; ReadOptions::SeekMode mode; if (options && options->getSeekTo(&seekTimeUs, &mode)) { if (mode == ReadOptions::SEEK_FRAME_INDEX) { return AMEDIA_ERROR_UNSUPPORTED; } if (!mExtractor->isLiveStreaming()) { clearPendingFrames(); // The audio we want is located by using the Cues to seek the video // stream to find the target Cluster then iterating to finalize for // audio. int64_t actualFrameTimeUs; mBlockIter.seek(seekTimeUs, mIsAudio, &actualFrameTimeUs); if (mode == ReadOptions::SEEK_CLOSEST) { targetSampleTimeUs = actualFrameTimeUs; } } } while (mPendingFrames.empty()) { media_status_t err = readBlock(); if (err != OK) { clearPendingFrames(); return err; } } if (mType == MP3) { return mp3FrameRead(out, options, targetSampleTimeUs); } MediaBufferHelper *frame = *mPendingFrames.begin(); mPendingFrames.erase(mPendingFrames.begin()); if ((mType != AVC && mType != HEVC) || mNALSizeLen == 0) { if (targetSampleTimeUs >= 0ll) { AMediaFormat_setInt64(frame->meta_data(), AMEDIAFORMAT_KEY_TARGET_TIME, targetSampleTimeUs); } if (mType == PCM) { int32_t bitPerFrame = 16; int32_t bigEndian = 0; AMediaFormat *meta = AMediaFormat_new(); if (getFormat(meta) == AMEDIA_OK && meta != NULL) { AMediaFormat_getInt32(meta, AMEDIAFORMAT_KEY_BITS_PER_SAMPLE, &bitPerFrame); AMediaFormat_getInt32(meta, AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN, &bigEndian); } AMediaFormat_delete(meta); if (bigEndian == 1 && bitPerFrame == 16) { // Big-endian -> little-endian uint16_t *dstData = (uint16_t *)frame->data() + frame->range_offset(); uint16_t *srcData = (uint16_t *)frame->data() + frame->range_offset(); for (size_t i = 0; i < frame->range_length() / 2; i++) { dstData[i] = ntohs(srcData[i]); } } } *out = frame; return AMEDIA_OK; } // Each input frame contains one or more NAL fragments, each fragment // is prefixed by mNALSizeLen bytes giving the fragment length, // followed by a corresponding number of bytes containing the fragment. // We output all these fragments into a single large buffer separated // by startcodes (0x00 0x00 0x00 0x01). // // When mNALSizeLen is 0, we assume the data is already in the format // desired. const uint8_t *srcPtr = (const uint8_t *)frame->data() + frame->range_offset(); size_t srcSize = frame->range_length(); size_t dstSize = 0; MediaBufferHelper *buffer = NULL; uint8_t *dstPtr = NULL; for (int32_t pass = 0; pass < 2; ++pass) { size_t srcOffset = 0; size_t dstOffset = 0; while (srcOffset + mNALSizeLen <= srcSize) { size_t NALsize; switch (mNALSizeLen) { case 1: NALsize = srcPtr[srcOffset]; break; case 2: NALsize = U16_AT(srcPtr + srcOffset); break; case 3: NALsize = U24_AT(srcPtr + srcOffset); break; case 4: NALsize = U32_AT(srcPtr + srcOffset); break; default: TRESPASS(); } if (srcOffset + mNALSizeLen + NALsize <= srcOffset + mNALSizeLen) { frame->release(); frame = NULL; return AMEDIA_ERROR_MALFORMED; } else if (srcOffset + mNALSizeLen + NALsize > srcSize) { break; } if (pass == 1) { memcpy(&dstPtr[dstOffset], "\x00\x00\x00\x01", 4); if (frame != buffer) { memcpy(&dstPtr[dstOffset + 4], &srcPtr[srcOffset + mNALSizeLen], NALsize); } } dstOffset += 4; // 0x00 00 00 01 dstOffset += NALsize; srcOffset += mNALSizeLen + NALsize; } if (srcOffset < srcSize) { // There were trailing bytes or not enough data to complete // a fragment. frame->release(); frame = NULL; return AMEDIA_ERROR_MALFORMED; } if (pass == 0) { dstSize = dstOffset; if (dstSize == srcSize && mNALSizeLen == 4) { // In this special case we can re-use the input buffer by substituting // each 4-byte nal size with a 4-byte start code buffer = frame; } else { mBufferGroup->acquire_buffer( &buffer, false /* nonblocking */, dstSize /* requested size */); buffer->set_range(0, dstSize); } AMediaFormat *frameMeta = frame->meta_data(); int64_t timeUs; CHECK(AMediaFormat_getInt64(frameMeta, AMEDIAFORMAT_KEY_TIME_US, &timeUs)); int32_t isSync; CHECK(AMediaFormat_getInt32(frameMeta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, &isSync)); AMediaFormat *bufMeta = buffer->meta_data(); AMediaFormat_setInt64(bufMeta, AMEDIAFORMAT_KEY_TIME_US, timeUs); AMediaFormat_setInt32(bufMeta, AMEDIAFORMAT_KEY_IS_SYNC_FRAME, isSync); dstPtr = (uint8_t *)buffer->data(); } } if (frame != buffer) { frame->release(); frame = NULL; } if (targetSampleTimeUs >= 0ll) { AMediaFormat_setInt64(buffer->meta_data(), AMEDIAFORMAT_KEY_TARGET_TIME, targetSampleTimeUs); } *out = buffer; return AMEDIA_OK; } //////////////////////////////////////////////////////////////////////////////// enum WaveID { MKV_RIFF_WAVE_FORMAT_PCM = 0x0001, MKV_RIFF_WAVE_FORMAT_ADPCM_ms = 0x0002, MKV_RIFF_WAVE_FORMAT_ADPCM_ima_wav = 0x0011, MKV_RIFF_WAVE_FORMAT_MPEGL12 = 0x0050, MKV_RIFF_WAVE_FORMAT_MPEGL3 = 0x0055, MKV_RIFF_WAVE_FORMAT_WMAV1 = 0x0160, MKV_RIFF_WAVE_FORMAT_WMAV2 = 0x0161, }; static const char *MKVWave2MIME(uint16_t id) { switch (id) { case MKV_RIFF_WAVE_FORMAT_MPEGL12: return MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II; case MKV_RIFF_WAVE_FORMAT_MPEGL3: return MEDIA_MIMETYPE_AUDIO_MPEG; case MKV_RIFF_WAVE_FORMAT_PCM: return MEDIA_MIMETYPE_AUDIO_RAW; case MKV_RIFF_WAVE_FORMAT_ADPCM_ms: return MEDIA_MIMETYPE_AUDIO_MS_ADPCM; case MKV_RIFF_WAVE_FORMAT_ADPCM_ima_wav: return MEDIA_MIMETYPE_AUDIO_DVI_IMA_ADPCM; case MKV_RIFF_WAVE_FORMAT_WMAV1: case MKV_RIFF_WAVE_FORMAT_WMAV2: return MEDIA_MIMETYPE_AUDIO_WMA; default: ALOGW("unknown wave %x", id); return ""; }; } static bool isMkvAudioCsdSizeOK(const char* mime, size_t csdSize) { if ((!strcmp(mime, MEDIA_MIMETYPE_AUDIO_MS_ADPCM) && csdSize < 50) || (!strcmp(mime, MEDIA_MIMETYPE_AUDIO_DVI_IMA_ADPCM) && csdSize < 20) || (!strcmp(mime, MEDIA_MIMETYPE_AUDIO_WMA) && csdSize < 28) || (!strcmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG) && csdSize < 30)) { return false; } return true; } // trans all FOURCC to lower char static uint32_t FourCCtoLower(uint32_t fourcc) { uint8_t ch_1 = tolower((fourcc >> 24) & 0xff); uint8_t ch_2 = tolower((fourcc >> 16) & 0xff); uint8_t ch_3 = tolower((fourcc >> 8) & 0xff); uint8_t ch_4 = tolower((fourcc) & 0xff); uint32_t fourcc_out = ch_1 << 24 | ch_2 << 16 | ch_3 << 8 | ch_4; return fourcc_out; } static const char *MKVFourCC2MIME(uint32_t fourcc) { ALOGV("MKVFourCC2MIME fourcc 0x%8.8x", fourcc); uint32_t lowerFourcc = FourCCtoLower(fourcc); switch (lowerFourcc) { case FOURCC("mp4v"): return MEDIA_MIMETYPE_VIDEO_MPEG4; case FOURCC("s263"): case FOURCC("h263"): return MEDIA_MIMETYPE_VIDEO_H263; case FOURCC("avc1"): case FOURCC("h264"): return MEDIA_MIMETYPE_VIDEO_AVC; case FOURCC("mpg2"): return MEDIA_MIMETYPE_VIDEO_MPEG2; case FOURCC("xvid"): return MEDIA_MIMETYPE_VIDEO_XVID; case FOURCC("divx"): case FOURCC("dx50"): return MEDIA_MIMETYPE_VIDEO_DIVX; case FOURCC("div3"): case FOURCC("div4"): return MEDIA_MIMETYPE_VIDEO_DIVX3; case FOURCC("mjpg"): case FOURCC("mppg"): return MEDIA_MIMETYPE_VIDEO_MJPEG; default: char fourccString[5]; MakeFourCCString(fourcc, fourccString); ALOGW("mkv unsupport fourcc %s", fourccString); return ""; } } MatroskaExtractor::MatroskaExtractor(DataSourceHelper *source) : mDataSource(source), mReader(new DataSourceBaseReader(mDataSource)), mSegment(NULL), mExtractedThumbnails(false), mIsWebm(false), mSeekPreRollNs(0) { off64_t size; mIsLiveStreaming = (mDataSource->flags() & (DataSourceBase::kWantsPrefetching | DataSourceBase::kIsCachingDataSource)) && mDataSource->getSize(&size) != OK; mkvparser::EBMLHeader ebmlHeader; long long pos; if (ebmlHeader.Parse(mReader, pos) < 0) { return; } if (ebmlHeader.m_docType && !strcmp("webm", ebmlHeader.m_docType)) { mIsWebm = true; } long long ret = mkvparser::Segment::CreateInstance(mReader, pos, mSegment); if (ret) { CHECK(mSegment == NULL); return; } if (mIsLiveStreaming) { // from mkvparser::Segment::Load(), but stop at first cluster ret = mSegment->ParseHeaders(); if (ret == 0) { long len; ret = mSegment->LoadCluster(pos, len); if (ret >= 1) { // no more clusters ret = 0; } } else if (ret > 0) { ret = mkvparser::E_BUFFER_NOT_FULL; } } else { ret = mSegment->ParseHeaders(); if (ret < 0) { ALOGE("Segment parse header return fail %lld", ret); delete mSegment; mSegment = NULL; return; } else if (ret == 0) { const mkvparser::Cues* mCues = mSegment->GetCues(); const mkvparser::SeekHead* mSH = mSegment->GetSeekHead(); if ((mCues == NULL) && (mSH != NULL)) { size_t count = mSH->GetCount(); const mkvparser::SeekHead::Entry* mEntry; for (size_t index = 0; index < count; index++) { mEntry = mSH->GetEntry(index); if (mEntry->id == libwebm::kMkvCues) { // Cues ID long len; long long pos; mSegment->ParseCues(mEntry->pos, pos, len); mCues = mSegment->GetCues(); ALOGV("find cue data by seekhead"); break; } } } if (mCues) { long len; ret = mSegment->LoadCluster(pos, len); ALOGV("has Cue data, Cluster num=%ld", mSegment->GetCount()); } else { long status_Load = mSegment->Load(); ALOGW("no Cue data,Segment Load status:%ld",status_Load); } } else if (ret > 0) { ret = mkvparser::E_BUFFER_NOT_FULL; } } if (ret < 0) { char uri[1024]; if(!mDataSource->getUri(uri, sizeof(uri))) { uri[0] = '\0'; } ALOGW("Corrupt %s source: %s", mIsWebm ? "webm" : "matroska", uriDebugString(uri).c_str()); delete mSegment; mSegment = NULL; return; } #if 0 const mkvparser::SegmentInfo *info = mSegment->GetInfo(); ALOGI("muxing app: %s, writing app: %s", info->GetMuxingAppAsUTF8(), info->GetWritingAppAsUTF8()); #endif addTracks(); } MatroskaExtractor::~MatroskaExtractor() { delete mSegment; mSegment = NULL; delete mReader; mReader = NULL; delete mDataSource; for (size_t i = 0; i < mTracks.size(); ++i) { TrackInfo *info = &mTracks.editItemAt(i); if (info->mMeta) { AMediaFormat_delete(info->mMeta); } } } size_t MatroskaExtractor::countTracks() { return mTracks.size(); } MediaTrackHelper *MatroskaExtractor::getTrack(size_t index) { if (index >= mTracks.size()) { return NULL; } return new MatroskaSource(this, index); } media_status_t MatroskaExtractor::getTrackMetaData( AMediaFormat *meta, size_t index, uint32_t flags) { if (index >= mTracks.size()) { return AMEDIA_ERROR_UNKNOWN; } if ((flags & kIncludeExtensiveMetaData) && !mExtractedThumbnails && !isLiveStreaming()) { findThumbnails(); mExtractedThumbnails = true; } return AMediaFormat_copy(meta, mTracks.itemAt(index).mMeta); } bool MatroskaExtractor::isLiveStreaming() const { return mIsLiveStreaming; } static int bytesForSize(size_t size) { // use at most 28 bits (4 times 7) CHECK(size <= 0xfffffff); if (size > 0x1fffff) { return 4; } else if (size > 0x3fff) { return 3; } else if (size > 0x7f) { return 2; } return 1; } static void storeSize(uint8_t *data, size_t &idx, size_t size) { int numBytes = bytesForSize(size); idx += numBytes; data += idx; size_t next = 0; while (numBytes--) { *--data = (size & 0x7f) | next; size >>= 7; next = 0x80; } } static void addESDSFromCodecPrivate( AMediaFormat *meta, bool isAudio, const void *priv, size_t privSize) { int privSizeBytesRequired = bytesForSize(privSize); int esdsSize2 = 14 + privSizeBytesRequired + privSize; int esdsSize2BytesRequired = bytesForSize(esdsSize2); int esdsSize1 = 4 + esdsSize2BytesRequired + esdsSize2; int esdsSize1BytesRequired = bytesForSize(esdsSize1); size_t esdsSize = 1 + esdsSize1BytesRequired + esdsSize1; uint8_t *esds = new uint8_t[esdsSize]; size_t idx = 0; esds[idx++] = 0x03; storeSize(esds, idx, esdsSize1); esds[idx++] = 0x00; // ES_ID esds[idx++] = 0x00; // ES_ID esds[idx++] = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag esds[idx++] = 0x04; storeSize(esds, idx, esdsSize2); esds[idx++] = isAudio ? 0x40 // Audio ISO/IEC 14496-3 : 0x20; // Visual ISO/IEC 14496-2 for (int i = 0; i < 12; i++) { esds[idx++] = 0x00; } esds[idx++] = 0x05; storeSize(esds, idx, privSize); memcpy(esds + idx, priv, privSize); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_0, priv, privSize); delete[] esds; esds = NULL; } status_t addVorbisCodecInfo( AMediaFormat *meta, const void *_codecPrivate, size_t codecPrivateSize) { // hexdump(_codecPrivate, codecPrivateSize); if (codecPrivateSize < 1) { return ERROR_MALFORMED; } const uint8_t *codecPrivate = (const uint8_t *)_codecPrivate; if (codecPrivate[0] != 0x02) { return ERROR_MALFORMED; } // codecInfo starts with two lengths, len1 and len2, that are // "Xiph-style-lacing encoded"... size_t offset = 1; size_t len1 = 0; while (offset < codecPrivateSize && codecPrivate[offset] == 0xff) { if (len1 > (SIZE_MAX - 0xff)) { return ERROR_MALFORMED; // would overflow } len1 += 0xff; ++offset; } if (offset >= codecPrivateSize) { return ERROR_MALFORMED; } if (len1 > (SIZE_MAX - codecPrivate[offset])) { return ERROR_MALFORMED; // would overflow } len1 += codecPrivate[offset++]; size_t len2 = 0; while (offset < codecPrivateSize && codecPrivate[offset] == 0xff) { if (len2 > (SIZE_MAX - 0xff)) { return ERROR_MALFORMED; // would overflow } len2 += 0xff; ++offset; } if (offset >= codecPrivateSize) { return ERROR_MALFORMED; } if (len2 > (SIZE_MAX - codecPrivate[offset])) { return ERROR_MALFORMED; // would overflow } len2 += codecPrivate[offset++]; if (len1 > SIZE_MAX - len2 || offset > SIZE_MAX - (len1 + len2) || codecPrivateSize < offset + len1 + len2) { return ERROR_MALFORMED; } if (codecPrivate[offset] != 0x01) { return ERROR_MALFORMED; } // formerly kKeyVorbisInfo AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_0, &codecPrivate[offset], len1); offset += len1; if (codecPrivate[offset] != 0x03) { return ERROR_MALFORMED; } offset += len2; if (codecPrivate[offset] != 0x05) { return ERROR_MALFORMED; } // formerly kKeyVorbisBooks AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_1, &codecPrivate[offset], codecPrivateSize - offset); return OK; } static status_t addFlacMetadata( AMediaFormat *meta, const void *codecPrivate, size_t codecPrivateSize) { // hexdump(codecPrivate, codecPrivateSize); // formerly kKeyFlacMetadata AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_0, codecPrivate, codecPrivateSize); int32_t maxInputSize = 64 << 10; FLACDecoder *flacDecoder = FLACDecoder::Create(); if (flacDecoder != NULL && flacDecoder->parseMetadata((const uint8_t*)codecPrivate, codecPrivateSize) == OK) { FLAC__StreamMetadata_StreamInfo streamInfo = flacDecoder->getStreamInfo(); maxInputSize = streamInfo.max_framesize; if (maxInputSize == 0) { // In case max framesize is not available, use raw data size as max framesize, // assuming there is no expansion. if (streamInfo.max_blocksize != 0 && streamInfo.channels != 0 && ((streamInfo.bits_per_sample + 7) / 8) > INT32_MAX / streamInfo.max_blocksize / streamInfo.channels) { delete flacDecoder; return ERROR_MALFORMED; } maxInputSize = ((streamInfo.bits_per_sample + 7) / 8) * streamInfo.max_blocksize * streamInfo.channels; } } AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_MAX_INPUT_SIZE, maxInputSize); delete flacDecoder; return OK; } status_t MatroskaExtractor::synthesizeAVCC(TrackInfo *trackInfo, size_t index) { BlockIterator iter(this, trackInfo->mTrackNum, index); if (iter.eos()) { return ERROR_MALFORMED; } const mkvparser::Block *block = iter.block(); if (block->GetFrameCount() <= 0) { return ERROR_MALFORMED; } const mkvparser::Block::Frame &frame = block->GetFrame(0); auto tmpData = heapbuffer<unsigned char>(frame.len); long n = frame.Read(mReader, tmpData.get()); if (n != 0) { return ERROR_MALFORMED; } if (!MakeAVCCodecSpecificData(trackInfo->mMeta, tmpData.get(), frame.len)) { return ERROR_MALFORMED; } // Override the synthesized nal length size, which is arbitrary trackInfo->mNalLengthSize = 0; return OK; } status_t MatroskaExtractor::synthesizeMPEG2(TrackInfo *trackInfo, size_t index) { ALOGV("synthesizeMPEG2"); BlockIterator iter(this, trackInfo->mTrackNum, index); if (iter.eos()) { return ERROR_MALFORMED; } const mkvparser::Block *block = iter.block(); if (block->GetFrameCount() <= 0) { return ERROR_MALFORMED; } const mkvparser::Block::Frame &frame = block->GetFrame(0); auto tmpData = heapbuffer<unsigned char>(frame.len); long n = frame.Read(mReader, tmpData.get()); if (n != 0) { return ERROR_MALFORMED; } size_t header_start = 0; size_t header_lenth = 0; for (header_start = 0; header_start < frame.len - 4; header_start++) { if (ntohl(0x000001b3) == *(uint32_t*)((uint8_t*)tmpData.get() + header_start)) { break; } } bool isComplete_csd = false; for (header_lenth = 0; header_lenth < frame.len - 4 - header_start; header_lenth++) { if (ntohl(0x000001b8) == *(uint32_t*)((uint8_t*)tmpData.get() + header_start + header_lenth)) { isComplete_csd = true; break; } } if (!isComplete_csd) { ALOGE("can't parse complete csd for MPEG2!"); return ERROR_MALFORMED; } addESDSFromCodecPrivate(trackInfo->mMeta, false, (uint8_t*)(tmpData.get()) + header_start, header_lenth); return OK; } status_t MatroskaExtractor::synthesizeMPEG4(TrackInfo *trackInfo, size_t index) { ALOGV("synthesizeMPEG4"); BlockIterator iter(this, trackInfo->mTrackNum, index); if (iter.eos()) { return ERROR_MALFORMED; } const mkvparser::Block *block = iter.block(); if (block->GetFrameCount() <= 0) { return ERROR_MALFORMED; } const mkvparser::Block::Frame &frame = block->GetFrame(0); auto tmpData = heapbuffer<unsigned char>(frame.len); long n = frame.Read(mReader, tmpData.get()); if (n != 0) { return ERROR_MALFORMED; } size_t vosend; bool isComplete_csd = false; for (vosend = 0; (long)vosend < frame.len - 4; vosend++) { if (ntohl(0x000001b6) == *(uint32_t*)((uint8_t*)tmpData.get() + vosend)) { isComplete_csd = true; break; // Send VOS until VOP } } if (!isComplete_csd) { ALOGE("can't parse complete csd for MPEG4!"); return ERROR_MALFORMED; } addESDSFromCodecPrivate(trackInfo->mMeta, false, tmpData.get(), vosend); return OK; } static inline bool isValidInt32ColourValue(long long value) { return value != mkvparser::Colour::kValueNotPresent && value >= INT32_MIN && value <= INT32_MAX; } static inline bool isValidUint16ColourValue(long long value) { return value != mkvparser::Colour::kValueNotPresent && value >= 0 && value <= UINT16_MAX; } static inline bool isValidPrimary(const mkvparser::PrimaryChromaticity *primary) { return primary != NULL && primary->x >= 0 && primary->x <= 1 && primary->y >= 0 && primary->y <= 1; } void MatroskaExtractor::getColorInformation( const mkvparser::VideoTrack *vtrack, AMediaFormat *meta) { const mkvparser::Colour *color = vtrack->GetColour(); if (color == NULL) { return; } // Color Aspects { int32_t primaries = 2; // ISO unspecified int32_t isotransfer = 2; // ISO unspecified int32_t coeffs = 2; // ISO unspecified bool fullRange = false; // default bool rangeSpecified = false; if (isValidInt32ColourValue(color->primaries)) { primaries = color->primaries; } if (isValidInt32ColourValue(color->transfer_characteristics)) { isotransfer = color->transfer_characteristics; } if (isValidInt32ColourValue(color->matrix_coefficients)) { coeffs = color->matrix_coefficients; } if (color->range != mkvparser::Colour::kValueNotPresent && color->range != 0 /* MKV unspecified */) { // We only support MKV broadcast range (== limited) and full range. // We treat all other value as the default limited range. fullRange = color->range == 2 /* MKV fullRange */; rangeSpecified = true; } int32_t range = 0; int32_t standard = 0; int32_t transfer = 0; ColorUtils::convertIsoColorAspectsToPlatformAspects( primaries, isotransfer, coeffs, fullRange, &range, &standard, &transfer); if (range != 0) { AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_COLOR_RANGE, range); } if (standard != 0) { AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_COLOR_STANDARD, standard); } if (transfer != 0) { AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_COLOR_TRANSFER, transfer); } } // HDR Static Info { HDRStaticInfo info, nullInfo; // nullInfo is a fully unspecified static info memset(&info, 0, sizeof(info)); memset(&nullInfo, 0, sizeof(nullInfo)); if (isValidUint16ColourValue(color->max_cll)) { info.sType1.mMaxContentLightLevel = color->max_cll; } if (isValidUint16ColourValue(color->max_fall)) { info.sType1.mMaxFrameAverageLightLevel = color->max_fall; } const mkvparser::MasteringMetadata *mastering = color->mastering_metadata; if (mastering != NULL) { // Convert matroska values to HDRStaticInfo equivalent values for each fully specified // group. See CTA-681.3 section 3.2.1 for more info. if (mastering->luminance_max >= 0.5 && mastering->luminance_max < 65535.5) { info.sType1.mMaxDisplayLuminance = (uint16_t)(mastering->luminance_max + 0.5); } if (mastering->luminance_min >= 0.00005 && mastering->luminance_min < 6.55355) { // HDRStaticInfo Type1 stores min luminance scaled 10000:1 info.sType1.mMinDisplayLuminance = (uint16_t)(10000 * mastering->luminance_min + 0.5); } // HDRStaticInfo Type1 stores primaries scaled 50000:1 if (isValidPrimary(mastering->white_point)) { info.sType1.mW.x = (uint16_t)(50000 * mastering->white_point->x + 0.5); info.sType1.mW.y = (uint16_t)(50000 * mastering->white_point->y + 0.5); } if (isValidPrimary(mastering->r) && isValidPrimary(mastering->g) && isValidPrimary(mastering->b)) { info.sType1.mR.x = (uint16_t)(50000 * mastering->r->x + 0.5); info.sType1.mR.y = (uint16_t)(50000 * mastering->r->y + 0.5); info.sType1.mG.x = (uint16_t)(50000 * mastering->g->x + 0.5); info.sType1.mG.y = (uint16_t)(50000 * mastering->g->y + 0.5); info.sType1.mB.x = (uint16_t)(50000 * mastering->b->x + 0.5); info.sType1.mB.y = (uint16_t)(50000 * mastering->b->y + 0.5); } } // Only advertise static info if at least one of the groups have been specified. if (memcmp(&info, &nullInfo, sizeof(info)) != 0) { info.mID = HDRStaticInfo::kType1; ColorUtils::setHDRStaticInfoIntoAMediaFormat(info, meta); } } } status_t MatroskaExtractor::initTrackInfo( const mkvparser::Track *track, AMediaFormat *meta, TrackInfo *trackInfo) { trackInfo->mTrackNum = track->GetNumber(); trackInfo->mMeta = meta; trackInfo->mExtractor = this; trackInfo->mEncrypted = false; trackInfo->mHeader = NULL; trackInfo->mHeaderLen = 0; trackInfo->mNalLengthSize = -1; for(size_t i = 0; i < track->GetContentEncodingCount(); i++) { const mkvparser::ContentEncoding *encoding = track->GetContentEncodingByIndex(i); for(size_t j = 0; j < encoding->GetEncryptionCount(); j++) { const mkvparser::ContentEncoding::ContentEncryption *encryption; encryption = encoding->GetEncryptionByIndex(j); AMediaFormat_setBuffer(trackInfo->mMeta, AMEDIAFORMAT_KEY_CRYPTO_KEY, encryption->key_id, encryption->key_id_len); trackInfo->mEncrypted = true; break; } for(size_t j = 0; j < encoding->GetCompressionCount(); j++) { const mkvparser::ContentEncoding::ContentCompression *compression; compression = encoding->GetCompressionByIndex(j); ALOGV("compression algo %llu settings_len %lld", compression->algo, compression->settings_len); if (compression->algo == 3 && compression->settings && compression->settings_len > 0) { trackInfo->mHeader = compression->settings; trackInfo->mHeaderLen = compression->settings_len; } } } return OK; } void MatroskaExtractor::addTracks() { const mkvparser::Tracks *tracks = mSegment->GetTracks(); AMediaFormat *meta = nullptr; for (size_t index = 0; index < tracks->GetTracksCount(); ++index) { const mkvparser::Track *track = tracks->GetTrackByIndex(index); if (track == NULL) { // Apparently this is currently valid (if unexpected) behaviour // of the mkv parser lib. continue; } const char *const codecID = track->GetCodecId(); ALOGV("codec id = %s", codecID); ALOGV("codec name = %s", track->GetCodecNameAsUTF8()); if (codecID == NULL) { ALOGW("unknown codecID is not supported."); continue; } size_t codecPrivateSize; const unsigned char *codecPrivate = track->GetCodecPrivate(codecPrivateSize); enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 }; if (meta) { AMediaFormat_clear(meta); } else { meta = AMediaFormat_new(); } status_t err = OK; int32_t nalSize = -1; bool isSetCsdFrom1stFrame = false; switch (track->GetType()) { case VIDEO_TRACK: { const mkvparser::VideoTrack *vtrack = static_cast<const mkvparser::VideoTrack *>(track); if (!strcmp("V_MPEG4/ISO/AVC", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_AVC); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_AVC, codecPrivate, codecPrivateSize); if (codecPrivateSize > 4) { nalSize = 1 + (codecPrivate[4] & 3); } } else if (!strcmp("V_MPEGH/ISO/HEVC", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_HEVC); if (codecPrivateSize > 0) { AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_HEVC, codecPrivate, codecPrivateSize); if (codecPrivateSize > 14 + 7) { nalSize = 1 + (codecPrivate[14 + 7] & 3); } } else { ALOGW("HEVC is detected, but does not have configuration."); continue; } } else if (!strcmp("V_MPEG4/ISO/ASP", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_MPEG4); if (codecPrivateSize > 0) { addESDSFromCodecPrivate( meta, false, codecPrivate, codecPrivateSize); } else { ALOGW("%s is detected, but does not have configuration.", codecID); isSetCsdFrom1stFrame = true; } } else if (!strcmp("V_VP8", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_VP8); } else if (!strcmp("V_VP9", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_VP9); if (codecPrivateSize > 0) { // 'csd-0' for VP9 is the Blob of Codec Private data as // specified in http://www.webmproject.org/vp9/profiles/. AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_0, codecPrivate, codecPrivateSize); } } else if (!strcmp("V_AV1", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_AV1); if (codecPrivateSize > 0) { // 'csd-0' for AV1 is the Blob of Codec Private data as // specified in https://aomediacodec.github.io/av1-isobmff/. AMediaFormat_setBuffer( meta, AMEDIAFORMAT_KEY_CSD_0, codecPrivate, codecPrivateSize); } } else if (!strcmp("V_MPEG2", codecID) || !strcmp("V_MPEG1", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_MPEG2); if (codecPrivate != NULL) { addESDSFromCodecPrivate(meta, false, codecPrivate, codecPrivateSize); } else { ALOGW("No specific codec private data, find it from the first frame"); isSetCsdFrom1stFrame = true; } } else if (!strcmp("V_MJPEG", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_VIDEO_MJPEG); } else if (!strcmp("V_MS/VFW/FOURCC", codecID)) { if (NULL == codecPrivate ||codecPrivateSize < 20) { ALOGE("V_MS/VFW/FOURCC has no valid private data(%p),codecPrivateSize:%zu", codecPrivate, codecPrivateSize); continue; } else { uint32_t fourcc = *(uint32_t *)(codecPrivate + 16); fourcc = ntohl(fourcc); const char* mime = MKVFourCC2MIME(fourcc); ALOGV("V_MS/VFW/FOURCC type is %s", mime); if (!strncasecmp("video/", mime, 6)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, mime); } else { ALOGE("V_MS/VFW/FOURCC continue,unsupport video type=%s,fourcc=0x%08x.", mime, fourcc); continue; } if (!strcmp(mime, MEDIA_MIMETYPE_VIDEO_AVC) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG4) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_XVID) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_DIVX) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_DIVX3) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_MPEG2) || !strcmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) { isSetCsdFrom1stFrame = true; } else { ALOGW("FourCC have unsupport codec, type=%s,fourcc=0x%08x.", mime, fourcc); continue; } } } else { ALOGW("%s is not supported.", codecID); continue; } const long long width = vtrack->GetWidth(); const long long height = vtrack->GetHeight(); if (width <= 0 || width > INT32_MAX) { ALOGW("track width exceeds int32_t, %lld", width); continue; } if (height <= 0 || height > INT32_MAX) { ALOGW("track height exceeds int32_t, %lld", height); continue; } AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_WIDTH, (int32_t)width); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_HEIGHT, (int32_t)height); // setting display width/height is optional const long long displayUnit = vtrack->GetDisplayUnit(); const long long displayWidth = vtrack->GetDisplayWidth(); const long long displayHeight = vtrack->GetDisplayHeight(); if (displayWidth > 0 && displayWidth <= INT32_MAX && displayHeight > 0 && displayHeight <= INT32_MAX) { switch (displayUnit) { case 0: // pixels AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_DISPLAY_WIDTH, (int32_t)displayWidth); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, (int32_t)displayHeight); break; case 1: // centimeters case 2: // inches case 3: // aspect ratio { // Physical layout size is treated the same as aspect ratio. // Note: displayWidth and displayHeight are never zero as they are // checked in the if above. const long long computedWidth = std::max(width, height * displayWidth / displayHeight); const long long computedHeight = std::max(height, width * displayHeight / displayWidth); if (computedWidth <= INT32_MAX && computedHeight <= INT32_MAX) { AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_DISPLAY_WIDTH, (int32_t)computedWidth); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_DISPLAY_HEIGHT, (int32_t)computedHeight); } break; } default: // unknown display units, perhaps future version of spec. break; } } getColorInformation(vtrack, meta); break; } case AUDIO_TRACK: { const mkvparser::AudioTrack *atrack = static_cast<const mkvparser::AudioTrack *>(track); if (!strcmp("A_AAC", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_AAC); if (codecPrivateSize < 2) { ALOGW("Incomplete AAC Codec Info %zu byte", codecPrivateSize); continue; } addESDSFromCodecPrivate( meta, true, codecPrivate, codecPrivateSize); } else if (!strcmp("A_VORBIS", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_VORBIS); err = addVorbisCodecInfo( meta, codecPrivate, codecPrivateSize); } else if (!strcmp("A_OPUS", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_OPUS); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_0, codecPrivate, codecPrivateSize); int64_t codecDelay = track->GetCodecDelay(); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_1, &codecDelay, sizeof(codecDelay)); mSeekPreRollNs = track->GetSeekPreRoll(); AMediaFormat_setBuffer(meta, AMEDIAFORMAT_KEY_CSD_2, &mSeekPreRollNs, sizeof(mSeekPreRollNs)); } else if (!strcmp("A_MPEG/L3", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MPEG); } else if (!strcmp("A_FLAC", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_FLAC); err = addFlacMetadata(meta, codecPrivate, codecPrivateSize); } else if (!strcmp("A_MPEG/L2", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II); } else if (!strcmp("A_PCM/INT/LIT", codecID) || !strcmp("A_PCM/INT/BIG", codecID)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, MEDIA_MIMETYPE_AUDIO_RAW); int32_t bigEndian = !strcmp("A_PCM/INT/BIG", codecID) ? 1: 0; AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_PCM_BIG_ENDIAN, bigEndian); } else if ((!strcmp("A_MS/ACM", codecID))) { if ((NULL == codecPrivate) || (codecPrivateSize < 18)) { ALOGW("unsupported audio: A_MS/ACM has no valid private data: %s, size: %zu", codecPrivate == NULL ? "null" : "non-null", codecPrivateSize); continue; } else { uint16_t ID = *(uint16_t *)codecPrivate; const char* mime = MKVWave2MIME(ID); ALOGV("A_MS/ACM type is %s", mime); if (!strncasecmp("audio/", mime, 6) && isMkvAudioCsdSizeOK(mime, codecPrivateSize)) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, mime); } else { ALOGE("A_MS/ACM continue, unsupported audio type=%s, csdSize:%zu", mime, codecPrivateSize); continue; } if (!strcmp(mime, MEDIA_MIMETYPE_AUDIO_WMA)) { addESDSFromCodecPrivate(meta, true, codecPrivate, codecPrivateSize); } else if (!strcmp(mime, MEDIA_MIMETYPE_AUDIO_MS_ADPCM) || !strcmp(mime, MEDIA_MIMETYPE_AUDIO_DVI_IMA_ADPCM)) { uint32_t blockAlign = *(uint16_t*)(codecPrivate + 12); addESDSFromCodecPrivate(meta, true, &blockAlign, sizeof(blockAlign)); } } } else { ALOGW("%s is not supported.", codecID); continue; } AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_SAMPLE_RATE, atrack->GetSamplingRate()); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_CHANNEL_COUNT, atrack->GetChannels()); AMediaFormat_setInt32(meta, AMEDIAFORMAT_KEY_BITS_PER_SAMPLE, atrack->GetBitDepth()); break; } default: continue; } const char *language = track->GetLanguage(); if (language != NULL) { char lang[4]; strncpy(lang, language, 3); lang[3] = '\0'; AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_LANGUAGE, lang); } if (err != OK) { ALOGE("skipping track, codec specific data was malformed."); continue; } long long durationNs = mSegment->GetDuration(); AMediaFormat_setInt64(meta, AMEDIAFORMAT_KEY_DURATION, (durationNs + 500) / 1000); const char *mimetype = ""; if (!AMediaFormat_getString(meta, AMEDIAFORMAT_KEY_MIME, &mimetype)) { // do not add this track to the track list ALOGW("ignoring track with unknown mime"); continue; } mTracks.push(); size_t n = mTracks.size() - 1; TrackInfo *trackInfo = &mTracks.editItemAt(n); initTrackInfo(track, meta, trackInfo); trackInfo->mNalLengthSize = nalSize; if ((!strcmp("V_MPEG4/ISO/AVC", codecID) && codecPrivateSize == 0) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_AVC) && isSetCsdFrom1stFrame)) { // Attempt to recover from AVC track without codec private data err = synthesizeAVCC(trackInfo, n); if (err != OK) { mTracks.pop(); continue; } } else if ((!strcmp("V_MPEG2", codecID) && codecPrivateSize == 0) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_MPEG2) && isSetCsdFrom1stFrame)) { // Attempt to recover from MPEG2 track without codec private data err = synthesizeMPEG2(trackInfo, n); if (err != OK) { mTracks.pop(); continue; } } else if ((!strcmp("V_MPEG4/ISO/ASP", codecID) && codecPrivateSize == 0) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_MPEG4) && isSetCsdFrom1stFrame) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_XVID) && isSetCsdFrom1stFrame) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_DIVX) && isSetCsdFrom1stFrame) || (!strcmp(mimetype, MEDIA_MIMETYPE_VIDEO_DIVX3) && isSetCsdFrom1stFrame)) { // Attempt to recover from MPEG4 track without codec private data err = synthesizeMPEG4(trackInfo, n); if (err != OK) { mTracks.pop(); continue; } } // the TrackInfo owns the metadata now meta = nullptr; } if (meta) { AMediaFormat_delete(meta); } } void MatroskaExtractor::findThumbnails() { for (size_t i = 0; i < mTracks.size(); ++i) { TrackInfo *info = &mTracks.editItemAt(i); const char *mime; CHECK(AMediaFormat_getString(info->mMeta, AMEDIAFORMAT_KEY_MIME, &mime)); if (strncasecmp(mime, "video/", 6)) { continue; } BlockIterator iter(this, info->mTrackNum, i); int32_t j = 0; int64_t thumbnailTimeUs = 0; size_t maxBlockSize = 0; while (!iter.eos() && j < 20) { if (iter.block()->IsKey()) { ++j; size_t blockSize = 0; for (int k = 0; k < iter.block()->GetFrameCount(); ++k) { blockSize += iter.block()->GetFrame(k).len; } if (blockSize > maxBlockSize) { maxBlockSize = blockSize; thumbnailTimeUs = iter.blockTimeUs(); } } iter.advance(); } AMediaFormat_setInt64(info->mMeta, AMEDIAFORMAT_KEY_THUMBNAIL_TIME, thumbnailTimeUs); } } media_status_t MatroskaExtractor::getMetaData(AMediaFormat *meta) { AMediaFormat_setString(meta, AMEDIAFORMAT_KEY_MIME, mIsWebm ? "video/webm" : MEDIA_MIMETYPE_CONTAINER_MATROSKA); return AMEDIA_OK; } uint32_t MatroskaExtractor::flags() const { uint32_t x = CAN_PAUSE; if (!isLiveStreaming()) { x |= CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK; } return x; } bool SniffMatroska( DataSourceHelper *source, float *confidence) { DataSourceBaseReader reader(source); mkvparser::EBMLHeader ebmlHeader; long long pos; if (ebmlHeader.Parse(&reader, pos) < 0) { return false; } *confidence = 0.6; return true; } static const char *extensions[] = { "mka", "mkv", "webm", NULL }; extern "C" { // This is the only symbol that needs to be exported __attribute__ ((visibility ("default"))) ExtractorDef GETEXTRACTORDEF() { return { EXTRACTORDEF_VERSION, UUID("abbedd92-38c4-4904-a4c1-b3f45f899980"), 1, "Matroska Extractor", { .v3 = { []( CDataSource *source, float *confidence, void **, FreeMetaFunc *) -> CreatorFunc { DataSourceHelper helper(source); if (SniffMatroska(&helper, confidence)) { return []( CDataSource *source, void *) -> CMediaExtractor* { return wrap(new MatroskaExtractor(new DataSourceHelper(source)));}; } return NULL; }, extensions } } }; } } // extern "C" } // namespace android