| /************************************************************************************* |
| * INTEL CONFIDENTIAL |
| * Copyright 2011 Intel Corporation All Rights Reserved. |
| * The source code contained or described herein and all documents related |
| * to the source code ("Material") are owned by Intel Corporation or its |
| * suppliers or licensors. Title to the Material remains with Intel |
| * Corporation or its suppliers and licensors. The Material contains trade |
| * secrets and proprietary and confidential information of Intel or its |
| * suppliers and licensors. The Material is protected by worldwide copyright |
| * and trade secret laws and treaty provisions. No part of the Material may |
| * be used, copied, reproduced, modified, published, uploaded, posted, |
| * transmitted, distributed, or disclosed in any way without Intel's prior |
| * express written permission. |
| * |
| * No license under any patent, copyright, trade secret or other intellectual |
| * property right is granted to or conferred upon you by disclosure or delivery |
| * of the Materials, either expressly, by implication, inducement, estoppel or |
| * otherwise. Any license under such intellectual property rights must be express |
| * and approved by Intel in writing. |
| ************************************************************************************/ |
| /* |
| * 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 "AsfExtractor" |
| #include <utils/Log.h> |
| |
| #include <arpa/inet.h> |
| |
| #include <ctype.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include <media/stagefright/foundation/ADebug.h> |
| #include <media/stagefright/DataSource.h> |
| #include <media/stagefright/MediaBuffer.h> |
| #include <media/stagefright/MediaBufferGroup.h> |
| #include <media/stagefright/MediaDefs.h> |
| #include <media/stagefright/MetaData.h> |
| #include <media/stagefright/Utils.h> |
| #include <utils/String8.h> |
| |
| #include "MetaDataExt.h" |
| #include "MediaBufferPool.h" |
| #include "AsfStreamParser.h" |
| #include "AsfExtractor.h" |
| |
| |
| namespace android { |
| |
| // The audio format tags that represent the input categories supported |
| // by the Windows Media Audio decoder, don't change it |
| enum WMAAudioFormats { |
| WAVE_FORMAT_MSAUDIO1 = 0x160, |
| WAVE_FORMAT_WMAUDIO2 = 0x161, |
| WAVE_FORMAT_WMAUDIO3X = 0x162, |
| WAVE_FORMAT_WMAUDIO_LOSSLESS = 0x163, |
| WAVE_FORMAT_WMAVOICE9 = 0x000A, |
| WAVE_FORMAT_WMAVOICE10 = 0x000B, |
| }; |
| |
| class ASFSource : public MediaSource { |
| public: |
| ASFSource(const sp<AsfExtractor> &extractor, int trackIndex) |
| : mExtractor(extractor), |
| mTrackIndex(trackIndex) { |
| } |
| |
| virtual status_t start(MetaData *params = NULL) { |
| return OK; |
| } |
| |
| virtual status_t stop() { |
| return OK; |
| } |
| |
| virtual sp<MetaData> getFormat() { |
| return mExtractor->getTrackMetaData(mTrackIndex, 0); |
| } |
| |
| virtual status_t read(MediaBuffer **buffer, const ReadOptions *options = NULL) { |
| return mExtractor->read(mTrackIndex, buffer, options); |
| } |
| |
| protected: |
| virtual ~ASFSource() { |
| mExtractor = NULL; |
| } |
| |
| private: |
| sp<AsfExtractor> mExtractor; |
| int mTrackIndex; |
| |
| ASFSource(const ASFSource &); |
| ASFSource &operator=(const ASFSource &); |
| }; |
| |
| |
| AsfExtractor::AsfExtractor(const sp<DataSource> &source) |
| : mDataSource(source), |
| mInitialized(false), |
| mHasIndexObject(false), |
| mFirstTrack(NULL), |
| mLastTrack(NULL), |
| mReadLock(), |
| mFileMetaData(new MetaData), |
| mParser(NULL), |
| mHeaderObjectSize(0), |
| mDataObjectSize(0), |
| mDataPacketBeginOffset(0), |
| mDataPacketEndOffset(0), |
| mDataPacketCurrentOffset(0), |
| mDataPacketSize(0), |
| mDataPacketData(NULL) { |
| mParser = new AsfStreamParser; |
| } |
| |
| AsfExtractor::~AsfExtractor() { |
| uninitialize(); |
| mDataSource = NULL; |
| mFileMetaData = NULL; |
| delete mParser; |
| mParser = NULL; |
| } |
| |
| sp<MetaData> AsfExtractor::getMetaData() { |
| status_t err = initialize(); |
| if (err != OK) { |
| return new MetaData; |
| } |
| |
| return mFileMetaData; |
| } |
| |
| size_t AsfExtractor::countTracks() { |
| status_t err = initialize(); |
| if (err != OK) { |
| return 0; |
| } |
| |
| size_t n = 0; |
| Track *track = mFirstTrack; |
| while (track) { |
| ++n; |
| track = track->next; |
| } |
| |
| ALOGV("track count is %d", n); |
| return n; |
| } |
| |
| sp<MetaData> AsfExtractor::getTrackMetaData(size_t index, uint32_t flags) { |
| status_t err = initialize(); |
| if (err != OK) { |
| return NULL; |
| } |
| |
| Track *track = getTrackByTrackIndex(index); |
| if (track == NULL) { |
| return NULL; |
| } |
| |
| // There is no thumbnail data so ignore flags: kIncludeExtensiveMetaData |
| return track->meta; |
| } |
| |
| sp<MediaSource> AsfExtractor::getTrack(size_t index) { |
| status_t err; |
| if ((err = initialize()) != OK) { |
| return NULL; |
| } |
| |
| Track *track = getTrackByTrackIndex(index); |
| if (track == NULL) { |
| return NULL; |
| } |
| |
| // Assume this track is active |
| track->skipTrack = false; |
| return new ASFSource(this, index); |
| } |
| |
| status_t AsfExtractor::read( |
| int trackIndex, |
| MediaBuffer **buffer, |
| const MediaSource::ReadOptions *options) { |
| Track *track = getTrackByTrackIndex(trackIndex); |
| if (track == NULL) { |
| return BAD_VALUE; |
| } |
| |
| int64_t seekTimeUs; |
| MediaSource::ReadOptions::SeekMode mode; |
| if (!mParser->hasVideo() || (mParser->hasVideo() && mHasIndexObject)) { |
| if (options != NULL && options->getSeekTo(&seekTimeUs, &mode)) { |
| status_t err = seek_l(track, seekTimeUs, mode); |
| if (err != OK) { |
| return err; |
| } |
| } |
| } else { |
| ALOGW("No index object. Seek may not be supported!!!"); |
| } |
| |
| return read_l(track, buffer); |
| } |
| |
| status_t AsfExtractor::initialize() { |
| if (mInitialized) { |
| return OK; |
| } |
| |
| status_t status = OK; |
| // header object is the first mandatory object. The first 16 bytes |
| // is GUID of object, the following 8 bytes is size of object |
| if (mDataSource->readAt(16, &mHeaderObjectSize, 8) != 8) { |
| return ERROR_IO; |
| } |
| |
| uint8_t* headerObjectData = new uint8_t [mHeaderObjectSize]; |
| if (headerObjectData == NULL) { |
| return NO_MEMORY; |
| } |
| |
| if (mDataSource->readAt(0, headerObjectData, mHeaderObjectSize) != mHeaderObjectSize) { |
| return ERROR_IO; |
| } |
| status = mParser->parseHeaderObject(headerObjectData, mHeaderObjectSize); |
| if (status != ASF_PARSER_SUCCESS) { |
| ALOGE("Failed to parse header object."); |
| return ERROR_MALFORMED; |
| } |
| |
| delete [] headerObjectData; |
| headerObjectData = NULL; |
| |
| uint8_t dataObjectHeaderData[ASF_DATA_OBJECT_HEADER_SIZE]; |
| if (mDataSource->readAt(mHeaderObjectSize, dataObjectHeaderData, ASF_DATA_OBJECT_HEADER_SIZE) |
| != ASF_DATA_OBJECT_HEADER_SIZE) { |
| return ERROR_IO; |
| } |
| status = mParser->parseDataObjectHeader(dataObjectHeaderData, ASF_DATA_OBJECT_HEADER_SIZE); |
| if (status != ASF_PARSER_SUCCESS) { |
| ALOGE("Failed to parse data object header."); |
| return ERROR_MALFORMED; |
| } |
| |
| // first 16 bytes is GUID of data object |
| mDataObjectSize = *(uint64_t*)(dataObjectHeaderData + 16); |
| mDataPacketBeginOffset = mHeaderObjectSize + ASF_DATA_OBJECT_HEADER_SIZE; |
| mDataPacketEndOffset = mHeaderObjectSize + mDataObjectSize; |
| mDataPacketCurrentOffset = mDataPacketBeginOffset; |
| |
| // allocate memory for data packet |
| mDataPacketSize = mParser->getDataPacketSize(); |
| mDataPacketData = new uint8_t [mDataPacketSize]; |
| if (mDataPacketData == NULL) { |
| return NO_MEMORY; |
| } |
| |
| const AsfFileMediaInfo *fileMediaInfo = mParser->getFileInfo(); |
| if (fileMediaInfo && fileMediaInfo->seekable) { |
| uint64_t offset = mDataPacketEndOffset; |
| |
| // Find simple index object for time seeking. |
| // object header include 16 bytes of object GUID and 8 bytes of object size. |
| uint8_t objectHeader[24]; |
| int64_t objectSize; |
| for (;;) { |
| if (mDataSource->readAt(offset, objectHeader, 24) != 24) { |
| break; |
| } |
| |
| objectSize = *(int64_t *)(objectHeader + 16); |
| if (!AsfStreamParser::isSimpleIndexObject(objectHeader)) { |
| offset += objectSize; |
| continue; |
| } |
| mHasIndexObject = true; |
| uint8_t* indexObjectData = new uint8_t [objectSize]; |
| if (indexObjectData == NULL) { |
| // don't report as error, we just lose time seeking capability. |
| break; |
| } |
| if (mDataSource->readAt(offset, indexObjectData, objectSize) == objectSize) { |
| // Ignore return value |
| mParser->parseSimpleIndexObject(indexObjectData, objectSize); |
| } |
| delete [] indexObjectData; |
| break; |
| } |
| } |
| |
| if (mParser->hasVideo()) { |
| ALOGV("MEDIA_MIMETYPE_CONTAINER_ASF"); |
| mFileMetaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_ASF); |
| } else if (mParser->hasAudio() && mParser->getAudioInfo()->codecID >= WAVE_FORMAT_MSAUDIO1 && |
| mParser->getAudioInfo()->codecID <= WAVE_FORMAT_WMAUDIO_LOSSLESS) { |
| LOGV("MEDIA_MIMETYPE_AUDIO_WMA", mParser->getAudioInfo()->codecID); |
| mFileMetaData->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_WMA); |
| } else { |
| ALOGE("Content does not have neither audio nor video."); |
| return ERROR_UNSUPPORTED; |
| } |
| |
| // duration returned from parser is in 100-nanosecond unit, converted it to microseconds (us) |
| ALOGV("Duration is %.2f (sec)", mParser->getDuration()/1E7); |
| mFileMetaData->setInt64(kKeyDuration, mParser->getDuration() / SCALE_100_NANOSEC_TO_USEC); |
| |
| setupTracks(); |
| mInitialized = true; |
| return OK; |
| } |
| |
| void AsfExtractor::uninitialize() { |
| if (mDataPacketData) { |
| delete [] mDataPacketData; |
| mDataPacketData = NULL; |
| } |
| mDataPacketSize = 0; |
| |
| Track* track = mFirstTrack; |
| MediaBuffer* p; |
| while (track != NULL) { |
| track->meta = NULL; |
| if (track->bufferActive) { |
| track->bufferActive->release(); |
| track->bufferActive = NULL; |
| } |
| |
| int size = track->bufferQueue.size(); |
| for (int i = 0; i < size; i++) { |
| p = track->bufferQueue.editItemAt(i); |
| p->release(); |
| } |
| |
| track->bufferQueue.clear(); |
| delete track->bufferPool; |
| |
| track->meta = NULL; |
| mFirstTrack = track->next; |
| delete track; |
| track = mFirstTrack; |
| } |
| mFirstTrack = NULL; |
| mLastTrack = NULL; |
| } |
| |
| static const char* FourCC2MIME(uint32_t fourcc) { |
| // The first charater of FOURCC characters appears in the least-significant byte |
| // WVC1 => 0x31435657 |
| switch (fourcc) { |
| //case FOURCC('W', 'M', 'V', '1'): |
| //case FOURCC('W', 'M', 'V', '2'): |
| //case FOURCC('W', 'M', 'V', 'A'): |
| case FOURCC('1', 'V', 'M', 'W'): |
| ALOGW("WMV1 format is not supported."); |
| return "video/wmv1"; |
| case FOURCC('2', 'V', 'M', 'W'): |
| ALOGW("WMV2 format is not supported."); |
| return "video/wmv2"; |
| case FOURCC('A', 'V', 'M', 'W'): |
| ALOGW("WMV Advanced profile, assuming as WVC1 for now"); |
| return MEDIA_MIMETYPE_VIDEO_WMV; |
| //case FOURCC('W', 'M', 'V', '3'): |
| //case FOURCC('W', 'V', 'C', '1'): |
| case FOURCC('3', 'V', 'M', 'W'): |
| case FOURCC('1', 'C', 'V', 'W'): |
| return MEDIA_MIMETYPE_VIDEO_WMV; |
| default: |
| ALOGE("Unknown video format."); |
| return "video/unknown-type"; |
| } |
| } |
| |
| static const char* CodecID2MIME(uint32_t codecID) { |
| switch (codecID) { |
| // WMA version 1 |
| case WAVE_FORMAT_MSAUDIO1: |
| // WMA version 2 (7, 8, 9 series) |
| case WAVE_FORMAT_WMAUDIO2: |
| return MEDIA_MIMETYPE_AUDIO_WMA; |
| // WMA 9 lossless |
| case WAVE_FORMAT_WMAUDIO_LOSSLESS: |
| //return MEDIA_MIMETYPE_AUDIO_WMA_LOSSLESS; |
| return MEDIA_MIMETYPE_AUDIO_WMA; |
| // WMA voice 9 |
| case WAVE_FORMAT_WMAVOICE9: |
| // WMA voice 10 |
| case WAVE_FORMAT_WMAVOICE10: |
| ALOGW("WMA voice 9/10 is not supported."); |
| return "audio/wma-voice"; |
| default: |
| ALOGE("Unsupported Audio codec ID: %#x", codecID); |
| return "audio/unknown-type"; |
| } |
| } |
| |
| |
| status_t AsfExtractor::setupTracks() { |
| AsfAudioStreamInfo* audioInfo = mParser->getAudioInfo(); |
| AsfVideoStreamInfo* videoInfo = mParser->getVideoInfo(); |
| Track* track; |
| while (audioInfo || videoInfo) { |
| track = new Track; |
| if (mLastTrack == NULL) { |
| mFirstTrack = track; |
| mLastTrack = track; |
| } else { |
| mLastTrack->next = track; |
| mLastTrack = track; |
| } |
| |
| // this flag will be set to false within getTrack |
| track->skipTrack = true; |
| track->seekCompleted = false; |
| track->next = NULL; |
| track->meta = new MetaData; |
| track->bufferActive = NULL; |
| track->bufferPool = new MediaBufferPool; |
| |
| if (audioInfo) { |
| LOGV("streamNumber = %d\n, encryptedContentFlag= %d\n, timeOffset = %lld\n, |
| codecID = %d\n, numChannels=%d\n, sampleRate=%d\n, avgBitRate = %d\n, |
| blockAlignment =%d\n, bitsPerSample=%d\n, codecDataSize=%d\n", |
| audioInfo->streamNumber, audioInfo->encryptedContentFlag, |
| audioInfo->timeOffset, audioInfo->codecID, audioInfo->numChannels, |
| audioInfo->sampleRate, audioInfo->avgByteRate*8, audioInfo->blockAlignment, |
| audioInfo->bitsPerSample, audioInfo->codecDataSize); |
| |
| track->streamNumber = audioInfo->streamNumber; |
| track->encrypted = audioInfo->encryptedContentFlag; |
| track->meta->setInt32(kKeyChannelCount, audioInfo->numChannels); |
| track->meta->setInt32(kKeySampleRate, audioInfo->sampleRate); |
| track->meta->setInt32(kKeyWmaBlockAlign, audioInfo->blockAlignment); |
| track->meta->setInt32(kKeyBitPerSample, audioInfo->bitsPerSample); |
| track->meta->setInt32(kKeyBitRate, audioInfo->avgByteRate*8); |
| track->meta->setInt32(kKeyWmaFormatTag, audioInfo->codecID); |
| |
| if (audioInfo->codecDataSize) { |
| track->meta->setData( |
| kKeyConfigData, |
| kTypeConfigData, |
| audioInfo->codecData, |
| audioInfo->codecDataSize); |
| } |
| // duration returned is in 100-nanosecond unit |
| track->meta->setInt64(kKeyDuration, mParser->getDuration() / SCALE_100_NANOSEC_TO_USEC); |
| track->meta->setCString(kKeyMIMEType, CodecID2MIME(audioInfo->codecID)); |
| track->meta->setInt32(kKeySuggestedBufferSize, mParser->getDataPacketSize()); |
| audioInfo = audioInfo->next; |
| } else { |
| track->streamNumber = videoInfo->streamNumber; |
| track->encrypted = videoInfo->encryptedContentFlag; |
| track->meta->setInt32(kKeyWidth, videoInfo->width); |
| track->meta->setInt32(kKeyHeight, videoInfo->height); |
| if (videoInfo->codecDataSize) { |
| track->meta->setData( |
| kKeyConfigData, |
| kTypeConfigData, |
| videoInfo->codecData, |
| videoInfo->codecDataSize); |
| } |
| // duration returned is in 100-nanosecond unit |
| track->meta->setInt64(kKeyDuration, mParser->getDuration() / SCALE_100_NANOSEC_TO_USEC); |
| track->meta->setCString(kKeyMIMEType, FourCC2MIME(videoInfo->fourCC)); |
| int maxSize = mParser->getMaxObjectSize(); |
| if (maxSize == 0) { |
| // estimated maximum packet size. |
| maxSize = 10 * mParser->getDataPacketSize(); |
| } |
| track->meta->setInt32(kKeySuggestedBufferSize, maxSize); |
| if (mHasIndexObject) { |
| // set arbitary thumbnail time |
| track->meta->setInt64(kKeyThumbnailTime, mParser->getDuration() / (SCALE_100_NANOSEC_TO_USEC * 2)); |
| } else { |
| track->meta->setInt64(kKeyThumbnailTime, 0); |
| } |
| videoInfo = videoInfo->next; |
| } |
| } |
| |
| return OK; |
| } |
| |
| status_t AsfExtractor::seek_l(Track* track, int64_t seekTimeUs, MediaSource::ReadOptions::SeekMode mode) { |
| Mutex::Autolock lockSeek(mReadLock); |
| |
| // It is expected seeking will happen on all the tracks with the same seeking options. |
| // Only the first track receiving the seeking command will perform seeking and all other |
| // tracks just siliently ignore it. |
| |
| // TODO: potential problems in the following case: |
| // audio seek |
| // video read |
| // video seek |
| // video read |
| |
| if (track->seekCompleted) { |
| // seeking is completed through a different track |
| track->seekCompleted = false; |
| return OK; |
| } |
| |
| uint64_t targetSampleTimeUs = 0; |
| |
| // seek to next sync sample or previous sync sample |
| bool nextSync = false; |
| switch (mode) { |
| case MediaSource::ReadOptions::SEEK_NEXT_SYNC: |
| nextSync = true; |
| break; |
| // Always seek to the closest previous sync frame |
| case MediaSource::ReadOptions::SEEK_PREVIOUS_SYNC: |
| case MediaSource::ReadOptions::SEEK_CLOSEST_SYNC: |
| |
| // Not supported, already seek to sync frame, so will not set kKeyTargetTime on bufferActive. |
| case MediaSource::ReadOptions::SEEK_CLOSEST: |
| default: |
| break; |
| } |
| |
| uint32_t packetNumber; |
| uint64_t targetTime; |
| // parser takes seek time in 100-nanosecond unit and returns target time in 100-nanosecond as well. |
| if (!mParser->seek(seekTimeUs * SCALE_100_NANOSEC_TO_USEC, nextSync, packetNumber, targetTime)) { |
| ALOGV("Seeking failed."); |
| return ERROR_END_OF_STREAM; |
| } |
| ALOGV("seek time = %.2f secs, actual time = %.2f secs", seekTimeUs/1E6, targetTime / 1E7); |
| |
| // convert to microseconds |
| targetSampleTimeUs = targetTime / SCALE_100_NANOSEC_TO_USEC; |
| mDataPacketCurrentOffset = mDataPacketBeginOffset + packetNumber * mDataPacketSize; |
| ALOGV("data packet offset = %lld", mDataPacketCurrentOffset); |
| |
| // flush all pending buffers on all the tracks |
| Track* temp = mFirstTrack; |
| while (temp != NULL) { |
| Mutex::Autolock lockTrack(temp->lock); |
| if (temp->bufferActive) { |
| temp->bufferActive->release(); |
| temp->bufferActive = NULL; |
| } |
| |
| int size = temp->bufferQueue.size(); |
| for (int i = 0; i < size; i++) { |
| MediaBuffer* buffer = temp->bufferQueue.editItemAt(i); |
| buffer->release(); |
| } |
| temp->bufferQueue.clear(); |
| |
| if (temp != track) { |
| // notify all other tracks seeking is completed. |
| // this flag is reset when seeking request is made on each track. |
| // don't set this flag on the driving track so a new seek can be made. |
| temp->seekCompleted = true; |
| } |
| temp = temp->next; |
| } |
| |
| return OK; |
| } |
| |
| status_t AsfExtractor::read_l(Track *track, MediaBuffer **buffer) { |
| status_t err = OK; |
| while (err == OK) { |
| Mutex::Autolock lock(track->lock); |
| if (track->bufferQueue.size() != 0) { |
| *buffer = track->bufferQueue[0]; |
| track->bufferQueue.removeAt(0); |
| return OK; |
| } |
| track->lock.unlock(); |
| |
| err = readPacket(); |
| } |
| ALOGE("read_l failed."); |
| return err; |
| } |
| |
| status_t AsfExtractor::readPacket() { |
| Mutex::Autolock lock(mReadLock); |
| if (mDataPacketCurrentOffset + mDataPacketSize > mDataPacketEndOffset) { |
| ALOGI("readPacket hits end of stream."); |
| return ERROR_END_OF_STREAM; |
| } |
| |
| if (mDataSource->readAt(mDataPacketCurrentOffset, mDataPacketData, mDataPacketSize) != |
| mDataPacketSize) { |
| return ERROR_END_OF_STREAM; |
| } |
| |
| // update next read position |
| mDataPacketCurrentOffset += mDataPacketSize; |
| AsfPayloadDataInfo *payloads = NULL; |
| int status = mParser->parseDataPacket(mDataPacketData, mDataPacketSize, &payloads); |
| if (status != ASF_PARSER_SUCCESS || payloads == NULL) { |
| ALOGE("Failed to parse data packet. status = %d", status); |
| return ERROR_END_OF_STREAM; |
| } |
| |
| AsfPayloadDataInfo* payload = payloads; |
| while (payload) { |
| Track* track = getTrackByStreamNumber(payload->streamNumber); |
| if (track == NULL || track->skipTrack) { |
| payload = payload->next; |
| continue; |
| } |
| if (payload->mediaObjectLength == payload->payloadSize || |
| payload->offsetIntoMediaObject == 0) { |
| // a comple object or the first payload of fragmented object |
| MediaBuffer *buffer = NULL; |
| status = track->bufferPool->acquire_buffer( |
| payload->mediaObjectLength, &buffer); |
| if (status != OK) { |
| ALOGE("Failed to acquire buffer."); |
| mParser->releasePayloadDataInfo(payloads); |
| return status; |
| } |
| memcpy(buffer->data(), |
| payload->payloadData, |
| payload->payloadSize); |
| |
| buffer->set_range(0, payload->mediaObjectLength); |
| // kKeyTime is in microsecond unit (usecs) |
| // presentationTime is in mililsecond unit (ms) |
| buffer->meta_data()->setInt64(kKeyTime, payload->presentationTime * 1000); |
| |
| if (payload->keyframe) { |
| buffer->meta_data()->setInt32(kKeyIsSyncFrame, 1); |
| } |
| |
| if (payload->mediaObjectLength == payload->payloadSize) { |
| Mutex::Autolock lockTrack(track->lock); |
| // a complete object |
| track->bufferQueue.push(buffer); |
| } else { |
| // the first payload of a fragmented object |
| track->bufferActive = buffer; |
| if (track->encrypted) { |
| Mutex::Autolock lockTrack(track->lock); |
| MediaBuffer* copy = NULL; |
| track->bufferPool->acquire_buffer(payload->payloadSize, ©); |
| copy->meta_data()->setInt64(kKeyTime, payload->presentationTime * 1000); |
| memcpy(copy->data(), payload->payloadData, payload->payloadSize); |
| copy->set_range(0, payload->payloadSize); |
| track->bufferQueue.push(copy); |
| } |
| } |
| } else { |
| if (track->bufferActive == NULL) { |
| ALOGE("Receiving corrupt or discontinuous data packet."); |
| payload = payload->next; |
| continue; |
| } |
| // TODO: check object number and buffer size!!!!!!!!!!!!!! |
| // the last payload or the middle payload of a fragmented object |
| memcpy( |
| (uint8_t*)track->bufferActive->data() + payload->offsetIntoMediaObject, |
| payload->payloadData, |
| payload->payloadSize); |
| |
| if (payload->offsetIntoMediaObject + payload->payloadSize == |
| payload->mediaObjectLength) { |
| // the last payload of a fragmented object |
| // for encrypted content, push a cloned media buffer to vector instead. |
| if (!track->encrypted) |
| { |
| Mutex::Autolock lockTrack(track->lock); |
| track->bufferQueue.push(track->bufferActive); |
| track->bufferActive = NULL; |
| } else { |
| Mutex::Autolock lockTrack(track->lock); |
| track->bufferActive->set_range(payload->offsetIntoMediaObject, payload->payloadSize); |
| track->bufferQueue.push(track->bufferActive); |
| track->bufferActive = NULL; |
| } |
| } else { |
| // middle payload of a fragmented object |
| if (track->encrypted) { |
| Mutex::Autolock lockTrack(track->lock); |
| MediaBuffer* copy = NULL; |
| int64_t keytime; |
| track->bufferPool->acquire_buffer(payload->payloadSize, ©); |
| track->bufferActive->meta_data()->findInt64(kKeyTime, &keytime); |
| copy->meta_data()->setInt64(kKeyTime, keytime); |
| memcpy(copy->data(), payload->payloadData, payload->payloadSize); |
| copy->set_range(0, payload->payloadSize); |
| track->bufferQueue.push(copy); |
| } |
| } |
| } |
| payload = payload->next; |
| }; |
| |
| mParser->releasePayloadDataInfo(payloads); |
| return OK; |
| } |
| |
| AsfExtractor::Track* AsfExtractor::getTrackByTrackIndex(int index) { |
| Track *track = mFirstTrack; |
| while (index > 0) { |
| if (track == NULL) { |
| return NULL; |
| } |
| |
| track = track->next; |
| --index; |
| } |
| return track; |
| } |
| |
| AsfExtractor::Track* AsfExtractor::getTrackByStreamNumber(int stream) { |
| Track *track = mFirstTrack; |
| while (track != NULL) { |
| if (track->streamNumber == stream) { |
| return track; |
| } |
| track = track->next; |
| } |
| return NULL; |
| } |
| |
| bool SniffAsf( |
| const sp<DataSource> &source, |
| String8 *mimeType, |
| float *confidence, |
| sp<AMessage> *) { |
| uint8_t guid[16]; |
| if (source->readAt(0, guid, 16) != 16) { |
| return false; |
| } |
| if (!AsfStreamParser::isHeaderObject(guid)) { |
| return false; |
| } |
| |
| *mimeType = MEDIA_MIMETYPE_CONTAINER_ASF; |
| *confidence = 0.4f; |
| return true; |
| } |
| |
| } // namespace android |
| |