Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
1447 lines (1206 sloc) 47.4 KB
/*
* MatroskaImportPrivate.cpp
*
* MatroskaImportPrivate.cpp - C++ code for interfacing with libmatroska to import.
*
*
* 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 <QuickTime/QuickTime.h>
#include <vector>
#include <string>
#include <ebml/EbmlHead.h>
#include <ebml/EbmlSubHead.h>
#include <matroska/KaxSegment.h>
#include <matroska/KaxSeekHead.h>
#include <matroska/KaxInfo.h>
#include <matroska/KaxInfoData.h>
#include <matroska/KaxTracks.h>
#include <matroska/KaxTrackVideo.h>
#include <matroska/KaxTrackAudio.h>
#include <matroska/KaxChapters.h>
#include <matroska/KaxCluster.h>
#include <matroska/KaxBlock.h>
#include <matroska/KaxAttachments.h>
#include <matroska/KaxAttached.h>
#include "MatroskaImport.h"
#include "MatroskaCodecIDs.h"
#include "SubImport.h"
#include "SubRenderer.h"
#include "CommonUtils.h"
#include "Codecprintf.h"
#include "bitstream_info.h"
#include "CompressCodecUtils.h"
extern "C" {
#include <libavutil/avutil.h>
#include "ff_private.h"
#undef CodecType
}
using namespace std;
using namespace libmatroska;
bool MatroskaImport::OpenFile()
{
bool valid = true;
int upperLevel = 0;
ioHandler = new DataHandlerCallback(dataRef, dataRefType, MODE_READ);
aStream = new EbmlStream(*ioHandler);
el_l0 = aStream->FindNextID(EbmlHead::ClassInfos, ~0);
if (el_l0 != NULL) {
EbmlElement *dummyElt = NULL;
el_l0->Read(*aStream, EbmlHead::ClassInfos.Context, upperLevel, dummyElt, true);
if (EbmlId(*el_l0) != EBML_ID(EbmlHead)) {
Codecprintf(NULL, "Not a Matroska file\n");
valid = false;
goto exit;
}
EbmlHead *head = static_cast<EbmlHead *>(el_l0);
EDocType docType = GetChild<EDocType>(*head);
if (string(docType) != "matroska" && string(docType) != "webm") {
Codecprintf(NULL, "Unknown Matroska doctype\n");
valid = false;
goto exit;
}
EDocTypeReadVersion readVersion = GetChild<EDocTypeReadVersion>(*head);
if (UInt64(readVersion) > 2) {
Codecprintf(NULL, "Matroska file too new to be read\n");
valid = false;
}
} else {
Codecprintf(NULL, "Matroska file missing EBML Head\n");
valid = false;
}
exit:
delete el_l0;
el_l0 = NULL;
return valid;
}
ComponentResult MatroskaImport::ProcessLevel1Element()
{
int upperLevel = 0;
EbmlElement *dummyElt = NULL;
if (EbmlId(*el_l1) == KaxInfo::ClassInfos.GlobalId) {
el_l1->Read(*aStream, KaxInfo::ClassInfos.Context, upperLevel, dummyElt, true);
return ReadSegmentInfo(*static_cast<KaxInfo *>(el_l1));
} else if (EbmlId(*el_l1) == KaxTracks::ClassInfos.GlobalId) {
el_l1->Read(*aStream, KaxTracks::ClassInfos.Context, upperLevel, dummyElt, true);
return ReadTracks(*static_cast<KaxTracks *>(el_l1));
} else if (EbmlId(*el_l1) == KaxChapters::ClassInfos.GlobalId) {
el_l1->Read(*aStream, KaxChapters::ClassInfos.Context, upperLevel, dummyElt, true);
return ReadChapters(*static_cast<KaxChapters *>(el_l1));
} else if (EbmlId(*el_l1) == KaxAttachments::ClassInfos.GlobalId) {
ComponentResult res;
el_l1->Read(*aStream, KaxAttachments::ClassInfos.Context, upperLevel, dummyElt, true);
res = ReadAttachments(*static_cast<KaxAttachments *>(el_l1));
PrerollSubtitleTracks();
return res;
} else if (EbmlId(*el_l1) == KaxSeekHead::ClassInfos.GlobalId) {
el_l1->Read(*aStream, KaxSeekHead::ClassInfos.Context, upperLevel, dummyElt, true);
return ReadMetaSeek(*static_cast<KaxSeekHead *>(el_l1));
}
return noErr;
}
ComponentResult MatroskaImport::SetupMovie()
{
ComponentResult err = noErr;
// once we've read the Tracks and Segment Info elements and Chapters if it's in the seek head,
// we don't need to read any more of the file
bool done = false;
el_l0 = aStream->FindNextID(KaxSegment::ClassInfos, ~0);
if (!el_l0) return err; // nothing in the file
segmentOffset = static_cast<KaxSegment *>(el_l0)->GetDataStart();
SetAutoTrackAlternatesEnabled(theMovie, false);
while (!done && NextLevel1Element()) {
if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) {
// all header elements are before clusters in sane files
done = true;
} else
err = ProcessLevel1Element();
if (err) return err;
}
// some final setup of info across elements since they can come in any order
for (int i = 0; i < tracks.size(); i++) {
tracks[i].timecodeScale = timecodeScale;
// chapter tracks have to be associated with other enabled tracks to display
if (chapterTrack) {
AddTrackReference(tracks[i].theTrack, chapterTrack, kTrackReferenceChapterList, NULL);
}
}
return err;
}
EbmlElement * MatroskaImport::NextLevel1Element()
{
int upperLevel = 0;
if (el_l1) {
el_l1->SkipData(*aStream, el_l1->Generic().Context);
delete el_l1;
el_l1 = NULL;
}
el_l1 = aStream->FindNextElement(el_l0->Generic().Context, upperLevel, 0xFFFFFFFFL, true);
// dummy element -> probably corrupt file, search for next element in meta seek and continue from there
if (el_l1 && el_l1->IsDummy()) {
vector<MatroskaSeek>::iterator nextElt;
MatroskaSeek currElt;
currElt.segmentPos = el_l1->GetElementPosition();
currElt.idLength = currElt.ebmlID = 0;
nextElt = find_if(levelOneElements.begin(), levelOneElements.end(), bind2nd(greater<MatroskaSeek>(), currElt));
if (nextElt != levelOneElements.end()) {
SetContext(nextElt->GetSeekContext(segmentOffset));
NextLevel1Element();
}
}
return el_l1;
}
ComponentResult MatroskaImport::ReadSegmentInfo(KaxInfo &segmentInfo)
{
if (seenInfo)
return noErr;
KaxDuration & duration = GetChild<KaxDuration>(segmentInfo);
KaxTimecodeScale & timecodeScale = GetChild<KaxTimecodeScale>(segmentInfo);
KaxTitle & title = GetChild<KaxTitle>(segmentInfo);
KaxWritingApp & writingApp = GetChild<KaxWritingApp>(segmentInfo);
movieDuration = Float64(duration);
this->timecodeScale = UInt64(timecodeScale);
SetMovieTimeScale(theMovie, S64Div(1000000000L, this->timecodeScale));
QTMetaDataRef movieMetaData;
OSStatus err = QTCopyMovieMetaData(theMovie, &movieMetaData);
if (err == noErr) {
OSType key;
if (!title.IsDefaultValue()) {
key = kQTMetaDataCommonKeyDisplayName;
QTMetaDataAddItem(movieMetaData,
kQTMetaDataStorageFormatQuickTime, kQTMetaDataKeyFormatCommon,
(UInt8 *)&key, sizeof(key),
(UInt8 *)UTFstring(title).GetUTF8().c_str(),
UTFstring(title).GetUTF8().size(),
kQTMetaDataTypeUTF8, NULL);
}
if (!writingApp.IsDefaultValue()) {
key = kQTMetaDataCommonKeySoftware;
QTMetaDataAddItem(movieMetaData,
kQTMetaDataStorageFormatQuickTime, kQTMetaDataKeyFormatCommon,
(UInt8 *)&key, sizeof(key),
(UInt8 *)UTFstring(writingApp).GetUTF8().c_str(),
UTFstring(writingApp).GetUTF8().size(),
kQTMetaDataTypeUTF8, NULL);
}
QTMetaDataRelease(movieMetaData);
}
seenInfo = true;
return noErr;
}
ComponentResult MatroskaImport::ReadTracks(KaxTracks &trackEntries)
{
Track firstVideoTrack = NULL; short firstVideoTrackLang = 0; bool videoEnabled = false;
Track firstAudioTrack = NULL; short firstAudioTrackLang = 0; bool audioEnabled = false;
Track firstSubtitleTrack = NULL; short firstSubtitleTrackLang = 0; bool subtitleEnabled = false;
ComponentResult err = noErr;
if (seenTracks)
return noErr;
// Since creating a subtitle track requires a video track to have already been created
// (so that it can be sized to fit exactly over the video track), we go through the
// track entries in two passes, first to add audio/video, second to add subtitle tracks.
for (int pass = 1; pass <= 2; pass++) {
for (int i = 0; i < trackEntries.ListSize(); i++) {
if (EbmlId(*trackEntries[i]) != KaxTrackEntry::ClassInfos.GlobalId)
continue;
KaxTrackEntry & track = *static_cast<KaxTrackEntry *>(trackEntries[i]);
KaxTrackNumber & number = GetChild<KaxTrackNumber>(track);
KaxTrackType & type = GetChild<KaxTrackType>(track);
KaxTrackDefaultDuration * defaultDuration = FindChild<KaxTrackDefaultDuration>(track);
KaxTrackFlagDefault & enabled = GetChild<KaxTrackFlagDefault>(track);
KaxTrackFlagLacing & lacing = GetChild<KaxTrackFlagLacing>(track);
MatroskaTrack mkvTrack;
mkvTrack.number = uint16(number);
mkvTrack.type = uint8(type);
if (defaultDuration)
mkvTrack.defaultDuration = uint32(*defaultDuration) / float(timecodeScale) + .5;
else
mkvTrack.defaultDuration = 0;
mkvTrack.isEnabled = uint8(enabled);
mkvTrack.usesLacing = uint8(lacing);
KaxTrackLanguage & trackLang = GetChild<KaxTrackLanguage>(track);
KaxTrackName & trackName = GetChild<KaxTrackName>(track);
KaxContentEncodings * encodings = FindChild<KaxContentEncodings>(track);
short qtLang = ISO639_2ToQTLangCode(string(trackLang).c_str());
switch (uint8(type)) {
case track_video:
if (pass == 2) continue;
err = AddVideoTrack(track, mkvTrack, encodings);
if (err) return err;
if (mkvTrack.isEnabled)
videoEnabled = true;
if (firstVideoTrack && qtLang != firstVideoTrackLang)
SetTrackAlternate(firstVideoTrack, mkvTrack.theTrack);
else {
firstVideoTrack = mkvTrack.theTrack;
firstVideoTrackLang = qtLang;
}
break;
case track_audio:
if (pass == 2) continue;
err = AddAudioTrack(track, mkvTrack, encodings);
if (err) return err;
if (mkvTrack.isEnabled)
audioEnabled = true;
if (firstAudioTrack && qtLang != firstAudioTrackLang)
SetTrackAlternate(firstAudioTrack, mkvTrack.theTrack);
else {
firstAudioTrack = mkvTrack.theTrack;
firstAudioTrackLang = qtLang;
}
break;
case track_subtitle:
if (pass == 1) continue;
err = AddSubtitleTrack(track, mkvTrack, encodings);
if (err) return err;
if (mkvTrack.theTrack == NULL) continue;
if (mkvTrack.isEnabled)
subtitleEnabled = true;
if (firstSubtitleTrack && qtLang != firstSubtitleTrackLang)
SetTrackAlternate(firstSubtitleTrack, mkvTrack.theTrack);
else {
firstSubtitleTrack = mkvTrack.theTrack;
firstSubtitleTrackLang = qtLang;
}
break;
case track_complex:
case track_logo:
case track_buttons:
case track_control:
// not likely to be implemented soon
default:
continue;
}
SetMediaLanguage(mkvTrack.theMedia, qtLang);
if (!trackName.IsDefaultValue()) {
QTMetaDataRef trackMetaData;
err = QTCopyTrackMetaData(mkvTrack.theTrack, &trackMetaData);
if (err == noErr) {
OSType key = 'name';
// QuickTime differentiates between the title of a track and its name
// so we set both
QTMetaDataAddItem(trackMetaData,
kQTMetaDataStorageFormatQuickTime,
kQTMetaDataKeyFormatCommon,
(UInt8 *)&key, sizeof(key),
(UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
UTFstring(trackName).GetUTF8().size(),
kQTMetaDataTypeUTF8, NULL);
QTMetaDataAddItem(trackMetaData,
kQTMetaDataStorageFormatUserData,
kQTMetaDataKeyFormatUserData,
(UInt8 *)&key, sizeof(key),
(UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
UTFstring(trackName).GetUTF8().size(),
kQTMetaDataTypeUTF8, NULL);
QTMetaDataRelease(trackMetaData);
}
}
tracks.push_back(mkvTrack);
}
}
for (int i = 0; i < tracks.size(); i++) {
SetTrackEnabled(tracks[i].theTrack, tracks[i].isEnabled);
}
// ffmpeg used to write a TrackDefault of 0 for all tracks
// ensure that at least one track of each media type is enabled, if none were originally
// this picks the first track, which may not be the best, but the situation is quite rare anyway
// FIXME: properly choose tracks based on forced/default/language flags, and consider turning auto-alternates back on
if (!videoEnabled && firstVideoTrack)
SetTrackEnabled(firstVideoTrack, 1);
if (!audioEnabled && firstAudioTrack)
SetTrackEnabled(firstAudioTrack, 1);
if (!subtitleEnabled && firstSubtitleTrack)
SetTrackEnabled(firstSubtitleTrack, 1);
seenTracks = true;
return noErr;
}
ComponentResult MatroskaImport::ReadVobSubContentEncodings(KaxContentEncodings *encodings, MatroskaTrack &mkvTrack)
{
KaxContentEncoding & encoding = GetChild<KaxContentEncoding>(*encodings);
int scope = uint32(GetChild<KaxContentEncodingScope>(encoding));
int type = uint32(GetChild<KaxContentEncodingType>(encoding));
if (scope != 1) {
Codecprintf(NULL, "Content encoding scope of %d not expected\n", scope);
return -1;
}
if (type != 0) {
Codecprintf(NULL, "Encrypted track\n");
return -2;
}
KaxContentCompression & comp = GetChild<KaxContentCompression>(encoding);
int algo = uint32(GetChild<KaxContentCompAlgo>(comp));
if (algo != 0)
Codecprintf(NULL, "MKV: warning, track compression algorithm %d not zlib\n", algo);
if ((*mkvTrack.desc)->dataFormat != kSubFormatVobSub)
Codecprintf(NULL, "MKV: warning, compressed track %s probably won't work (not VobSub)\n", FourCCString((*mkvTrack.desc)->dataFormat));
Handle ext = NewHandle(1);
**ext = algo;
if (mkvTrack.type == track_audio)
AddSoundDescriptionExtension((SoundDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
else
AddImageDescriptionExtension((ImageDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
return noErr;
}
ComponentResult MatroskaImport::AddVideoTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack, KaxContentEncodings *encodings)
{
ComponentResult err = noErr;
ImageDescriptionHandle imgDesc;
Fixed width, height;
KaxTrackVideo &videoTrack = GetChild<KaxTrackVideo>(kaxTrack);
KaxVideoDisplayWidth & disp_width = GetChild<KaxVideoDisplayWidth>(videoTrack);
KaxVideoDisplayHeight & disp_height = GetChild<KaxVideoDisplayHeight>(videoTrack);
KaxVideoPixelWidth & pxl_width = GetChild<KaxVideoPixelWidth>(videoTrack);
KaxVideoPixelHeight & pxl_height = GetChild<KaxVideoPixelHeight>(videoTrack);
// Use the PixelWidth if the DisplayWidth is not set
if (disp_width.ValueIsSet() || disp_height.ValueIsSet()) {
// some files ignore the spec and treat display width/height as a ratio, not as pixels
// so scale the display size to be at least as large as the pixel size here
// but don't let it be bigger in both dimensions
uint32 displayWidth = disp_width.ValueIsSet() ? uint32(disp_width) : uint32(pxl_width);
uint32 displayHeight = disp_height.ValueIsSet() ? uint32(disp_height) : uint32(pxl_height);
float horizRatio = float(uint32(pxl_width)) / displayWidth;
float vertRatio = float(uint32(pxl_height)) / displayHeight;
if (vertRatio > horizRatio && vertRatio > 1) {
width = FloatToFixed(displayWidth * vertRatio);
height = FloatToFixed(displayHeight * vertRatio);
} else if (horizRatio > 1) {
width = FloatToFixed(displayWidth * horizRatio);
height = FloatToFixed(displayHeight * horizRatio);
} else {
float dar = displayWidth / (float)displayHeight;
float p_ratio = uint32(pxl_width) / (float)uint32(pxl_height);
if (dar > p_ratio) {
width = FloatToFixed(uint32(pxl_height) * dar);
height = IntToFixed(uint32(pxl_height));
} else {
width = IntToFixed(uint32(pxl_width));
height = FloatToFixed(uint32(pxl_width) / dar);
}
}
} else if (pxl_width.ValueIsSet() && pxl_height.ValueIsSet()) {
width = IntToFixed(uint32(pxl_width));
height = IntToFixed(uint32(pxl_height));
} else {
Codecprintf(NULL, "MKV: Video has unknown dimensions.\n");
return invalidTrack;
}
mkvTrack.theTrack = NewMovieTrack(theMovie, width, height, kNoVolume);
if (mkvTrack.theTrack == NULL)
return GetMoviesError();
mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
if (mkvTrack.theMedia == NULL) {
DisposeMovieTrack(mkvTrack.theTrack);
return GetMoviesError();
}
imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
(*imgDesc)->idSize = sizeof(ImageDescription);
(*imgDesc)->width = uint16(pxl_width);
(*imgDesc)->height = uint16(pxl_height);
(*imgDesc)->frameCount = 1;
(*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
(*imgDesc)->depth = 24;
(*imgDesc)->clutID = -1;
set_track_clean_aperture_ext(imgDesc, width, height, IntToFixed(uint32(pxl_width)), IntToFixed(uint32(pxl_height)));
set_track_colorspace_ext(imgDesc, width, height);
mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
// this sets up anything else needed in the description for the specific codec.
err = MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
if (err) return err;
if(encodings)
{
KaxContentEncoding & encoding = GetChild<KaxContentEncoding>(*encodings);
int scope = uint32(GetChild<KaxContentEncodingScope>(encoding));
int type = uint32(GetChild<KaxContentEncodingType>(encoding));
if (scope != 1) {
Codecprintf(NULL, "Content encoding scope of %d not expected\n", scope);
}
if (type != 0) {
Codecprintf(NULL, "Encrypted track\n");
}
if(scope == 1 && type == 0)
{
KaxContentCompression & comp = GetChild<KaxContentCompression>(encoding);
int algo = uint32(GetChild<KaxContentCompAlgo>(comp));
if (algo != 3)
Codecprintf(NULL, "MKV: warning, track compression algorithm %d not stripped header\n", algo);
SampleDescriptionHandle sampleDesc = mkvTrack.desc;
OSType compressedType = compressStreamFourCC((*sampleDesc)->dataFormat);
if (compressedType == 0)
Codecprintf(NULL, "MKV: warning, compressed track %s probably won't work\n", FourCCString((*mkvTrack.desc)->dataFormat));
else
{
Handle ext = NewHandle(4);
memcpy(*ext, &algo, 4);
(*sampleDesc)->dataFormat = compressedType;
AddImageDescriptionExtension((ImageDescriptionHandle)mkvTrack.desc, ext, kCompressionAlgorithm);
DisposeHandle(ext);
KaxContentCompSettings & settings = GetChild<KaxContentCompSettings>(comp);
uint8_t *compSettings = (uint8_t *)settings.GetBuffer();
int compSize = settings.GetSize();
if(compSize > 0) {
ext = NewHandle(compSize);
memcpy(*ext, compSettings, compSize);
AddImageDescriptionExtension((ImageDescriptionHandle)mkvTrack.desc, ext, kCompressionSettingsExtension);
DisposeHandle(ext);
}
}
}
}
// video tracks can have display offsets, so create a sample table
err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
if (err) return err;
err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
return err;
}
ComponentResult MatroskaImport::AddAudioTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack, KaxContentEncodings *encodings)
{
SoundDescriptionHandle sndDesc = NULL;
AudioStreamBasicDescription asbd = {0};
AudioChannelLayout acl = {0};
AudioChannelLayout *pacl = &acl;
ByteCount acl_size = sizeof(acl);
ByteCount ioSize = sizeof(asbd);
ByteCount cookieSize = 0;
Handle cookieH = NULL;
Ptr cookie = NULL;
OSStatus err = noErr;
mkvTrack.theTrack = NewMovieTrack(theMovie, 0, 0, kFullVolume);
if (mkvTrack.theTrack == NULL)
return GetMoviesError();
mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'soun', GetMovieTimeScale(theMovie), dataRef, dataRefType);
if (mkvTrack.theMedia == NULL) {
DisposeMovieTrack(mkvTrack.theTrack);
return GetMoviesError();
}
KaxTrackAudio & audioTrack = GetChild<KaxTrackAudio>(kaxTrack);
KaxAudioSamplingFreq & sampleFreq = GetChild<KaxAudioSamplingFreq>(audioTrack);
KaxAudioChannels & numChannels = GetChild<KaxAudioChannels>(audioTrack);
KaxAudioBitDepth & bitDepth = GetChild<KaxAudioBitDepth>(audioTrack);
if (bitDepth.ValueIsSet()) asbd.mBitsPerChannel = uint32(bitDepth);
asbd.mFormatID = MkvGetFourCC(&kaxTrack);
asbd.mSampleRate = Float64(sampleFreq);
asbd.mChannelsPerFrame = uint32(numChannels);
MkvFinishAudioDescription(&kaxTrack, &cookieH, &asbd, &acl);
if (cookieH) {
cookie = *cookieH;
cookieSize = GetHandleSize(cookieH);
}
// get more info about the codec
err = AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, cookieSize, cookie, &ioSize, &asbd);
if(asbd.mChannelsPerFrame == 0 || asbd.mFormatID == 0) {
Codecprintf(NULL, "Audio channels or format not set in MKV\n");
goto err; // better to fail than import with the wrong number of channels...
}
if (err) {
Codecprintf(NULL, "AudioFormatGetProperty failed (error %lx / format %lx)\n", err, asbd.mFormatID);
}
// see ff_private.c initialize_audio_map
if (!asbd.mFramesPerPacket && asbd.mFormatID == kAudioFormatMPEGLayer3)
asbd.mFramesPerPacket = asbd.mSampleRate > 24000 ? 1152 : 576;
// FIXME: mChannelLayoutTag == 0 is valid
// but we don't use channel position lists (yet) so it's safe for now
if (acl.mChannelLayoutTag == 0) acl = GetDefaultChannelLayout(&asbd);
if (acl.mChannelLayoutTag == 0) {
pacl = NULL;
acl_size = 0;
}
if(encodings)
{
KaxContentEncoding & encoding = GetChild<KaxContentEncoding>(*encodings);
int scope = uint32(GetChild<KaxContentEncodingScope>(encoding));
int type = uint32(GetChild<KaxContentEncodingType>(encoding));
if (scope != 1) {
Codecprintf(NULL, "Content encoding scope of %d not expected\n", scope);
}
if (type != 0) {
Codecprintf(NULL, "Encrypted track\n");
}
if(scope == 1 && type == 0)
{
KaxContentCompression & comp = GetChild<KaxContentCompression>(encoding);
int algo = uint32(GetChild<KaxContentCompAlgo>(comp));
if (algo != 3)
Codecprintf(NULL, "MKV: warning, track compression algorithm %d not stripped header\n", algo);
OSType compressedType = compressStreamFourCC(asbd.mFormatID);
if (compressedType == 0)
Codecprintf(NULL, "MKV: warning, compressed track %s probably won't work\n", FourCCString(asbd.mFormatID));
else
{
asbd.mFormatID = compressedType;
uint32_t algoHeader[] = {
EndianS32_NtoB(12),
EndianS32_NtoB(kCompressionAlgorithm),
EndianS32_NtoB(algo)
};
Handle newCookieHandle;
PtrToHand(algoHeader, &newCookieHandle, sizeof(algoHeader));
KaxContentCompSettings & settings = GetChild<KaxContentCompSettings>(comp);
uint8_t *compSettings = (uint8_t *)settings.GetBuffer();
int compSize = settings.GetSize();
if(compSize > 0) {
uint32_t settingsHeader[] = {
EndianS32_NtoB(8 + compSize),
EndianS32_NtoB(kCompressionSettingsExtension),
};
PtrAndHand(settingsHeader, newCookieHandle, sizeof(settingsHeader));
PtrAndHand(compSettings, newCookieHandle, compSize);
}
if(cookieSize)
{
HandAndHand(cookieH, newCookieHandle);
DisposeHandle(cookieH);
}
cookieH = newCookieHandle;
cookie = *cookieH;
cookieSize = GetHandleSize(cookieH);
}
}
}
err = QTSoundDescriptionCreate(&asbd, pacl, acl_size, cookie, cookieSize,
kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
if (err) {
Codecprintf(NULL, "Borked audio track entry, hoping we can parse the track for asbd\n");
DisposeHandle((Handle)sndDesc);
return noErr;
}
mkvTrack.desc = (SampleDescriptionHandle) sndDesc;
err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
if (err) goto err;
err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
err:
if (cookieH)
DisposeHandle(cookieH);
return err;
}
ComponentResult MatroskaImport::AddSubtitleTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack, KaxContentEncodings *encodings)
{
Fixed trackWidth, trackHeight;
Rect movieBox;
MediaHandler mh;
ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
// we assume that a video track has already been created, so we can set the subtitle track
// to have the same dimensions as it. Note that this doesn't work so well with multiple
// video tracks with different dimensions; but QuickTime doesn't expect that; how should we handle them?
GetMovieBox(theMovie, &movieBox);
trackWidth = IntToFixed(movieBox.right - movieBox.left);
trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
(*imgDesc)->idSize = sizeof(ImageDescription);
(*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
(*imgDesc)->frameCount = 1;
(*imgDesc)->depth = 32;
(*imgDesc)->clutID = -1;
if ((*imgDesc)->cType == kSubFormatVobSub) {
int width, height;
// bitmap width & height is in the codec private in text format
KaxCodecPrivate & idxFile = GetChild<KaxCodecPrivate>(kaxTrack);
string idxFileStr((char *)idxFile.GetBuffer(), idxFile.GetSize());
string::size_type loc = idxFileStr.find("size:", 0);
if (loc == string::npos)
return -1;
sscanf(&idxFileStr.c_str()[loc], "size: %dx%d", &width, &height);
(*imgDesc)->width = width;
(*imgDesc)->height = height;
if (trackWidth == 0 || trackHeight == 0) {
trackWidth = IntToFixed(width);
trackHeight = IntToFixed(height);
}
set_track_colorspace_ext(imgDesc, width, height);
mkvTrack.theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
if (mkvTrack.theTrack == NULL)
return GetMoviesError();
mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
if (mkvTrack.theMedia == NULL)
return GetMoviesError();
// finally, say that we're transparent
mh = GetMediaHandler(mkvTrack.theMedia);
SetSubtitleMediaHandlerTransparent(mh);
// subtitle tracks should be above the video track, which should be layer 0
SetTrackLayer(mkvTrack.theTrack, -1);
mkvTrack.is_vobsub = true;
} else if ((*imgDesc)->cType == kSubFormatUTF8 || (*imgDesc)->cType == kSubFormatSSA || (*imgDesc)->cType == kSubFormatASS) {
if ((*imgDesc)->cType == kSubFormatASS) (*imgDesc)->cType = kSubFormatSSA; // no real reason to treat them differently
UInt32 emptyDataRefExtension[2]; // FIXME: the various uses of this bit of code should be unified
mkvTrack.subDataRefHandler = NewHandleClear(sizeof(Handle) + 1);
emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
PtrAndHand(&emptyDataRefExtension[0], mkvTrack.subDataRefHandler, sizeof(emptyDataRefExtension));
mkvTrack.theTrack = CreatePlaintextSubTrack(theMovie, imgDesc, GetMovieTimeScale(theMovie), mkvTrack.subDataRefHandler, HandleDataHandlerSubType, (*imgDesc)->cType, NULL, movieBox);
if (mkvTrack.theTrack == NULL)
return GetMoviesError();
mkvTrack.theMedia = GetTrackMedia(mkvTrack.theTrack);
mkvTrack.is_vobsub = false;
BeginMediaEdits(mkvTrack.theMedia);
} else {
Codecprintf(NULL, "MKV: Unsupported subtitle type\n");
return noErr;
}
// this sets up anything else needed in the description for the specific codec.
ComponentResult result = MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
if(encodings) {
ReadVobSubContentEncodings(encodings, mkvTrack);
}
return result;
}
ComponentResult MatroskaImport::ReadChapters(KaxChapters &chapterEntries)
{
KaxEditionEntry & edition = GetChild<KaxEditionEntry>(chapterEntries);
UInt32 emptyDataRefExtension[2];
if (seenChapters)
return noErr;
chapterTrack = NewMovieTrack(theMovie, 0, 0, kNoVolume);
if (chapterTrack == NULL) {
Codecprintf(NULL, "MKV: Error creating chapter track %d\n", GetMoviesError());
return GetMoviesError();
}
// we use a handle data reference here because I don't see any way to add textual
// sample references (TextMediaAddTextSample() will behave the same as AddSample()
// in that it modifies the original file if that's the data reference of the media)
Handle dataRef = NewHandleClear(sizeof(Handle) + 1);
emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
PtrAndHand(&emptyDataRefExtension[0], dataRef, sizeof(emptyDataRefExtension));
Media chapterMedia = NewTrackMedia(chapterTrack, TextMediaType, GetMovieTimeScale(theMovie),
dataRef, HandleDataHandlerSubType);
if (chapterMedia == NULL) {
OSErr err = GetMoviesError();
Codecprintf(NULL, "MKV: Error creating chapter media %d\n", err);
DisposeMovieTrack(chapterTrack);
return err;
}
// Name the chapter track "Chapters" for easy distinguishing
QTMetaDataRef trackMetaData;
OSErr err = QTCopyTrackMetaData(chapterTrack, &trackMetaData);
if (err == noErr) {
OSType key = kUserDataName;
string chapterName("Chapters");
QTMetaDataAddItem(trackMetaData, kQTMetaDataStorageFormatUserData,
kQTMetaDataKeyFormatUserData, (UInt8 *)&key, sizeof(key),
(UInt8 *)chapterName.c_str(), chapterName.size(),
kQTMetaDataTypeUTF8, NULL);
QTMetaDataRelease(trackMetaData);
}
BeginMediaEdits(chapterMedia);
// tell the text media handler the upcoming text samples are
// encoded in Unicode with a byte order mark (BOM)
MediaHandler mediaHandler = GetMediaHandler(chapterMedia);
SInt32 dataPtr = kTextEncodingUnicodeDefault;
TextMediaSetTextSampleData(mediaHandler, &dataPtr, kTXNTextEncodingAttribute);
KaxChapterAtom *chapterAtom = FindChild<KaxChapterAtom>(edition);
while (chapterAtom && chapterAtom->GetSize() > 0) {
AddChapterAtom(chapterAtom);
chapterAtom = &GetNextChild<KaxChapterAtom>(edition, *chapterAtom);
}
EndMediaEdits(chapterMedia);
SetTrackEnabled(chapterTrack, false);
seenChapters = true;
return noErr;
}
void MatroskaImport::AddChapterAtom(KaxChapterAtom *atom)
{
KaxChapterAtom *subChapter = FindChild<KaxChapterAtom>(*atom);
bool addThisChapter = true;
// since QuickTime only supports linear chapter tracks (no nesting), only add chapter leaves
if (subChapter && subChapter->GetSize() > 0) {
while (subChapter && subChapter->GetSize() > 0) {
KaxChapterFlagHidden &hideChapter = GetChild<KaxChapterFlagHidden>(*subChapter);
if (!uint8_t(hideChapter)) {
AddChapterAtom(subChapter);
addThisChapter = false;
}
subChapter = &GetNextChild(*atom, *subChapter);
}
}
if (addThisChapter) {
// add the chapter to the track if it has no children
KaxChapterTimeStart & startTime = GetChild<KaxChapterTimeStart>(*atom);
KaxChapterDisplay & chapDisplay = GetChild<KaxChapterDisplay>(*atom);
KaxChapterString & chapString = GetChild<KaxChapterString>(chapDisplay);
MediaHandler mh = GetMediaHandler(GetTrackMedia(chapterTrack));
TimeValue start = UInt64(startTime) / timecodeScale;
if (start > movieDuration) {
Codecprintf(NULL, "MKV: Chapter time is beyond the end of the file\n");
return;
}
Rect bounds = {0, 0, 0, 0};
TimeValue inserted;
OSErr err = TextMediaAddTextSample(mh,
const_cast<Ptr>(UTFstring(chapString).GetUTF8().c_str()),
UTFstring(chapString).GetUTF8().size(),
0, 0, 0, NULL, NULL,
teCenter, &bounds, dfClipToTextBox,
0, 0, 0, NULL, 1, &inserted);
if (err)
Codecprintf(NULL, "MKV: Error adding text sample %d\n", err);
else {
InsertMediaIntoTrack(chapterTrack, start, inserted, 1, fixed1);
}
}
}
ComponentResult MatroskaImport::ReadAttachments(KaxAttachments &attachments)
{
KaxAttached *attachedFile = FindChild<KaxAttached>(attachments);
while (attachedFile && attachedFile->GetSize() > 0) {
string fileMimeType = GetChild<KaxMimeType>(*attachedFile);
string fileName = UTFstring(GetChild<KaxFileName>(*attachedFile)).GetUTF8();
/* The only attachments handled here are fonts, which currently can be truetype or opentype.
application/x-* is probably not a permanent MIME type, but it is current practice... */
if ((fileMimeType == "application/x-truetype-font" || fileMimeType == "application/x-font-otf") &&
ShouldImportFontFileName(fileName.c_str())) {
KaxFileData & fontData = GetChild<KaxFileData>(*attachedFile);
if (fontData.GetSize()) {
ATSFontContainerRef container;
ATSFontActivateFromMemory(fontData.GetBuffer(), fontData.GetSize(), kATSFontContextLocal,
kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &container);
}
}
bool isCoverArt = false, isJPEG;
if (fileName == "cover.jpg") {
isCoverArt = isJPEG = true;
} else if (fileName == "cover.png") {
isCoverArt = true;
isJPEG = false;
}
if (isCoverArt) {
KaxFileData & fileData = GetChild<KaxFileData>(*attachedFile);
FourCharCode key = 'covr'; //iTunes cover art tag
QTMetaDataRef movieMetaData;
QTCopyMovieMetaData(theMovie, &movieMetaData);
OSErr err;
err = QTMetaDataAddItem(movieMetaData,
kQTMetaDataStorageFormatiTunes, kQTMetaDataKeyFormatiTunesShortForm,
(UInt8 *)&key, sizeof(key),
fileData.GetBuffer(),
fileData.GetSize(),
isJPEG ? kQTMetaDataTypeJPEGImage : kQTMetaDataTypePNGImage, NULL);
if (err)
Codecprintf(NULL, "MKV: Error adding cover art %d\n", (int)err);
QTMetaDataRelease(movieMetaData);
}
attachedFile = &GetNextChild<KaxAttached>(attachments, *attachedFile);
}
return noErr;
}
ComponentResult MatroskaImport::ReadMetaSeek(KaxSeekHead &seekHead)
{
ComponentResult err = noErr;
KaxSeek *seekEntry = FindChild<KaxSeek>(seekHead);
// don't re-read a seek head that's already been read
uint64_t currPos = seekHead.GetElementPosition();
vector<MatroskaSeek>::iterator itr = levelOneElements.begin();
for (; itr != levelOneElements.end(); itr++) {
if (itr->GetID() == KaxSeekHead::ClassInfos.GlobalId &&
itr->segmentPos + segmentOffset == currPos)
return noErr;
}
while (seekEntry && seekEntry->GetSize() > 0) {
MatroskaSeek newSeekEntry;
KaxSeekID & seekID = GetChild<KaxSeekID>(*seekEntry);
KaxSeekPosition & position = GetChild<KaxSeekPosition>(*seekEntry);
EbmlId elementID = EbmlId(seekID.GetBuffer(), seekID.GetSize());
newSeekEntry.ebmlID = elementID.Value;
newSeekEntry.idLength = elementID.Length;
newSeekEntry.segmentPos = position;
// recursively read seek heads that are pointed to by the current one
// as well as the level one elements we care about
if (elementID == KaxInfo::ClassInfos.GlobalId ||
elementID == KaxTracks::ClassInfos.GlobalId ||
elementID == KaxChapters::ClassInfos.GlobalId ||
elementID == KaxAttachments::ClassInfos.GlobalId ||
elementID == KaxSeekHead::ClassInfos.GlobalId) {
MatroskaSeekContext savedContext = SaveContext();
SetContext(newSeekEntry.GetSeekContext(segmentOffset));
if (NextLevel1Element())
err = ProcessLevel1Element();
SetContext(savedContext);
if (err) return err;
}
levelOneElements.push_back(newSeekEntry);
seekEntry = &GetNextChild<KaxSeek>(seekHead, *seekEntry);
}
sort(levelOneElements.begin(), levelOneElements.end());
return noErr;
}
void MatroskaImport::ImportCluster(KaxCluster &cluster, bool addToTrack)
{
KaxSegment & segment = *static_cast<KaxSegment *>(el_l0);
KaxClusterTimecode & clusterTime = GetChild<KaxClusterTimecode>(cluster);
cluster.SetParent(segment);
cluster.InitTimecode(uint64(clusterTime), timecodeScale);
for (int i = 0; i < cluster.ListSize(); i++) {
const EbmlId & elementID = EbmlId(*cluster[i]);
KaxInternalBlock *block = NULL;
uint32_t duration = 0; // set to track's default duration in AddBlock if 0
short flags = 0;
if (elementID == KaxBlockGroup::ClassInfos.GlobalId) {
KaxBlockGroup & blockGroup = *static_cast<KaxBlockGroup *>(cluster[i]);
KaxBlockDuration & blkDuration = GetChild<KaxBlockDuration>(blockGroup);
block = &GetChild<KaxBlock>(blockGroup);
if (blkDuration.ValueIsSet())
duration = uint32(blkDuration);
flags = blockGroup.ReferenceCount() > 0 ? mediaSampleNotSync : 0;
} else if (elementID == KaxSimpleBlock::ClassInfos.GlobalId) {
KaxSimpleBlock & simpleBlock = *static_cast<KaxSimpleBlock *>(cluster[i]);
block = &simpleBlock;
if (!simpleBlock.IsKeyframe())
flags |= mediaSampleNotSync;
if (simpleBlock.IsDiscardable() && IsFrameDroppingEnabled())
flags |= mediaSampleDroppable;
}
if (block) {
block->SetParent(cluster);
for (int j = 0; j < tracks.size(); j++) {
if (tracks[j].number == block->TrackNum()) {
tracks[j].AddBlock(*block, duration, flags);
break;
}
}
}
}
if (addToTrack) {
for (int i = 0; i < tracks.size(); i++)
tracks[i].AddSamplesToTrack();
loadState = kMovieLoadStatePlayable;
}
}
MatroskaSeekContext MatroskaImport::SaveContext()
{
MatroskaSeekContext ret = { el_l1, ioHandler->getFilePointer() };
el_l1 = NULL;
return ret;
}
void MatroskaImport::SetContext(MatroskaSeekContext context)
{
if (el_l1)
delete el_l1;
el_l1 = context.el_l1;
ioHandler->setFilePointer(context.position);
}
void MatroskaImport::PrerollSubtitleTracks()
{
if (!seenTracks) return;
for (int i = 0; i < tracks.size(); i++) {
MatroskaTrack *track = &tracks[i];
if (track->type == track_subtitle) {
Handle subtitleDescriptionExt;
OSErr err = GetImageDescriptionExtension((ImageDescriptionHandle)track->desc, &subtitleDescriptionExt, kSubFormatSSA, 1);
if (err || !subtitleDescriptionExt) continue;
SubRendererPrerollFromHeader(*subtitleDescriptionExt, GetHandleSize(subtitleDescriptionExt));
}
}
}
MatroskaTrack::MatroskaTrack()
{
number = 0;
type = -1;
theTrack = NULL;
theMedia = NULL;
desc = NULL;
sampleTable = NULL;
qtSampleDesc = 0;
timecodeScale = 1000000;
maxLoadedTime = 0;
seenFirstBlock = false;
firstSample = -1;
numSamples = 0;
durationToAdd = 0;
displayOffsetSum = 0;
durationSinceZeroSum = 0;
subtitleSerializer = new CXXSubSerializer;
subDataRefHandler = NULL;
is_vobsub = false;
isEnabled = true;
defaultDuration = 0;
usesLacing = true;
currentFrame = 0;
}
MatroskaTrack::MatroskaTrack(const MatroskaTrack &copy)
{
number = copy.number;
type = copy.type;
theTrack = copy.theTrack;
theMedia = copy.theMedia;
if (copy.desc) {
desc = (SampleDescriptionHandle) NewHandle((*copy.desc)->descSize);
memcpy(*desc, *copy.desc, (*copy.desc)->descSize);
} else
desc = NULL;
sampleTable = copy.sampleTable;
if (sampleTable)
QTSampleTableRetain(sampleTable);
qtSampleDesc = copy.qtSampleDesc;
timecodeScale = copy.timecodeScale;
maxLoadedTime = copy.maxLoadedTime;
for (int i = 0; i < copy.lastFrames.size(); i++)
lastFrames.push_back(copy.lastFrames[i]);
seenFirstBlock = copy.seenFirstBlock;
firstSample = copy.firstSample;
numSamples = copy.numSamples;
durationToAdd = copy.durationToAdd;
displayOffsetSum = copy.displayOffsetSum;
durationSinceZeroSum = copy.durationSinceZeroSum;
subtitleSerializer = copy.subtitleSerializer;
subtitleSerializer->retain();
subDataRefHandler = copy.subDataRefHandler;
is_vobsub = copy.is_vobsub;
isEnabled = copy.isEnabled;
defaultDuration = copy.defaultDuration;
usesLacing = copy.usesLacing;
currentFrame = copy.currentFrame;
}
MatroskaTrack::~MatroskaTrack()
{
if (desc)
DisposeHandle((Handle) desc);
if (sampleTable)
QTSampleTableRelease(sampleTable);
if (subtitleSerializer)
subtitleSerializer->release();
}
void MatroskaTrack::ParseFirstBlock(KaxInternalBlock &block)
{
AudioStreamBasicDescription asbd = {0};
AudioChannelLayout acl = {0};
bool replaceSoundDesc = false;
lowestPTS = block.GlobalTimecode();
if (desc) {
switch ((*desc)->dataFormat) {
case kAudioFormatAC3:
replaceSoundDesc = parse_ac3_bitstream(&asbd, &acl, block.GetBuffer(0).Buffer(), block.GetFrameSize(0));
break;
}
}
if (replaceSoundDesc) {
// successful in parsing, so the acl and asbd are more correct than what we generated in
// AddAudioTrack() so replace our sound description
SoundDescriptionHandle sndDesc = NULL;
OSStatus err = QTSoundDescriptionCreate(&asbd, &acl, sizeof(AudioChannelLayout), NULL, 0,
kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
if (err == noErr) {
DisposeHandle((Handle) desc);
desc = (SampleDescriptionHandle) sndDesc;
QTSampleTableAddSampleDescription(sampleTable, desc, 0, &qtSampleDesc);
}
}
}
void MatroskaTrack::AddBlock(KaxInternalBlock &block, uint32 frameDuration, short flags)
{
if (!seenFirstBlock) {
ParseFirstBlock(block);
seenFirstBlock = true;
}
// tracks w/ lacing can't have b-frames, and neither can any known audio codec
if (usesLacing || type != track_video) {
// don't add the blocks until we get one with a new timecode
TimeValue64 duration = 0;
if (lastFrames.size() > 0)
duration = block.GlobalTimecode() / timecodeScale - lastFrames[0].pts;
if (duration > 0) {
for (int i = 0; i < lastFrames.size(); i++) {
// since there can be multiple frames in one block, split the duration evenly between them
// giving the remainder to the latter blocks
int remainder = duration % lastFrames.size() >= lastFrames.size() - i ? 1 : 0;
lastFrames[i].duration = duration / lastFrames.size() + remainder;
AddFrame(lastFrames[i]);
}
lastFrames.clear();
}
} else if (ptsReorder.size() - currentFrame > MAX_DECODE_DELAY + 1) {
map<TimeValue64, TimeValue>::iterator duration;
MatroskaFrame &curr = lastFrames[currentFrame];
MatroskaFrame &next = lastFrames[currentFrame+1];
currentFrame++;
// pts -> dts works this way: we assume that the first frame has correct dts
// (we start at a keyframe, so pts = dts), and then we fill up a buffer with
// frames until we have the frame whose pts is equal to the next dts
// Then, we sort this buffer, extract the pts as dts, and calculate the duration.
ptsReorder.sort();
next.dts = *(++ptsReorder.begin());
ptsReorder.pop_front();
// Duration calculation has to be done between consecutive pts. Since we reorder
// the pts into the dts, which have to be in order, we calculate the duration then
// from the dts, then save it for the frame with the same pts.
durationForPTS[curr.dts] = next.dts - curr.dts;
duration = durationForPTS.find(lastFrames[0].pts);
if (duration != durationForPTS.end()) {
lastFrames[0].duration = duration->second;
AddFrame(lastFrames[0]);
lastFrames.erase(lastFrames.begin());
durationForPTS.erase(duration);
currentFrame--;
}
}
for (int i = 0; i < block.NumberFrames(); i++) {
MatroskaFrame newFrame;
newFrame.pts = block.GlobalTimecode() / timecodeScale;
newFrame.dts = newFrame.pts;
if (frameDuration > 0)
newFrame.duration = frameDuration;
else
newFrame.duration = defaultDuration;
newFrame.offset = block.GetDataPosition(i);
newFrame.size = block.GetFrameSize(i);
newFrame.flags = flags;
if (type == track_subtitle) {
newFrame.buffer = &block.GetBuffer(i);
AddFrame(newFrame);
}
else {
lastFrames.push_back(newFrame);
if (!usesLacing && type == track_video) {
//Codecprintf(NULL, "push_back pts %lld dts %lld\n", newFrame.pts, newFrame.dts);
ptsReorder.push_back(newFrame.pts);
}
}
newFrame.buffer = NULL;
}
}
void MatroskaTrack::AddFrame(MatroskaFrame &frame)
{
ComponentResult err = noErr;
TimeValue sampleTime = 0;
TimeValue64 displayOffset = frame.pts - frame.dts;
if (desc == NULL) return;
if (frame.duration == 0 && type != track_subtitle) {
// try to correct duplicate timestamps
// not that we can be sure what the real duration is...
frame.duration = MAX(defaultDuration, 1);
}
if (type != track_video)
frame.flags &= ~mediaSampleDroppable;
if (type == track_subtitle && !is_vobsub) {
Handle packet=NULL; unsigned start=0, end=0;
if (frame.size > 0 && frame.duration > 0)
subtitleSerializer->pushLine((const char*)frame.buffer->Buffer(), frame.buffer->Size(), frame.pts, frame.pts + frame.duration);
packet = subtitleSerializer->popPacket(&start, &end);
if (packet) {
err = AddMediaSample(theMedia, packet, 0, GetHandleSize(packet), end - start, desc, 1, 0, &sampleTime);
if (err) {
Codecprintf(NULL, "MKV: error adding subtitle sample %d\n", (int)err);
return;
}
DisposeHandle(packet);
frame.pts = start;
frame.duration = end - start;
} else return;
} else if (sampleTable) {
if(frame.duration) {
SInt64 sampleNum;
//assert(displayOffset >= 0);
//Codecprintf(NULL, "Add frame offset %lld, size %lld, duration %ld, offset %lld\n", frame.offset, frame.size, frame.duration, displayOffset);
err = QTSampleTableAddSampleReferences(sampleTable, frame.offset, frame.size, frame.duration,
displayOffset, 1, frame.flags, qtSampleDesc, &sampleNum);
if (err) {
Codecprintf(NULL, "MKV: error adding sample reference to table %d\n", (int)err);
return;
}
if (firstSample == -1)
firstSample = sampleNum;
numSamples++;
}
} else {
SampleReference64Record sample;
sample.dataOffset = SInt64ToWide(frame.offset);
sample.dataSize = frame.size;
sample.durationPerSample = frame.duration;
sample.numberOfSamples = 1;
sample.sampleFlags = frame.flags;
err = AddMediaSampleReferences64(theMedia, desc, 1, &sample, &sampleTime);
if (err) {
Codecprintf(NULL, "MKV: error adding sample reference to media %d\n", (int)err);
return;
}
}
// add to track immediately if subtitle, otherwise we let it be added elsewhere when we can do several at once
if (type == track_subtitle) {
err = InsertMediaIntoTrack(theTrack, frame.pts, sampleTime, frame.duration, fixed1);
if (err) {
Codecprintf(NULL, "MKV: error adding subtitle media into track %d\n", (int)err);
return;
}
} else {
durationSinceZeroSum += frame.duration;
displayOffsetSum += displayOffset;
if (displayOffsetSum == 0) {
durationToAdd += durationSinceZeroSum;
durationSinceZeroSum = 0;
}
}
}
void MatroskaTrack::AddSamplesToTrack()
{
OSStatus err = noErr;
if (type == track_subtitle)
return; // handled in AddFrame()
if (durationToAdd == 0 && numSamples == 0)
// nothing to add
return;
if (sampleTable) {
if (firstSample == -1)
return; // nothing to add
err = AddSampleTableToMedia(theMedia, sampleTable, firstSample, numSamples, NULL);
if (err) {
Codecprintf(NULL, "MKV: error adding sample table to the media %d\n", (int)err);
durationToAdd = 0;
firstSample = -1;
numSamples = 0;
return;
}
firstSample = -1;
numSamples = 0;
}
err = InsertMediaIntoTrack(theTrack, -1, maxLoadedTime, durationToAdd, fixed1);
if (err)
Codecprintf(NULL, "MKV: error inserting media into track %d\n", (int)err);
if (!err) {
if (!maxLoadedTime && lowestPTS)
SetTrackOffset(theTrack, lowestPTS / timecodeScale);
maxLoadedTime += durationToAdd;
}
durationToAdd = 0;
}
void MatroskaTrack::FinishTrack()
{
if (type == track_subtitle && !is_vobsub)
{
subtitleSerializer->setFinished();
do {
MatroskaFrame fr = {0};
AddFrame(fr); // add empty frames to flush the subtitle packet queue
} while (!subtitleSerializer->empty());
EndMediaEdits(theMedia);
} else {
if (lastFrames.size()) {
#if 0
// the values produced by this seem to be worse than before
// also, the dts in lastFrames seems to be out of order?
for (int i = 0; i < lastFrames.size()-1; i++) {
MatroskaFrame &curr = lastFrames[i], &next = lastFrames[i+1];
printf("cur: pts %lld dts %lld dur %d, next: pts %lld dts %lld dur %d\n", curr.pts, curr.dts, (int)curr.duration, next.pts, next.dts, (int)next.duration);
curr.duration = next.dts - curr.dts;
//assert(curr.duration < 65536 && curr.duration > 0);
}
#endif
for (int i = 0; i < lastFrames.size(); i++)
AddFrame(lastFrames[i]);
lastFrames.resize(0);
}
AddSamplesToTrack();
}
}