am df17b084
: Merge "Support for media extraction from .mkv/.mka Matroska files in stagefright." into kraken
This commit is contained in:
@ -22,6 +22,7 @@ namespace android {
|
||||
|
||||
extern const char *MEDIA_MIMETYPE_IMAGE_JPEG;
|
||||
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_VPX;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_AVC;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_MPEG4;
|
||||
extern const char *MEDIA_MIMETYPE_VIDEO_H263;
|
||||
@ -38,6 +39,7 @@ extern const char *MEDIA_MIMETYPE_AUDIO_RAW;
|
||||
extern const char *MEDIA_MIMETYPE_CONTAINER_MPEG4;
|
||||
extern const char *MEDIA_MIMETYPE_CONTAINER_WAV;
|
||||
extern const char *MEDIA_MIMETYPE_CONTAINER_OGG;
|
||||
extern const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA;
|
||||
|
||||
} // namespace android
|
||||
|
||||
|
@ -46,8 +46,9 @@ public class MediaFile {
|
||||
public static final int FILE_TYPE_WMA = 6;
|
||||
public static final int FILE_TYPE_OGG = 7;
|
||||
public static final int FILE_TYPE_AAC = 8;
|
||||
public static final int FILE_TYPE_MKA = 9;
|
||||
private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3;
|
||||
private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_AAC;
|
||||
private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_MKA;
|
||||
|
||||
// MIDI file types
|
||||
public static final int FILE_TYPE_MID = 11;
|
||||
@ -63,8 +64,9 @@ public class MediaFile {
|
||||
public static final int FILE_TYPE_3GPP2 = 24;
|
||||
public static final int FILE_TYPE_WMV = 25;
|
||||
public static final int FILE_TYPE_ASF = 26;
|
||||
public static final int FILE_TYPE_MKV = 27;
|
||||
private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4;
|
||||
private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_ASF;
|
||||
private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_MKV;
|
||||
|
||||
// Image file types
|
||||
public static final int FILE_TYPE_JPEG = 31;
|
||||
@ -134,6 +136,7 @@ public class MediaFile {
|
||||
addFileType("OGG", FILE_TYPE_OGG, "application/ogg");
|
||||
addFileType("OGA", FILE_TYPE_OGG, "application/ogg");
|
||||
addFileType("AAC", FILE_TYPE_AAC, "audio/aac");
|
||||
addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska");
|
||||
|
||||
addFileType("MID", FILE_TYPE_MID, "audio/midi");
|
||||
addFileType("MIDI", FILE_TYPE_MID, "audio/midi");
|
||||
@ -151,6 +154,8 @@ public class MediaFile {
|
||||
addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp");
|
||||
addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2");
|
||||
addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2");
|
||||
addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska");
|
||||
addFileType("WEBM", FILE_TYPE_MKV, "video/x-matroska");
|
||||
if (isWMVEnabled()) {
|
||||
addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv");
|
||||
addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf");
|
||||
|
@ -75,7 +75,8 @@ LOCAL_STATIC_LIBRARIES := \
|
||||
libstagefright_avcdec \
|
||||
libstagefright_m4vh263dec \
|
||||
libstagefright_mp3dec \
|
||||
libstagefright_vorbisdec
|
||||
libstagefright_vorbisdec \
|
||||
libstagefright_matroska \
|
||||
|
||||
LOCAL_SHARED_LIBRARIES += \
|
||||
libstagefright_amrnb_common \
|
||||
|
@ -20,6 +20,8 @@
|
||||
#include "include/WAVExtractor.h"
|
||||
#include "include/OggExtractor.h"
|
||||
|
||||
#include "matroska/MatroskaExtractor.h"
|
||||
|
||||
#include <media/stagefright/CachingDataSource.h>
|
||||
#include <media/stagefright/DataSource.h>
|
||||
#include <media/stagefright/FileSource.h>
|
||||
@ -94,6 +96,7 @@ void DataSource::RegisterDefaultSniffers() {
|
||||
RegisterSniffer(SniffAMR);
|
||||
RegisterSniffer(SniffWAV);
|
||||
RegisterSniffer(SniffOgg);
|
||||
RegisterSniffer(SniffMatroska);
|
||||
}
|
||||
|
||||
// static
|
||||
|
@ -20,6 +20,7 @@ namespace android {
|
||||
|
||||
const char *MEDIA_MIMETYPE_IMAGE_JPEG = "image/jpeg";
|
||||
|
||||
const char *MEDIA_MIMETYPE_VIDEO_VPX = "video/x-vnd.on2.vp8";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_MPEG4 = "video/mp4v-es";
|
||||
const char *MEDIA_MIMETYPE_VIDEO_H263 = "video/3gpp";
|
||||
@ -36,5 +37,6 @@ const char *MEDIA_MIMETYPE_AUDIO_RAW = "audio/raw";
|
||||
const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mpeg4";
|
||||
const char *MEDIA_MIMETYPE_CONTAINER_WAV = "audio/wav";
|
||||
const char *MEDIA_MIMETYPE_CONTAINER_OGG = "application/ogg";
|
||||
const char *MEDIA_MIMETYPE_CONTAINER_MATROSKA = "video/x-matroska";
|
||||
|
||||
} // namespace android
|
||||
|
@ -24,6 +24,8 @@
|
||||
#include "include/WAVExtractor.h"
|
||||
#include "include/OggExtractor.h"
|
||||
|
||||
#include "matroska/MatroskaExtractor.h"
|
||||
|
||||
#include <media/stagefright/DataSource.h>
|
||||
#include <media/stagefright/MediaDefs.h>
|
||||
#include <media/stagefright/MediaExtractor.h>
|
||||
@ -69,6 +71,8 @@ sp<MediaExtractor> MediaExtractor::Create(
|
||||
return new WAVExtractor(source);
|
||||
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
|
||||
return new OggExtractor(source);
|
||||
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
|
||||
return new MatroskaExtractor(source);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
@ -42,7 +42,8 @@ static bool FileHasAcceptableExtension(const char *extension) {
|
||||
static const char *kValidExtensions[] = {
|
||||
".mp3", ".mp4", ".m4a", ".3gp", ".3gpp", ".3g2", ".3gpp2",
|
||||
".mpeg", ".ogg", ".mid", ".smf", ".imy", ".wma", ".aac",
|
||||
".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota"
|
||||
".wav", ".amr", ".midi", ".xmf", ".rtttl", ".rtx", ".ota",
|
||||
".mkv", ".mka", ".webm"
|
||||
};
|
||||
static const size_t kNumValidExtensions =
|
||||
sizeof(kValidExtensions) / sizeof(kValidExtensions[0]);
|
||||
|
@ -378,6 +378,24 @@ void StagefrightMetadataRetriever::parseMetaData() {
|
||||
// The duration value is a string representing the duration in ms.
|
||||
sprintf(tmp, "%lld", (maxDurationUs + 500) / 1000);
|
||||
mMetaData.add(METADATA_KEY_DURATION, String8(tmp));
|
||||
|
||||
if (numTracks == 1) {
|
||||
const char *fileMIME;
|
||||
CHECK(meta->findCString(kKeyMIMEType, &fileMIME));
|
||||
|
||||
if (!strcasecmp(fileMIME, "video/x-matroska")) {
|
||||
sp<MetaData> trackMeta = mExtractor->getTrackMetaData(0);
|
||||
const char *trackMIME;
|
||||
CHECK(trackMeta->findCString(kKeyMIMEType, &trackMIME));
|
||||
|
||||
if (!strncasecmp("audio/", trackMIME, 6)) {
|
||||
// The matroska file only contains a single audio track,
|
||||
// rewrite its mime type.
|
||||
mMetaData.add(
|
||||
METADATA_KEY_MIMETYPE, String8("audio/x-matroska"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
16
media/libstagefright/matroska/Android.mk
Normal file
16
media/libstagefright/matroska/Android.mk
Normal file
@ -0,0 +1,16 @@
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_SRC_FILES:= \
|
||||
MatroskaExtractor.cpp \
|
||||
mkvparser.cpp \
|
||||
|
||||
LOCAL_C_INCLUDES:= \
|
||||
$(JNI_H_INCLUDE) \
|
||||
$(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
|
||||
|
||||
LOCAL_CFLAGS += -Wno-multichar
|
||||
|
||||
LOCAL_MODULE:= libstagefright_matroska
|
||||
|
||||
include $(BUILD_STATIC_LIBRARY)
|
504
media/libstagefright/matroska/MatroskaExtractor.cpp
Normal file
504
media/libstagefright/matroska/MatroskaExtractor.cpp
Normal file
@ -0,0 +1,504 @@
|
||||
/*
|
||||
* 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 "MatroskaExtractor"
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include "MatroskaExtractor.h"
|
||||
|
||||
#include "mkvparser.hpp"
|
||||
|
||||
#include <media/stagefright/DataSource.h>
|
||||
#include <media/stagefright/MediaBuffer.h>
|
||||
#include <media/stagefright/MediaDebug.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>
|
||||
|
||||
namespace android {
|
||||
|
||||
struct DataSourceReader : public mkvparser::IMkvReader {
|
||||
DataSourceReader(const sp<DataSource> &source)
|
||||
: mSource(source) {
|
||||
}
|
||||
|
||||
virtual int Read(long long position, long length, unsigned char* buffer) {
|
||||
CHECK(position >= 0);
|
||||
CHECK(length >= 0);
|
||||
|
||||
if (length == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t n = mSource->readAt(position, buffer, length);
|
||||
|
||||
if (n <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
virtual int Length(long long* total, long long* available) {
|
||||
off_t size;
|
||||
if (mSource->getSize(&size) != OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (total) {
|
||||
*total = size;
|
||||
}
|
||||
|
||||
if (available) {
|
||||
*available = size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private:
|
||||
sp<DataSource> mSource;
|
||||
|
||||
DataSourceReader(const DataSourceReader &);
|
||||
DataSourceReader &operator=(const DataSourceReader &);
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <ctype.h>
|
||||
static void hexdump(const void *_data, size_t size) {
|
||||
const uint8_t *data = (const uint8_t *)_data;
|
||||
size_t offset = 0;
|
||||
while (offset < size) {
|
||||
printf("0x%04x ", offset);
|
||||
|
||||
size_t n = size - offset;
|
||||
if (n > 16) {
|
||||
n = 16;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < 16; ++i) {
|
||||
if (i == 8) {
|
||||
printf(" ");
|
||||
}
|
||||
|
||||
if (offset + i < size) {
|
||||
printf("%02x ", data[offset + i]);
|
||||
} else {
|
||||
printf(" ");
|
||||
}
|
||||
}
|
||||
|
||||
printf(" ");
|
||||
|
||||
for (size_t i = 0; i < n; ++i) {
|
||||
if (isprint(data[offset + i])) {
|
||||
printf("%c", data[offset + i]);
|
||||
} else {
|
||||
printf(".");
|
||||
}
|
||||
}
|
||||
|
||||
printf("\n");
|
||||
|
||||
offset += 16;
|
||||
}
|
||||
}
|
||||
|
||||
struct MatroskaSource : public MediaSource {
|
||||
MatroskaSource(
|
||||
const sp<MatroskaExtractor> &extractor, size_t index);
|
||||
|
||||
virtual status_t start(MetaData *params);
|
||||
virtual status_t stop();
|
||||
|
||||
virtual sp<MetaData> getFormat();
|
||||
|
||||
virtual status_t read(
|
||||
MediaBuffer **buffer, const ReadOptions *options);
|
||||
|
||||
private:
|
||||
enum Type {
|
||||
AVC,
|
||||
AAC,
|
||||
OTHER
|
||||
};
|
||||
|
||||
sp<MatroskaExtractor> mExtractor;
|
||||
size_t mTrackIndex;
|
||||
unsigned long mTrackNum;
|
||||
Type mType;
|
||||
mkvparser::Cluster *mCluster;
|
||||
const mkvparser::BlockEntry *mBlockEntry;
|
||||
|
||||
status_t advance();
|
||||
|
||||
MatroskaSource(const MatroskaSource &);
|
||||
MatroskaSource &operator=(const MatroskaSource &);
|
||||
};
|
||||
|
||||
MatroskaSource::MatroskaSource(
|
||||
const sp<MatroskaExtractor> &extractor, size_t index)
|
||||
: mExtractor(extractor),
|
||||
mTrackIndex(index),
|
||||
mType(OTHER),
|
||||
mCluster(NULL),
|
||||
mBlockEntry(NULL) {
|
||||
mTrackNum = mExtractor->mTracks.itemAt(index).mTrackNum;
|
||||
|
||||
const char *mime;
|
||||
CHECK(mExtractor->mTracks.itemAt(index).mMeta->
|
||||
findCString(kKeyMIMEType, &mime));
|
||||
|
||||
if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) {
|
||||
mType = AVC;
|
||||
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)) {
|
||||
mType = AAC;
|
||||
}
|
||||
}
|
||||
|
||||
status_t MatroskaSource::start(MetaData *params) {
|
||||
mCluster = NULL;
|
||||
mBlockEntry = NULL;
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t MatroskaSource::stop() {
|
||||
return OK;
|
||||
}
|
||||
|
||||
sp<MetaData> MatroskaSource::getFormat() {
|
||||
return mExtractor->mTracks.itemAt(mTrackIndex).mMeta;
|
||||
}
|
||||
|
||||
status_t MatroskaSource::advance() {
|
||||
for (;;) {
|
||||
if (mBlockEntry == NULL || mBlockEntry->EOS()) {
|
||||
if (mCluster == NULL) {
|
||||
mCluster = mExtractor->mSegment->GetFirst();
|
||||
} else {
|
||||
mCluster = mExtractor->mSegment->GetNext(mCluster);
|
||||
}
|
||||
if (mCluster == NULL || mCluster->EOS()) {
|
||||
return ERROR_END_OF_STREAM;
|
||||
}
|
||||
mBlockEntry = mCluster->GetFirst();
|
||||
}
|
||||
|
||||
if (mBlockEntry->GetBlock()->GetTrackNumber() != mTrackNum) {
|
||||
mBlockEntry = mCluster->GetNext(mBlockEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t MatroskaSource::read(
|
||||
MediaBuffer **out, const ReadOptions *options) {
|
||||
*out = NULL;
|
||||
|
||||
int64_t seekTimeUs;
|
||||
if (options && options->getSeekTo(&seekTimeUs)) {
|
||||
mBlockEntry = NULL;
|
||||
mCluster = mExtractor->mSegment->GetCluster(seekTimeUs * 1000ll);
|
||||
|
||||
status_t err;
|
||||
while ((err = advance()) == OK && !mBlockEntry->GetBlock()->IsKey()) {
|
||||
mBlockEntry = mCluster->GetNext(mBlockEntry);
|
||||
}
|
||||
|
||||
if (err != OK) {
|
||||
return ERROR_END_OF_STREAM;
|
||||
}
|
||||
}
|
||||
|
||||
if (advance() != OK) {
|
||||
return ERROR_END_OF_STREAM;
|
||||
}
|
||||
|
||||
const mkvparser::Block *block = mBlockEntry->GetBlock();
|
||||
size_t size = block->GetSize();
|
||||
long long timeNs = block->GetTime(mCluster);
|
||||
|
||||
MediaBuffer *buffer = new MediaBuffer(size + 2);
|
||||
buffer->meta_data()->setInt64(kKeyTime, (timeNs + 500) / 1000);
|
||||
|
||||
long res = block->Read(
|
||||
mExtractor->mReader, (unsigned char *)buffer->data() + 2);
|
||||
|
||||
if (res != 0) {
|
||||
return ERROR_END_OF_STREAM;
|
||||
}
|
||||
|
||||
buffer->set_range(2, size);
|
||||
|
||||
if (mType == AVC) {
|
||||
CHECK(size >= 2);
|
||||
|
||||
uint8_t *data = (uint8_t *)buffer->data();
|
||||
|
||||
unsigned NALsize = data[2] << 8 | data[3];
|
||||
CHECK_EQ(size, NALsize + 2);
|
||||
|
||||
memcpy(data, "\x00\x00\x00\x01", 4);
|
||||
buffer->set_range(0, size + 2);
|
||||
} else if (mType == AAC) {
|
||||
// There's strange junk at the beginning...
|
||||
|
||||
const uint8_t *data = (const uint8_t *)buffer->data() + 2;
|
||||
size_t offset = 0;
|
||||
while (offset < size && data[offset] != 0x21) {
|
||||
++offset;
|
||||
}
|
||||
buffer->set_range(2 + offset, size - offset);
|
||||
}
|
||||
|
||||
*out = buffer;
|
||||
|
||||
#if 0
|
||||
hexdump((const uint8_t *)buffer->data() + buffer->range_offset(),
|
||||
buffer->range_length());
|
||||
#endif
|
||||
|
||||
mBlockEntry = mCluster->GetNext(mBlockEntry);
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
MatroskaExtractor::MatroskaExtractor(const sp<DataSource> &source)
|
||||
: mDataSource(source),
|
||||
mReader(new DataSourceReader(mDataSource)),
|
||||
mSegment(NULL) {
|
||||
mkvparser::EBMLHeader ebmlHeader;
|
||||
long long pos;
|
||||
if (ebmlHeader.Parse(mReader, pos) < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
long long ret =
|
||||
mkvparser::Segment::CreateInstance(mReader, pos, mSegment);
|
||||
|
||||
if (ret) {
|
||||
CHECK(mSegment == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = mSegment->Load();
|
||||
|
||||
if (ret < 0) {
|
||||
delete mSegment;
|
||||
mSegment = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
addTracks();
|
||||
}
|
||||
|
||||
MatroskaExtractor::~MatroskaExtractor() {
|
||||
delete mSegment;
|
||||
mSegment = NULL;
|
||||
|
||||
delete mReader;
|
||||
mReader = NULL;
|
||||
}
|
||||
|
||||
size_t MatroskaExtractor::countTracks() {
|
||||
return mTracks.size();
|
||||
}
|
||||
|
||||
sp<MediaSource> MatroskaExtractor::getTrack(size_t index) {
|
||||
if (index >= mTracks.size()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new MatroskaSource(this, index);
|
||||
}
|
||||
|
||||
sp<MetaData> MatroskaExtractor::getTrackMetaData(
|
||||
size_t index, uint32_t flags) {
|
||||
if (index >= mTracks.size()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return mTracks.itemAt(index).mMeta;
|
||||
}
|
||||
|
||||
static void addESDSFromAudioSpecificInfo(
|
||||
const sp<MetaData> &meta, const void *asi, size_t asiSize) {
|
||||
static const uint8_t kStaticESDS[] = {
|
||||
0x03, 22,
|
||||
0x00, 0x00, // ES_ID
|
||||
0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag
|
||||
|
||||
0x04, 17,
|
||||
0x40, // Audio ISO/IEC 14496-3
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
|
||||
0x05,
|
||||
// AudioSpecificInfo (with size prefix) follows
|
||||
};
|
||||
|
||||
CHECK(asiSize < 128);
|
||||
size_t esdsSize = sizeof(kStaticESDS) + asiSize + 1;
|
||||
uint8_t *esds = new uint8_t[esdsSize];
|
||||
memcpy(esds, kStaticESDS, sizeof(kStaticESDS));
|
||||
uint8_t *ptr = esds + sizeof(kStaticESDS);
|
||||
*ptr++ = asiSize;
|
||||
memcpy(ptr, asi, asiSize);
|
||||
|
||||
meta->setData(kKeyESDS, 0, esds, esdsSize);
|
||||
|
||||
delete[] esds;
|
||||
esds = NULL;
|
||||
}
|
||||
|
||||
void addVorbisCodecInfo(
|
||||
const sp<MetaData> &meta,
|
||||
const void *_codecPrivate, size_t codecPrivateSize) {
|
||||
// printf("vorbis private data follows:\n");
|
||||
// hexdump(_codecPrivate, codecPrivateSize);
|
||||
|
||||
CHECK(codecPrivateSize >= 3);
|
||||
|
||||
const uint8_t *codecPrivate = (const uint8_t *)_codecPrivate;
|
||||
CHECK(codecPrivate[0] == 0x02);
|
||||
|
||||
size_t len1 = codecPrivate[1];
|
||||
size_t len2 = codecPrivate[2];
|
||||
|
||||
CHECK(codecPrivateSize > 3 + len1 + len2);
|
||||
|
||||
CHECK(codecPrivate[3] == 0x01);
|
||||
meta->setData(kKeyVorbisInfo, 0, &codecPrivate[3], len1);
|
||||
|
||||
CHECK(codecPrivate[len1 + 3] == 0x03);
|
||||
|
||||
CHECK(codecPrivate[len1 + len2 + 3] == 0x05);
|
||||
meta->setData(
|
||||
kKeyVorbisBooks, 0, &codecPrivate[len1 + len2 + 3],
|
||||
codecPrivateSize - len1 - len2 - 3);
|
||||
}
|
||||
|
||||
void MatroskaExtractor::addTracks() {
|
||||
const mkvparser::Tracks *tracks = mSegment->GetTracks();
|
||||
|
||||
for (size_t index = 0; index < tracks->GetTracksCount(); ++index) {
|
||||
const mkvparser::Track *track = tracks->GetTrackByIndex(index);
|
||||
|
||||
const char *const codecID = track->GetCodecId();
|
||||
LOGV("codec id = %s", codecID);
|
||||
LOGV("codec name = %s", track->GetCodecNameAsUTF8());
|
||||
|
||||
size_t codecPrivateSize;
|
||||
const unsigned char *codecPrivate =
|
||||
track->GetCodecPrivate(&codecPrivateSize);
|
||||
|
||||
enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 };
|
||||
|
||||
sp<MetaData> meta = new MetaData;
|
||||
|
||||
switch (track->GetType()) {
|
||||
case VIDEO_TRACK:
|
||||
{
|
||||
const mkvparser::VideoTrack *vtrack =
|
||||
static_cast<const mkvparser::VideoTrack *>(track);
|
||||
|
||||
if (!strcmp("V_MPEG4/ISO/AVC", codecID)) {
|
||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC);
|
||||
meta->setData(kKeyAVCC, 0, codecPrivate, codecPrivateSize);
|
||||
} else if (!strcmp("V_VP8", codecID)) {
|
||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_VPX);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
meta->setInt32(kKeyWidth, vtrack->GetWidth());
|
||||
meta->setInt32(kKeyHeight, vtrack->GetHeight());
|
||||
break;
|
||||
}
|
||||
|
||||
case AUDIO_TRACK:
|
||||
{
|
||||
const mkvparser::AudioTrack *atrack =
|
||||
static_cast<const mkvparser::AudioTrack *>(track);
|
||||
|
||||
if (!strcmp("A_AAC", codecID)) {
|
||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC);
|
||||
CHECK(codecPrivateSize >= 2);
|
||||
|
||||
addESDSFromAudioSpecificInfo(
|
||||
meta, codecPrivate, codecPrivateSize);
|
||||
} else if (!strcmp("A_VORBIS", codecID)) {
|
||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_VORBIS);
|
||||
|
||||
addVorbisCodecInfo(meta, codecPrivate, codecPrivateSize);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
meta->setInt32(kKeySampleRate, atrack->GetSamplingRate());
|
||||
meta->setInt32(kKeyChannelCount, atrack->GetChannels());
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
long long durationNs = mSegment->GetDuration();
|
||||
meta->setInt64(kKeyDuration, (durationNs + 500) / 1000);
|
||||
|
||||
mTracks.push();
|
||||
TrackInfo *trackInfo = &mTracks.editItemAt(mTracks.size() - 1);
|
||||
trackInfo->mTrackNum = track->GetNumber();
|
||||
trackInfo->mMeta = meta;
|
||||
}
|
||||
}
|
||||
|
||||
sp<MetaData> MatroskaExtractor::getMetaData() {
|
||||
sp<MetaData> meta = new MetaData;
|
||||
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_CONTAINER_MATROSKA);
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
bool SniffMatroska(
|
||||
const sp<DataSource> &source, String8 *mimeType, float *confidence) {
|
||||
DataSourceReader reader(source);
|
||||
mkvparser::EBMLHeader ebmlHeader;
|
||||
long long pos;
|
||||
if (ebmlHeader.Parse(&reader, pos) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mimeType->setTo(MEDIA_MIMETYPE_CONTAINER_MATROSKA);
|
||||
*confidence = 0.6;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace android
|
74
media/libstagefright/matroska/MatroskaExtractor.h
Normal file
74
media/libstagefright/matroska/MatroskaExtractor.h
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
* 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 MATROSKA_EXTRACTOR_H_
|
||||
|
||||
#define MATROSKA_EXTRACTOR_H_
|
||||
|
||||
#include <media/stagefright/MediaExtractor.h>
|
||||
#include <utils/Vector.h>
|
||||
|
||||
namespace mkvparser {
|
||||
struct Segment;
|
||||
};
|
||||
|
||||
namespace android {
|
||||
|
||||
class String8;
|
||||
|
||||
struct DataSourceReader;
|
||||
struct MatroskaSource;
|
||||
|
||||
struct MatroskaExtractor : public MediaExtractor {
|
||||
MatroskaExtractor(const sp<DataSource> &source);
|
||||
|
||||
virtual size_t countTracks();
|
||||
|
||||
virtual sp<MediaSource> getTrack(size_t index);
|
||||
|
||||
virtual sp<MetaData> getTrackMetaData(
|
||||
size_t index, uint32_t flags);
|
||||
|
||||
virtual sp<MetaData> getMetaData();
|
||||
|
||||
protected:
|
||||
virtual ~MatroskaExtractor();
|
||||
|
||||
private:
|
||||
friend struct MatroskaSource;
|
||||
|
||||
struct TrackInfo {
|
||||
unsigned long mTrackNum;
|
||||
sp<MetaData> mMeta;
|
||||
};
|
||||
Vector<TrackInfo> mTracks;
|
||||
|
||||
sp<DataSource> mDataSource;
|
||||
DataSourceReader *mReader;
|
||||
mkvparser::Segment *mSegment;
|
||||
|
||||
void addTracks();
|
||||
|
||||
MatroskaExtractor(const MatroskaExtractor &);
|
||||
MatroskaExtractor &operator=(const MatroskaExtractor &);
|
||||
};
|
||||
|
||||
bool SniffMatroska(
|
||||
const sp<DataSource> &source, String8 *mimeType, float *confidence);
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // MATROSKA_EXTRACTOR_H_
|
3103
media/libstagefright/matroska/mkvparser.cpp
Normal file
3103
media/libstagefright/matroska/mkvparser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
428
media/libstagefright/matroska/mkvparser.hpp
Normal file
428
media/libstagefright/matroska/mkvparser.hpp
Normal file
@ -0,0 +1,428 @@
|
||||
#ifndef MKVPARSER_HPP
|
||||
#define MKVPARSER_HPP
|
||||
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
|
||||
namespace mkvparser
|
||||
{
|
||||
|
||||
const int E_FILE_FORMAT_INVALID = -2;
|
||||
const int E_BUFFER_NOT_FULL = -3;
|
||||
|
||||
class IMkvReader
|
||||
{
|
||||
public:
|
||||
virtual int Read(long long position, long length, unsigned char* buffer) = 0;
|
||||
virtual int Length(long long* total, long long* available) = 0;
|
||||
protected:
|
||||
virtual ~IMkvReader();
|
||||
};
|
||||
|
||||
long long GetUIntLength(IMkvReader*, long long, long&);
|
||||
long long ReadUInt(IMkvReader*, long long, long&);
|
||||
long long SyncReadUInt(IMkvReader*, long long pos, long long stop, long&);
|
||||
long long UnserializeUInt(IMkvReader*, long long pos, long long size);
|
||||
float Unserialize4Float(IMkvReader*, long long);
|
||||
double Unserialize8Double(IMkvReader*, long long);
|
||||
short Unserialize2SInt(IMkvReader*, long long);
|
||||
signed char Unserialize1SInt(IMkvReader*, long long);
|
||||
bool Match(IMkvReader*, long long&, unsigned long, long long&);
|
||||
bool Match(IMkvReader*, long long&, unsigned long, char*&);
|
||||
bool Match(IMkvReader*, long long&, unsigned long,unsigned char*&,
|
||||
size_t *optionalSize = NULL);
|
||||
bool Match(IMkvReader*, long long&, unsigned long, double&);
|
||||
bool Match(IMkvReader*, long long&, unsigned long, short&);
|
||||
|
||||
|
||||
struct EBMLHeader
|
||||
{
|
||||
EBMLHeader();
|
||||
~EBMLHeader();
|
||||
long long m_version;
|
||||
long long m_readVersion;
|
||||
long long m_maxIdLength;
|
||||
long long m_maxSizeLength;
|
||||
char* m_docType;
|
||||
long long m_docTypeVersion;
|
||||
long long m_docTypeReadVersion;
|
||||
|
||||
long long Parse(IMkvReader*, long long&);
|
||||
};
|
||||
|
||||
|
||||
class Segment;
|
||||
class Track;
|
||||
class Cluster;
|
||||
|
||||
class Block
|
||||
{
|
||||
Block(const Block&);
|
||||
Block& operator=(const Block&);
|
||||
|
||||
public:
|
||||
const long long m_start;
|
||||
const long long m_size;
|
||||
|
||||
Block(long long start, long long size, IMkvReader*);
|
||||
|
||||
unsigned long GetTrackNumber() const;
|
||||
|
||||
long long GetTimeCode(Cluster*) const; //absolute, but not scaled
|
||||
long long GetTime(Cluster*) const; //absolute, and scaled (nanosecond units)
|
||||
bool IsKey() const;
|
||||
void SetKey(bool);
|
||||
|
||||
long GetSize() const;
|
||||
long Read(IMkvReader*, unsigned char*) const;
|
||||
|
||||
private:
|
||||
long long m_track; //Track::Number()
|
||||
short m_timecode; //relative to cluster
|
||||
unsigned char m_flags;
|
||||
long long m_frameOff;
|
||||
long m_frameSize;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class BlockEntry
|
||||
{
|
||||
BlockEntry(const BlockEntry&);
|
||||
BlockEntry& operator=(const BlockEntry&);
|
||||
|
||||
public:
|
||||
virtual ~BlockEntry();
|
||||
virtual bool EOS() const = 0;
|
||||
virtual Cluster* GetCluster() const = 0;
|
||||
virtual size_t GetIndex() const = 0;
|
||||
virtual const Block* GetBlock() const = 0;
|
||||
virtual bool IsBFrame() const = 0;
|
||||
|
||||
protected:
|
||||
BlockEntry();
|
||||
|
||||
};
|
||||
|
||||
|
||||
class SimpleBlock : public BlockEntry
|
||||
{
|
||||
SimpleBlock(const SimpleBlock&);
|
||||
SimpleBlock& operator=(const SimpleBlock&);
|
||||
|
||||
public:
|
||||
SimpleBlock(Cluster*, size_t, long long start, long long size);
|
||||
|
||||
bool EOS() const;
|
||||
Cluster* GetCluster() const;
|
||||
size_t GetIndex() const;
|
||||
const Block* GetBlock() const;
|
||||
bool IsBFrame() const;
|
||||
|
||||
protected:
|
||||
Cluster* const m_pCluster;
|
||||
const size_t m_index;
|
||||
Block m_block;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class BlockGroup : public BlockEntry
|
||||
{
|
||||
BlockGroup(const BlockGroup&);
|
||||
BlockGroup& operator=(const BlockGroup&);
|
||||
|
||||
public:
|
||||
BlockGroup(Cluster*, size_t, long long, long long);
|
||||
~BlockGroup();
|
||||
|
||||
bool EOS() const;
|
||||
Cluster* GetCluster() const;
|
||||
size_t GetIndex() const;
|
||||
const Block* GetBlock() const;
|
||||
bool IsBFrame() const;
|
||||
|
||||
short GetPrevTimeCode() const; //relative to block's time
|
||||
short GetNextTimeCode() const; //as above
|
||||
|
||||
protected:
|
||||
Cluster* const m_pCluster;
|
||||
const size_t m_index;
|
||||
|
||||
private:
|
||||
BlockGroup(Cluster*, size_t, unsigned long);
|
||||
void ParseBlock(long long start, long long size);
|
||||
|
||||
short m_prevTimeCode;
|
||||
short m_nextTimeCode;
|
||||
|
||||
//TODO: the Matroska spec says you can have multiple blocks within the
|
||||
//same block group, with blocks ranked by priority (the flag bits).
|
||||
//For now we just cache a single block.
|
||||
#if 0
|
||||
typedef std::deque<Block*> blocks_t;
|
||||
blocks_t m_blocks; //In practice should contain only a single element.
|
||||
#else
|
||||
Block* m_pBlock;
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
|
||||
class Track
|
||||
{
|
||||
Track(const Track&);
|
||||
Track& operator=(const Track&);
|
||||
|
||||
public:
|
||||
Segment* const m_pSegment;
|
||||
virtual ~Track();
|
||||
|
||||
long long GetType() const;
|
||||
unsigned long GetNumber() const;
|
||||
const char* GetNameAsUTF8() const;
|
||||
const char* GetCodecNameAsUTF8() const;
|
||||
const char* GetCodecId() const;
|
||||
const unsigned char* GetCodecPrivate(
|
||||
size_t *optionalSize = NULL) const;
|
||||
|
||||
const BlockEntry* GetEOS() const;
|
||||
|
||||
struct Settings
|
||||
{
|
||||
long long start;
|
||||
long long size;
|
||||
};
|
||||
|
||||
struct Info
|
||||
{
|
||||
long long type;
|
||||
long long number;
|
||||
long long uid;
|
||||
char* nameAsUTF8;
|
||||
char* codecId;
|
||||
unsigned char* codecPrivate;
|
||||
size_t codecPrivateSize;
|
||||
char* codecNameAsUTF8;
|
||||
Settings settings;
|
||||
Info();
|
||||
void Clear();
|
||||
};
|
||||
|
||||
long GetFirst(const BlockEntry*&) const;
|
||||
long GetNext(const BlockEntry* pCurr, const BlockEntry*& pNext) const;
|
||||
virtual bool VetEntry(const BlockEntry*) const = 0;
|
||||
|
||||
protected:
|
||||
Track(Segment*, const Info&);
|
||||
const Info m_info;
|
||||
|
||||
class EOSBlock : public BlockEntry
|
||||
{
|
||||
public:
|
||||
EOSBlock();
|
||||
|
||||
bool EOS() const;
|
||||
Cluster* GetCluster() const;
|
||||
size_t GetIndex() const;
|
||||
const Block* GetBlock() const;
|
||||
bool IsBFrame() const;
|
||||
};
|
||||
|
||||
EOSBlock m_eos;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class VideoTrack : public Track
|
||||
{
|
||||
VideoTrack(const VideoTrack&);
|
||||
VideoTrack& operator=(const VideoTrack&);
|
||||
|
||||
public:
|
||||
VideoTrack(Segment*, const Info&);
|
||||
long long GetWidth() const;
|
||||
long long GetHeight() const;
|
||||
double GetFrameRate() const;
|
||||
|
||||
bool VetEntry(const BlockEntry*) const;
|
||||
|
||||
private:
|
||||
long long m_width;
|
||||
long long m_height;
|
||||
double m_rate;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class AudioTrack : public Track
|
||||
{
|
||||
AudioTrack(const AudioTrack&);
|
||||
AudioTrack& operator=(const AudioTrack&);
|
||||
|
||||
public:
|
||||
AudioTrack(Segment*, const Info&);
|
||||
double GetSamplingRate() const;
|
||||
long long GetChannels() const;
|
||||
long long GetBitDepth() const;
|
||||
bool VetEntry(const BlockEntry*) const;
|
||||
|
||||
private:
|
||||
double m_rate;
|
||||
long long m_channels;
|
||||
long long m_bitDepth;
|
||||
};
|
||||
|
||||
|
||||
class Tracks
|
||||
{
|
||||
Tracks(const Tracks&);
|
||||
Tracks& operator=(const Tracks&);
|
||||
|
||||
public:
|
||||
Segment* const m_pSegment;
|
||||
const long long m_start;
|
||||
const long long m_size;
|
||||
|
||||
Tracks(Segment*, long long start, long long size);
|
||||
virtual ~Tracks();
|
||||
|
||||
Track* GetTrackByNumber(unsigned long tn) const;
|
||||
Track* GetTrackByIndex(unsigned long idx) const;
|
||||
|
||||
private:
|
||||
Track** m_trackEntries;
|
||||
Track** m_trackEntriesEnd;
|
||||
|
||||
void ParseTrackEntry(long long, long long, Track*&);
|
||||
|
||||
public:
|
||||
unsigned long GetTracksCount() const;
|
||||
};
|
||||
|
||||
|
||||
class SegmentInfo
|
||||
{
|
||||
SegmentInfo(const SegmentInfo&);
|
||||
SegmentInfo& operator=(const SegmentInfo&);
|
||||
|
||||
public:
|
||||
Segment* const m_pSegment;
|
||||
const long long m_start;
|
||||
const long long m_size;
|
||||
|
||||
SegmentInfo(Segment*, long long start, long long size);
|
||||
~SegmentInfo();
|
||||
long long GetTimeCodeScale() const;
|
||||
long long GetDuration() const; //scaled
|
||||
const char* GetMuxingAppAsUTF8() const;
|
||||
const char* GetWritingAppAsUTF8() const;
|
||||
const char* GetTitleAsUTF8() const;
|
||||
|
||||
private:
|
||||
long long m_timecodeScale;
|
||||
double m_duration;
|
||||
char* m_pMuxingAppAsUTF8;
|
||||
char* m_pWritingAppAsUTF8;
|
||||
char* m_pTitleAsUTF8;
|
||||
};
|
||||
|
||||
|
||||
class Cluster
|
||||
{
|
||||
Cluster(const Cluster&);
|
||||
Cluster& operator=(const Cluster&);
|
||||
|
||||
public:
|
||||
Segment* const m_pSegment;
|
||||
const size_t m_index;
|
||||
|
||||
public:
|
||||
static Cluster* Parse(Segment*, size_t, long long off);
|
||||
|
||||
Cluster(); //EndOfStream
|
||||
~Cluster();
|
||||
|
||||
bool EOS() const;
|
||||
|
||||
long long GetTimeCode(); //absolute, but not scaled
|
||||
long long GetTime(); //absolute, and scaled (nanosecond units)
|
||||
|
||||
const BlockEntry* GetFirst();
|
||||
const BlockEntry* GetLast();
|
||||
const BlockEntry* GetNext(const BlockEntry*) const;
|
||||
const BlockEntry* GetEntry(const Track*);
|
||||
protected:
|
||||
Cluster(Segment*, size_t, long long off);
|
||||
|
||||
private:
|
||||
long long m_start;
|
||||
long long m_size;
|
||||
long long m_timecode;
|
||||
BlockEntry** m_pEntries;
|
||||
size_t m_entriesCount;
|
||||
|
||||
void Load();
|
||||
void LoadBlockEntries();
|
||||
void ParseBlockGroup(long long, long long, size_t);
|
||||
void ParseSimpleBlock(long long, long long, size_t);
|
||||
|
||||
};
|
||||
|
||||
|
||||
class Segment
|
||||
{
|
||||
Segment(const Segment&);
|
||||
Segment& operator=(const Segment&);
|
||||
|
||||
private:
|
||||
Segment(IMkvReader*, long long pos, long long size);
|
||||
|
||||
public:
|
||||
IMkvReader* const m_pReader;
|
||||
const long long m_start; //posn of segment payload
|
||||
const long long m_size; //size of segment payload
|
||||
Cluster m_eos; //TODO: make private?
|
||||
|
||||
static long long CreateInstance(IMkvReader*, long long, Segment*&);
|
||||
~Segment();
|
||||
|
||||
//for big-bang loading (source filter)
|
||||
long Load();
|
||||
|
||||
//for incremental loading (splitter)
|
||||
long long Unparsed() const;
|
||||
long long ParseHeaders();
|
||||
long ParseCluster(Cluster*&, long long& newpos) const;
|
||||
bool AddCluster(Cluster*, long long);
|
||||
|
||||
Tracks* GetTracks() const;
|
||||
const SegmentInfo* const GetInfo() const;
|
||||
long long GetDuration() const;
|
||||
|
||||
//NOTE: this turned out to be too inefficient.
|
||||
//long long Load(long long time_nanoseconds);
|
||||
|
||||
Cluster* GetFirst();
|
||||
Cluster* GetLast();
|
||||
unsigned long GetCount() const;
|
||||
|
||||
Cluster* GetNext(const Cluster*);
|
||||
Cluster* GetCluster(long long time_nanoseconds);
|
||||
|
||||
private:
|
||||
long long m_pos; //absolute file posn; what has been consumed so far
|
||||
SegmentInfo* m_pInfo;
|
||||
Tracks* m_pTracks;
|
||||
Cluster** m_clusters;
|
||||
size_t m_clusterCount;
|
||||
|
||||
void ParseSeekHead(long long pos, long long size, size_t*);
|
||||
void ParseSeekEntry(long long pos, long long size, size_t*);
|
||||
void ParseSecondarySeekHead(long long off, size_t*);
|
||||
};
|
||||
|
||||
|
||||
} //end namespace mkvparser
|
||||
|
||||
#endif //MKVPARSER_HPP
|
Reference in New Issue
Block a user