blob: 0fa0e153d2e4e3393b1230fd2a548501e0e89c9e [file] [log] [blame]
/*************************************************************************************
* 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,(uint64_t) 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);
copy->meta_data()->setInt64(kKeyTime,(uint64_t) 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, &copy);
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