source: trunk/MatroskaImportPrivate.cpp @ 1246

Revision 1246, 42.0 KB checked in by astrange, 4 years ago (diff)

Reformat FIXME/TODO comments to show up in Xcode browser menu.

Closes #451.

Line 
1/*
2 *  MatroskaImportPrivate.cpp
3 *
4 *    MatroskaImportPrivate.cpp - C++ code for interfacing with libmatroska to import.
5 *
6 *
7 *  Copyright (c) 2006  David Conrad
8 *
9 *  This program is free software; you can redistribute it and/or
10 *  modify it under the terms of the GNU Lesser General Public
11 *  License as published by the Free Software Foundation;
12 *  version 2.1 of the License.
13 *
14 *  This program is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 *  Lesser General Public License for more details.
18 *
19 *  You should have received a copy of the GNU Lesser General Public
20 *  License along with this program; if not, write to the Free Software
21 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 */
24
25#include <QuickTime/QuickTime.h>
26#include <vector>
27#include <string>
28
29#include <ebml/EbmlHead.h>
30#include <ebml/EbmlSubHead.h>
31#include <matroska/KaxSegment.h>
32#include <matroska/KaxSeekHead.h>
33#include <matroska/KaxInfo.h>
34#include <matroska/KaxInfoData.h>
35#include <matroska/KaxTracks.h>
36#include <matroska/KaxTrackVideo.h>
37#include <matroska/KaxTrackAudio.h>
38#include <matroska/KaxChapters.h>
39#include <matroska/KaxCluster.h>
40#include <matroska/KaxBlock.h>
41#include <matroska/KaxAttachments.h>
42#include <matroska/KaxAttached.h>
43
44#include "MatroskaImport.h"
45#include "MatroskaCodecIDs.h"
46#include "SubImport.h"
47#include "CommonUtils.h"
48#include "Codecprintf.h"
49#include "bitstream_info.h"
50
51extern "C" {
52#include "avutil.h"
53#define CodecType AVCodecType
54#include "ff_private.h"
55#undef CodecType       
56}
57
58using namespace std;
59using namespace libmatroska;
60
61bool MatroskaImport::OpenFile()
62{
63        bool valid = true;
64        int upperLevel = 0;
65       
66        ioHandler = new DataHandlerCallback(dataRef, dataRefType, MODE_READ);
67       
68        aStream = new EbmlStream(*ioHandler);
69       
70        el_l0 = aStream->FindNextID(EbmlHead::ClassInfos, ~0);
71        if (el_l0 != NULL) {
72                EbmlElement *dummyElt = NULL;
73               
74                el_l0->Read(*aStream, EbmlHead::ClassInfos.Context, upperLevel, dummyElt, true);
75                EbmlHead *head = static_cast<EbmlHead *>(el_l0);
76               
77                EDocType docType = GetChild<EDocType>(*head);
78                if (string(docType) != "matroska") {
79                        Codecprintf(NULL, "Not a Matroska file\n");
80                        valid = false;
81                }
82               
83                EDocTypeReadVersion readVersion = GetChild<EDocTypeReadVersion>(*head);
84                if (UInt64(readVersion) > 2) {
85                        Codecprintf(NULL, "Matroska file too new to be read\n");
86                        valid = false;
87                }
88        } else {
89                Codecprintf(NULL, "Matroska file missing EBML Head\n");
90                valid = false;
91        }
92       
93        delete el_l0;
94        el_l0 = NULL;
95        return valid;
96}
97
98ComponentResult MatroskaImport::ProcessLevel1Element()
99{
100        int upperLevel = 0;
101        EbmlElement *dummyElt = NULL;
102       
103        if (EbmlId(*el_l1) == KaxInfo::ClassInfos.GlobalId) {
104                el_l1->Read(*aStream, KaxInfo::ClassInfos.Context, upperLevel, dummyElt, true);
105                return ReadSegmentInfo(*static_cast<KaxInfo *>(el_l1));
106                                                               
107        } else if (EbmlId(*el_l1) == KaxTracks::ClassInfos.GlobalId) {
108                el_l1->Read(*aStream, KaxTracks::ClassInfos.Context, upperLevel, dummyElt, true);
109                return ReadTracks(*static_cast<KaxTracks *>(el_l1));
110               
111        } else if (EbmlId(*el_l1) == KaxChapters::ClassInfos.GlobalId) {
112                el_l1->Read(*aStream, KaxChapters::ClassInfos.Context, upperLevel, dummyElt, true);
113                return ReadChapters(*static_cast<KaxChapters *>(el_l1));
114               
115        } else if (EbmlId(*el_l1) == KaxAttachments::ClassInfos.GlobalId) {
116                ComponentResult res;
117                el_l1->Read(*aStream, KaxAttachments::ClassInfos.Context, upperLevel, dummyElt, true);
118                res = ReadAttachments(*static_cast<KaxAttachments *>(el_l1));
119                PrerollSubtitleTracks();
120                return res;
121        } else if (EbmlId(*el_l1) == KaxSeekHead::ClassInfos.GlobalId) {
122                el_l1->Read(*aStream, KaxSeekHead::ClassInfos.Context, upperLevel, dummyElt, true);
123                return ReadMetaSeek(*static_cast<KaxSeekHead *>(el_l1));
124        }
125        return noErr;
126}
127
128ComponentResult MatroskaImport::SetupMovie()
129{
130        ComponentResult err = noErr;
131        // once we've read the Tracks and Segment Info elements and Chapters if it's in the seek head,
132        // we don't need to read any more of the file
133        bool done = false;
134       
135        el_l0 = aStream->FindNextID(KaxSegment::ClassInfos, ~0);
136        if (!el_l0) return err;         // nothing in the file
137       
138        segmentOffset = static_cast<KaxSegment *>(el_l0)->GetDataStart();
139       
140        SetAutoTrackAlternatesEnabled(theMovie, false);
141       
142        while (!done && NextLevel1Element()) {
143                if (EbmlId(*el_l1) == KaxCluster::ClassInfos.GlobalId) {
144                        // all header elements are before clusters in sane files
145                        done = true;
146                } else
147                        err = ProcessLevel1Element();
148               
149                if (err) return err;
150        }
151       
152        // some final setup of info across elements since they can come in any order
153        for (int i = 0; i < tracks.size(); i++) {
154                tracks[i].timecodeScale = timecodeScale;
155               
156                // chapter tracks have to be associated with other enabled tracks to display
157                if (chapterTrack) {
158                        AddTrackReference(tracks[i].theTrack, chapterTrack, kTrackReferenceChapterList, NULL);
159                }
160        }
161       
162        return err;
163}
164
165EbmlElement * MatroskaImport::NextLevel1Element()
166{
167        int upperLevel = 0;
168       
169        if (el_l1) {
170                el_l1->SkipData(*aStream, el_l1->Generic().Context);
171                delete el_l1;
172        }
173       
174        el_l1 = aStream->FindNextElement(el_l0->Generic().Context, upperLevel, 0xFFFFFFFFL, true);
175       
176        // dummy element -> probably corrupt file, search for next element in meta seek and continue from there
177        if (el_l1 && el_l1->IsDummy()) {
178                vector<MatroskaSeek>::iterator nextElt;
179                MatroskaSeek currElt;
180                currElt.segmentPos = el_l1->GetElementPosition();
181                currElt.idLength = currElt.ebmlID = 0;
182               
183                nextElt = find_if(levelOneElements.begin(), levelOneElements.end(), bind2nd(greater<MatroskaSeek>(), currElt));
184                if (nextElt != levelOneElements.end()) {
185                        SetContext(nextElt->GetSeekContext(segmentOffset));
186                        NextLevel1Element();
187                }
188        }
189       
190        return el_l1;
191}
192
193ComponentResult MatroskaImport::ReadSegmentInfo(KaxInfo &segmentInfo)
194{
195        if (seenInfo)
196                return noErr;
197       
198        KaxDuration & duration = GetChild<KaxDuration>(segmentInfo);
199        KaxTimecodeScale & timecodeScale = GetChild<KaxTimecodeScale>(segmentInfo);
200        KaxTitle & title = GetChild<KaxTitle>(segmentInfo);
201        KaxWritingApp & writingApp = GetChild<KaxWritingApp>(segmentInfo);
202       
203        movieDuration = Float64(duration);
204        this->timecodeScale = UInt64(timecodeScale);
205        SetMovieTimeScale(theMovie, S64Div(1000000000L, this->timecodeScale));
206       
207        QTMetaDataRef movieMetaData;
208        OSStatus err = QTCopyMovieMetaData(theMovie, &movieMetaData);
209        if (err == noErr) {
210                OSType key;
211                if (!title.IsDefaultValue()) {
212                        key = kQTMetaDataCommonKeyDisplayName;
213                        QTMetaDataAddItem(movieMetaData, 
214                                                          kQTMetaDataStorageFormatQuickTime, kQTMetaDataKeyFormatCommon, 
215                                                          (UInt8 *)&key, sizeof(key), 
216                                                          (UInt8 *)UTFstring(title).GetUTF8().c_str(), 
217                                                          UTFstring(title).GetUTF8().size(), 
218                                                          kQTMetaDataTypeUTF8, NULL);
219                }
220                if (!writingApp.IsDefaultValue()) {
221                        key = kQTMetaDataCommonKeySoftware;
222                        QTMetaDataAddItem(movieMetaData, 
223                                                          kQTMetaDataStorageFormatQuickTime, kQTMetaDataKeyFormatCommon, 
224                                                          (UInt8 *)&key, sizeof(key), 
225                                                          (UInt8 *)UTFstring(writingApp).GetUTF8().c_str(), 
226                                                          UTFstring(writingApp).GetUTF8().size(), 
227                                                          kQTMetaDataTypeUTF8, NULL);
228                }
229                QTMetaDataRelease(movieMetaData);
230        }
231        seenInfo = true;
232        return noErr;
233}
234
235ComponentResult MatroskaImport::ReadTracks(KaxTracks &trackEntries)
236{
237        Track firstVideoTrack = NULL; short firstVideoTrackLang = 0; bool videoEnabled = false;
238        Track firstAudioTrack = NULL; short firstAudioTrackLang = 0; bool audioEnabled = false;
239        Track firstSubtitleTrack = NULL; short firstSubtitleTrackLang = 0; bool subtitleEnabled = false;
240        ComponentResult err = noErr;
241       
242        if (seenTracks)
243                return noErr;
244       
245        // Since creating a subtitle track requires a video track to have already been created
246    // (so that it can be sized to fit exactly over the video track), we go through the
247    // track entries in two passes, first to add audio/video, second to add subtitle tracks.
248    for (int pass = 1; pass <= 2; pass++) {
249                for (int i = 0; i < trackEntries.ListSize(); i++) {
250                        if (EbmlId(*trackEntries[i]) != KaxTrackEntry::ClassInfos.GlobalId)
251                                continue;
252                        KaxTrackEntry & track = *static_cast<KaxTrackEntry *>(trackEntries[i]);
253                        KaxTrackNumber & number = GetChild<KaxTrackNumber>(track);
254                        KaxTrackType & type = GetChild<KaxTrackType>(track);
255                        KaxTrackDefaultDuration & defaultDuration = GetChild<KaxTrackDefaultDuration>(track);
256                        KaxTrackFlagDefault & enabled = GetChild<KaxTrackFlagDefault>(track);
257                        KaxTrackFlagLacing & lacing = GetChild<KaxTrackFlagLacing>(track);
258                        MatroskaTrack mkvTrack;
259                       
260                        mkvTrack.number = uint16(number);
261                        mkvTrack.type = uint8(type);
262                        mkvTrack.defaultDuration = uint32(defaultDuration) / float(timecodeScale) + .5;
263                        mkvTrack.isEnabled = uint8(enabled);
264                        mkvTrack.usesLacing = uint8(lacing);
265                       
266                        KaxTrackLanguage & trackLang = GetChild<KaxTrackLanguage>(track);
267                        KaxTrackName & trackName = GetChild<KaxTrackName>(track);
268                        KaxContentEncodings * encodings = FindChild<KaxContentEncodings>(track);
269                        short qtLang = ISO639_2ToQTLangCode(string(trackLang).c_str());
270                       
271                        switch (uint8(type)) {
272                                case track_video:
273                                        if (pass == 2) continue;
274                                        err = AddVideoTrack(track, mkvTrack);
275                                        if (err) return err;
276                                       
277                                        if (mkvTrack.isEnabled)
278                                                videoEnabled = true;
279                                       
280                                        if (firstVideoTrack && qtLang != firstVideoTrackLang)
281                                                SetTrackAlternate(firstVideoTrack, mkvTrack.theTrack);
282                                        else {
283                                                firstVideoTrack = mkvTrack.theTrack;
284                                                firstVideoTrackLang = qtLang;
285                                        }
286                                        break;
287                                       
288                                case track_audio:
289                                        if (pass == 2) continue;
290                                        err = AddAudioTrack(track, mkvTrack);
291                                        if (err) return err;
292                                       
293                                        if (mkvTrack.isEnabled)
294                                                audioEnabled = true;
295                                       
296                                        if (firstAudioTrack && qtLang != firstAudioTrackLang)
297                                                SetTrackAlternate(firstAudioTrack, mkvTrack.theTrack);
298                                        else {
299                                                firstAudioTrack = mkvTrack.theTrack;
300                                                firstAudioTrackLang = qtLang;
301                                        }
302                                        break;
303                                       
304                                case track_subtitle:
305                                        if (pass == 1) continue;
306                                        err = AddSubtitleTrack(track, mkvTrack);
307                                        if (err) return err;
308                                        if (mkvTrack.theTrack == NULL) continue;
309                                       
310                                        if (mkvTrack.isEnabled)
311                                                subtitleEnabled = true;
312                                       
313                                        if (firstSubtitleTrack && qtLang != firstSubtitleTrackLang)
314                                                SetTrackAlternate(firstSubtitleTrack, mkvTrack.theTrack);
315                                        else {
316                                                firstSubtitleTrack = mkvTrack.theTrack;
317                                                firstSubtitleTrackLang = qtLang;
318                                        }
319                                        break;
320                                       
321                                case track_complex:
322                                case track_logo:
323                                case track_buttons:
324                                case track_control:
325                                        // not likely to be implemented soon
326                                default:
327                                        continue;
328                        }
329                       
330                        if (encodings) {
331                                err = ReadContentEncodings(*encodings, mkvTrack);
332                                // just ignore the track if there's some problem with this element
333                                if (err) continue;
334                        }
335                       
336                        SetMediaLanguage(mkvTrack.theMedia, qtLang);
337                       
338                        if (!trackName.IsDefaultValue()) {
339                                QTMetaDataRef trackMetaData;
340                                err = QTCopyTrackMetaData(mkvTrack.theTrack, &trackMetaData);
341                               
342                                if (err == noErr) {
343                                        OSType key = 'name';
344                                        // QuickTime differentiates between the title of a track and its name
345                                        // so we set both
346                                        QTMetaDataAddItem(trackMetaData,
347                                                                          kQTMetaDataStorageFormatQuickTime,
348                                                                          kQTMetaDataKeyFormatCommon,
349                                                                          (UInt8 *)&key, sizeof(key),
350                                                                          (UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
351                                                                          UTFstring(trackName).GetUTF8().size(),
352                                                                          kQTMetaDataTypeUTF8, NULL);
353                                       
354                                        QTMetaDataAddItem(trackMetaData,
355                                                                          kQTMetaDataStorageFormatUserData,
356                                                                          kQTMetaDataKeyFormatUserData,
357                                                                          (UInt8 *)&key, sizeof(key),
358                                                                          (UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
359                                                                          UTFstring(trackName).GetUTF8().size(),
360                                                                          kQTMetaDataTypeUTF8, NULL);
361                                       
362                                        QTMetaDataRelease(trackMetaData);
363                                }
364                        }
365                        tracks.push_back(mkvTrack);
366                }
367        }
368       
369        for (int i = 0; i < tracks.size(); i++) {
370                SetTrackEnabled(tracks[i].theTrack, tracks[i].isEnabled);
371        }
372        // ffmpeg used to write a TrackDefault of 0 for all tracks
373        // ensure that at least one track of each media type is enabled, if none were originally
374        // this picks the first track, which may not be the best, but the situation is quite rare anyway
375        // FIXME: properly choose tracks based on forced/default/language flags, and consider turning auto-alternates back on
376        if (!videoEnabled && firstVideoTrack)
377                SetTrackEnabled(firstVideoTrack, 1);
378        if (!audioEnabled && firstAudioTrack)
379                SetTrackEnabled(firstAudioTrack, 1);
380        if (!subtitleEnabled && firstSubtitleTrack)
381                SetTrackEnabled(firstSubtitleTrack, 1);
382       
383        seenTracks = true;
384        return noErr;
385}
386
387ComponentResult MatroskaImport::ReadContentEncodings(KaxContentEncodings &encodings, MatroskaTrack &mkvTrack)
388{
389        KaxContentEncoding & encoding = GetChild<KaxContentEncoding>(encodings);
390        int scope = uint32(GetChild<KaxContentEncodingScope>(encoding));
391        int type = uint32(GetChild<KaxContentEncodingType>(encoding));
392       
393        if (scope != 1) {
394                Codecprintf(NULL, "Content encoding scope of %d not expected\n", scope);
395                return -1;
396        }
397        if (type != 0) {
398                Codecprintf(NULL, "Encrypted track\n");
399                return -2;
400        }
401       
402        KaxContentCompression & comp = GetChild<KaxContentCompression>(encoding);
403        int algo = uint32(GetChild<KaxContentCompAlgo>(comp));
404       
405        if (algo != 0)
406                Codecprintf(NULL, "MKV: warning, track compression algorithm %d not zlib\n", algo);
407       
408        if ((*mkvTrack.desc)->dataFormat != kSubFormatVobSub)
409                Codecprintf(NULL, "MKV: warning, compressed track %4.4s probably won't work (not VobSub)\n", &(*mkvTrack.desc)->dataFormat);
410       
411        Handle ext = NewHandle(1);
412        **ext = algo;
413       
414        if (mkvTrack.type == track_audio)
415                AddSoundDescriptionExtension((SoundDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
416        else
417                AddImageDescriptionExtension((ImageDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
418       
419        return noErr;
420}
421
422ComponentResult MatroskaImport::AddVideoTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
423{
424        ComponentResult err = noErr;
425        ImageDescriptionHandle imgDesc;
426        Fixed width, height;
427       
428        KaxTrackVideo &videoTrack = GetChild<KaxTrackVideo>(kaxTrack);
429        KaxVideoDisplayWidth & disp_width = GetChild<KaxVideoDisplayWidth>(videoTrack);
430        KaxVideoDisplayHeight & disp_height = GetChild<KaxVideoDisplayHeight>(videoTrack);
431        KaxVideoPixelWidth & pxl_width = GetChild<KaxVideoPixelWidth>(videoTrack);
432        KaxVideoPixelHeight & pxl_height = GetChild<KaxVideoPixelHeight>(videoTrack);
433       
434        // Use the PixelWidth if the DisplayWidth is not set
435        if (disp_width.ValueIsSet() && disp_height.ValueIsSet()) {
436                // some files ignore the spec and treat display width/height as a ratio, not as pixels
437                // so scale the display size to be at least as large as the pixel size here
438                // but don't let it be bigger in both dimensions
439                float horizRatio = float(uint32(pxl_width)) / uint32(disp_width);
440                float vertRatio = float(uint32(pxl_height)) / uint32(disp_height);
441               
442                if (vertRatio > horizRatio && vertRatio > 1) {
443                        width = FloatToFixed(uint32(disp_width) * vertRatio);
444                        height = FloatToFixed(uint32(disp_height) * vertRatio);
445                } else if (horizRatio > 1) {
446                        width = FloatToFixed(uint32(disp_width) * horizRatio);
447                        height = FloatToFixed(uint32(disp_height) * horizRatio);
448                } else {
449                        float dar = uint32(disp_width) / (float)uint32(disp_height);
450                        float p_ratio = uint32(pxl_width) / (float)uint32(pxl_height);
451                       
452                        if (dar > p_ratio) {
453                                width  = FloatToFixed(uint32(pxl_height) * dar);
454                                height = IntToFixed(uint32(pxl_height));
455                        } else {
456                                width  = IntToFixed(uint32(pxl_width));
457                                height = FloatToFixed(uint32(pxl_width) / dar);
458                        }
459                }                               
460        } else if (pxl_width.ValueIsSet() && pxl_height.ValueIsSet()) {
461                width = IntToFixed(uint32(pxl_width));
462                height = IntToFixed(uint32(pxl_height));
463        } else {
464                Codecprintf(NULL, "MKV: Video has unknown dimensions.\n");
465                return invalidTrack;
466        }
467               
468        mkvTrack.theTrack = NewMovieTrack(theMovie, width, height, kNoVolume);
469        if (mkvTrack.theTrack == NULL)
470                return GetMoviesError();
471       
472        mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
473        if (mkvTrack.theMedia == NULL) {
474                DisposeMovieTrack(mkvTrack.theTrack);
475                return GetMoviesError();
476        }
477       
478        imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
479        (*imgDesc)->idSize = sizeof(ImageDescription);
480    (*imgDesc)->width = uint16(pxl_width);
481    (*imgDesc)->height = uint16(pxl_height);
482    (*imgDesc)->frameCount = 1;
483        (*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
484    (*imgDesc)->depth = 24;
485    (*imgDesc)->clutID = -1;
486       
487        set_track_clean_aperture_ext(imgDesc, width, height, IntToFixed(uint32(pxl_width)), IntToFixed(uint32(pxl_height)));
488        set_track_colorspace_ext(imgDesc, width, height);
489        mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
490       
491        // this sets up anything else needed in the description for the specific codec.
492        err = MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
493        if (err) return err;
494       
495        // video tracks can have display offsets, so create a sample table
496        err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
497        if (err) return err;
498       
499        err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
500               
501        return err;
502}
503
504ComponentResult MatroskaImport::AddAudioTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
505{
506        SoundDescriptionHandle sndDesc = NULL;
507        AudioStreamBasicDescription asbd = {0};
508        AudioChannelLayout acl = {0};
509    AudioChannelLayout *pacl = &acl;
510    ByteCount acl_size = sizeof(acl);
511        ByteCount ioSize = sizeof(asbd);
512        ByteCount cookieSize = 0;
513        Handle cookieH = NULL;
514        Ptr cookie = NULL;
515       
516        mkvTrack.theTrack = NewMovieTrack(theMovie, 0, 0, kFullVolume);
517        if (mkvTrack.theTrack == NULL)
518                return GetMoviesError();
519       
520        mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'soun', GetMovieTimeScale(theMovie), dataRef, dataRefType);
521        if (mkvTrack.theMedia == NULL) {
522                DisposeMovieTrack(mkvTrack.theTrack);
523                return GetMoviesError();
524        }
525       
526        KaxTrackAudio & audioTrack = GetChild<KaxTrackAudio>(kaxTrack);
527        KaxAudioSamplingFreq & sampleFreq = GetChild<KaxAudioSamplingFreq>(audioTrack);
528        KaxAudioChannels & numChannels = GetChild<KaxAudioChannels>(audioTrack);
529        KaxAudioBitDepth & bitDepth = GetChild<KaxAudioBitDepth>(audioTrack);
530       
531        if (bitDepth.ValueIsSet()) asbd.mBitsPerChannel = uint32(bitDepth);
532        asbd.mFormatID = MkvGetFourCC(&kaxTrack);
533        asbd.mSampleRate = Float64(sampleFreq);
534        asbd.mChannelsPerFrame = uint32(numChannels);
535       
536        MkvFinishAudioDescription(&kaxTrack, &cookieH, &asbd, &acl);
537        if (cookieH) {
538                cookie = *cookieH;
539                cookieSize = GetHandleSize(cookieH);
540        }
541       
542        // get more info about the codec
543        AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, cookieSize, cookie, &ioSize, &asbd);
544        if(asbd.mChannelsPerFrame == 0)
545                asbd.mChannelsPerFrame = 1;             // avoid a div by zero
546        if (!asbd.mFramesPerPacket && !asbd.mBytesPerPacket && asbd.mFormatID == kAudioFormatMPEGLayer3) //see ff_private.c initialize_audio_map
547                asbd.mFramesPerPacket = 1152;
548       
549        // FIXME: mChannelLayoutTag == 0 is valid
550        // but we don't use channel position lists (yet) so it's safe for now
551        if (acl.mChannelLayoutTag == 0) acl = GetDefaultChannelLayout(&asbd);
552        if (acl.mChannelLayoutTag == 0) {
553                pacl = NULL;
554                acl_size = 0;
555        }
556       
557        OSStatus err = QTSoundDescriptionCreate(&asbd, pacl, acl_size, cookie, cookieSize, 
558                                                                                        kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
559        DisposeHandle(cookieH);
560        if (err) {
561                Codecprintf(NULL, "Borked audio track entry, hoping we can parse the track for asbd\n");
562                DisposeHandle((Handle)sndDesc);
563                return noErr;
564        }
565       
566        mkvTrack.desc = (SampleDescriptionHandle) sndDesc;
567       
568        err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
569        if (err) return err;
570       
571        err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
572        return err;
573}
574
575ComponentResult MatroskaImport::AddSubtitleTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
576{
577        Fixed trackWidth, trackHeight;
578        Rect movieBox;
579        MediaHandler mh;
580        ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
581        mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
582       
583        // we assume that a video track has already been created, so we can set the subtitle track
584        // to have the same dimensions as it. Note that this doesn't work so well with multiple
585        // video tracks with different dimensions; but QuickTime doesn't expect that; how should we handle them?
586        GetMovieBox(theMovie, &movieBox);
587        trackWidth = IntToFixed(movieBox.right - movieBox.left);
588        trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
589       
590        (*imgDesc)->idSize = sizeof(ImageDescription);
591        (*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
592        (*imgDesc)->frameCount = 1;
593        (*imgDesc)->depth = 32;
594        (*imgDesc)->clutID = -1;
595       
596        if ((*imgDesc)->cType == kSubFormatVobSub) {
597                int width, height;
598               
599                // bitmap width & height is in the codec private in text format
600                KaxCodecPrivate & idxFile = GetChild<KaxCodecPrivate>(kaxTrack);
601                string idxFileStr((char *)idxFile.GetBuffer(), idxFile.GetSize());
602                string::size_type loc = idxFileStr.find("size:", 0);
603                if (loc == string::npos)
604                        return -1;
605               
606                sscanf(&idxFileStr.c_str()[loc], "size: %dx%d", &width, &height);
607                (*imgDesc)->width = width;
608                (*imgDesc)->height = height;
609               
610                if (trackWidth == 0 || trackHeight == 0) {
611                        trackWidth = IntToFixed(width);
612                        trackHeight = IntToFixed(height);
613                }
614               
615                set_track_colorspace_ext(imgDesc, width, height);
616               
617                mkvTrack.theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
618                if (mkvTrack.theTrack == NULL)
619                        return GetMoviesError();
620               
621                mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
622                if (mkvTrack.theMedia == NULL)
623                        return GetMoviesError();
624               
625                // finally, say that we're transparent
626                mh = GetMediaHandler(mkvTrack.theMedia);
627                SetSubtitleMediaHandlerTransparent(mh);
628               
629                // subtitle tracks should be above the video track, which should be layer 0
630                SetTrackLayer(mkvTrack.theTrack, -1);
631               
632                mkvTrack.is_vobsub = true;
633               
634        } else if ((*imgDesc)->cType == kSubFormatUTF8 || (*imgDesc)->cType == kSubFormatSSA || (*imgDesc)->cType == kSubFormatASS) {
635                if ((*imgDesc)->cType == kSubFormatASS) (*imgDesc)->cType = kSubFormatSSA; // no real reason to treat them differently
636                UInt32 emptyDataRefExtension[2]; // FIXME: the various uses of this bit of code should be unified
637                mkvTrack.subDataRefHandler = NewHandleClear(sizeof(Handle) + 1);
638                emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
639                emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
640               
641                PtrAndHand(&emptyDataRefExtension[0], mkvTrack.subDataRefHandler, sizeof(emptyDataRefExtension));
642
643                mkvTrack.theTrack = CreatePlaintextSubTrack(theMovie, imgDesc, GetMovieTimeScale(theMovie), mkvTrack.subDataRefHandler, HandleDataHandlerSubType, (*imgDesc)->cType, NULL, movieBox);
644                if (mkvTrack.theTrack == NULL)
645                        return GetMoviesError();
646               
647                mkvTrack.theMedia = GetTrackMedia(mkvTrack.theTrack);
648                mkvTrack.is_vobsub = false;
649
650                BeginMediaEdits(mkvTrack.theMedia);
651        } else {
652                Codecprintf(NULL, "MKV: Unsupported subtitle type\n");
653                return noErr;
654        }
655       
656        // this sets up anything else needed in the description for the specific codec.
657        return MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
658}
659
660ComponentResult MatroskaImport::ReadChapters(KaxChapters &chapterEntries)
661{
662        KaxEditionEntry & edition = GetChild<KaxEditionEntry>(chapterEntries);
663        UInt32 emptyDataRefExtension[2];
664       
665        if (seenChapters)
666                return noErr;
667       
668        chapterTrack = NewMovieTrack(theMovie, 0, 0, kNoVolume);
669        if (chapterTrack == NULL) {
670                Codecprintf(NULL, "MKV: Error creating chapter track %d\n", GetMoviesError());
671                return GetMoviesError();
672        }
673       
674        // we use a handle data reference here because I don't see any way to add textual
675        // sample references (TextMediaAddTextSample() will behave the same as AddSample()
676        // in that it modifies the original file if that's the data reference of the media)
677        Handle dataRef = NewHandleClear(sizeof(Handle) + 1);
678
679        emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
680        emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
681       
682        PtrAndHand(&emptyDataRefExtension[0], dataRef, sizeof(emptyDataRefExtension));
683       
684        Media chapterMedia = NewTrackMedia(chapterTrack, TextMediaType, GetMovieTimeScale(theMovie), 
685                                                                           dataRef, HandleDataHandlerSubType);
686        if (chapterMedia == NULL) {
687                OSErr err = GetMoviesError();
688                Codecprintf(NULL, "MKV: Error creating chapter media %d\n", err);
689                DisposeMovieTrack(chapterTrack);
690                return err;
691        }
692       
693        // Name the chapter track "Chapters" for easy distinguishing
694        QTMetaDataRef trackMetaData;
695        OSErr err = QTCopyTrackMetaData(chapterTrack, &trackMetaData);
696        if (err == noErr) {
697                OSType key = kUserDataName;
698                string chapterName("Chapters");
699                QTMetaDataAddItem(trackMetaData, kQTMetaDataStorageFormatUserData,
700                                                  kQTMetaDataKeyFormatUserData, (UInt8 *)&key, sizeof(key),
701                                                  (UInt8 *)chapterName.c_str(), chapterName.size(),
702                                                  kQTMetaDataTypeUTF8, NULL);
703                QTMetaDataRelease(trackMetaData);
704        }
705       
706        BeginMediaEdits(chapterMedia);
707       
708        // tell the text media handler the upcoming text samples are
709        // encoded in Unicode with a byte order mark (BOM)
710        MediaHandler mediaHandler = GetMediaHandler(chapterMedia);
711        SInt32 dataPtr = kTextEncodingUnicodeDefault;
712        TextMediaSetTextSampleData(mediaHandler, &dataPtr, kTXNTextEncodingAttribute);
713       
714        KaxChapterAtom *chapterAtom = FindChild<KaxChapterAtom>(edition);
715        while (chapterAtom && chapterAtom->GetSize() > 0) {
716                AddChapterAtom(chapterAtom, chapterTrack);
717                chapterAtom = &GetNextChild<KaxChapterAtom>(edition, *chapterAtom);
718        }
719       
720        EndMediaEdits(chapterMedia);
721        SetTrackEnabled(chapterTrack, false);
722        seenChapters = true;
723        return noErr;
724}
725
726void MatroskaImport::AddChapterAtom(KaxChapterAtom *atom, Track chapterTrack)
727{
728        KaxChapterAtom *subChapter = FindChild<KaxChapterAtom>(*atom);
729        bool addThisChapter = true;
730       
731        // since QuickTime only supports linear chapter tracks (no nesting), only add chapter leaves
732        if (subChapter && subChapter->GetSize() > 0) {
733                while (subChapter && subChapter->GetSize() > 0) {
734                        KaxChapterFlagHidden &hideChapter = GetChild<KaxChapterFlagHidden>(*subChapter);
735                       
736                        if (!uint8_t(hideChapter)) {
737                                AddChapterAtom(subChapter, chapterTrack);
738                                addThisChapter = false;
739                        }
740                        subChapter = &GetNextChild(*atom, *subChapter);
741                }
742        } 
743        if (addThisChapter) {
744                // add the chapter to the track if it has no children
745                KaxChapterTimeStart & startTime = GetChild<KaxChapterTimeStart>(*atom);
746                KaxChapterDisplay & chapDisplay = GetChild<KaxChapterDisplay>(*atom);
747                KaxChapterString & chapString = GetChild<KaxChapterString>(chapDisplay);
748                MediaHandler mh = GetMediaHandler(GetTrackMedia(chapterTrack));
749                TimeValue start = UInt64(startTime) / timecodeScale;
750
751                if (start > movieDuration) {
752                        Codecprintf(NULL, "MKV: Chapter time is beyond the end of the file\n");
753                        return;
754                }
755               
756                Rect bounds = {0, 0, 0, 0};
757                TimeValue inserted;
758                OSErr err = TextMediaAddTextSample(mh, 
759                                                                                   const_cast<Ptr>(UTFstring(chapString).GetUTF8().c_str()), 
760                                                                                   UTFstring(chapString).GetUTF8().size(), 
761                                                                                   0, 0, 0, NULL, NULL, 
762                                                                                   teCenter, &bounds, dfClipToTextBox, 
763                                                                                   0, 0, 0, NULL, 1, &inserted);
764                if (err)
765                        Codecprintf(NULL, "MKV: Error adding text sample %d\n", err);
766                else {
767                        InsertMediaIntoTrack(chapterTrack, start, inserted, 1, fixed1);
768                }
769        }
770}
771
772ComponentResult MatroskaImport::ReadAttachments(KaxAttachments &attachments)
773{
774        KaxAttached *attachedFile = FindChild<KaxAttached>(attachments);
775       
776        while (attachedFile && attachedFile->GetSize() > 0) {
777                string fileMimeType = GetChild<KaxMimeType>(*attachedFile);
778                string fileName = UTFstring(GetChild<KaxFileName>(*attachedFile)).GetUTF8();
779               
780                /* The only attachments handled here are fonts, which currently can be truetype or opentype.
781                   application/x-* is probably not a permanent MIME type, but it is current practice... */
782                if ((fileMimeType == "application/x-truetype-font" || fileMimeType == "application/x-font-otf") &&
783                        ShouldImportFontFileName(fileName.c_str())) {
784                        KaxFileData & fontData = GetChild<KaxFileData>(*attachedFile);
785                       
786                        if (fontData.GetSize()) {
787                                ATSFontContainerRef container;
788                                ATSFontActivateFromMemory(fontData.GetBuffer(), fontData.GetSize(), kATSFontContextLocal, 
789                                                          kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &container);
790                        }
791                }
792               
793                bool isCoverArt = false, isJPEG;
794               
795                if (fileName == "cover.jpg") {
796                        isCoverArt = isJPEG = true;
797                } else if (fileName == "cover.png") {
798                        isCoverArt = true;
799                        isJPEG = false;
800                }
801               
802                if (isCoverArt) {
803                        KaxFileData & fileData = GetChild<KaxFileData>(*attachedFile);
804                        FourCharCode key = 'covr'; //iTunes cover art tag
805                        QTMetaDataRef movieMetaData;
806                        OSStatus err = QTCopyMovieMetaData(theMovie, &movieMetaData);
807
808                        err = QTMetaDataAddItem(movieMetaData, 
809                                                          kQTMetaDataStorageFormatiTunes, kQTMetaDataKeyFormatiTunesShortForm, 
810                                                          (UInt8 *)&key, sizeof(key),
811                                                          fileData.GetBuffer(), 
812                                                          fileData.GetSize(), 
813                                                          isJPEG ? kQTMetaDataTypeJPEGImage : kQTMetaDataTypePNGImage, NULL);
814                        if (err)
815                                Codecprintf(NULL, "MKV: Error adding cover art %d\n", err);
816                       
817                        QTMetaDataRelease(movieMetaData);
818                }
819               
820                attachedFile = &GetNextChild<KaxAttached>(attachments, *attachedFile);
821        }
822        return noErr;
823}
824
825ComponentResult MatroskaImport::ReadMetaSeek(KaxSeekHead &seekHead)
826{
827        ComponentResult err = noErr;
828        KaxSeek *seekEntry = FindChild<KaxSeek>(seekHead);
829       
830        // don't re-read a seek head that's already been read
831        uint64_t currPos = seekHead.GetElementPosition();
832        vector<MatroskaSeek>::iterator itr = levelOneElements.begin();
833        for (; itr != levelOneElements.end(); itr++) {
834                if (itr->GetID() == KaxSeekHead::ClassInfos.GlobalId && 
835                        itr->segmentPos + segmentOffset == currPos)
836                        return noErr;
837        }
838       
839        while (seekEntry && seekEntry->GetSize() > 0) {
840                MatroskaSeek newSeekEntry;
841                KaxSeekID & seekID = GetChild<KaxSeekID>(*seekEntry);
842                KaxSeekPosition & position = GetChild<KaxSeekPosition>(*seekEntry);
843                EbmlId elementID = EbmlId(seekID.GetBuffer(), seekID.GetSize());
844               
845                newSeekEntry.ebmlID = elementID.Value;
846                newSeekEntry.idLength = elementID.Length;
847                newSeekEntry.segmentPos = position;
848               
849                // recursively read seek heads that are pointed to by the current one
850                // as well as the level one elements we care about
851                if (elementID == KaxInfo::ClassInfos.GlobalId || 
852                        elementID == KaxTracks::ClassInfos.GlobalId || 
853                        elementID == KaxChapters::ClassInfos.GlobalId || 
854                        elementID == KaxAttachments::ClassInfos.GlobalId || 
855                        elementID == KaxSeekHead::ClassInfos.GlobalId) {
856                       
857                        MatroskaSeekContext savedContext = SaveContext();
858                        SetContext(newSeekEntry.GetSeekContext(segmentOffset));
859                        if (NextLevel1Element())
860                                err = ProcessLevel1Element();
861                       
862                        SetContext(savedContext);
863                        if (err) return err;
864                }
865               
866                levelOneElements.push_back(newSeekEntry);
867                seekEntry = &GetNextChild<KaxSeek>(seekHead, *seekEntry);
868        }
869       
870        sort(levelOneElements.begin(), levelOneElements.end());
871       
872        return noErr;
873}
874
875void MatroskaImport::ImportCluster(KaxCluster &cluster, bool addToTrack)
876{
877        KaxSegment & segment = *static_cast<KaxSegment *>(el_l0);
878        KaxClusterTimecode & clusterTime = GetChild<KaxClusterTimecode>(cluster);
879        CXXAutoreleasePool pool;
880                               
881        cluster.SetParent(segment);
882        cluster.InitTimecode(uint64(clusterTime), timecodeScale);
883       
884        for (int i = 0; i < cluster.ListSize(); i++) {
885                const EbmlId & elementID = EbmlId(*cluster[i]);
886                KaxInternalBlock *block = NULL;
887                uint32_t duration = 0;          // set to track's default duration in AddBlock if 0
888                short flags = 0;
889               
890                if (elementID == KaxBlockGroup::ClassInfos.GlobalId) {
891                        KaxBlockGroup & blockGroup = *static_cast<KaxBlockGroup *>(cluster[i]);
892                        KaxBlockDuration & blkDuration = GetChild<KaxBlockDuration>(blockGroup);
893                        block = &GetChild<KaxBlock>(blockGroup);
894                        if (blkDuration.ValueIsSet())
895                                duration = uint32(blkDuration);
896                        flags = blockGroup.ReferenceCount() > 0 ? mediaSampleNotSync : 0;
897                       
898                } else if (elementID == KaxSimpleBlock::ClassInfos.GlobalId) {
899                        KaxSimpleBlock & simpleBlock = *static_cast<KaxSimpleBlock *>(cluster[i]);
900                        block = &simpleBlock;
901                        if (!simpleBlock.IsKeyframe())
902                                flags |= mediaSampleNotSync;
903                        if (simpleBlock.IsDiscardable() && IsFrameDroppingEnabled())
904                                flags |= mediaSampleDroppable;
905                }
906               
907                if (block) {
908                        block->SetParent(cluster);
909                       
910                        for (int i = 0; i < tracks.size(); i++) {
911                                if (tracks[i].number == block->TrackNum()) {
912                                        tracks[i].AddBlock(*block, duration, flags);
913                                        break;
914                                }
915                        }
916                }
917        }
918       
919        if (addToTrack) {
920                for (int i = 0; i < tracks.size(); i++)
921                        tracks[i].AddSamplesToTrack();
922               
923                loadState = kMovieLoadStatePlayable;
924        }
925}
926
927MatroskaSeekContext MatroskaImport::SaveContext()
928{
929        MatroskaSeekContext ret = { el_l1, ioHandler->getFilePointer() };
930        el_l1 = NULL;
931        return ret;
932}
933
934void MatroskaImport::SetContext(MatroskaSeekContext context)
935{
936        if (el_l1)
937                delete el_l1;
938       
939        el_l1 = context.el_l1;
940        ioHandler->setFilePointer(context.position);
941}
942
943void MatroskaImport::PrerollSubtitleTracks()
944{
945        if (!seenTracks) return;
946               
947        for (int i = 0; i < tracks.size(); i++) {
948                MatroskaTrack *track = &tracks[i];
949               
950                if (track->type == track_subtitle) {
951                        Handle subtitleDescriptionExt;
952                        OSErr err = GetImageDescriptionExtension((ImageDescriptionHandle)track->desc, &subtitleDescriptionExt, kSubFormatSSA, 1);
953                       
954                        if (err || !subtitleDescriptionExt) continue;
955                       
956                        SubPrerollFromHeader(*subtitleDescriptionExt, GetHandleSize(subtitleDescriptionExt));
957                }
958        }
959}
960
961MatroskaTrack::MatroskaTrack()
962{
963        number = 0;
964        type = -1;
965        theTrack = NULL;
966        theMedia = NULL;
967        desc = NULL;
968        sampleTable = NULL;
969        qtSampleDesc = 0;
970        timecodeScale = 1000000;
971        maxLoadedTime = 0;
972        seenFirstBlock = false;
973        firstSample = -1;
974        numSamples = 0;
975        durationToAdd = 0;
976        displayOffsetSum = 0;
977        durationSinceZeroSum = 0;
978        subtitleSerializer = new CXXSubSerializer;
979        subDataRefHandler = NULL;
980        is_vobsub = false;
981        isEnabled = true;
982        defaultDuration = 0;
983        usesLacing = true;
984        currentFrame = 0;
985}
986
987MatroskaTrack::MatroskaTrack(const MatroskaTrack &copy)
988{
989        number = copy.number;
990        type = copy.type;
991        theTrack = copy.theTrack;
992        theMedia = copy.theMedia;
993       
994        if (copy.desc) {
995                desc = (SampleDescriptionHandle) NewHandle((*copy.desc)->descSize);
996                memcpy(*desc, *copy.desc, (*copy.desc)->descSize);
997        } else
998                desc = NULL;
999       
1000        sampleTable = copy.sampleTable;
1001        if (sampleTable)
1002                QTSampleTableRetain(sampleTable);
1003       
1004        qtSampleDesc = copy.qtSampleDesc;
1005        timecodeScale = copy.timecodeScale;
1006        maxLoadedTime = copy.maxLoadedTime;
1007       
1008        for (int i = 0; i < copy.lastFrames.size(); i++)
1009                lastFrames.push_back(copy.lastFrames[i]);
1010       
1011        seenFirstBlock = copy.seenFirstBlock;
1012        firstSample = copy.firstSample;
1013        numSamples = copy.numSamples;
1014        durationToAdd = copy.durationToAdd;
1015        displayOffsetSum = copy.displayOffsetSum;
1016        durationSinceZeroSum = copy.durationSinceZeroSum;
1017       
1018        subtitleSerializer = copy.subtitleSerializer;
1019        subtitleSerializer->retain();
1020               
1021        subDataRefHandler = copy.subDataRefHandler;
1022       
1023        is_vobsub = copy.is_vobsub;
1024        isEnabled = copy.isEnabled;
1025        defaultDuration = copy.defaultDuration;
1026        usesLacing = copy.usesLacing;
1027        currentFrame = copy.currentFrame;
1028}
1029
1030MatroskaTrack::~MatroskaTrack()
1031{
1032        if (desc)
1033                DisposeHandle((Handle) desc);
1034       
1035        if (sampleTable)
1036                QTSampleTableRelease(sampleTable);
1037       
1038        if (subtitleSerializer)
1039                subtitleSerializer->release();
1040}
1041
1042void MatroskaTrack::ParseFirstBlock(KaxInternalBlock &block)
1043{
1044        AudioStreamBasicDescription asbd = {0};
1045        AudioChannelLayout acl = {0};
1046        bool replaceSoundDesc = false;
1047       
1048        lowestPTS = block.GlobalTimecode();
1049       
1050        if (desc) {
1051                switch ((*desc)->dataFormat) {
1052                        case kAudioFormatAC3:
1053                                replaceSoundDesc = parse_ac3_bitstream(&asbd, &acl, block.GetBuffer(0).Buffer(), block.GetFrameSize(0));
1054                                break;
1055                }
1056        }
1057       
1058        if (replaceSoundDesc) {
1059                // successful in parsing, so the acl and asbd are more correct than what we generated in
1060                // AddAudioTrack() so replace our sound description
1061                SoundDescriptionHandle sndDesc = NULL;
1062               
1063                OSStatus err = QTSoundDescriptionCreate(&asbd, &acl, sizeof(AudioChannelLayout), NULL, 0, 
1064                                                        kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
1065                if (err == noErr) {
1066                        DisposeHandle((Handle) desc);
1067                        desc = (SampleDescriptionHandle) sndDesc;
1068                        err = QTSampleTableAddSampleDescription(sampleTable, desc, 0, &qtSampleDesc);
1069                }
1070        }
1071}
1072
1073void MatroskaTrack::AddBlock(KaxInternalBlock &block, uint32 duration, short flags)
1074{
1075        if (!seenFirstBlock) {
1076                ParseFirstBlock(block);
1077                seenFirstBlock = true;
1078        }
1079       
1080        // tracks w/ lacing can't have b-frames, and neither can any known audio codec
1081        if (usesLacing || type != track_video) {
1082                // don't add the blocks until we get one with a new timecode
1083                TimeValue64 duration = 0;
1084                if (lastFrames.size() > 0)
1085                        duration = block.GlobalTimecode() / timecodeScale - lastFrames[0].pts;
1086               
1087                if (duration > 0) {
1088                        for (int i = 0; i < lastFrames.size(); i++) {
1089                                // since there can be multiple frames in one block, split the duration evenly between them
1090                                // giving the remainder to the latter blocks
1091                                int remainder = duration % lastFrames.size() >= lastFrames.size() - i ? 1 : 0;
1092                               
1093                                lastFrames[i].duration = duration / lastFrames.size() + remainder;
1094                               
1095                                AddFrame(lastFrames[i]);
1096                        }
1097                        lastFrames.clear();
1098                }
1099        } else if (ptsReorder.size() - currentFrame > MAX_DECODE_DELAY + 1) {
1100                map<TimeValue64, TimeValue>::iterator duration;
1101                MatroskaFrame &curr = lastFrames[currentFrame];
1102                MatroskaFrame &next = lastFrames[currentFrame+1];
1103                currentFrame++;
1104               
1105                // pts -> dts works this way: we assume that the first frame has correct dts
1106                // (we start at a keyframe, so pts = dts), and then we fill up a buffer with
1107                // frames until we have the frame whose pts is equal to the next dts
1108                // Then, we sort this buffer, extract the pts as dts, and calculate the duration.
1109               
1110                ptsReorder.sort();
1111                next.dts = *(++ptsReorder.begin());
1112                ptsReorder.pop_front();
1113               
1114                // Duration calculation has to be done between consecutive pts. Since we reorder
1115                // the pts into the dts, which have to be in order, we calculate the duration then
1116                // from the dts, then save it for the frame with the same pts.
1117               
1118                durationForPTS[curr.dts] = next.dts - curr.dts;
1119               
1120                duration = durationForPTS.find(lastFrames[0].pts);
1121                if (duration != durationForPTS.end()) {
1122                        lastFrames[0].duration = duration->second;
1123                        AddFrame(lastFrames[0]);
1124                        lastFrames.erase(lastFrames.begin());
1125                        durationForPTS.erase(duration);
1126                        currentFrame--;
1127                }
1128        }
1129       
1130        for (int i = 0; i < block.NumberFrames(); i++) {
1131                MatroskaFrame newFrame;
1132                newFrame.pts = block.GlobalTimecode() / timecodeScale;
1133                newFrame.dts = newFrame.pts;
1134                if (duration > 0)
1135                        newFrame.duration = duration;
1136                else
1137                        newFrame.duration = defaultDuration;
1138                newFrame.offset = block.GetDataPosition(i);
1139                newFrame.size = block.GetFrameSize(i);
1140                newFrame.flags = flags;
1141
1142                if (type == track_subtitle) {
1143                        newFrame.buffer = &block.GetBuffer(i);
1144                        AddFrame(newFrame);
1145                }
1146                else {
1147                        lastFrames.push_back(newFrame);
1148                        if (!usesLacing && type == track_video)
1149                                ptsReorder.push_back(newFrame.pts);
1150                }
1151               
1152                newFrame.buffer = NULL;
1153        }
1154}
1155
1156void MatroskaTrack::AddFrame(MatroskaFrame &frame)
1157{
1158        ComponentResult err = noErr;
1159        TimeValue sampleTime;
1160        TimeValue64 displayOffset = frame.pts - frame.dts;
1161       
1162        if (desc == NULL) return;
1163       
1164        if (type == track_subtitle && !is_vobsub) {
1165                const char *packet=NULL; size_t size=0; unsigned start=0, end=0;
1166               
1167                if (frame.size > 0)
1168                        subtitleSerializer->pushLine((const char*)frame.buffer->Buffer(), frame.buffer->Size(), frame.pts, frame.pts + frame.duration);
1169
1170                packet = subtitleSerializer->popPacket(&size, &start, &end);
1171                if (packet) {
1172                        Handle sampleH;
1173                        PtrToHand(packet, &sampleH, size);
1174                        err = AddMediaSample(theMedia, sampleH, 0, size, end - start, desc, 1, 0, &sampleTime);
1175                        if (err) {
1176                                Codecprintf(NULL, "MKV: error adding subtitle sample %d\n", err);
1177                                return;
1178                        }
1179                        DisposeHandle(sampleH);
1180                        frame.pts = start;
1181                        frame.duration = end - start;
1182                } else return;
1183        } else if (sampleTable) {
1184                SInt64 sampleNum;
1185               
1186                err = QTSampleTableAddSampleReferences(sampleTable, frame.offset, frame.size, frame.duration, 
1187                                                                                           displayOffset, 1, frame.flags, qtSampleDesc, &sampleNum);
1188                if (err) {
1189                        Codecprintf(NULL, "MKV: error adding sample reference to table %d\n", err);
1190                        return;
1191                }
1192               
1193                if (firstSample == -1)
1194                        firstSample = sampleNum;
1195                numSamples++;
1196        } else {
1197                SampleReference64Record sample;
1198                sample.dataOffset = SInt64ToWide(frame.offset);
1199                sample.dataSize = frame.size;
1200                sample.durationPerSample = frame.duration;
1201                sample.numberOfSamples = 1;
1202                sample.sampleFlags = frame.flags;
1203               
1204                err = AddMediaSampleReferences64(theMedia, desc, 1, &sample, &sampleTime);
1205                if (err) {
1206                        Codecprintf(NULL, "MKV: error adding sample reference to media %d\n", err);
1207                        return;
1208                }
1209        }
1210       
1211        // add to track immediately if subtitle, otherwise we let it be added elsewhere when we can do several at once
1212        if (type == track_subtitle) {
1213                err = InsertMediaIntoTrack(theTrack, frame.pts, sampleTime, frame.duration, fixed1);
1214                if (err) {
1215                        Codecprintf(NULL, "MKV: error adding subtitle media into track %d\n", err);
1216                        return;
1217                }
1218        } else {
1219                durationSinceZeroSum += frame.duration;
1220                displayOffsetSum += displayOffset;
1221                if (displayOffsetSum == 0) {
1222                        durationToAdd += durationSinceZeroSum;
1223                        durationSinceZeroSum = 0;
1224                }
1225        }
1226}
1227
1228void MatroskaTrack::AddSamplesToTrack()
1229{
1230        OSStatus err = noErr;
1231       
1232        if (type == track_subtitle)
1233                return;                 // handled in AddFrame()
1234
1235        if (durationToAdd == 0 && numSamples == 0)
1236                // nothing to add
1237                return;
1238       
1239        if (sampleTable) {
1240                if (firstSample == -1)
1241                        return;         // nothing to add
1242               
1243                err = AddSampleTableToMedia(theMedia, sampleTable, firstSample, numSamples, NULL);
1244                firstSample = -1;
1245                numSamples = 0;
1246                if (err) {
1247                        Codecprintf(NULL, "MKV: error adding sample table to the media %d\n", err);
1248                        durationToAdd = 0;
1249                        return;
1250                }
1251        }
1252       
1253        err = InsertMediaIntoTrack(theTrack, -1, maxLoadedTime, durationToAdd, fixed1);
1254        if (err)
1255                Codecprintf(NULL, "MKV: error inserting media into track %d\n", err);
1256       
1257        if (!err) {
1258                if (!maxLoadedTime && lowestPTS)
1259                        SetTrackOffset(theTrack, lowestPTS / timecodeScale);
1260               
1261                maxLoadedTime += durationToAdd;
1262        }
1263       
1264        durationToAdd = 0;
1265}
1266
1267void MatroskaTrack::FinishTrack()
1268{
1269        CXXAutoreleasePool pool;
1270       
1271        if (type == track_subtitle && !is_vobsub)
1272        {
1273                 subtitleSerializer->setFinished();
1274                 do {
1275                         MatroskaFrame fr = {0};
1276                         AddFrame(fr); // add empty frames to flush the subtitle packet queue
1277                 } while (!subtitleSerializer->empty());
1278                 EndMediaEdits(theMedia);
1279        } else {
1280                AddSamplesToTrack();
1281        }
1282}
Note: See TracBrowser for help on using the repository browser.