blob: 82712efd5ef308a1a9a0974d0a76e9fdc8b29d88 [file] [log] [blame]
/*
* Copyright (C) 2012 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 "FragmentedMP4Extractor"
#include <utils/Log.h>
#include "include/FragmentedMP4Extractor.h"
#include "include/SampleTable.h"
#include "include/ESDS.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <cutils/properties.h> // for property_get
#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/DataSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
#include <media/stagefright/Utils.h>
#include <utils/String8.h>
namespace android {
class FragmentedMPEG4Source : public MediaSource {
public:
// Caller retains ownership of the Parser
FragmentedMPEG4Source(bool audio,
const sp<MetaData> &format,
const sp<FragmentedMP4Parser> &parser,
const sp<FragmentedMP4Extractor> &extractor);
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);
protected:
virtual ~FragmentedMPEG4Source();
private:
Mutex mLock;
sp<MetaData> mFormat;
sp<FragmentedMP4Parser> mParser;
sp<FragmentedMP4Extractor> mExtractor;
bool mIsAudioTrack;
uint32_t mCurrentSampleIndex;
bool mIsAVC;
size_t mNALLengthSize;
bool mStarted;
MediaBufferGroup *mGroup;
bool mWantsNALFragments;
uint8_t *mSrcBuffer;
FragmentedMPEG4Source(const FragmentedMPEG4Source &);
FragmentedMPEG4Source &operator=(const FragmentedMPEG4Source &);
};
FragmentedMP4Extractor::FragmentedMP4Extractor(const sp<DataSource> &source)
: mLooper(new ALooper),
mParser(new FragmentedMP4Parser()),
mDataSource(source),
mInitCheck(NO_INIT),
mFileMetaData(new MetaData) {
ALOGV("FragmentedMP4Extractor");
mLooper->registerHandler(mParser);
mLooper->start(false /* runOnCallingThread */);
mParser->start(mDataSource);
bool hasVideo = mParser->getFormat(false /* audio */, true /* synchronous */) != NULL;
bool hasAudio = mParser->getFormat(true /* audio */, true /* synchronous */) != NULL;
ALOGV("number of tracks: %d", countTracks());
if (hasVideo) {
mFileMetaData->setCString(
kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MPEG4);
} else if (hasAudio) {
mFileMetaData->setCString(kKeyMIMEType, "audio/mp4");
} else {
ALOGE("no audio and no video, no idea what file type this is");
}
// tracks are numbered such that video track is first, audio track is second
if (hasAudio && hasVideo) {
mTrackCount = 2;
mAudioTrackIndex = 1;
} else if (hasAudio) {
mTrackCount = 1;
mAudioTrackIndex = 0;
} else if (hasVideo) {
mTrackCount = 1;
mAudioTrackIndex = -1;
} else {
mTrackCount = 0;
mAudioTrackIndex = -1;
}
}
FragmentedMP4Extractor::~FragmentedMP4Extractor() {
ALOGV("~FragmentedMP4Extractor");
mLooper->stop();
}
uint32_t FragmentedMP4Extractor::flags() const {
return CAN_PAUSE |
(mParser->isSeekable() ? (CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD | CAN_SEEK) : 0);
}
sp<MetaData> FragmentedMP4Extractor::getMetaData() {
return mFileMetaData;
}
size_t FragmentedMP4Extractor::countTracks() {
return mTrackCount;
}
sp<MetaData> FragmentedMP4Extractor::getTrackMetaData(
size_t index, uint32_t flags) {
if (index >= countTracks()) {
return NULL;
}
sp<AMessage> msg = mParser->getFormat(index == mAudioTrackIndex, true /* synchronous */);
if (msg == NULL) {
ALOGV("got null format for track %d", index);
return NULL;
}
sp<MetaData> meta = new MetaData();
convertMessageToMetaData(msg, meta);
return meta;
}
static void MakeFourCCString(uint32_t x, char *s) {
s[0] = x >> 24;
s[1] = (x >> 16) & 0xff;
s[2] = (x >> 8) & 0xff;
s[3] = x & 0xff;
s[4] = '\0';
}
sp<MediaSource> FragmentedMP4Extractor::getTrack(size_t index) {
if (index >= countTracks()) {
return NULL;
}
return new FragmentedMPEG4Source(index == mAudioTrackIndex, getTrackMetaData(index, 0), mParser, this);
}
////////////////////////////////////////////////////////////////////////////////
FragmentedMPEG4Source::FragmentedMPEG4Source(
bool audio,
const sp<MetaData> &format,
const sp<FragmentedMP4Parser> &parser,
const sp<FragmentedMP4Extractor> &extractor)
: mFormat(format),
mParser(parser),
mExtractor(extractor),
mIsAudioTrack(audio),
mStarted(false),
mGroup(NULL),
mWantsNALFragments(false),
mSrcBuffer(NULL) {
}
FragmentedMPEG4Source::~FragmentedMPEG4Source() {
if (mStarted) {
stop();
}
}
status_t FragmentedMPEG4Source::start(MetaData *params) {
Mutex::Autolock autoLock(mLock);
CHECK(!mStarted);
int32_t val;
if (params && params->findInt32(kKeyWantsNALFragments, &val)
&& val != 0) {
mWantsNALFragments = true;
} else {
mWantsNALFragments = false;
}
ALOGV("caller wants NAL fragments: %s", mWantsNALFragments ? "yes" : "no");
mGroup = new MediaBufferGroup;
int32_t max_size = 65536;
// XXX CHECK(mFormat->findInt32(kKeyMaxInputSize, &max_size));
mGroup->add_buffer(new MediaBuffer(max_size));
mSrcBuffer = new uint8_t[max_size];
mStarted = true;
return OK;
}
status_t FragmentedMPEG4Source::stop() {
Mutex::Autolock autoLock(mLock);
CHECK(mStarted);
delete[] mSrcBuffer;
mSrcBuffer = NULL;
delete mGroup;
mGroup = NULL;
mStarted = false;
mCurrentSampleIndex = 0;
return OK;
}
sp<MetaData> FragmentedMPEG4Source::getFormat() {
Mutex::Autolock autoLock(mLock);
return mFormat;
}
status_t FragmentedMPEG4Source::read(
MediaBuffer **out, const ReadOptions *options) {
int64_t seekTimeUs;
ReadOptions::SeekMode mode;
if (options && options->getSeekTo(&seekTimeUs, &mode)) {
mParser->seekTo(mIsAudioTrack, seekTimeUs);
}
MediaBuffer *buffer = NULL;
mGroup->acquire_buffer(&buffer);
sp<ABuffer> parseBuffer;
status_t ret = mParser->dequeueAccessUnit(mIsAudioTrack, &parseBuffer, true /* synchronous */);
if (ret != OK) {
buffer->release();
ALOGV("returning %d", ret);
return ret;
}
sp<AMessage> meta = parseBuffer->meta();
int64_t timeUs;
CHECK(meta->findInt64("timeUs", &timeUs));
buffer->meta_data()->setInt64(kKeyTime, timeUs);
buffer->set_range(0, parseBuffer->size());
memcpy(buffer->data(), parseBuffer->data(), parseBuffer->size());
*out = buffer;
return OK;
}
static bool isCompatibleBrand(uint32_t fourcc) {
static const uint32_t kCompatibleBrands[] = {
FOURCC('i', 's', 'o', 'm'),
FOURCC('i', 's', 'o', '2'),
FOURCC('a', 'v', 'c', '1'),
FOURCC('3', 'g', 'p', '4'),
FOURCC('m', 'p', '4', '1'),
FOURCC('m', 'p', '4', '2'),
// Won't promise that the following file types can be played.
// Just give these file types a chance.
FOURCC('q', 't', ' ', ' '), // Apple's QuickTime
FOURCC('M', 'S', 'N', 'V'), // Sony's PSP
FOURCC('3', 'g', '2', 'a'), // 3GPP2
FOURCC('3', 'g', '2', 'b'),
};
for (size_t i = 0;
i < sizeof(kCompatibleBrands) / sizeof(kCompatibleBrands[0]);
++i) {
if (kCompatibleBrands[i] == fourcc) {
return true;
}
}
return false;
}
// Attempt to actually parse the 'ftyp' atom and determine if a suitable
// compatible brand is present.
// Also try to identify where this file's metadata ends
// (end of the 'moov' atom) and report it to the caller as part of
// the metadata.
static bool Sniff(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *meta) {
// We scan up to 128k bytes to identify this file as an MP4.
static const off64_t kMaxScanOffset = 128ll * 1024ll;
off64_t offset = 0ll;
bool foundGoodFileType = false;
bool isFragmented = false;
off64_t moovAtomEndOffset = -1ll;
bool done = false;
while (!done && offset < kMaxScanOffset) {
uint32_t hdr[2];
if (source->readAt(offset, hdr, 8) < 8) {
return false;
}
uint64_t chunkSize = ntohl(hdr[0]);
uint32_t chunkType = ntohl(hdr[1]);
off64_t chunkDataOffset = offset + 8;
if (chunkSize == 1) {
if (source->readAt(offset + 8, &chunkSize, 8) < 8) {
return false;
}
chunkSize = ntoh64(chunkSize);
chunkDataOffset += 8;
if (chunkSize < 16) {
// The smallest valid chunk is 16 bytes long in this case.
return false;
}
} else if (chunkSize < 8) {
// The smallest valid chunk is 8 bytes long.
return false;
}
off64_t chunkDataSize = offset + chunkSize - chunkDataOffset;
char chunkstring[5];
MakeFourCCString(chunkType, chunkstring);
ALOGV("saw chunk type %s, size %lld @ %lld", chunkstring, chunkSize, offset);
switch (chunkType) {
case FOURCC('f', 't', 'y', 'p'):
{
if (chunkDataSize < 8) {
return false;
}
uint32_t numCompatibleBrands = (chunkDataSize - 8) / 4;
for (size_t i = 0; i < numCompatibleBrands + 2; ++i) {
if (i == 1) {
// Skip this index, it refers to the minorVersion,
// not a brand.
continue;
}
uint32_t brand;
if (source->readAt(
chunkDataOffset + 4 * i, &brand, 4) < 4) {
return false;
}
brand = ntohl(brand);
char brandstring[5];
MakeFourCCString(brand, brandstring);
ALOGV("Brand: %s", brandstring);
if (isCompatibleBrand(brand)) {
foundGoodFileType = true;
break;
}
}
if (!foundGoodFileType) {
return false;
}
break;
}
case FOURCC('m', 'o', 'o', 'v'):
{
moovAtomEndOffset = offset + chunkSize;
break;
}
case FOURCC('m', 'o', 'o', 'f'):
{
// this is kind of broken, since we might not actually find a
// moof box in the first 128k.
isFragmented = true;
done = true;
break;
}
default:
break;
}
offset += chunkSize;
}
if (!foundGoodFileType || !isFragmented) {
return false;
}
*mimeType = MEDIA_MIMETYPE_CONTAINER_MPEG4;
*confidence = 0.5f; // slightly more than MPEG4Extractor
if (moovAtomEndOffset >= 0) {
*meta = new AMessage;
(*meta)->setInt64("meta-data-size", moovAtomEndOffset);
(*meta)->setInt32("fragmented", 1); // tell MediaExtractor what to instantiate
ALOGV("found metadata size: %lld", moovAtomEndOffset);
}
return true;
}
// used by DataSource::RegisterDefaultSniffers
bool SniffFragmentedMP4(
const sp<DataSource> &source, String8 *mimeType, float *confidence,
sp<AMessage> *meta) {
ALOGV("SniffFragmentedMP4");
char prop[PROPERTY_VALUE_MAX];
if (property_get("media.stagefright.use-fragmp4", prop, NULL)
&& (!strcmp(prop, "1") || !strcasecmp(prop, "true"))) {
return Sniff(source, mimeType, confidence, meta);
}
return false;
}
} // namespace android