| /* |
| * 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" |
| |
| #include <inttypes.h> |
| #include <netinet/in.h> |
| |
| namespace android { |
| |
| ElementaryStreamQueue::ElementaryStreamQueue(Mode mode, uint32_t flags) |
| : mMode(mode), |
| mFlags(flags), |
| mEOSReached(false) { |
| } |
| |
| sp<MetaData> ElementaryStreamQueue::getFormat() { |
| return mFormat; |
| } |
| |
| void ElementaryStreamQueue::clear(bool clearFormat) { |
| if (mBuffer != NULL) { |
| mBuffer->setRange(0, 0); |
| } |
| |
| mRangeInfos.clear(); |
| |
| if (clearFormat) { |
| mFormat.clear(); |
| } |
| } |
| |
| // Parse AC3 header assuming the current ptr is start position of syncframe, |
| // update metadata only applicable, and return the payload size |
| static unsigned parseAC3SyncFrame( |
| const uint8_t *ptr, size_t size, sp<MetaData> *metaData) { |
| static const unsigned channelCountTable[] = {2, 1, 2, 3, 3, 4, 4, 5}; |
| static const unsigned samplingRateTable[] = {48000, 44100, 32000}; |
| |
| static const unsigned frameSizeTable[19][3] = { |
| { 64, 69, 96 }, |
| { 80, 87, 120 }, |
| { 96, 104, 144 }, |
| { 112, 121, 168 }, |
| { 128, 139, 192 }, |
| { 160, 174, 240 }, |
| { 192, 208, 288 }, |
| { 224, 243, 336 }, |
| { 256, 278, 384 }, |
| { 320, 348, 480 }, |
| { 384, 417, 576 }, |
| { 448, 487, 672 }, |
| { 512, 557, 768 }, |
| { 640, 696, 960 }, |
| { 768, 835, 1152 }, |
| { 896, 975, 1344 }, |
| { 1024, 1114, 1536 }, |
| { 1152, 1253, 1728 }, |
| { 1280, 1393, 1920 }, |
| }; |
| |
| ABitReader bits(ptr, size); |
| if (bits.numBitsLeft() < 16) { |
| return 0; |
| } |
| if (bits.getBits(16) != 0x0B77) { |
| return 0; |
| } |
| |
| if (bits.numBitsLeft() < 16 + 2 + 6 + 5 + 3 + 3) { |
| ALOGV("Not enough bits left for further parsing"); |
| return 0; |
| } |
| bits.skipBits(16); // crc1 |
| |
| unsigned fscod = bits.getBits(2); |
| if (fscod == 3) { |
| ALOGW("Incorrect fscod in AC3 header"); |
| return 0; |
| } |
| |
| unsigned frmsizecod = bits.getBits(6); |
| if (frmsizecod > 37) { |
| ALOGW("Incorrect frmsizecod in AC3 header"); |
| return 0; |
| } |
| |
| unsigned bsid = bits.getBits(5); |
| if (bsid > 8) { |
| ALOGW("Incorrect bsid in AC3 header. Possibly E-AC-3?"); |
| return 0; |
| } |
| |
| unsigned bsmod __unused = bits.getBits(3); |
| unsigned acmod = bits.getBits(3); |
| unsigned cmixlev __unused = 0; |
| unsigned surmixlev __unused = 0; |
| unsigned dsurmod __unused = 0; |
| |
| if ((acmod & 1) > 0 && acmod != 1) { |
| if (bits.numBitsLeft() < 2) { |
| return 0; |
| } |
| cmixlev = bits.getBits(2); |
| } |
| if ((acmod & 4) > 0) { |
| if (bits.numBitsLeft() < 2) { |
| return 0; |
| } |
| surmixlev = bits.getBits(2); |
| } |
| if (acmod == 2) { |
| if (bits.numBitsLeft() < 2) { |
| return 0; |
| } |
| dsurmod = bits.getBits(2); |
| } |
| |
| if (bits.numBitsLeft() < 1) { |
| return 0; |
| } |
| unsigned lfeon = bits.getBits(1); |
| |
| unsigned samplingRate = samplingRateTable[fscod]; |
| unsigned payloadSize = frameSizeTable[frmsizecod >> 1][fscod]; |
| if (fscod == 1) { |
| payloadSize += frmsizecod & 1; |
| } |
| payloadSize <<= 1; // convert from 16-bit words to bytes |
| |
| unsigned channelCount = channelCountTable[acmod] + lfeon; |
| |
| if (metaData != NULL) { |
| (*metaData)->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AC3); |
| (*metaData)->setInt32(kKeyChannelCount, channelCount); |
| (*metaData)->setInt32(kKeySampleRate, samplingRate); |
| } |
| |
| return payloadSize; |
| } |
| |
| static bool IsSeeminglyValidAC3Header(const uint8_t *ptr, size_t size) { |
| return parseAC3SyncFrame(ptr, size, NULL) > 0; |
| } |
| |
| static bool IsSeeminglyValidADTSHeader( |
| const uint8_t *ptr, size_t size, size_t *frameLength) { |
| if (size < 7) { |
| // 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; |
| } |
| |
| size_t frameLengthInHeader = |
| ((ptr[3] & 3) << 11) + (ptr[4] << 3) + ((ptr[5] >> 5) & 7); |
| if (frameLengthInHeader > size) { |
| return false; |
| } |
| |
| *frameLength = frameLengthInHeader; |
| 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 (mEOSReached) { |
| ALOGE("appending data after EOS"); |
| return ERROR_MALFORMED; |
| } |
| 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 + 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 %zd", |
| 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 %zd", |
| 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; |
| size_t frameLength; |
| for (size_t i = 0; i < size; ++i) { |
| if (IsSeeminglyValidADTSHeader( |
| &ptr[i], size - i, &frameLength)) { |
| startOffset = i; |
| break; |
| } |
| } |
| |
| if (startOffset < 0) { |
| return ERROR_MALFORMED; |
| } |
| |
| if (startOffset > 0) { |
| ALOGI("found something resembling an AAC syncword at " |
| "offset %zd", |
| startOffset); |
| } |
| |
| if (frameLength != size - startOffset) { |
| ALOGV("First ADTS AAC frame length is %zd bytes, " |
| "while the buffer size is %zd bytes.", |
| frameLength, size - startOffset); |
| } |
| |
| data = &ptr[startOffset]; |
| size -= startOffset; |
| #endif |
| break; |
| } |
| |
| case AC3: |
| { |
| uint8_t *ptr = (uint8_t *)data; |
| |
| ssize_t startOffset = -1; |
| for (size_t i = 0; i < size; ++i) { |
| if (IsSeeminglyValidAC3Header(&ptr[i], size - i)) { |
| startOffset = i; |
| break; |
| } |
| } |
| |
| if (startOffset < 0) { |
| return ERROR_MALFORMED; |
| } |
| |
| if (startOffset > 0) { |
| ALOGI("found something resembling an AC3 syncword at " |
| "offset %zd", |
| startOffset); |
| } |
| |
| data = &ptr[startOffset]; |
| size -= startOffset; |
| 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 %zd", |
| startOffset); |
| } |
| |
| data = &ptr[startOffset]; |
| size -= startOffset; |
| break; |
| } |
| |
| case PCM_AUDIO: |
| { |
| 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 %zu", 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 = %zu, timeUs = %.2f secs", size, timeUs / 1E6); |
| hexdump(data, size); |
| } |
| #endif |
| |
| return OK; |
| } |
| |
| sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnit() { |
| if ((mFlags & kFlag_AlignedData) && mMode == H264) { |
| if (mRangeInfos.empty()) { |
| return NULL; |
| } |
| |
| RangeInfo info = *mRangeInfos.begin(); |
| mRangeInfos.erase(mRangeInfos.begin()); |
| |
| sp<ABuffer> accessUnit = new ABuffer(info.mLength); |
| memcpy(accessUnit->data(), mBuffer->data(), info.mLength); |
| accessUnit->meta()->setInt64("timeUs", info.mTimestampUs); |
| |
| memmove(mBuffer->data(), |
| mBuffer->data() + info.mLength, |
| mBuffer->size() - info.mLength); |
| |
| mBuffer->setRange(0, mBuffer->size() - info.mLength); |
| |
| if (mFormat == NULL) { |
| mFormat = MakeAVCCodecSpecificData(accessUnit); |
| } |
| |
| return accessUnit; |
| } |
| |
| switch (mMode) { |
| case H264: |
| return dequeueAccessUnitH264(); |
| case AAC: |
| return dequeueAccessUnitAAC(); |
| case AC3: |
| return dequeueAccessUnitAC3(); |
| case MPEG_VIDEO: |
| return dequeueAccessUnitMPEGVideo(); |
| case MPEG4_VIDEO: |
| return dequeueAccessUnitMPEG4Video(); |
| case PCM_AUDIO: |
| return dequeueAccessUnitPCMAudio(); |
| default: |
| CHECK_EQ((unsigned)mMode, (unsigned)MPEG_AUDIO); |
| return dequeueAccessUnitMPEGAudio(); |
| } |
| } |
| |
| sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAC3() { |
| unsigned syncStartPos = 0; // in bytes |
| unsigned payloadSize = 0; |
| sp<MetaData> format = new MetaData; |
| while (true) { |
| if (syncStartPos + 2 >= mBuffer->size()) { |
| return NULL; |
| } |
| |
| payloadSize = parseAC3SyncFrame( |
| mBuffer->data() + syncStartPos, |
| mBuffer->size() - syncStartPos, |
| &format); |
| if (payloadSize > 0) { |
| break; |
| } |
| ++syncStartPos; |
| } |
| |
| if (mBuffer->size() < syncStartPos + payloadSize) { |
| ALOGV("Not enough buffer size for AC3"); |
| return NULL; |
| } |
| |
| if (mFormat == NULL) { |
| mFormat = format; |
| } |
| |
| sp<ABuffer> accessUnit = new ABuffer(syncStartPos + payloadSize); |
| memcpy(accessUnit->data(), mBuffer->data(), syncStartPos + payloadSize); |
| |
| int64_t timeUs = fetchTimestamp(syncStartPos + payloadSize); |
| CHECK_GE(timeUs, 0ll); |
| accessUnit->meta()->setInt64("timeUs", timeUs); |
| |
| memmove( |
| mBuffer->data(), |
| mBuffer->data() + syncStartPos + payloadSize, |
| mBuffer->size() - syncStartPos - payloadSize); |
| |
| mBuffer->setRange(0, mBuffer->size() - syncStartPos - payloadSize); |
| |
| return accessUnit; |
| } |
| |
| sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitPCMAudio() { |
| if (mBuffer->size() < 4) { |
| return NULL; |
| } |
| |
| ABitReader bits(mBuffer->data(), 4); |
| CHECK_EQ(bits.getBits(8), 0xa0); |
| unsigned numAUs = bits.getBits(8); |
| bits.skipBits(8); |
| unsigned quantization_word_length __unused = bits.getBits(2); |
| unsigned audio_sampling_frequency = bits.getBits(3); |
| unsigned num_channels = bits.getBits(3); |
| |
| CHECK_EQ(audio_sampling_frequency, 2); // 48kHz |
| CHECK_EQ(num_channels, 1u); // stereo! |
| |
| if (mFormat == NULL) { |
| mFormat = new MetaData; |
| mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); |
| mFormat->setInt32(kKeyChannelCount, 2); |
| mFormat->setInt32(kKeySampleRate, 48000); |
| } |
| |
| static const size_t kFramesPerAU = 80; |
| size_t frameSize = 2 /* numChannels */ * sizeof(int16_t); |
| |
| size_t payloadSize = numAUs * frameSize * kFramesPerAU; |
| |
| if (mBuffer->size() < 4 + payloadSize) { |
| return NULL; |
| } |
| |
| sp<ABuffer> accessUnit = new ABuffer(payloadSize); |
| memcpy(accessUnit->data(), mBuffer->data() + 4, payloadSize); |
| |
| int64_t timeUs = fetchTimestamp(payloadSize + 4); |
| CHECK_GE(timeUs, 0ll); |
| accessUnit->meta()->setInt64("timeUs", timeUs); |
| |
| int16_t *ptr = (int16_t *)accessUnit->data(); |
| for (size_t i = 0; i < payloadSize / sizeof(int16_t); ++i) { |
| ptr[i] = ntohs(ptr[i]); |
| } |
| |
| memmove( |
| mBuffer->data(), |
| mBuffer->data() + 4 + payloadSize, |
| mBuffer->size() - 4 - payloadSize); |
| |
| mBuffer->setRange(0, mBuffer->size() - 4 - payloadSize); |
| |
| return accessUnit; |
| } |
| |
| sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() { |
| if (mBuffer->size() == 0) { |
| return NULL; |
| } |
| |
| CHECK(!mRangeInfos.empty()); |
| |
| const RangeInfo &info = *mRangeInfos.begin(); |
| if (mBuffer->size() < info.mLength) { |
| return NULL; |
| } |
| |
| CHECK_GE(info.mTimestampUs, 0ll); |
| |
| // The idea here is consume all AAC frames starting at offsets before |
| // info.mLength so we can assign a meaningful timestamp without |
| // having to interpolate. |
| // The final AAC frame may well extend into the next RangeInfo but |
| // that's ok. |
| // TODO: the logic commented above is skipped because codec cannot take |
| // arbitrary sized input buffers; |
| size_t offset = 0; |
| while (offset < info.mLength) { |
| if (offset + 7 > mBuffer->size()) { |
| return NULL; |
| } |
| |
| 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 __unused = 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); |
| |
| mFormat->setInt32(kKeyIsADTS, true); |
| |
| 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()) { |
| return NULL; |
| } |
| |
| size_t headerSize __unused = protection_absent ? 7 : 9; |
| |
| offset += aac_frame_length; |
| // TODO: move back to concatenation when codec can support arbitrary input buffers. |
| // For now only queue a single buffer |
| break; |
| } |
| |
| int64_t timeUs = fetchTimestampAAC(offset); |
| |
| sp<ABuffer> accessUnit = new ABuffer(offset); |
| memcpy(accessUnit->data(), mBuffer->data(), offset); |
| |
| memmove(mBuffer->data(), mBuffer->data() + offset, |
| mBuffer->size() - offset); |
| mBuffer->setRange(0, mBuffer->size() - offset); |
| |
| accessUnit->meta()->setInt64("timeUs", timeUs); |
| |
| 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; |
| size = 0; |
| } else { |
| size -= info->mLength; |
| |
| mRangeInfos.erase(mRangeInfos.begin()); |
| info = NULL; |
| } |
| |
| } |
| |
| if (timeUs == 0ll) { |
| ALOGV("Returning 0 timestamp"); |
| } |
| |
| return timeUs; |
| } |
| |
| // TODO: avoid interpolating timestamps once codec supports arbitrary sized input buffers |
| int64_t ElementaryStreamQueue::fetchTimestampAAC(size_t size) { |
| int64_t timeUs = -1; |
| bool first = true; |
| |
| size_t samplesize = size; |
| while (size > 0) { |
| CHECK(!mRangeInfos.empty()); |
| |
| RangeInfo *info = &*mRangeInfos.begin(); |
| |
| if (first) { |
| timeUs = info->mTimestampUs; |
| first = false; |
| } |
| |
| if (info->mLength > size) { |
| int32_t sampleRate; |
| CHECK(mFormat->findInt32(kKeySampleRate, &sampleRate)); |
| info->mLength -= size; |
| size_t numSamples = 1024 * size / samplesize; |
| info->mTimestampUs += numSamples * 1000000ll / sampleRate; |
| 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) { |
| if (nalSize == 0) continue; |
| |
| 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 (nalType == 6) { |
| sp<ABuffer> sei = new ABuffer(pos.nalSize); |
| memcpy(sei->data(), mBuffer->data() + pos.nalOffset, pos.nalSize); |
| accessUnit->meta()->setBuffer("sei", sei); |
| } |
| |
| #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; |
| } |
| |
| #if !LOG_NDEBUG |
| ALOGV("accessUnit contains nal types %s", out.c_str()); |
| #endif |
| |
| 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 %" PRId64 " 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 %" PRId64 " 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; |
| } |
| |
| void ElementaryStreamQueue::signalEOS() { |
| if (!mEOSReached) { |
| if (mMode == MPEG_VIDEO) { |
| const char *theEnd = "\x00\x00\x01\x00"; |
| appendData(theEnd, 4, 0); |
| } |
| mEOSReached = true; |
| } else { |
| ALOGW("EOS already signaled"); |
| } |
| } |
| |
| |
| } // namespace android |