diff options
author | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 09:12:55 -0700 |
---|---|---|
committer | Anatol Pomozov <anatol.pomozov@gmail.com> | 2012-03-28 12:02:47 -0700 |
commit | b0b2b4d890cf3bfb274797a759642b4e733343d7 (patch) | |
tree | 12ad21cbad346f02d542aa4d672ffd76407d58a9 /media/libstagefright/mpeg2ts | |
parent | 51f8eec23a2bcc2cc190373cdd1195972d9b8804 (diff) | |
parent | 5a5491c17d74bd2c80cf451c6ddbba22d5d5f08a (diff) | |
download | frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.zip frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.gz frameworks_av-b0b2b4d890cf3bfb274797a759642b4e733343d7.tar.bz2 |
Merge media files with history from frameworks/base.git
Diffstat (limited to 'media/libstagefright/mpeg2ts')
-rw-r--r-- | media/libstagefright/mpeg2ts/ATSParser.cpp | 1012 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/ATSParser.h | 114 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/Android.mk | 22 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/AnotherPacketSource.cpp | 222 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/AnotherPacketSource.h | 77 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/ESQueue.cpp | 954 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/ESQueue.h | 76 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp | 715 | ||||
-rw-r--r-- | media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp | 266 |
9 files changed, 3458 insertions, 0 deletions
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp new file mode 100644 index 0000000..3f4de1f --- /dev/null +++ b/media/libstagefright/mpeg2ts/ATSParser.cpp @@ -0,0 +1,1012 @@ +/* + * 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 "ATSParser" +#include <utils/Log.h> + +#include "ATSParser.h" + +#include "AnotherPacketSource.h" +#include "ESQueue.h" +#include "include/avc_utils.h" + +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <media/IStreamSource.h> +#include <utils/KeyedVector.h> + +namespace android { + +// I want the expression "y" evaluated even if verbose logging is off. +#define MY_LOGV(x, y) \ + do { unsigned tmp = y; ALOGV(x, tmp); } while (0) + +static const size_t kTSPacketSize = 188; + +struct ATSParser::Program : public RefBase { + Program(ATSParser *parser, unsigned programNumber, unsigned programMapPID); + + bool parsePID( + unsigned pid, unsigned payload_unit_start_indicator, + ABitReader *br, status_t *err); + + void signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra); + + void signalEOS(status_t finalResult); + + sp<MediaSource> getSource(SourceType type); + + int64_t convertPTSToTimestamp(uint64_t PTS); + + bool PTSTimeDeltaEstablished() const { + return mFirstPTSValid; + } + + unsigned number() const { return mProgramNumber; } + + void updateProgramMapPID(unsigned programMapPID) { + mProgramMapPID = programMapPID; + } + +private: + ATSParser *mParser; + unsigned mProgramNumber; + unsigned mProgramMapPID; + KeyedVector<unsigned, sp<Stream> > mStreams; + bool mFirstPTSValid; + uint64_t mFirstPTS; + + status_t parseProgramMap(ABitReader *br); + + DISALLOW_EVIL_CONSTRUCTORS(Program); +}; + +struct ATSParser::Stream : public RefBase { + Stream(Program *program, unsigned elementaryPID, unsigned streamType); + + unsigned type() const { return mStreamType; } + unsigned pid() const { return mElementaryPID; } + void setPID(unsigned pid) { mElementaryPID = pid; } + + status_t parse( + unsigned payload_unit_start_indicator, + ABitReader *br); + + void signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra); + + void signalEOS(status_t finalResult); + + sp<MediaSource> getSource(SourceType type); + +protected: + virtual ~Stream(); + +private: + Program *mProgram; + unsigned mElementaryPID; + unsigned mStreamType; + + sp<ABuffer> mBuffer; + sp<AnotherPacketSource> mSource; + bool mPayloadStarted; + + ElementaryStreamQueue *mQueue; + + status_t flush(); + status_t parsePES(ABitReader *br); + + void onPayloadData( + unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS, + const uint8_t *data, size_t size); + + void extractAACFrames(const sp<ABuffer> &buffer); + + bool isAudio() const; + bool isVideo() const; + + DISALLOW_EVIL_CONSTRUCTORS(Stream); +}; + +//////////////////////////////////////////////////////////////////////////////// + +ATSParser::Program::Program( + ATSParser *parser, unsigned programNumber, unsigned programMapPID) + : mParser(parser), + mProgramNumber(programNumber), + mProgramMapPID(programMapPID), + mFirstPTSValid(false), + mFirstPTS(0) { + ALOGV("new program number %u", programNumber); +} + +bool ATSParser::Program::parsePID( + unsigned pid, unsigned payload_unit_start_indicator, + ABitReader *br, status_t *err) { + *err = OK; + + if (pid == mProgramMapPID) { + if (payload_unit_start_indicator) { + unsigned skip = br->getBits(8); + br->skipBits(skip * 8); + } + + *err = parseProgramMap(br); + + return true; + } + + ssize_t index = mStreams.indexOfKey(pid); + if (index < 0) { + return false; + } + + *err = mStreams.editValueAt(index)->parse( + payload_unit_start_indicator, br); + + return true; +} + +void ATSParser::Program::signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra) { + for (size_t i = 0; i < mStreams.size(); ++i) { + mStreams.editValueAt(i)->signalDiscontinuity(type, extra); + } +} + +void ATSParser::Program::signalEOS(status_t finalResult) { + for (size_t i = 0; i < mStreams.size(); ++i) { + mStreams.editValueAt(i)->signalEOS(finalResult); + } +} + +struct StreamInfo { + unsigned mType; + unsigned mPID; +}; + +status_t ATSParser::Program::parseProgramMap(ABitReader *br) { + unsigned table_id = br->getBits(8); + ALOGV(" table_id = %u", table_id); + CHECK_EQ(table_id, 0x02u); + + unsigned section_syntax_indicator = br->getBits(1); + ALOGV(" section_syntax_indicator = %u", section_syntax_indicator); + CHECK_EQ(section_syntax_indicator, 1u); + + CHECK_EQ(br->getBits(1), 0u); + MY_LOGV(" reserved = %u", br->getBits(2)); + + unsigned section_length = br->getBits(12); + ALOGV(" section_length = %u", section_length); + CHECK_EQ(section_length & 0xc00, 0u); + CHECK_LE(section_length, 1021u); + + MY_LOGV(" program_number = %u", br->getBits(16)); + MY_LOGV(" reserved = %u", br->getBits(2)); + MY_LOGV(" version_number = %u", br->getBits(5)); + MY_LOGV(" current_next_indicator = %u", br->getBits(1)); + MY_LOGV(" section_number = %u", br->getBits(8)); + MY_LOGV(" last_section_number = %u", br->getBits(8)); + MY_LOGV(" reserved = %u", br->getBits(3)); + MY_LOGV(" PCR_PID = 0x%04x", br->getBits(13)); + MY_LOGV(" reserved = %u", br->getBits(4)); + + unsigned program_info_length = br->getBits(12); + ALOGV(" program_info_length = %u", program_info_length); + CHECK_EQ(program_info_length & 0xc00, 0u); + + br->skipBits(program_info_length * 8); // skip descriptors + + Vector<StreamInfo> infos; + + // infoBytesRemaining is the number of bytes that make up the + // variable length section of ES_infos. It does not include the + // final CRC. + size_t infoBytesRemaining = section_length - 9 - program_info_length - 4; + + while (infoBytesRemaining > 0) { + CHECK_GE(infoBytesRemaining, 5u); + + unsigned streamType = br->getBits(8); + ALOGV(" stream_type = 0x%02x", streamType); + + MY_LOGV(" reserved = %u", br->getBits(3)); + + unsigned elementaryPID = br->getBits(13); + ALOGV(" elementary_PID = 0x%04x", elementaryPID); + + MY_LOGV(" reserved = %u", br->getBits(4)); + + unsigned ES_info_length = br->getBits(12); + ALOGV(" ES_info_length = %u", ES_info_length); + CHECK_EQ(ES_info_length & 0xc00, 0u); + + CHECK_GE(infoBytesRemaining - 5, ES_info_length); + +#if 0 + br->skipBits(ES_info_length * 8); // skip descriptors +#else + unsigned info_bytes_remaining = ES_info_length; + while (info_bytes_remaining >= 2) { + MY_LOGV(" tag = 0x%02x", br->getBits(8)); + + unsigned descLength = br->getBits(8); + ALOGV(" len = %u", descLength); + + CHECK_GE(info_bytes_remaining, 2 + descLength); + + br->skipBits(descLength * 8); + + info_bytes_remaining -= descLength + 2; + } + CHECK_EQ(info_bytes_remaining, 0u); +#endif + + StreamInfo info; + info.mType = streamType; + info.mPID = elementaryPID; + infos.push(info); + + infoBytesRemaining -= 5 + ES_info_length; + } + + CHECK_EQ(infoBytesRemaining, 0u); + MY_LOGV(" CRC = 0x%08x", br->getBits(32)); + + bool PIDsChanged = false; + for (size_t i = 0; i < infos.size(); ++i) { + StreamInfo &info = infos.editItemAt(i); + + ssize_t index = mStreams.indexOfKey(info.mPID); + + if (index >= 0 && mStreams.editValueAt(index)->type() != info.mType) { + ALOGI("uh oh. stream PIDs have changed."); + PIDsChanged = true; + break; + } + } + + if (PIDsChanged) { +#if 0 + ALOGI("before:"); + for (size_t i = 0; i < mStreams.size(); ++i) { + sp<Stream> stream = mStreams.editValueAt(i); + + ALOGI("PID 0x%08x => type 0x%02x", stream->pid(), stream->type()); + } + + ALOGI("after:"); + for (size_t i = 0; i < infos.size(); ++i) { + StreamInfo &info = infos.editItemAt(i); + + ALOGI("PID 0x%08x => type 0x%02x", info.mPID, info.mType); + } +#endif + + // The only case we can recover from is if we have two streams + // and they switched PIDs. + + bool success = false; + + if (mStreams.size() == 2 && infos.size() == 2) { + const StreamInfo &info1 = infos.itemAt(0); + const StreamInfo &info2 = infos.itemAt(1); + + sp<Stream> s1 = mStreams.editValueAt(0); + sp<Stream> s2 = mStreams.editValueAt(1); + + bool caseA = + info1.mPID == s1->pid() && info1.mType == s2->type() + && info2.mPID == s2->pid() && info2.mType == s1->type(); + + bool caseB = + info1.mPID == s2->pid() && info1.mType == s1->type() + && info2.mPID == s1->pid() && info2.mType == s2->type(); + + if (caseA || caseB) { + unsigned pid1 = s1->pid(); + unsigned pid2 = s2->pid(); + s1->setPID(pid2); + s2->setPID(pid1); + + mStreams.clear(); + mStreams.add(s1->pid(), s1); + mStreams.add(s2->pid(), s2); + + success = true; + } + } + + if (!success) { + ALOGI("Stream PIDs changed and we cannot recover."); + return ERROR_MALFORMED; + } + } + + for (size_t i = 0; i < infos.size(); ++i) { + StreamInfo &info = infos.editItemAt(i); + + ssize_t index = mStreams.indexOfKey(info.mPID); + + if (index < 0) { + sp<Stream> stream = new Stream(this, info.mPID, info.mType); + mStreams.add(info.mPID, stream); + } + } + + return OK; +} + +sp<MediaSource> ATSParser::Program::getSource(SourceType type) { + size_t index = (type == AUDIO) ? 0 : 0; + + for (size_t i = 0; i < mStreams.size(); ++i) { + sp<MediaSource> source = mStreams.editValueAt(i)->getSource(type); + if (source != NULL) { + if (index == 0) { + return source; + } + --index; + } + } + + return NULL; +} + +int64_t ATSParser::Program::convertPTSToTimestamp(uint64_t PTS) { + if (!(mParser->mFlags & TS_TIMESTAMPS_ARE_ABSOLUTE)) { + if (!mFirstPTSValid) { + mFirstPTSValid = true; + mFirstPTS = PTS; + PTS = 0; + } else if (PTS < mFirstPTS) { + PTS = 0; + } else { + PTS -= mFirstPTS; + } + } + + return (PTS * 100) / 9; +} + +//////////////////////////////////////////////////////////////////////////////// + +ATSParser::Stream::Stream( + Program *program, unsigned elementaryPID, unsigned streamType) + : mProgram(program), + mElementaryPID(elementaryPID), + mStreamType(streamType), + mPayloadStarted(false), + mQueue(NULL) { + switch (mStreamType) { + case STREAMTYPE_H264: + mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::H264); + break; + case STREAMTYPE_MPEG2_AUDIO_ADTS: + mQueue = new ElementaryStreamQueue(ElementaryStreamQueue::AAC); + break; + case STREAMTYPE_MPEG1_AUDIO: + case STREAMTYPE_MPEG2_AUDIO: + mQueue = new ElementaryStreamQueue( + ElementaryStreamQueue::MPEG_AUDIO); + break; + + case STREAMTYPE_MPEG1_VIDEO: + case STREAMTYPE_MPEG2_VIDEO: + mQueue = new ElementaryStreamQueue( + ElementaryStreamQueue::MPEG_VIDEO); + break; + + case STREAMTYPE_MPEG4_VIDEO: + mQueue = new ElementaryStreamQueue( + ElementaryStreamQueue::MPEG4_VIDEO); + break; + + default: + break; + } + + ALOGV("new stream PID 0x%02x, type 0x%02x", elementaryPID, streamType); + + if (mQueue != NULL) { + mBuffer = new ABuffer(192 * 1024); + mBuffer->setRange(0, 0); + } +} + +ATSParser::Stream::~Stream() { + delete mQueue; + mQueue = NULL; +} + +status_t ATSParser::Stream::parse( + unsigned payload_unit_start_indicator, ABitReader *br) { + if (mQueue == NULL) { + return OK; + } + + if (payload_unit_start_indicator) { + if (mPayloadStarted) { + // Otherwise we run the danger of receiving the trailing bytes + // of a PES packet that we never saw the start of and assuming + // we have a a complete PES packet. + + status_t err = flush(); + + if (err != OK) { + return err; + } + } + + mPayloadStarted = true; + } + + if (!mPayloadStarted) { + return OK; + } + + size_t payloadSizeBits = br->numBitsLeft(); + CHECK_EQ(payloadSizeBits % 8, 0u); + + size_t neededSize = mBuffer->size() + payloadSizeBits / 8; + if (mBuffer->capacity() < neededSize) { + // Increment in multiples of 64K. + neededSize = (neededSize + 65535) & ~65535; + + ALOGI("resizing buffer to %d bytes", neededSize); + + sp<ABuffer> newBuffer = new ABuffer(neededSize); + memcpy(newBuffer->data(), mBuffer->data(), mBuffer->size()); + newBuffer->setRange(0, mBuffer->size()); + mBuffer = newBuffer; + } + + memcpy(mBuffer->data() + mBuffer->size(), br->data(), payloadSizeBits / 8); + mBuffer->setRange(0, mBuffer->size() + payloadSizeBits / 8); + + return OK; +} + +bool ATSParser::Stream::isVideo() const { + switch (mStreamType) { + case STREAMTYPE_H264: + case STREAMTYPE_MPEG1_VIDEO: + case STREAMTYPE_MPEG2_VIDEO: + case STREAMTYPE_MPEG4_VIDEO: + return true; + + default: + return false; + } +} + +bool ATSParser::Stream::isAudio() const { + switch (mStreamType) { + case STREAMTYPE_MPEG1_AUDIO: + case STREAMTYPE_MPEG2_AUDIO: + case STREAMTYPE_MPEG2_AUDIO_ADTS: + return true; + + default: + return false; + } +} + +void ATSParser::Stream::signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra) { + if (mQueue == NULL) { + return; + } + + mPayloadStarted = false; + mBuffer->setRange(0, 0); + + bool clearFormat = false; + if (isAudio()) { + if (type & DISCONTINUITY_AUDIO_FORMAT) { + clearFormat = true; + } + } else { + if (type & DISCONTINUITY_VIDEO_FORMAT) { + clearFormat = true; + } + } + + mQueue->clear(clearFormat); + + if (type & DISCONTINUITY_TIME) { + uint64_t resumeAtPTS; + if (extra != NULL + && extra->findInt64( + IStreamListener::kKeyResumeAtPTS, + (int64_t *)&resumeAtPTS)) { + int64_t resumeAtMediaTimeUs = + mProgram->convertPTSToTimestamp(resumeAtPTS); + + extra->setInt64("resume-at-mediatimeUs", resumeAtMediaTimeUs); + } + } + + if (mSource != NULL) { + mSource->queueDiscontinuity(type, extra); + } +} + +void ATSParser::Stream::signalEOS(status_t finalResult) { + if (mSource != NULL) { + mSource->signalEOS(finalResult); + } +} + +status_t ATSParser::Stream::parsePES(ABitReader *br) { + unsigned packet_startcode_prefix = br->getBits(24); + + ALOGV("packet_startcode_prefix = 0x%08x", packet_startcode_prefix); + + if (packet_startcode_prefix != 1) { + ALOGV("Supposedly payload_unit_start=1 unit does not start " + "with startcode."); + + return ERROR_MALFORMED; + } + + CHECK_EQ(packet_startcode_prefix, 0x000001u); + + unsigned stream_id = br->getBits(8); + ALOGV("stream_id = 0x%02x", stream_id); + + unsigned PES_packet_length = br->getBits(16); + ALOGV("PES_packet_length = %u", PES_packet_length); + + if (stream_id != 0xbc // program_stream_map + && stream_id != 0xbe // padding_stream + && stream_id != 0xbf // private_stream_2 + && stream_id != 0xf0 // ECM + && stream_id != 0xf1 // EMM + && stream_id != 0xff // program_stream_directory + && stream_id != 0xf2 // DSMCC + && stream_id != 0xf8) { // H.222.1 type E + CHECK_EQ(br->getBits(2), 2u); + + MY_LOGV("PES_scrambling_control = %u", br->getBits(2)); + MY_LOGV("PES_priority = %u", br->getBits(1)); + MY_LOGV("data_alignment_indicator = %u", br->getBits(1)); + MY_LOGV("copyright = %u", br->getBits(1)); + MY_LOGV("original_or_copy = %u", br->getBits(1)); + + unsigned PTS_DTS_flags = br->getBits(2); + ALOGV("PTS_DTS_flags = %u", PTS_DTS_flags); + + unsigned ESCR_flag = br->getBits(1); + ALOGV("ESCR_flag = %u", ESCR_flag); + + unsigned ES_rate_flag = br->getBits(1); + ALOGV("ES_rate_flag = %u", ES_rate_flag); + + unsigned DSM_trick_mode_flag = br->getBits(1); + ALOGV("DSM_trick_mode_flag = %u", DSM_trick_mode_flag); + + unsigned additional_copy_info_flag = br->getBits(1); + ALOGV("additional_copy_info_flag = %u", additional_copy_info_flag); + + MY_LOGV("PES_CRC_flag = %u", br->getBits(1)); + MY_LOGV("PES_extension_flag = %u", br->getBits(1)); + + unsigned PES_header_data_length = br->getBits(8); + ALOGV("PES_header_data_length = %u", PES_header_data_length); + + unsigned optional_bytes_remaining = PES_header_data_length; + + uint64_t PTS = 0, DTS = 0; + + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + CHECK_GE(optional_bytes_remaining, 5u); + + CHECK_EQ(br->getBits(4), PTS_DTS_flags); + + PTS = ((uint64_t)br->getBits(3)) << 30; + CHECK_EQ(br->getBits(1), 1u); + PTS |= ((uint64_t)br->getBits(15)) << 15; + CHECK_EQ(br->getBits(1), 1u); + PTS |= br->getBits(15); + CHECK_EQ(br->getBits(1), 1u); + + ALOGV("PTS = %llu", PTS); + // ALOGI("PTS = %.2f secs", PTS / 90000.0f); + + optional_bytes_remaining -= 5; + + if (PTS_DTS_flags == 3) { + CHECK_GE(optional_bytes_remaining, 5u); + + CHECK_EQ(br->getBits(4), 1u); + + DTS = ((uint64_t)br->getBits(3)) << 30; + CHECK_EQ(br->getBits(1), 1u); + DTS |= ((uint64_t)br->getBits(15)) << 15; + CHECK_EQ(br->getBits(1), 1u); + DTS |= br->getBits(15); + CHECK_EQ(br->getBits(1), 1u); + + ALOGV("DTS = %llu", DTS); + + optional_bytes_remaining -= 5; + } + } + + if (ESCR_flag) { + CHECK_GE(optional_bytes_remaining, 6u); + + br->getBits(2); + + uint64_t ESCR = ((uint64_t)br->getBits(3)) << 30; + CHECK_EQ(br->getBits(1), 1u); + ESCR |= ((uint64_t)br->getBits(15)) << 15; + CHECK_EQ(br->getBits(1), 1u); + ESCR |= br->getBits(15); + CHECK_EQ(br->getBits(1), 1u); + + ALOGV("ESCR = %llu", ESCR); + MY_LOGV("ESCR_extension = %u", br->getBits(9)); + + CHECK_EQ(br->getBits(1), 1u); + + optional_bytes_remaining -= 6; + } + + if (ES_rate_flag) { + CHECK_GE(optional_bytes_remaining, 3u); + + CHECK_EQ(br->getBits(1), 1u); + MY_LOGV("ES_rate = %u", br->getBits(22)); + CHECK_EQ(br->getBits(1), 1u); + + optional_bytes_remaining -= 3; + } + + br->skipBits(optional_bytes_remaining * 8); + + // ES data follows. + + if (PES_packet_length != 0) { + CHECK_GE(PES_packet_length, PES_header_data_length + 3); + + unsigned dataLength = + PES_packet_length - 3 - PES_header_data_length; + + if (br->numBitsLeft() < dataLength * 8) { + ALOGE("PES packet does not carry enough data to contain " + "payload. (numBitsLeft = %d, required = %d)", + br->numBitsLeft(), dataLength * 8); + + return ERROR_MALFORMED; + } + + CHECK_GE(br->numBitsLeft(), dataLength * 8); + + onPayloadData( + PTS_DTS_flags, PTS, DTS, br->data(), dataLength); + + br->skipBits(dataLength * 8); + } else { + onPayloadData( + PTS_DTS_flags, PTS, DTS, + br->data(), br->numBitsLeft() / 8); + + size_t payloadSizeBits = br->numBitsLeft(); + CHECK_EQ(payloadSizeBits % 8, 0u); + + ALOGV("There's %d bytes of payload.", payloadSizeBits / 8); + } + } else if (stream_id == 0xbe) { // padding_stream + CHECK_NE(PES_packet_length, 0u); + br->skipBits(PES_packet_length * 8); + } else { + CHECK_NE(PES_packet_length, 0u); + br->skipBits(PES_packet_length * 8); + } + + return OK; +} + +status_t ATSParser::Stream::flush() { + if (mBuffer->size() == 0) { + return OK; + } + + ALOGV("flushing stream 0x%04x size = %d", mElementaryPID, mBuffer->size()); + + ABitReader br(mBuffer->data(), mBuffer->size()); + + status_t err = parsePES(&br); + + mBuffer->setRange(0, 0); + + return err; +} + +void ATSParser::Stream::onPayloadData( + unsigned PTS_DTS_flags, uint64_t PTS, uint64_t DTS, + const uint8_t *data, size_t size) { + ALOGV("onPayloadData mStreamType=0x%02x", mStreamType); + + int64_t timeUs = 0ll; // no presentation timestamp available. + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + timeUs = mProgram->convertPTSToTimestamp(PTS); + } + + status_t err = mQueue->appendData(data, size, timeUs); + + if (err != OK) { + return; + } + + sp<ABuffer> accessUnit; + while ((accessUnit = mQueue->dequeueAccessUnit()) != NULL) { + if (mSource == NULL) { + sp<MetaData> meta = mQueue->getFormat(); + + if (meta != NULL) { + ALOGV("Stream PID 0x%08x of type 0x%02x now has data.", + mElementaryPID, mStreamType); + + mSource = new AnotherPacketSource(meta); + mSource->queueAccessUnit(accessUnit); + } + } else if (mQueue->getFormat() != NULL) { + // After a discontinuity we invalidate the queue's format + // and won't enqueue any access units to the source until + // the queue has reestablished the new format. + + if (mSource->getFormat() == NULL) { + mSource->setFormat(mQueue->getFormat()); + } + mSource->queueAccessUnit(accessUnit); + } + } +} + +sp<MediaSource> ATSParser::Stream::getSource(SourceType type) { + switch (type) { + case VIDEO: + { + if (isVideo()) { + return mSource; + } + break; + } + + case AUDIO: + { + if (isAudio()) { + return mSource; + } + break; + } + + default: + break; + } + + return NULL; +} + +//////////////////////////////////////////////////////////////////////////////// + +ATSParser::ATSParser(uint32_t flags) + : mFlags(flags) { +} + +ATSParser::~ATSParser() { +} + +status_t ATSParser::feedTSPacket(const void *data, size_t size) { + CHECK_EQ(size, kTSPacketSize); + + ABitReader br((const uint8_t *)data, kTSPacketSize); + return parseTS(&br); +} + +void ATSParser::signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra) { + for (size_t i = 0; i < mPrograms.size(); ++i) { + mPrograms.editItemAt(i)->signalDiscontinuity(type, extra); + } +} + +void ATSParser::signalEOS(status_t finalResult) { + CHECK_NE(finalResult, (status_t)OK); + + for (size_t i = 0; i < mPrograms.size(); ++i) { + mPrograms.editItemAt(i)->signalEOS(finalResult); + } +} + +void ATSParser::parseProgramAssociationTable(ABitReader *br) { + unsigned table_id = br->getBits(8); + ALOGV(" table_id = %u", table_id); + CHECK_EQ(table_id, 0x00u); + + unsigned section_syntax_indictor = br->getBits(1); + ALOGV(" section_syntax_indictor = %u", section_syntax_indictor); + CHECK_EQ(section_syntax_indictor, 1u); + + CHECK_EQ(br->getBits(1), 0u); + MY_LOGV(" reserved = %u", br->getBits(2)); + + unsigned section_length = br->getBits(12); + ALOGV(" section_length = %u", section_length); + CHECK_EQ(section_length & 0xc00, 0u); + + MY_LOGV(" transport_stream_id = %u", br->getBits(16)); + MY_LOGV(" reserved = %u", br->getBits(2)); + MY_LOGV(" version_number = %u", br->getBits(5)); + MY_LOGV(" current_next_indicator = %u", br->getBits(1)); + MY_LOGV(" section_number = %u", br->getBits(8)); + MY_LOGV(" last_section_number = %u", br->getBits(8)); + + size_t numProgramBytes = (section_length - 5 /* header */ - 4 /* crc */); + CHECK_EQ((numProgramBytes % 4), 0u); + + for (size_t i = 0; i < numProgramBytes / 4; ++i) { + unsigned program_number = br->getBits(16); + ALOGV(" program_number = %u", program_number); + + MY_LOGV(" reserved = %u", br->getBits(3)); + + if (program_number == 0) { + MY_LOGV(" network_PID = 0x%04x", br->getBits(13)); + } else { + unsigned programMapPID = br->getBits(13); + + ALOGV(" program_map_PID = 0x%04x", programMapPID); + + bool found = false; + for (size_t index = 0; index < mPrograms.size(); ++index) { + const sp<Program> &program = mPrograms.itemAt(index); + + if (program->number() == program_number) { + program->updateProgramMapPID(programMapPID); + found = true; + break; + } + } + + if (!found) { + mPrograms.push( + new Program(this, program_number, programMapPID)); + } + } + } + + MY_LOGV(" CRC = 0x%08x", br->getBits(32)); +} + +status_t ATSParser::parsePID( + ABitReader *br, unsigned PID, + unsigned payload_unit_start_indicator) { + if (PID == 0) { + if (payload_unit_start_indicator) { + unsigned skip = br->getBits(8); + br->skipBits(skip * 8); + } + parseProgramAssociationTable(br); + return OK; + } + + bool handled = false; + for (size_t i = 0; i < mPrograms.size(); ++i) { + status_t err; + if (mPrograms.editItemAt(i)->parsePID( + PID, payload_unit_start_indicator, br, &err)) { + if (err != OK) { + return err; + } + + handled = true; + break; + } + } + + if (!handled) { + ALOGV("PID 0x%04x not handled.", PID); + } + + return OK; +} + +void ATSParser::parseAdaptationField(ABitReader *br) { + unsigned adaptation_field_length = br->getBits(8); + if (adaptation_field_length > 0) { + br->skipBits(adaptation_field_length * 8); // XXX + } +} + +status_t ATSParser::parseTS(ABitReader *br) { + ALOGV("---"); + + unsigned sync_byte = br->getBits(8); + CHECK_EQ(sync_byte, 0x47u); + + MY_LOGV("transport_error_indicator = %u", br->getBits(1)); + + unsigned payload_unit_start_indicator = br->getBits(1); + ALOGV("payload_unit_start_indicator = %u", payload_unit_start_indicator); + + MY_LOGV("transport_priority = %u", br->getBits(1)); + + unsigned PID = br->getBits(13); + ALOGV("PID = 0x%04x", PID); + + MY_LOGV("transport_scrambling_control = %u", br->getBits(2)); + + unsigned adaptation_field_control = br->getBits(2); + ALOGV("adaptation_field_control = %u", adaptation_field_control); + + unsigned continuity_counter = br->getBits(4); + ALOGV("continuity_counter = %u", continuity_counter); + + // ALOGI("PID = 0x%04x, continuity_counter = %u", PID, continuity_counter); + + if (adaptation_field_control == 2 || adaptation_field_control == 3) { + parseAdaptationField(br); + } + + if (adaptation_field_control == 1 || adaptation_field_control == 3) { + return parsePID(br, PID, payload_unit_start_indicator); + } + + return OK; +} + +sp<MediaSource> ATSParser::getSource(SourceType type) { + int which = -1; // any + + for (size_t i = 0; i < mPrograms.size(); ++i) { + const sp<Program> &program = mPrograms.editItemAt(i); + + if (which >= 0 && (int)program->number() != which) { + continue; + } + + sp<MediaSource> source = program->getSource(type); + + if (source != NULL) { + return source; + } + } + + return NULL; +} + +bool ATSParser::PTSTimeDeltaEstablished() { + if (mPrograms.isEmpty()) { + return false; + } + + return mPrograms.editItemAt(0)->PTSTimeDeltaEstablished(); +} + +} // namespace android diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h new file mode 100644 index 0000000..c8038d1 --- /dev/null +++ b/media/libstagefright/mpeg2ts/ATSParser.h @@ -0,0 +1,114 @@ +/* + * 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. + */ + +#ifndef A_TS_PARSER_H_ + +#define A_TS_PARSER_H_ + +#include <sys/types.h> + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AMessage.h> +#include <utils/Vector.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABitReader; +struct ABuffer; +struct MediaSource; + +struct ATSParser : public RefBase { + enum DiscontinuityType { + DISCONTINUITY_NONE = 0, + DISCONTINUITY_TIME = 1, + DISCONTINUITY_AUDIO_FORMAT = 2, + DISCONTINUITY_VIDEO_FORMAT = 4, + + DISCONTINUITY_SEEK = DISCONTINUITY_TIME, + + // For legacy reasons this also implies a time discontinuity. + DISCONTINUITY_FORMATCHANGE = + DISCONTINUITY_AUDIO_FORMAT + | DISCONTINUITY_VIDEO_FORMAT + | DISCONTINUITY_TIME, + }; + + enum Flags { + // The 90kHz clock (PTS/DTS) is absolute, i.e. PTS=0 corresponds to + // a media time of 0. + // If this flag is _not_ specified, the first PTS encountered in a + // program of this stream will be assumed to correspond to media time 0 + // instead. + TS_TIMESTAMPS_ARE_ABSOLUTE = 1 + }; + + ATSParser(uint32_t flags = 0); + + status_t feedTSPacket(const void *data, size_t size); + + void signalDiscontinuity( + DiscontinuityType type, const sp<AMessage> &extra); + + void signalEOS(status_t finalResult); + + enum SourceType { + VIDEO, + AUDIO + }; + sp<MediaSource> getSource(SourceType type); + + bool PTSTimeDeltaEstablished(); + + enum { + // From ISO/IEC 13818-1: 2000 (E), Table 2-29 + STREAMTYPE_RESERVED = 0x00, + STREAMTYPE_MPEG1_VIDEO = 0x01, + STREAMTYPE_MPEG2_VIDEO = 0x02, + STREAMTYPE_MPEG1_AUDIO = 0x03, + STREAMTYPE_MPEG2_AUDIO = 0x04, + STREAMTYPE_MPEG2_AUDIO_ADTS = 0x0f, + STREAMTYPE_MPEG4_VIDEO = 0x10, + STREAMTYPE_H264 = 0x1b, + }; + +protected: + virtual ~ATSParser(); + +private: + struct Program; + struct Stream; + + uint32_t mFlags; + Vector<sp<Program> > mPrograms; + + void parseProgramAssociationTable(ABitReader *br); + void parseProgramMap(ABitReader *br); + void parsePES(ABitReader *br); + + status_t parsePID( + ABitReader *br, unsigned PID, + unsigned payload_unit_start_indicator); + + void parseAdaptationField(ABitReader *br); + status_t parseTS(ABitReader *br); + + DISALLOW_EVIL_CONSTRUCTORS(ATSParser); +}; + +} // namespace android + +#endif // A_TS_PARSER_H_ diff --git a/media/libstagefright/mpeg2ts/Android.mk b/media/libstagefright/mpeg2ts/Android.mk new file mode 100644 index 0000000..eaa139d --- /dev/null +++ b/media/libstagefright/mpeg2ts/Android.mk @@ -0,0 +1,22 @@ +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES:= \ + AnotherPacketSource.cpp \ + ATSParser.cpp \ + ESQueue.cpp \ + MPEG2PSExtractor.cpp \ + MPEG2TSExtractor.cpp \ + +LOCAL_C_INCLUDES:= \ + $(TOP)/frameworks/base/media/libstagefright \ + $(TOP)/frameworks/native/include/media/openmax + +LOCAL_MODULE:= libstagefright_mpeg2ts + +ifeq ($(TARGET_ARCH),arm) + LOCAL_CFLAGS += -Wno-psabi +endif + +include $(BUILD_STATIC_LIBRARY) diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp new file mode 100644 index 0000000..e1ac53c --- /dev/null +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp @@ -0,0 +1,222 @@ +/* + * 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. + */ + +#include "AnotherPacketSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <utils/Vector.h> + +namespace android { + +AnotherPacketSource::AnotherPacketSource(const sp<MetaData> &meta) + : mIsAudio(false), + mFormat(meta), + mEOSResult(OK) { + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp("audio/", mime, 6)) { + mIsAudio = true; + } else { + CHECK(!strncasecmp("video/", mime, 6)); + } +} + +void AnotherPacketSource::setFormat(const sp<MetaData> &meta) { + CHECK(mFormat == NULL); + mFormat = meta; +} + +AnotherPacketSource::~AnotherPacketSource() { +} + +status_t AnotherPacketSource::start(MetaData *params) { + return OK; +} + +status_t AnotherPacketSource::stop() { + return OK; +} + +sp<MetaData> AnotherPacketSource::getFormat() { + return mFormat; +} + +status_t AnotherPacketSource::dequeueAccessUnit(sp<ABuffer> *buffer) { + buffer->clear(); + + Mutex::Autolock autoLock(mLock); + while (mEOSResult == OK && mBuffers.empty()) { + mCondition.wait(mLock); + } + + if (!mBuffers.empty()) { + *buffer = *mBuffers.begin(); + mBuffers.erase(mBuffers.begin()); + + int32_t discontinuity; + if ((*buffer)->meta()->findInt32("discontinuity", &discontinuity)) { + if (wasFormatChange(discontinuity)) { + mFormat.clear(); + } + + return INFO_DISCONTINUITY; + } + + return OK; + } + + return mEOSResult; +} + +status_t AnotherPacketSource::read( + MediaBuffer **out, const ReadOptions *) { + *out = NULL; + + Mutex::Autolock autoLock(mLock); + while (mEOSResult == OK && mBuffers.empty()) { + mCondition.wait(mLock); + } + + if (!mBuffers.empty()) { + const sp<ABuffer> buffer = *mBuffers.begin(); + mBuffers.erase(mBuffers.begin()); + + int32_t discontinuity; + if (buffer->meta()->findInt32("discontinuity", &discontinuity)) { + if (wasFormatChange(discontinuity)) { + mFormat.clear(); + } + + return INFO_DISCONTINUITY; + } else { + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + + MediaBuffer *mediaBuffer = new MediaBuffer(buffer); + + mediaBuffer->meta_data()->setInt64(kKeyTime, timeUs); + + int32_t scrambling; + if (buffer->meta()->findInt32("scrambling", &scrambling) + && scrambling != 0) { + mediaBuffer->meta_data()->setInt32(kKeyScrambling, scrambling); + } + + *out = mediaBuffer; + return OK; + } + } + + return mEOSResult; +} + +bool AnotherPacketSource::wasFormatChange( + int32_t discontinuityType) const { + if (mIsAudio) { + return (discontinuityType & ATSParser::DISCONTINUITY_AUDIO_FORMAT) != 0; + } + + return (discontinuityType & ATSParser::DISCONTINUITY_VIDEO_FORMAT) != 0; +} + +void AnotherPacketSource::queueAccessUnit(const sp<ABuffer> &buffer) { + int32_t damaged; + if (buffer->meta()->findInt32("damaged", &damaged) && damaged) { + // LOG(VERBOSE) << "discarding damaged AU"; + return; + } + + int64_t timeUs; + CHECK(buffer->meta()->findInt64("timeUs", &timeUs)); + ALOGV("queueAccessUnit timeUs=%lld us (%.2f secs)", timeUs, timeUs / 1E6); + + Mutex::Autolock autoLock(mLock); + mBuffers.push_back(buffer); + mCondition.signal(); +} + +void AnotherPacketSource::queueDiscontinuity( + ATSParser::DiscontinuityType type, + const sp<AMessage> &extra) { + Mutex::Autolock autoLock(mLock); + + // Leave only discontinuities in the queue. + List<sp<ABuffer> >::iterator it = mBuffers.begin(); + while (it != mBuffers.end()) { + sp<ABuffer> oldBuffer = *it; + + int32_t oldDiscontinuityType; + if (!oldBuffer->meta()->findInt32( + "discontinuity", &oldDiscontinuityType)) { + it = mBuffers.erase(it); + continue; + } + + ++it; + } + + mEOSResult = OK; + + sp<ABuffer> buffer = new ABuffer(0); + buffer->meta()->setInt32("discontinuity", static_cast<int32_t>(type)); + buffer->meta()->setMessage("extra", extra); + + mBuffers.push_back(buffer); + mCondition.signal(); +} + +void AnotherPacketSource::signalEOS(status_t result) { + CHECK(result != OK); + + Mutex::Autolock autoLock(mLock); + mEOSResult = result; + mCondition.signal(); +} + +bool AnotherPacketSource::hasBufferAvailable(status_t *finalResult) { + Mutex::Autolock autoLock(mLock); + if (!mBuffers.empty()) { + return true; + } + + *finalResult = mEOSResult; + return false; +} + +status_t AnotherPacketSource::nextBufferTime(int64_t *timeUs) { + *timeUs = 0; + + Mutex::Autolock autoLock(mLock); + + if (mBuffers.empty()) { + return mEOSResult != OK ? mEOSResult : -EWOULDBLOCK; + } + + sp<ABuffer> buffer = *mBuffers.begin(); + CHECK(buffer->meta()->findInt64("timeUs", timeUs)); + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h new file mode 100644 index 0000000..c99f7f2 --- /dev/null +++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h @@ -0,0 +1,77 @@ +/* + * 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. + */ + +#ifndef ANOTHER_PACKET_SOURCE_H_ + +#define ANOTHER_PACKET_SOURCE_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MediaSource.h> +#include <utils/threads.h> +#include <utils/List.h> + +#include "ATSParser.h" + +namespace android { + +struct ABuffer; + +struct AnotherPacketSource : public MediaSource { + AnotherPacketSource(const sp<MetaData> &meta); + + void setFormat(const sp<MetaData> &meta); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + + bool hasBufferAvailable(status_t *finalResult); + + status_t nextBufferTime(int64_t *timeUs); + + void queueAccessUnit(const sp<ABuffer> &buffer); + + void queueDiscontinuity( + ATSParser::DiscontinuityType type, const sp<AMessage> &extra); + + void signalEOS(status_t result); + + status_t dequeueAccessUnit(sp<ABuffer> *buffer); + +protected: + virtual ~AnotherPacketSource(); + +private: + Mutex mLock; + Condition mCondition; + + bool mIsAudio; + sp<MetaData> mFormat; + List<sp<ABuffer> > mBuffers; + status_t mEOSResult; + + bool wasFormatChange(int32_t discontinuityType) const; + + DISALLOW_EVIL_CONSTRUCTORS(AnotherPacketSource); +}; + + +} // namespace android + +#endif // ANOTHER_PACKET_SOURCE_H_ diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp new file mode 100644 index 0000000..7fd99a8 --- /dev/null +++ b/media/libstagefright/mpeg2ts/ESQueue.cpp @@ -0,0 +1,954 @@ +/* + * 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 "ESQueue" +#include <media/stagefright/foundation/ADebug.h> + +#include "ESQueue.h" + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +#include "include/avc_utils.h" + +namespace android { + +ElementaryStreamQueue::ElementaryStreamQueue(Mode mode) + : mMode(mode) { +} + +sp<MetaData> ElementaryStreamQueue::getFormat() { + return mFormat; +} + +void ElementaryStreamQueue::clear(bool clearFormat) { + if (mBuffer != NULL) { + mBuffer->setRange(0, 0); + } + + mRangeInfos.clear(); + + if (clearFormat) { + mFormat.clear(); + } +} + +static bool IsSeeminglyValidADTSHeader(const uint8_t *ptr, size_t size) { + if (size < 3) { + // Not enough data to verify header. + return false; + } + + if (ptr[0] != 0xff || (ptr[1] >> 4) != 0x0f) { + return false; + } + + unsigned layer = (ptr[1] >> 1) & 3; + + if (layer != 0) { + return false; + } + + unsigned ID = (ptr[1] >> 3) & 1; + unsigned profile_ObjectType = ptr[2] >> 6; + + if (ID == 1 && profile_ObjectType == 3) { + // MPEG-2 profile 3 is reserved. + return false; + } + + return true; +} + +static bool IsSeeminglyValidMPEGAudioHeader(const uint8_t *ptr, size_t size) { + if (size < 3) { + // Not enough data to verify header. + return false; + } + + if (ptr[0] != 0xff || (ptr[1] >> 5) != 0x07) { + return false; + } + + unsigned ID = (ptr[1] >> 3) & 3; + + if (ID == 1) { + return false; // reserved + } + + unsigned layer = (ptr[1] >> 1) & 3; + + if (layer == 0) { + return false; // reserved + } + + unsigned bitrateIndex = (ptr[2] >> 4); + + if (bitrateIndex == 0x0f) { + return false; // reserved + } + + unsigned samplingRateIndex = (ptr[2] >> 2) & 3; + + if (samplingRateIndex == 3) { + return false; // reserved + } + + return true; +} + +status_t ElementaryStreamQueue::appendData( + const void *data, size_t size, int64_t timeUs) { + if (mBuffer == NULL || mBuffer->size() == 0) { + switch (mMode) { + case H264: + case MPEG_VIDEO: + { +#if 0 + if (size < 4 || memcmp("\x00\x00\x00\x01", data, 4)) { + return ERROR_MALFORMED; + } +#else + uint8_t *ptr = (uint8_t *)data; + + ssize_t startOffset = -1; + for (size_t i = 0; i + 3 < size; ++i) { + if (!memcmp("\x00\x00\x00\x01", &ptr[i], 4)) { + startOffset = i; + break; + } + } + + if (startOffset < 0) { + return ERROR_MALFORMED; + } + + if (startOffset > 0) { + ALOGI("found something resembling an H.264/MPEG syncword at " + "offset %ld", + startOffset); + } + + data = &ptr[startOffset]; + size -= startOffset; +#endif + break; + } + + case MPEG4_VIDEO: + { +#if 0 + if (size < 3 || memcmp("\x00\x00\x01", data, 3)) { + return ERROR_MALFORMED; + } +#else + uint8_t *ptr = (uint8_t *)data; + + ssize_t startOffset = -1; + for (size_t i = 0; i + 2 < size; ++i) { + if (!memcmp("\x00\x00\x01", &ptr[i], 3)) { + startOffset = i; + break; + } + } + + if (startOffset < 0) { + return ERROR_MALFORMED; + } + + if (startOffset > 0) { + ALOGI("found something resembling an H.264/MPEG syncword at " + "offset %ld", + startOffset); + } + + data = &ptr[startOffset]; + size -= startOffset; +#endif + break; + } + + case AAC: + { + uint8_t *ptr = (uint8_t *)data; + +#if 0 + if (size < 2 || ptr[0] != 0xff || (ptr[1] >> 4) != 0x0f) { + return ERROR_MALFORMED; + } +#else + ssize_t startOffset = -1; + for (size_t i = 0; i < size; ++i) { + if (IsSeeminglyValidADTSHeader(&ptr[i], size - i)) { + startOffset = i; + break; + } + } + + if (startOffset < 0) { + return ERROR_MALFORMED; + } + + if (startOffset > 0) { + ALOGI("found something resembling an AAC syncword at offset %ld", + startOffset); + } + + data = &ptr[startOffset]; + size -= startOffset; +#endif + break; + } + + case MPEG_AUDIO: + { + uint8_t *ptr = (uint8_t *)data; + + ssize_t startOffset = -1; + for (size_t i = 0; i < size; ++i) { + if (IsSeeminglyValidMPEGAudioHeader(&ptr[i], size - i)) { + startOffset = i; + break; + } + } + + if (startOffset < 0) { + return ERROR_MALFORMED; + } + + if (startOffset > 0) { + ALOGI("found something resembling an MPEG audio " + "syncword at offset %ld", + startOffset); + } + + data = &ptr[startOffset]; + size -= startOffset; + break; + } + + default: + TRESPASS(); + break; + } + } + + size_t neededSize = (mBuffer == NULL ? 0 : mBuffer->size()) + size; + if (mBuffer == NULL || neededSize > mBuffer->capacity()) { + neededSize = (neededSize + 65535) & ~65535; + + ALOGV("resizing buffer to size %d", neededSize); + + sp<ABuffer> buffer = new ABuffer(neededSize); + if (mBuffer != NULL) { + memcpy(buffer->data(), mBuffer->data(), mBuffer->size()); + buffer->setRange(0, mBuffer->size()); + } else { + buffer->setRange(0, 0); + } + + mBuffer = buffer; + } + + memcpy(mBuffer->data() + mBuffer->size(), data, size); + mBuffer->setRange(0, mBuffer->size() + size); + + RangeInfo info; + info.mLength = size; + info.mTimestampUs = timeUs; + mRangeInfos.push_back(info); + +#if 0 + if (mMode == AAC) { + ALOGI("size = %d, timeUs = %.2f secs", size, timeUs / 1E6); + hexdump(data, size); + } +#endif + + return OK; +} + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() { + switch (mMode) { + case H264: + return dequeueAccessUnitH264(); + case AAC: + return dequeueAccessUnitAAC(); + case MPEG_VIDEO: + return dequeueAccessUnitMPEGVideo(); + case MPEG4_VIDEO: + return dequeueAccessUnitMPEG4Video(); + default: + CHECK_EQ((unsigned)mMode, (unsigned)MPEG_AUDIO); + return dequeueAccessUnitMPEGAudio(); + } +} + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() { + Vector<size_t> ranges; + Vector<size_t> frameOffsets; + Vector<size_t> frameSizes; + size_t auSize = 0; + + size_t offset = 0; + while (offset + 7 <= mBuffer->size()) { + ABitReader bits(mBuffer->data() + offset, mBuffer->size() - offset); + + // adts_fixed_header + + CHECK_EQ(bits.getBits(12), 0xfffu); + bits.skipBits(3); // ID, layer + bool protection_absent = bits.getBits(1) != 0; + + if (mFormat == NULL) { + unsigned profile = bits.getBits(2); + CHECK_NE(profile, 3u); + unsigned sampling_freq_index = bits.getBits(4); + bits.getBits(1); // private_bit + unsigned channel_configuration = bits.getBits(3); + CHECK_NE(channel_configuration, 0u); + bits.skipBits(2); // original_copy, home + + mFormat = MakeAACCodecSpecificData( + profile, sampling_freq_index, channel_configuration); + + int32_t sampleRate; + int32_t numChannels; + CHECK(mFormat->findInt32(kKeySampleRate, &sampleRate)); + CHECK(mFormat->findInt32(kKeyChannelCount, &numChannels)); + + ALOGI("found AAC codec config (%d Hz, %d channels)", + sampleRate, numChannels); + } else { + // profile_ObjectType, sampling_frequency_index, private_bits, + // channel_configuration, original_copy, home + bits.skipBits(12); + } + + // adts_variable_header + + // copyright_identification_bit, copyright_identification_start + bits.skipBits(2); + + unsigned aac_frame_length = bits.getBits(13); + + bits.skipBits(11); // adts_buffer_fullness + + unsigned number_of_raw_data_blocks_in_frame = bits.getBits(2); + + if (number_of_raw_data_blocks_in_frame != 0) { + // To be implemented. + TRESPASS(); + } + + if (offset + aac_frame_length > mBuffer->size()) { + break; + } + + size_t headerSize = protection_absent ? 7 : 9; + + ranges.push(aac_frame_length); + frameOffsets.push(offset + headerSize); + frameSizes.push(aac_frame_length - headerSize); + auSize += aac_frame_length - headerSize; + + offset += aac_frame_length; + } + + if (offset == 0) { + return NULL; + } + + int64_t timeUs = -1; + + for (size_t i = 0; i < ranges.size(); ++i) { + int64_t tmpUs = fetchTimestamp(ranges.itemAt(i)); + + if (i == 0) { + timeUs = tmpUs; + } + } + + sp<ABuffer> accessUnit = new ABuffer(auSize); + size_t dstOffset = 0; + for (size_t i = 0; i < frameOffsets.size(); ++i) { + size_t frameOffset = frameOffsets.itemAt(i); + + memcpy(accessUnit->data() + dstOffset, + mBuffer->data() + frameOffset, + frameSizes.itemAt(i)); + + dstOffset += frameSizes.itemAt(i); + } + + memmove(mBuffer->data(), mBuffer->data() + offset, + mBuffer->size() - offset); + mBuffer->setRange(0, mBuffer->size() - offset); + + if (timeUs >= 0) { + accessUnit->meta()->setInt64("timeUs", timeUs); + } else { + ALOGW("no time for AAC access unit"); + } + + return accessUnit; +} + +int64_t ElementaryStreamQueue::fetchTimestamp(size_t size) { + int64_t timeUs = -1; + bool first = true; + + while (size > 0) { + CHECK(!mRangeInfos.empty()); + + RangeInfo *info = &*mRangeInfos.begin(); + + if (first) { + timeUs = info->mTimestampUs; + first = false; + } + + if (info->mLength > size) { + info->mLength -= size; + + if (first) { + info->mTimestampUs = -1; + } + + size = 0; + } else { + size -= info->mLength; + + mRangeInfos.erase(mRangeInfos.begin()); + info = NULL; + } + } + + if (timeUs == 0ll) { + ALOGV("Returning 0 timestamp"); + } + + return timeUs; +} + +struct NALPosition { + size_t nalOffset; + size_t nalSize; +}; + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitH264() { + const uint8_t *data = mBuffer->data(); + size_t size = mBuffer->size(); + + Vector<NALPosition> nals; + + size_t totalSize = 0; + + status_t err; + const uint8_t *nalStart; + size_t nalSize; + bool foundSlice = false; + while ((err = getNextNALUnit(&data, &size, &nalStart, &nalSize)) == OK) { + CHECK_GT(nalSize, 0u); + + unsigned nalType = nalStart[0] & 0x1f; + bool flush = false; + + if (nalType == 1 || nalType == 5) { + if (foundSlice) { + ABitReader br(nalStart + 1, nalSize); + unsigned first_mb_in_slice = parseUE(&br); + + if (first_mb_in_slice == 0) { + // This slice starts a new frame. + + flush = true; + } + } + + foundSlice = true; + } else if ((nalType == 9 || nalType == 7) && foundSlice) { + // Access unit delimiter and SPS will be associated with the + // next frame. + + flush = true; + } + + if (flush) { + // The access unit will contain all nal units up to, but excluding + // the current one, separated by 0x00 0x00 0x00 0x01 startcodes. + + size_t auSize = 4 * nals.size() + totalSize; + sp<ABuffer> accessUnit = new ABuffer(auSize); + +#if !LOG_NDEBUG + AString out; +#endif + + size_t dstOffset = 0; + for (size_t i = 0; i < nals.size(); ++i) { + const NALPosition &pos = nals.itemAt(i); + + unsigned nalType = mBuffer->data()[pos.nalOffset] & 0x1f; + +#if !LOG_NDEBUG + char tmp[128]; + sprintf(tmp, "0x%02x", nalType); + if (i > 0) { + out.append(", "); + } + out.append(tmp); +#endif + + memcpy(accessUnit->data() + dstOffset, "\x00\x00\x00\x01", 4); + + memcpy(accessUnit->data() + dstOffset + 4, + mBuffer->data() + pos.nalOffset, + pos.nalSize); + + dstOffset += pos.nalSize + 4; + } + + ALOGV("accessUnit contains nal types %s", out.c_str()); + + const NALPosition &pos = nals.itemAt(nals.size() - 1); + size_t nextScan = pos.nalOffset + pos.nalSize; + + memmove(mBuffer->data(), + mBuffer->data() + nextScan, + mBuffer->size() - nextScan); + + mBuffer->setRange(0, mBuffer->size() - nextScan); + + int64_t timeUs = fetchTimestamp(nextScan); + CHECK_GE(timeUs, 0ll); + + accessUnit->meta()->setInt64("timeUs", timeUs); + + if (mFormat == NULL) { + mFormat = MakeAVCCodecSpecificData(accessUnit); + } + + return accessUnit; + } + + NALPosition pos; + pos.nalOffset = nalStart - mBuffer->data(); + pos.nalSize = nalSize; + + nals.push(pos); + + totalSize += nalSize; + } + CHECK_EQ(err, (status_t)-EAGAIN); + + return NULL; +} + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEGAudio() { + const uint8_t *data = mBuffer->data(); + size_t size = mBuffer->size(); + + if (size < 4) { + return NULL; + } + + uint32_t header = U32_AT(data); + + size_t frameSize; + int samplingRate, numChannels, bitrate, numSamples; + CHECK(GetMPEGAudioFrameSize( + header, &frameSize, &samplingRate, &numChannels, + &bitrate, &numSamples)); + + if (size < frameSize) { + return NULL; + } + + unsigned layer = 4 - ((header >> 17) & 3); + + sp<ABuffer> accessUnit = new ABuffer(frameSize); + memcpy(accessUnit->data(), data, frameSize); + + memmove(mBuffer->data(), + mBuffer->data() + frameSize, + mBuffer->size() - frameSize); + + mBuffer->setRange(0, mBuffer->size() - frameSize); + + int64_t timeUs = fetchTimestamp(frameSize); + CHECK_GE(timeUs, 0ll); + + accessUnit->meta()->setInt64("timeUs", timeUs); + + if (mFormat == NULL) { + mFormat = new MetaData; + + switch (layer) { + case 1: + mFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_I); + break; + case 2: + mFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG_LAYER_II); + break; + case 3: + mFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_MPEG); + break; + default: + TRESPASS(); + } + + mFormat->setInt32(kKeySampleRate, samplingRate); + mFormat->setInt32(kKeyChannelCount, numChannels); + } + + return accessUnit; +} + +static void EncodeSize14(uint8_t **_ptr, size_t size) { + CHECK_LE(size, 0x3fff); + + uint8_t *ptr = *_ptr; + + *ptr++ = 0x80 | (size >> 7); + *ptr++ = size & 0x7f; + + *_ptr = ptr; +} + +static sp<ABuffer> MakeMPEGVideoESDS(const sp<ABuffer> &csd) { + sp<ABuffer> esds = new ABuffer(csd->size() + 25); + + uint8_t *ptr = esds->data(); + *ptr++ = 0x03; + EncodeSize14(&ptr, 22 + csd->size()); + + *ptr++ = 0x00; // ES_ID + *ptr++ = 0x00; + + *ptr++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag + + *ptr++ = 0x04; + EncodeSize14(&ptr, 16 + csd->size()); + + *ptr++ = 0x40; // Audio ISO/IEC 14496-3 + + for (size_t i = 0; i < 12; ++i) { + *ptr++ = 0x00; + } + + *ptr++ = 0x05; + EncodeSize14(&ptr, csd->size()); + + memcpy(ptr, csd->data(), csd->size()); + + return esds; +} + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEGVideo() { + const uint8_t *data = mBuffer->data(); + size_t size = mBuffer->size(); + + bool sawPictureStart = false; + int pprevStartCode = -1; + int prevStartCode = -1; + int currentStartCode = -1; + + size_t offset = 0; + while (offset + 3 < size) { + if (memcmp(&data[offset], "\x00\x00\x01", 3)) { + ++offset; + continue; + } + + pprevStartCode = prevStartCode; + prevStartCode = currentStartCode; + currentStartCode = data[offset + 3]; + + if (currentStartCode == 0xb3 && mFormat == NULL) { + memmove(mBuffer->data(), mBuffer->data() + offset, size - offset); + size -= offset; + (void)fetchTimestamp(offset); + offset = 0; + mBuffer->setRange(0, size); + } + + if ((prevStartCode == 0xb3 && currentStartCode != 0xb5) + || (pprevStartCode == 0xb3 && prevStartCode == 0xb5)) { + // seqHeader without/with extension + + if (mFormat == NULL) { + CHECK_GE(size, 7u); + + unsigned width = + (data[4] << 4) | data[5] >> 4; + + unsigned height = + ((data[5] & 0x0f) << 8) | data[6]; + + mFormat = new MetaData; + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG2); + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); + + ALOGI("found MPEG2 video codec config (%d x %d)", width, height); + + sp<ABuffer> csd = new ABuffer(offset); + memcpy(csd->data(), data, offset); + + memmove(mBuffer->data(), + mBuffer->data() + offset, + mBuffer->size() - offset); + + mBuffer->setRange(0, mBuffer->size() - offset); + size -= offset; + (void)fetchTimestamp(offset); + offset = 0; + + // hexdump(csd->data(), csd->size()); + + sp<ABuffer> esds = MakeMPEGVideoESDS(csd); + mFormat->setData( + kKeyESDS, kTypeESDS, esds->data(), esds->size()); + + return NULL; + } + } + + if (mFormat != NULL && currentStartCode == 0x00) { + // Picture start + + if (!sawPictureStart) { + sawPictureStart = true; + } else { + sp<ABuffer> accessUnit = new ABuffer(offset); + memcpy(accessUnit->data(), data, offset); + + memmove(mBuffer->data(), + mBuffer->data() + offset, + mBuffer->size() - offset); + + mBuffer->setRange(0, mBuffer->size() - offset); + + int64_t timeUs = fetchTimestamp(offset); + CHECK_GE(timeUs, 0ll); + + offset = 0; + + accessUnit->meta()->setInt64("timeUs", timeUs); + + ALOGV("returning MPEG video access unit at time %lld us", + timeUs); + + // hexdump(accessUnit->data(), accessUnit->size()); + + return accessUnit; + } + } + + ++offset; + } + + return NULL; +} + +static ssize_t getNextChunkSize( + const uint8_t *data, size_t size) { + static const char kStartCode[] = "\x00\x00\x01"; + + if (size < 3) { + return -EAGAIN; + } + + if (memcmp(kStartCode, data, 3)) { + TRESPASS(); + } + + size_t offset = 3; + while (offset + 2 < size) { + if (!memcmp(&data[offset], kStartCode, 3)) { + return offset; + } + + ++offset; + } + + return -EAGAIN; +} + +sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitMPEG4Video() { + uint8_t *data = mBuffer->data(); + size_t size = mBuffer->size(); + + enum { + SKIP_TO_VISUAL_OBJECT_SEQ_START, + EXPECT_VISUAL_OBJECT_START, + EXPECT_VO_START, + EXPECT_VOL_START, + WAIT_FOR_VOP_START, + SKIP_TO_VOP_START, + + } state; + + if (mFormat == NULL) { + state = SKIP_TO_VISUAL_OBJECT_SEQ_START; + } else { + state = SKIP_TO_VOP_START; + } + + int32_t width = -1, height = -1; + + size_t offset = 0; + ssize_t chunkSize; + while ((chunkSize = getNextChunkSize( + &data[offset], size - offset)) > 0) { + bool discard = false; + + unsigned chunkType = data[offset + 3]; + + switch (state) { + case SKIP_TO_VISUAL_OBJECT_SEQ_START: + { + if (chunkType == 0xb0) { + // Discard anything before this marker. + + state = EXPECT_VISUAL_OBJECT_START; + } else { + discard = true; + } + break; + } + + case EXPECT_VISUAL_OBJECT_START: + { + CHECK_EQ(chunkType, 0xb5); + state = EXPECT_VO_START; + break; + } + + case EXPECT_VO_START: + { + CHECK_LE(chunkType, 0x1f); + state = EXPECT_VOL_START; + break; + } + + case EXPECT_VOL_START: + { + CHECK((chunkType & 0xf0) == 0x20); + + CHECK(ExtractDimensionsFromVOLHeader( + &data[offset], chunkSize, + &width, &height)); + + state = WAIT_FOR_VOP_START; + break; + } + + case WAIT_FOR_VOP_START: + { + if (chunkType == 0xb3 || chunkType == 0xb6) { + // group of VOP or VOP start. + + mFormat = new MetaData; + mFormat->setCString( + kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); + + ALOGI("found MPEG4 video codec config (%d x %d)", + width, height); + + sp<ABuffer> csd = new ABuffer(offset); + memcpy(csd->data(), data, offset); + + // hexdump(csd->data(), csd->size()); + + sp<ABuffer> esds = MakeMPEGVideoESDS(csd); + mFormat->setData( + kKeyESDS, kTypeESDS, + esds->data(), esds->size()); + + discard = true; + state = SKIP_TO_VOP_START; + } + + break; + } + + case SKIP_TO_VOP_START: + { + if (chunkType == 0xb6) { + offset += chunkSize; + + sp<ABuffer> accessUnit = new ABuffer(offset); + memcpy(accessUnit->data(), data, offset); + + memmove(data, &data[offset], size - offset); + size -= offset; + mBuffer->setRange(0, size); + + int64_t timeUs = fetchTimestamp(offset); + CHECK_GE(timeUs, 0ll); + + offset = 0; + + accessUnit->meta()->setInt64("timeUs", timeUs); + + ALOGV("returning MPEG4 video access unit at time %lld us", + timeUs); + + // hexdump(accessUnit->data(), accessUnit->size()); + + return accessUnit; + } else if (chunkType != 0xb3) { + offset += chunkSize; + discard = true; + } + + break; + } + + default: + TRESPASS(); + } + + if (discard) { + (void)fetchTimestamp(offset); + memmove(data, &data[offset], size - offset); + size -= offset; + offset = 0; + mBuffer->setRange(0, size); + } else { + offset += chunkSize; + } + } + + return NULL; +} + +} // namespace android diff --git a/media/libstagefright/mpeg2ts/ESQueue.h b/media/libstagefright/mpeg2ts/ESQueue.h new file mode 100644 index 0000000..4035ed3 --- /dev/null +++ b/media/libstagefright/mpeg2ts/ESQueue.h @@ -0,0 +1,76 @@ +/* + * 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. + */ + +#ifndef ES_QUEUE_H_ + +#define ES_QUEUE_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/Errors.h> +#include <utils/List.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +struct MetaData; + +struct ElementaryStreamQueue { + enum Mode { + H264, + AAC, + MPEG_AUDIO, + MPEG_VIDEO, + MPEG4_VIDEO, + }; + ElementaryStreamQueue(Mode mode); + + status_t appendData(const void *data, size_t size, int64_t timeUs); + void clear(bool clearFormat); + + sp<ABuffer> dequeueAccessUnit(); + + sp<MetaData> getFormat(); + +private: + struct RangeInfo { + int64_t mTimestampUs; + size_t mLength; + }; + + Mode mMode; + + sp<ABuffer> mBuffer; + List<RangeInfo> mRangeInfos; + + sp<MetaData> mFormat; + + sp<ABuffer> dequeueAccessUnitH264(); + sp<ABuffer> dequeueAccessUnitAAC(); + sp<ABuffer> dequeueAccessUnitMPEGAudio(); + sp<ABuffer> dequeueAccessUnitMPEGVideo(); + sp<ABuffer> dequeueAccessUnitMPEG4Video(); + + // consume a logical (compressed) access unit of size "size", + // returns its timestamp in us (or -1 if no time information). + int64_t fetchTimestamp(size_t size); + + DISALLOW_EVIL_CONSTRUCTORS(ElementaryStreamQueue); +}; + +} // namespace android + +#endif // ES_QUEUE_H_ diff --git a/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp new file mode 100644 index 0000000..dd714c9 --- /dev/null +++ b/media/libstagefright/mpeg2ts/MPEG2PSExtractor.cpp @@ -0,0 +1,715 @@ +/* + * Copyright (C) 2011 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 "MPEG2PSExtractor" +#include <utils/Log.h> + +#include "include/MPEG2PSExtractor.h" + +#include "AnotherPacketSource.h" +#include "ESQueue.h" + +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> +#include <utils/String8.h> + +namespace android { + +struct MPEG2PSExtractor::Track : public MediaSource { + Track(MPEG2PSExtractor *extractor, + unsigned stream_id, unsigned stream_type); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + +protected: + virtual ~Track(); + +private: + friend struct MPEG2PSExtractor; + + MPEG2PSExtractor *mExtractor; + + unsigned mStreamID; + unsigned mStreamType; + ElementaryStreamQueue *mQueue; + sp<AnotherPacketSource> mSource; + + status_t appendPESData( + unsigned PTS_DTS_flags, + uint64_t PTS, uint64_t DTS, + const uint8_t *data, size_t size); + + DISALLOW_EVIL_CONSTRUCTORS(Track); +}; + +struct MPEG2PSExtractor::WrappedTrack : public MediaSource { + WrappedTrack(const sp<MPEG2PSExtractor> &extractor, const sp<Track> &track); + + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options); + +protected: + virtual ~WrappedTrack(); + +private: + sp<MPEG2PSExtractor> mExtractor; + sp<MPEG2PSExtractor::Track> mTrack; + + DISALLOW_EVIL_CONSTRUCTORS(WrappedTrack); +}; + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::MPEG2PSExtractor(const sp<DataSource> &source) + : mDataSource(source), + mOffset(0), + mFinalResult(OK), + mBuffer(new ABuffer(0)), + mScanning(true), + mProgramStreamMapValid(false) { + for (size_t i = 0; i < 500; ++i) { + if (feedMore() != OK) { + break; + } + } + + // Remove all tracks that were unable to determine their format. + for (size_t i = mTracks.size(); i-- > 0;) { + if (mTracks.valueAt(i)->getFormat() == NULL) { + mTracks.removeItemsAt(i); + } + } + + mScanning = false; +} + +MPEG2PSExtractor::~MPEG2PSExtractor() { +} + +size_t MPEG2PSExtractor::countTracks() { + return mTracks.size(); +} + +sp<MediaSource> MPEG2PSExtractor::getTrack(size_t index) { + if (index >= mTracks.size()) { + return NULL; + } + + return new WrappedTrack(this, mTracks.valueAt(index)); +} + +sp<MetaData> MPEG2PSExtractor::getTrackMetaData(size_t index, uint32_t flags) { + if (index >= mTracks.size()) { + return NULL; + } + + return mTracks.valueAt(index)->getFormat(); +} + +sp<MetaData> MPEG2PSExtractor::getMetaData() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG2PS); + + return meta; +} + +uint32_t MPEG2PSExtractor::flags() const { + return CAN_PAUSE; +} + +status_t MPEG2PSExtractor::feedMore() { + Mutex::Autolock autoLock(mLock); + + // How much data we're reading at a time + static const size_t kChunkSize = 8192; + + for (;;) { + status_t err = dequeueChunk(); + + if (err == -EAGAIN && mFinalResult == OK) { + memmove(mBuffer->base(), mBuffer->data(), mBuffer->size()); + mBuffer->setRange(0, mBuffer->size()); + + if (mBuffer->size() + kChunkSize > mBuffer->capacity()) { + size_t newCapacity = mBuffer->capacity() + kChunkSize; + sp<ABuffer> newBuffer = new ABuffer(newCapacity); + memcpy(newBuffer->data(), mBuffer->data(), mBuffer->size()); + newBuffer->setRange(0, mBuffer->size()); + mBuffer = newBuffer; + } + + ssize_t n = mDataSource->readAt( + mOffset, mBuffer->data() + mBuffer->size(), kChunkSize); + + if (n < (ssize_t)kChunkSize) { + mFinalResult = (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; + return mFinalResult; + } + + mBuffer->setRange(mBuffer->offset(), mBuffer->size() + n); + mOffset += n; + } else if (err != OK) { + mFinalResult = err; + return err; + } else { + return OK; + } + } +} + +status_t MPEG2PSExtractor::dequeueChunk() { + if (mBuffer->size() < 4) { + return -EAGAIN; + } + + if (memcmp("\x00\x00\x01", mBuffer->data(), 3)) { + return ERROR_MALFORMED; + } + + unsigned chunkType = mBuffer->data()[3]; + + ssize_t res; + + switch (chunkType) { + case 0xba: + { + res = dequeuePack(); + break; + } + + case 0xbb: + { + res = dequeueSystemHeader(); + break; + } + + default: + { + res = dequeuePES(); + break; + } + } + + if (res > 0) { + if (mBuffer->size() < (size_t)res) { + return -EAGAIN; + } + + mBuffer->setRange(mBuffer->offset() + res, mBuffer->size() - res); + res = OK; + } + + return res; +} + +ssize_t MPEG2PSExtractor::dequeuePack() { + // 32 + 2 + 3 + 1 + 15 + 1 + 15+ 1 + 9 + 1 + 22 + 1 + 1 | +5 + + if (mBuffer->size() < 14) { + return -EAGAIN; + } + + unsigned pack_stuffing_length = mBuffer->data()[13] & 7; + + return pack_stuffing_length + 14; +} + +ssize_t MPEG2PSExtractor::dequeueSystemHeader() { + if (mBuffer->size() < 6) { + return -EAGAIN; + } + + unsigned header_length = U16_AT(mBuffer->data() + 4); + + return header_length + 6; +} + +ssize_t MPEG2PSExtractor::dequeuePES() { + if (mBuffer->size() < 6) { + return -EAGAIN; + } + + unsigned PES_packet_length = U16_AT(mBuffer->data() + 4); + CHECK_NE(PES_packet_length, 0u); + + size_t n = PES_packet_length + 6; + + if (mBuffer->size() < n) { + return -EAGAIN; + } + + ABitReader br(mBuffer->data(), n); + + unsigned packet_startcode_prefix = br.getBits(24); + + ALOGV("packet_startcode_prefix = 0x%08x", packet_startcode_prefix); + + if (packet_startcode_prefix != 1) { + ALOGV("Supposedly payload_unit_start=1 unit does not start " + "with startcode."); + + return ERROR_MALFORMED; + } + + CHECK_EQ(packet_startcode_prefix, 0x000001u); + + unsigned stream_id = br.getBits(8); + ALOGV("stream_id = 0x%02x", stream_id); + + /* unsigned PES_packet_length = */br.getBits(16); + + if (stream_id == 0xbc) { + // program_stream_map + + if (!mScanning) { + return n; + } + + mStreamTypeByESID.clear(); + + /* unsigned current_next_indicator = */br.getBits(1); + /* unsigned reserved = */br.getBits(2); + /* unsigned program_stream_map_version = */br.getBits(5); + /* unsigned reserved = */br.getBits(7); + /* unsigned marker_bit = */br.getBits(1); + unsigned program_stream_info_length = br.getBits(16); + + size_t offset = 0; + while (offset < program_stream_info_length) { + if (offset + 2 > program_stream_info_length) { + return ERROR_MALFORMED; + } + + unsigned descriptor_tag = br.getBits(8); + unsigned descriptor_length = br.getBits(8); + + ALOGI("found descriptor tag 0x%02x of length %u", + descriptor_tag, descriptor_length); + + if (offset + 2 + descriptor_length > program_stream_info_length) { + return ERROR_MALFORMED; + } + + br.skipBits(8 * descriptor_length); + + offset += 2 + descriptor_length; + } + + unsigned elementary_stream_map_length = br.getBits(16); + + offset = 0; + while (offset < elementary_stream_map_length) { + if (offset + 4 > elementary_stream_map_length) { + return ERROR_MALFORMED; + } + + unsigned stream_type = br.getBits(8); + unsigned elementary_stream_id = br.getBits(8); + + ALOGI("elementary stream id 0x%02x has stream type 0x%02x", + elementary_stream_id, stream_type); + + mStreamTypeByESID.add(elementary_stream_id, stream_type); + + unsigned elementary_stream_info_length = br.getBits(16); + + if (offset + 4 + elementary_stream_info_length + > elementary_stream_map_length) { + return ERROR_MALFORMED; + } + + offset += 4 + elementary_stream_info_length; + } + + /* unsigned CRC32 = */br.getBits(32); + + mProgramStreamMapValid = true; + } else if (stream_id != 0xbe // padding_stream + && stream_id != 0xbf // private_stream_2 + && stream_id != 0xf0 // ECM + && stream_id != 0xf1 // EMM + && stream_id != 0xff // program_stream_directory + && stream_id != 0xf2 // DSMCC + && stream_id != 0xf8) { // H.222.1 type E + CHECK_EQ(br.getBits(2), 2u); + + /* unsigned PES_scrambling_control = */br.getBits(2); + /* unsigned PES_priority = */br.getBits(1); + /* unsigned data_alignment_indicator = */br.getBits(1); + /* unsigned copyright = */br.getBits(1); + /* unsigned original_or_copy = */br.getBits(1); + + unsigned PTS_DTS_flags = br.getBits(2); + ALOGV("PTS_DTS_flags = %u", PTS_DTS_flags); + + unsigned ESCR_flag = br.getBits(1); + ALOGV("ESCR_flag = %u", ESCR_flag); + + unsigned ES_rate_flag = br.getBits(1); + ALOGV("ES_rate_flag = %u", ES_rate_flag); + + unsigned DSM_trick_mode_flag = br.getBits(1); + ALOGV("DSM_trick_mode_flag = %u", DSM_trick_mode_flag); + + unsigned additional_copy_info_flag = br.getBits(1); + ALOGV("additional_copy_info_flag = %u", additional_copy_info_flag); + + /* unsigned PES_CRC_flag = */br.getBits(1); + /* PES_extension_flag = */br.getBits(1); + + unsigned PES_header_data_length = br.getBits(8); + ALOGV("PES_header_data_length = %u", PES_header_data_length); + + unsigned optional_bytes_remaining = PES_header_data_length; + + uint64_t PTS = 0, DTS = 0; + + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + CHECK_GE(optional_bytes_remaining, 5u); + + CHECK_EQ(br.getBits(4), PTS_DTS_flags); + + PTS = ((uint64_t)br.getBits(3)) << 30; + CHECK_EQ(br.getBits(1), 1u); + PTS |= ((uint64_t)br.getBits(15)) << 15; + CHECK_EQ(br.getBits(1), 1u); + PTS |= br.getBits(15); + CHECK_EQ(br.getBits(1), 1u); + + ALOGV("PTS = %llu", PTS); + // ALOGI("PTS = %.2f secs", PTS / 90000.0f); + + optional_bytes_remaining -= 5; + + if (PTS_DTS_flags == 3) { + CHECK_GE(optional_bytes_remaining, 5u); + + CHECK_EQ(br.getBits(4), 1u); + + DTS = ((uint64_t)br.getBits(3)) << 30; + CHECK_EQ(br.getBits(1), 1u); + DTS |= ((uint64_t)br.getBits(15)) << 15; + CHECK_EQ(br.getBits(1), 1u); + DTS |= br.getBits(15); + CHECK_EQ(br.getBits(1), 1u); + + ALOGV("DTS = %llu", DTS); + + optional_bytes_remaining -= 5; + } + } + + if (ESCR_flag) { + CHECK_GE(optional_bytes_remaining, 6u); + + br.getBits(2); + + uint64_t ESCR = ((uint64_t)br.getBits(3)) << 30; + CHECK_EQ(br.getBits(1), 1u); + ESCR |= ((uint64_t)br.getBits(15)) << 15; + CHECK_EQ(br.getBits(1), 1u); + ESCR |= br.getBits(15); + CHECK_EQ(br.getBits(1), 1u); + + ALOGV("ESCR = %llu", ESCR); + /* unsigned ESCR_extension = */br.getBits(9); + + CHECK_EQ(br.getBits(1), 1u); + + optional_bytes_remaining -= 6; + } + + if (ES_rate_flag) { + CHECK_GE(optional_bytes_remaining, 3u); + + CHECK_EQ(br.getBits(1), 1u); + /* unsigned ES_rate = */br.getBits(22); + CHECK_EQ(br.getBits(1), 1u); + + optional_bytes_remaining -= 3; + } + + br.skipBits(optional_bytes_remaining * 8); + + // ES data follows. + + CHECK_GE(PES_packet_length, PES_header_data_length + 3); + + unsigned dataLength = + PES_packet_length - 3 - PES_header_data_length; + + if (br.numBitsLeft() < dataLength * 8) { + ALOGE("PES packet does not carry enough data to contain " + "payload. (numBitsLeft = %d, required = %d)", + br.numBitsLeft(), dataLength * 8); + + return ERROR_MALFORMED; + } + + CHECK_GE(br.numBitsLeft(), dataLength * 8); + + ssize_t index = mTracks.indexOfKey(stream_id); + if (index < 0 && mScanning) { + unsigned streamType; + + ssize_t streamTypeIndex; + if (mProgramStreamMapValid + && (streamTypeIndex = + mStreamTypeByESID.indexOfKey(stream_id)) >= 0) { + streamType = mStreamTypeByESID.valueAt(streamTypeIndex); + } else if ((stream_id & ~0x1f) == 0xc0) { + // ISO/IEC 13818-3 or ISO/IEC 11172-3 or ISO/IEC 13818-7 + // or ISO/IEC 14496-3 audio + streamType = ATSParser::STREAMTYPE_MPEG2_AUDIO; + } else if ((stream_id & ~0x0f) == 0xe0) { + // ISO/IEC 13818-2 or ISO/IEC 11172-2 or ISO/IEC 14496-2 video + streamType = ATSParser::STREAMTYPE_MPEG2_VIDEO; + } else { + streamType = ATSParser::STREAMTYPE_RESERVED; + } + + index = mTracks.add( + stream_id, new Track(this, stream_id, streamType)); + } + + status_t err = OK; + + if (index >= 0) { + err = + mTracks.editValueAt(index)->appendPESData( + PTS_DTS_flags, PTS, DTS, br.data(), dataLength); + } + + br.skipBits(dataLength * 8); + + if (err != OK) { + return err; + } + } else if (stream_id == 0xbe) { // padding_stream + CHECK_NE(PES_packet_length, 0u); + br.skipBits(PES_packet_length * 8); + } else { + CHECK_NE(PES_packet_length, 0u); + br.skipBits(PES_packet_length * 8); + } + + return n; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::Track::Track( + MPEG2PSExtractor *extractor, unsigned stream_id, unsigned stream_type) + : mExtractor(extractor), + mStreamID(stream_id), + mStreamType(stream_type), + mQueue(NULL) { + bool supported = true; + ElementaryStreamQueue::Mode mode; + + switch (mStreamType) { + case ATSParser::STREAMTYPE_H264: + mode = ElementaryStreamQueue::H264; + break; + case ATSParser::STREAMTYPE_MPEG2_AUDIO_ADTS: + mode = ElementaryStreamQueue::AAC; + break; + case ATSParser::STREAMTYPE_MPEG1_AUDIO: + case ATSParser::STREAMTYPE_MPEG2_AUDIO: + mode = ElementaryStreamQueue::MPEG_AUDIO; + break; + + case ATSParser::STREAMTYPE_MPEG1_VIDEO: + case ATSParser::STREAMTYPE_MPEG2_VIDEO: + mode = ElementaryStreamQueue::MPEG_VIDEO; + break; + + case ATSParser::STREAMTYPE_MPEG4_VIDEO: + mode = ElementaryStreamQueue::MPEG4_VIDEO; + break; + + default: + supported = false; + break; + } + + if (supported) { + mQueue = new ElementaryStreamQueue(mode); + } else { + ALOGI("unsupported stream ID 0x%02x", stream_id); + } +} + +MPEG2PSExtractor::Track::~Track() { + delete mQueue; + mQueue = NULL; +} + +status_t MPEG2PSExtractor::Track::start(MetaData *params) { + if (mSource == NULL) { + return NO_INIT; + } + + return mSource->start(params); +} + +status_t MPEG2PSExtractor::Track::stop() { + if (mSource == NULL) { + return NO_INIT; + } + + return mSource->stop(); +} + +sp<MetaData> MPEG2PSExtractor::Track::getFormat() { + if (mSource == NULL) { + return NULL; + } + + return mSource->getFormat(); +} + +status_t MPEG2PSExtractor::Track::read( + MediaBuffer **buffer, const ReadOptions *options) { + if (mSource == NULL) { + return NO_INIT; + } + + status_t finalResult; + while (!mSource->hasBufferAvailable(&finalResult)) { + if (finalResult != OK) { + return ERROR_END_OF_STREAM; + } + + status_t err = mExtractor->feedMore(); + + if (err != OK) { + mSource->signalEOS(err); + } + } + + return mSource->read(buffer, options); +} + +status_t MPEG2PSExtractor::Track::appendPESData( + unsigned PTS_DTS_flags, + uint64_t PTS, uint64_t DTS, + const uint8_t *data, size_t size) { + if (mQueue == NULL) { + return OK; + } + + int64_t timeUs; + if (PTS_DTS_flags == 2 || PTS_DTS_flags == 3) { + timeUs = (PTS * 100) / 9; + } else { + timeUs = 0; + } + + status_t err = mQueue->appendData(data, size, timeUs); + + if (err != OK) { + return err; + } + + sp<ABuffer> accessUnit; + while ((accessUnit = mQueue->dequeueAccessUnit()) != NULL) { + if (mSource == NULL) { + sp<MetaData> meta = mQueue->getFormat(); + + if (meta != NULL) { + ALOGV("Stream ID 0x%02x now has data.", mStreamID); + + mSource = new AnotherPacketSource(meta); + mSource->queueAccessUnit(accessUnit); + } + } else if (mQueue->getFormat() != NULL) { + mSource->queueAccessUnit(accessUnit); + } + } + + return OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2PSExtractor::WrappedTrack::WrappedTrack( + const sp<MPEG2PSExtractor> &extractor, const sp<Track> &track) + : mExtractor(extractor), + mTrack(track) { +} + +MPEG2PSExtractor::WrappedTrack::~WrappedTrack() { +} + +status_t MPEG2PSExtractor::WrappedTrack::start(MetaData *params) { + return mTrack->start(params); +} + +status_t MPEG2PSExtractor::WrappedTrack::stop() { + return mTrack->stop(); +} + +sp<MetaData> MPEG2PSExtractor::WrappedTrack::getFormat() { + return mTrack->getFormat(); +} + +status_t MPEG2PSExtractor::WrappedTrack::read( + MediaBuffer **buffer, const ReadOptions *options) { + return mTrack->read(buffer, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SniffMPEG2PS( + const sp<DataSource> &source, String8 *mimeType, float *confidence, + sp<AMessage> *) { + uint8_t header[5]; + if (source->readAt(0, header, sizeof(header)) < (ssize_t)sizeof(header)) { + return false; + } + + if (memcmp("\x00\x00\x01\xba", header, 4) || (header[4] >> 6) != 1) { + return false; + } + + *confidence = 0.25f; // Slightly larger than .mp3 extractor's confidence + + mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2PS); + + return true; +} + +} // namespace android diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp new file mode 100644 index 0000000..e1589b4 --- /dev/null +++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp @@ -0,0 +1,266 @@ +/* + * 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 "MPEG2TSExtractor" +#include <utils/Log.h> + +#include "include/MPEG2TSExtractor.h" +#include "include/LiveSession.h" +#include "include/NuCachedSource2.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/DataSource.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <utils/String8.h> + +#include "AnotherPacketSource.h" +#include "ATSParser.h" + +namespace android { + +static const size_t kTSPacketSize = 188; + +struct MPEG2TSSource : public MediaSource { + MPEG2TSSource( + const sp<MPEG2TSExtractor> &extractor, + const sp<AnotherPacketSource> &impl, + bool seekable); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual sp<MetaData> getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +private: + sp<MPEG2TSExtractor> mExtractor; + sp<AnotherPacketSource> mImpl; + + // If there are both audio and video streams, only the video stream + // will be seekable, otherwise the single stream will be seekable. + bool mSeekable; + + DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSSource); +}; + +MPEG2TSSource::MPEG2TSSource( + const sp<MPEG2TSExtractor> &extractor, + const sp<AnotherPacketSource> &impl, + bool seekable) + : mExtractor(extractor), + mImpl(impl), + mSeekable(seekable) { +} + +status_t MPEG2TSSource::start(MetaData *params) { + return mImpl->start(params); +} + +status_t MPEG2TSSource::stop() { + return mImpl->stop(); +} + +sp<MetaData> MPEG2TSSource::getFormat() { + sp<MetaData> meta = mImpl->getFormat(); + + int64_t durationUs; + if (mExtractor->mLiveSession != NULL + && mExtractor->mLiveSession->getDuration(&durationUs) == OK) { + meta->setInt64(kKeyDuration, durationUs); + } + + return meta; +} + +status_t MPEG2TSSource::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + + int64_t seekTimeUs; + ReadOptions::SeekMode seekMode; + if (mSeekable && options && options->getSeekTo(&seekTimeUs, &seekMode)) { + mExtractor->seekTo(seekTimeUs); + } + + status_t finalResult; + while (!mImpl->hasBufferAvailable(&finalResult)) { + if (finalResult != OK) { + return ERROR_END_OF_STREAM; + } + + status_t err = mExtractor->feedMore(); + if (err != OK) { + mImpl->signalEOS(err); + } + } + + return mImpl->read(out, options); +} + +//////////////////////////////////////////////////////////////////////////////// + +MPEG2TSExtractor::MPEG2TSExtractor(const sp<DataSource> &source) + : mDataSource(source), + mParser(new ATSParser), + mOffset(0) { + init(); +} + +size_t MPEG2TSExtractor::countTracks() { + return mSourceImpls.size(); +} + +sp<MediaSource> MPEG2TSExtractor::getTrack(size_t index) { + if (index >= mSourceImpls.size()) { + return NULL; + } + + bool seekable = true; + if (mSourceImpls.size() > 1) { + CHECK_EQ(mSourceImpls.size(), 2u); + + sp<MetaData> meta = mSourceImpls.editItemAt(index)->getFormat(); + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + if (!strncasecmp("audio/", mime, 6)) { + seekable = false; + } + } + + return new MPEG2TSSource(this, mSourceImpls.editItemAt(index), seekable); +} + +sp<MetaData> MPEG2TSExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + return index < mSourceImpls.size() + ? mSourceImpls.editItemAt(index)->getFormat() : NULL; +} + +sp<MetaData> MPEG2TSExtractor::getMetaData() { + sp<MetaData> meta = new MetaData; + meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG2TS); + + return meta; +} + +void MPEG2TSExtractor::init() { + bool haveAudio = false; + bool haveVideo = false; + int numPacketsParsed = 0; + + while (feedMore() == OK) { + ATSParser::SourceType type; + if (haveAudio && haveVideo) { + break; + } + if (!haveVideo) { + sp<AnotherPacketSource> impl = + (AnotherPacketSource *)mParser->getSource( + ATSParser::VIDEO).get(); + + if (impl != NULL) { + haveVideo = true; + mSourceImpls.push(impl); + } + } + + if (!haveAudio) { + sp<AnotherPacketSource> impl = + (AnotherPacketSource *)mParser->getSource( + ATSParser::AUDIO).get(); + + if (impl != NULL) { + haveAudio = true; + mSourceImpls.push(impl); + } + } + + if (++numPacketsParsed > 10000) { + break; + } + } + + ALOGI("haveAudio=%d, haveVideo=%d", haveAudio, haveVideo); +} + +status_t MPEG2TSExtractor::feedMore() { + Mutex::Autolock autoLock(mLock); + + uint8_t packet[kTSPacketSize]; + ssize_t n = mDataSource->readAt(mOffset, packet, kTSPacketSize); + + if (n < (ssize_t)kTSPacketSize) { + return (n < 0) ? (status_t)n : ERROR_END_OF_STREAM; + } + + mOffset += n; + return mParser->feedTSPacket(packet, kTSPacketSize); +} + +void MPEG2TSExtractor::setLiveSession(const sp<LiveSession> &liveSession) { + Mutex::Autolock autoLock(mLock); + + mLiveSession = liveSession; +} + +void MPEG2TSExtractor::seekTo(int64_t seekTimeUs) { + Mutex::Autolock autoLock(mLock); + + if (mLiveSession == NULL) { + return; + } + + mLiveSession->seekTo(seekTimeUs); +} + +uint32_t MPEG2TSExtractor::flags() const { + Mutex::Autolock autoLock(mLock); + + uint32_t flags = CAN_PAUSE; + + if (mLiveSession != NULL && mLiveSession->isSeekable()) { + flags |= CAN_SEEK_FORWARD | CAN_SEEK_BACKWARD | CAN_SEEK; + } + + return flags; +} + +//////////////////////////////////////////////////////////////////////////////// + +bool SniffMPEG2TS( + const sp<DataSource> &source, String8 *mimeType, float *confidence, + sp<AMessage> *) { + for (int i = 0; i < 5; ++i) { + char header; + if (source->readAt(kTSPacketSize * i, &header, 1) != 1 + || header != 0x47) { + return false; + } + } + + *confidence = 0.1f; + mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MPEG2TS); + + return true; +} + +} // namespace android |