Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
407 lines (320 sloc) 10.4 KB
/*
* MatroskaImport.cpp
*
* MatroskaImport.cpp - QuickTime importer interface for opening a Matroska file.
*
*
* Copyright (c) 2006 David Conrad
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <Carbon/Carbon.h>
#include <QuickTime/QuickTime.h>
#include <matroska/KaxSegment.h>
#include <matroska/KaxCluster.h>
#include <matroska/KaxBlock.h>
#include <ebml/StdIOCallback.h>
#include "PerianResourceIDs.h"
#include "MatroskaImport.h"
#include "SubImport.h"
#include "Codecprintf.h"
extern "C"
ComponentResult create_placeholder_track(Movie movie, Track *placeholderTrack, TimeValue duration, Handle dataRef, OSType dataRefType);
using namespace libmatroska;
#pragma mark- Component Dispatch
// Setup required for ComponentDispatchHelper.c since I don't feel like writing a dispatcher
// for a C++ class akin to Apple's ACCodec.cpp classes for Core Audio codecs.
#define MOVIEIMPORT_BASENAME() MatroskaImport
#define MOVIEIMPORT_GLOBALS() MatroskaImport *storage
#define CALLCOMPONENT_BASENAME() MOVIEIMPORT_BASENAME()
#define CALLCOMPONENT_GLOBALS() MOVIEIMPORT_GLOBALS()
#define COMPONENT_DISPATCH_FILE "MatroskaImportDispatch.h"
#define COMPONENT_UPP_SELECT_ROOT() MovieImport
extern "C" {
#include <CoreServices/Components.k.h>
#include <QuickTime/QuickTimeComponents.k.h>
#pragma GCC visibility push(default)
#include <QuickTime/ComponentDispatchHelper.c>
#pragma GCC visibility pop
}
#pragma mark-
// Component Open Request - Required
ComponentResult MatroskaImportOpen(MatroskaImport *store, ComponentInstance self)
{
// Allocate memory for our globals, and inform the component manager that we've done so
store = new MatroskaImport(self);
if (store == NULL)
return memFullErr;
SetComponentInstanceStorage(self, (Handle)store);
return noErr;
}
// Component Close Request - Required
ComponentResult MatroskaImportClose(MatroskaImport *store, ComponentInstance self)
{
// Make sure to dealocate our storage
delete store;
return noErr;
}
// Component Version Request - Required
ComponentResult MatroskaImportVersion(MatroskaImport *store)
{
return kMatroskaImportVersion;
}
#pragma mark-
// MovieImportFile
ComponentResult MatroskaImportFile(MatroskaImport *store, const FSSpec *theFile, Movie theMovie, Track targetTrack,
Track *usedTrack, TimeValue atTime, TimeValue *durationAdded, long inFlags, long *outFlags)
{
OSErr err = noErr;
AliasHandle alias = NULL;
FSRef theFileFSRef;
*outFlags = 0;
err = FSpMakeFSRef(theFile, &theFileFSRef);
if (err) goto bail;
err = FSNewAliasMinimal(&theFileFSRef, &alias);
if (err) goto bail;
err = store->ImportDataRef((Handle)alias, rAliasType, theMovie, targetTrack,
usedTrack, atTime, durationAdded, inFlags, outFlags);
bail:
if (alias)
DisposeHandle((Handle)alias);
return err;
}
// MovieImportDataRef
ComponentResult MatroskaImportDataRef(MatroskaImport *store, Handle dataRef, OSType dataRefType, Movie theMovie, Track targetTrack,
Track *usedTrack, TimeValue atTime, TimeValue *durationAdded, long inFlags, long *outFlags)
{
ComponentResult res = store->ImportDataRef(dataRef, dataRefType, theMovie, targetTrack,
usedTrack, atTime, durationAdded, inFlags, outFlags);
LoadExternalSubtitlesFromFileDataRef(dataRef, dataRefType, theMovie);
return res;
}
// MovieImportValidate
ComponentResult MatroskaImportValidate(MatroskaImport *store, const FSSpec *theFile, Handle theData, Boolean *valid)
{
OSErr err = noErr;
AliasHandle alias = NULL;
FSRef theFileFSRef;
*valid = false;
err = FSpMakeFSRef(theFile, &theFileFSRef);
if (err) goto bail;
err = FSNewAliasMinimal(&theFileFSRef, &alias);
if (err) goto bail;
err = store->ValidateDataRef((Handle)alias, rAliasType, (UInt8 *)valid);
bail:
if (alias)
DisposeHandle((Handle)alias);
return err;
}
// MovieImportGetMIMETypeList
ComponentResult MatroskaImportGetMIMETypeList(MatroskaImport *store, QTAtomContainer *retMimeInfo)
{
// Note that GetComponentResource is only available in QuickTime 3.0 or later.
// However, it's safe to call it here because GetMIMETypeList is only defined in QuickTime 3.0 or later.
return GetComponentResource((Component)store->Component(), 'mime', 510, (Handle *)retMimeInfo);
}
// MovieImportValidateDataRef
ComponentResult MatroskaImportValidateDataRef(MatroskaImport *store, Handle dataRef, OSType dataRefType, UInt8 *valid)
{
return store->ValidateDataRef(dataRef, dataRefType, valid);
}
ComponentResult MatroskaImportIdle(MatroskaImport *store, long inFlags, long *outFlags)
{
return store->Idle(inFlags, outFlags);
}
ComponentResult MatroskaImportSetIdleManager(MatroskaImport *store, IdleManager im)
{
return store->SetIdleManager(im);
}
ComponentResult MatroskaImportGetMaxLoadedTime(MatroskaImport *store, TimeValue *time)
{
return store->GetMaxLoadedTime(time);
}
ComponentResult MatroskaImportGetLoadState(MatroskaImport *store, long *importerLoadState)
{
return store->GetLoadState(importerLoadState);
}
MatroskaImport::MatroskaImport(ComponentInstance self)
{
this->self = self;
dataRef = NULL;
dataRefType = 0;
theMovie = NULL;
chapterTrack = NULL;
baseTrack = NULL;
timecodeScale = 1000000;
movieDuration = 0;
idleManager = NULL;
loadState = kMovieLoadStateLoading;
lastIdleTime = 0;
idlesSinceLastAdd = 0;
ioHandler = NULL;
aStream = NULL;
el_l0 = NULL;
el_l1 = NULL;
segmentOffset = 0;
seenInfo = false;
seenTracks = false;
seenChapters = false;
}
MatroskaImport::~MatroskaImport()
{
if (el_l1)
delete el_l1;
if (el_l0)
delete el_l0;
if (aStream)
delete aStream;
if (ioHandler)
delete ioHandler;
}
ComponentResult MatroskaImport::ImportDataRef(Handle dataRef, OSType dataRefType, Movie theMovie,
Track targetTrack, Track *usedTrack,
TimeValue atTime, TimeValue *durationAdded,
long inFlags, long *outFlags)
{
ComponentResult err = noErr;
this->dataRef = dataRef;
this->dataRefType = dataRefType;
this->theMovie = theMovie;
*outFlags = 0;
if (inFlags & movieImportMustUseTrack)
return paramErr;
loadState = kMovieLoadStateLoading;
try {
if (!OpenFile())
// invalid file, validate should catch this
return invalidMovie;
err = SetupMovie();
if (err) return err;
// SetupMovie() couldn't find any level one elements, so nothing to import
if (el_l1 == NULL)
return noErr;
if (inFlags & movieImportWithIdle) {
create_placeholder_track(theMovie, &baseTrack, movieDuration, dataRef, dataRefType);
// try to import one cluster so we have at least something
if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) {
int upperLevel = 0;
EbmlElement *dummyElt = NULL;
el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true);
KaxCluster & cluster = *static_cast<KaxCluster *>(el_l1);
ImportCluster(cluster, true);
}
if (!NextLevel1Element()) {
*outFlags |= movieImportResultComplete;
for (int i = 0; i < tracks.size(); i++)
tracks[i].FinishTrack();
}
else {
*outFlags |= movieImportResultNeedIdles;
}
return noErr;
}
do {
if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) {
int upperLevel = 0;
EbmlElement *dummyElt = NULL;
el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true);
KaxCluster & cluster = *static_cast<KaxCluster *>(el_l1);
ImportCluster(cluster, false);
}
} while (NextLevel1Element());
// insert the a/v tracks' samples
for (int i = 0; i < tracks.size(); i++)
tracks[i].FinishTrack();
} catch (CRTError &err) {
return err.getError();
}
loadState = kMovieLoadStateComplete;
return noErr;
}
ComponentResult MatroskaImport::ValidateDataRef(Handle dataRef, OSType dataRefType, UInt8 *valid)
{
this->dataRef = dataRef;
this->dataRefType = dataRefType;
*valid = 0;
try {
if (OpenFile())
*valid = 128;
} catch (CRTError &err) {
return err.getError();
}
return noErr;
}
ComponentResult MatroskaImport::Idle(long inFlags, long *outFlags)
{
TimeValue currentIdleTime = GetMovieTime(theMovie, NULL);
TimeValue maxLoadedTime;
GetMaxLoadedTime(&maxLoadedTime);
TimeScale movieTimeScale = GetMovieTimeScale(theMovie);
ComponentResult err = noErr;
idlesSinceLastAdd++;
try {
if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) {
int upperLevel = 0;
EbmlElement *dummyElt = NULL;
el_l1->Read(*aStream, KaxCluster::ClassInfos.Context, upperLevel, dummyElt, true);
KaxCluster & cluster = *static_cast<KaxCluster *>(el_l1);
// Only add samples every 20 idles when paused
// or if we're playing and only have 5 seconds away from the end of
// what's already been added
if ((currentIdleTime == lastIdleTime && idlesSinceLastAdd > 20) ||
maxLoadedTime < currentIdleTime + 5*movieTimeScale)
{
idlesSinceLastAdd = 0;
ImportCluster(cluster, true);
}
else
ImportCluster(cluster, false);
}
if (!NextLevel1Element())
goto finish;
} catch (CRTError &cerr) {
err = cerr.getError();
Codecprintf(NULL, "MKV: caught an error (%d): %s\n", (int)err, cerr.what());
goto finish;
}
lastIdleTime = currentIdleTime;
return noErr;
finish:
lastIdleTime = currentIdleTime;
if (baseTrack)
DisposeMovieTrack(baseTrack);
*outFlags |= movieImportResultComplete;
loadState = kMovieLoadStateComplete;
for (int i = 0; i < tracks.size(); i++)
tracks[i].FinishTrack();
return err;
}
ComponentResult MatroskaImport::SetIdleManager(IdleManager im)
{
idleManager = im;
return noErr;
}
ComponentResult MatroskaImport::GetMaxLoadedTime(TimeValue *time)
{
*time = 0;
for (int i = 0; i < tracks.size(); i++) {
if (tracks[i].maxLoadedTime > *time)
*time = tracks[i].maxLoadedTime;
}
return noErr;
}
ComponentResult MatroskaImport::GetLoadState(long *importerLoadState)
{
*importerLoadState = loadState;
return noErr;
}