source: branches/perian-1.1/MatroskaImportPrivate.cpp @ 887

Revision 887, 39.4 KB checked in by astrange, 7 years ago (diff)

Merge trunk to 1.1 branch.

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;
238        Track firstAudioTrack = NULL;
239        Track firstSubtitleTrack = NULL;
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                        KaxTrackEntry & track = *static_cast<KaxTrackEntry *>(trackEntries[i]);
251                        KaxTrackNumber & number = GetChild<KaxTrackNumber>(track);
252                        KaxTrackType & type = GetChild<KaxTrackType>(track);
253                        KaxTrackDefaultDuration & defaultDuration = GetChild<KaxTrackDefaultDuration>(track);
254                        KaxTrackFlagDefault & enabled = GetChild<KaxTrackFlagDefault>(track);
255                        KaxTrackFlagLacing & lacing = GetChild<KaxTrackFlagLacing>(track);
256                        MatroskaTrack mkvTrack;
257                       
258                        mkvTrack.number = uint16(number);
259                        mkvTrack.type = uint8(type);
260                        mkvTrack.defaultDuration = uint32(defaultDuration) / float(timecodeScale) + .5;
261                       
262                        switch (uint8(type)) {
263                                case track_video:
264                                        if (pass == 2) continue;
265                                        err = AddVideoTrack(track, mkvTrack);
266                                        if (err) return err;
267                                       
268                                        if (firstVideoTrack)
269                                                SetTrackAlternate(firstVideoTrack, mkvTrack.theTrack);
270                                        else
271                                                firstVideoTrack = mkvTrack.theTrack;
272                                        break;
273                                       
274                                case track_audio:
275                                        if (pass == 2) continue;
276                                        err = AddAudioTrack(track, mkvTrack);
277                                        if (err) return err;
278                                       
279                                        if (firstAudioTrack)
280                                                SetTrackAlternate(firstAudioTrack, mkvTrack.theTrack);
281                                        else
282                                                firstAudioTrack = mkvTrack.theTrack;
283                                        break;
284                                       
285                                case track_subtitle:
286                                        if (pass == 1) continue;
287                                        err = AddSubtitleTrack(track, mkvTrack);
288                                        if (err) return err;
289                                        if (mkvTrack.theTrack == NULL) continue;
290                                       
291                                        if (firstSubtitleTrack)
292                                                SetTrackAlternate(firstSubtitleTrack, mkvTrack.theTrack);
293                                        else
294                                                firstSubtitleTrack = mkvTrack.theTrack;
295                                        break;
296                                       
297                                case track_complex:
298                                case track_logo:
299                                case track_buttons:
300                                case track_control:
301                                        // not likely to be implemented soon
302                                default:
303                                        continue;
304                        }
305                       
306                        KaxTrackLanguage & trackLang = GetChild<KaxTrackLanguage>(track);
307                        KaxTrackName & trackName = GetChild<KaxTrackName>(track);
308                        KaxContentEncodings * encodings = FindChild<KaxContentEncodings>(track);
309                       
310                        if (encodings) {
311                                err = ReadContentEncodings(*encodings, mkvTrack);
312                                // just ignore the track if there's some problem with this element
313                                if (err) continue;
314                        }
315                       
316                        long qtLang = ISO639_2ToQTLangCode(string(trackLang).c_str());
317                        SetMediaLanguage(mkvTrack.theMedia, qtLang);
318                       
319                        mkvTrack.isEnabled = uint8(enabled);
320                        mkvTrack.usesLacing = uint8(lacing);
321                       
322                        if (!trackName.IsDefaultValue()) {
323                                QTMetaDataRef trackMetaData;
324                                err = QTCopyTrackMetaData(mkvTrack.theTrack, &trackMetaData);
325                               
326                                if (err == noErr) {
327                                        OSType key = 'name';
328                                        // QuickTime differentiates between the title of a track and its name
329                                        // so we set both
330                                        QTMetaDataAddItem(trackMetaData,
331                                                                          kQTMetaDataStorageFormatQuickTime,
332                                                                          kQTMetaDataKeyFormatCommon,
333                                                                          (UInt8 *)&key, sizeof(key),
334                                                                          (UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
335                                                                          UTFstring(trackName).GetUTF8().size(),
336                                                                          kQTMetaDataTypeUTF8, NULL);
337                                       
338                                        QTMetaDataAddItem(trackMetaData,
339                                                                          kQTMetaDataStorageFormatUserData,
340                                                                          kQTMetaDataKeyFormatUserData,
341                                                                          (UInt8 *)&key, sizeof(key),
342                                                                          (UInt8 *)UTFstring(trackName).GetUTF8().c_str(),
343                                                                          UTFstring(trackName).GetUTF8().size(),
344                                                                          kQTMetaDataTypeUTF8, NULL);
345                                       
346                                        QTMetaDataRelease(trackMetaData);
347                                }
348                        }
349                        tracks.push_back(mkvTrack);
350                }
351        }
352       
353        for (int i = 0; i < tracks.size(); i++) {
354                SetTrackEnabled(tracks[i].theTrack, tracks[i].isEnabled);
355        }
356        seenTracks = true;
357        return noErr;
358}
359
360ComponentResult MatroskaImport::ReadContentEncodings(KaxContentEncodings &encodings, MatroskaTrack &mkvTrack)
361{
362        KaxContentEncoding & encoding = GetChild<KaxContentEncoding>(encodings);
363        int scope = uint32(GetChild<KaxContentEncodingScope>(encoding));
364        int type = uint32(GetChild<KaxContentEncodingType>(encoding));
365       
366        if (scope != 1) {
367                Codecprintf(NULL, "Content encoding scope of %d not expected\n", scope);
368                return -1;
369        }
370        if (type != 0) {
371                Codecprintf(NULL, "Encrypted track\n");
372                return -2;
373        }
374       
375        KaxContentCompression & comp = GetChild<KaxContentCompression>(encoding);
376        int algo = uint32(GetChild<KaxContentCompAlgo>(comp));
377       
378        if (algo != 0)
379                Codecprintf(NULL, "MKV: warning, track compression algorithm %d not zlib\n", algo);
380       
381        if ((*mkvTrack.desc)->dataFormat != kSubFormatVobSub)
382                Codecprintf(NULL, "MKV: warning, compressed track %4.4s probably won't work (not VobSub)\n", &(*mkvTrack.desc)->dataFormat);
383       
384        Handle ext = NewHandle(1);
385        **ext = algo;
386       
387        if (mkvTrack.type == track_audio)
388                AddSoundDescriptionExtension((SoundDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
389        else
390                AddImageDescriptionExtension((ImageDescriptionHandle)mkvTrack.desc, ext, kMKVCompressionExtension);
391       
392        return noErr;
393}
394
395ComponentResult MatroskaImport::AddVideoTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
396{
397        ComponentResult err = noErr;
398        ImageDescriptionHandle imgDesc;
399        Fixed width, height;
400       
401        KaxTrackVideo &videoTrack = GetChild<KaxTrackVideo>(kaxTrack);
402        KaxVideoDisplayWidth & disp_width = GetChild<KaxVideoDisplayWidth>(videoTrack);
403        KaxVideoDisplayHeight & disp_height = GetChild<KaxVideoDisplayHeight>(videoTrack);
404        KaxVideoPixelWidth & pxl_width = GetChild<KaxVideoPixelWidth>(videoTrack);
405        KaxVideoPixelHeight & pxl_height = GetChild<KaxVideoPixelHeight>(videoTrack);
406       
407        // Use the PixelWidth if the DisplayWidth is not set
408        if (disp_width.ValueIsSet() && disp_height.ValueIsSet()) {
409                // some files ignore the spec and treat display width/height as a ratio, not as pixels
410                // so scale the display size to be at least as large as the pixel size here
411                // but don't let it be bigger in both dimensions
412                float horizRatio = float(uint32(pxl_width)) / uint32(disp_width);
413                float vertRatio = float(uint32(pxl_height)) / uint32(disp_height);
414               
415                if (vertRatio > horizRatio && vertRatio > 1) {
416                        width = FloatToFixed(uint32(disp_width) * vertRatio);
417                        height = FloatToFixed(uint32(disp_height) * vertRatio);
418                } else if (horizRatio > 1) {
419                        width = FloatToFixed(uint32(disp_width) * horizRatio);
420                        height = FloatToFixed(uint32(disp_height) * horizRatio);
421                } else {
422                        float dar = uint32(disp_width) / (float)uint32(disp_height);
423                        float p_ratio = uint32(pxl_width) / (float)uint32(pxl_height);
424                       
425                        if (dar > p_ratio) {
426                                width  = FloatToFixed(uint32(pxl_height) * dar);
427                                height = IntToFixed(uint32(pxl_height));
428                        } else {
429                                width  = IntToFixed(uint32(pxl_width));
430                                height = FloatToFixed(uint32(pxl_width) / dar);
431                        }
432                }                               
433        } else if (pxl_width.ValueIsSet() && pxl_height.ValueIsSet()) {
434                width = IntToFixed(uint32(pxl_width));
435                height = IntToFixed(uint32(pxl_height));
436        } else {
437                Codecprintf(NULL, "MKV: Video has unknown dimensions.\n");
438                return invalidTrack;
439        }
440               
441        mkvTrack.theTrack = NewMovieTrack(theMovie, width, height, kNoVolume);
442        if (mkvTrack.theTrack == NULL)
443                return GetMoviesError();
444       
445        mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
446        if (mkvTrack.theMedia == NULL) {
447                DisposeMovieTrack(mkvTrack.theTrack);
448                return GetMoviesError();
449        }
450       
451        imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
452        (*imgDesc)->idSize = sizeof(ImageDescription);
453    (*imgDesc)->width = uint16(pxl_width);
454    (*imgDesc)->height = uint16(pxl_height);
455    (*imgDesc)->frameCount = 1;
456        (*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
457    (*imgDesc)->depth = 24;
458    (*imgDesc)->clutID = -1;
459       
460        set_track_clean_aperture_ext(imgDesc, width, height, IntToFixed(uint32(pxl_width)), IntToFixed(uint32(pxl_height)));
461        mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
462       
463        // this sets up anything else needed in the description for the specific codec.
464        err = MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
465        if (err) return err;
466       
467        // video tracks can have display offsets, so create a sample table
468        err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
469        if (err) return err;
470       
471        err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
472               
473        return err;
474}
475
476ComponentResult MatroskaImport::AddAudioTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
477{
478        SoundDescriptionHandle sndDesc;
479        AudioStreamBasicDescription asbd = {0};
480        AudioChannelLayout acl = {0};
481    AudioChannelLayout *pacl = &acl;
482    ByteCount acl_size = sizeof(acl);
483        ByteCount ioSize = sizeof(asbd);
484        ByteCount cookieSize = 0;
485        Ptr magicCookie = NULL;
486       
487        mkvTrack.theTrack = NewMovieTrack(theMovie, 0, 0, kFullVolume);
488        if (mkvTrack.theTrack == NULL)
489                return GetMoviesError();
490       
491        mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'soun', GetMovieTimeScale(theMovie), dataRef, dataRefType);
492        if (mkvTrack.theMedia == NULL) {
493                DisposeMovieTrack(mkvTrack.theTrack);
494                return GetMoviesError();
495        }
496       
497        KaxTrackAudio & audioTrack = GetChild<KaxTrackAudio>(kaxTrack);
498        KaxAudioSamplingFreq & sampleFreq = GetChild<KaxAudioSamplingFreq>(audioTrack);
499        KaxAudioChannels & numChannels = GetChild<KaxAudioChannels>(audioTrack);
500        KaxAudioBitDepth & bitDepth = GetChild<KaxAudioBitDepth>(audioTrack);
501       
502        if (bitDepth.ValueIsSet()) asbd.mBitsPerChannel = uint32(bitDepth);
503        asbd.mFormatID = MkvGetFourCC(&kaxTrack);
504        asbd.mSampleRate = Float64(sampleFreq);
505        asbd.mChannelsPerFrame = uint32(numChannels);
506        asbd.mFramesPerPacket = 1;              // needed for mp3 and v1 SoundDescription, maybe others
507       
508        MkvFinishAudioDescriptions(&kaxTrack, &asbd, &acl);
509       
510        // get more info about the codec
511        AudioFormatGetProperty(kAudioFormatProperty_FormatInfo, 0, NULL, &ioSize, &asbd);
512        if(asbd.mChannelsPerFrame == 0)
513                asbd.mChannelsPerFrame = 1;             // avoid a div by zero
514       
515        // FIXME mChannelLayoutTag == 0 is valid
516        // but we don't use channel position lists (yet) so it's safe for now
517        if (acl.mChannelLayoutTag == 0) acl = GetDefaultChannelLayout(&asbd);
518        if (acl.mChannelLayoutTag == 0) {
519                pacl = NULL;
520                acl_size = 0;
521        }
522       
523        OSStatus err = QTSoundDescriptionCreate(&asbd, pacl, acl_size, magicCookie, cookieSize, 
524                                                                                        kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
525        if (err) {
526                Codecprintf(NULL, "Borked audio track entry, hoping we can parse the track for asbd\n");
527                DisposeHandle((Handle)sndDesc);
528                return noErr;
529        }
530       
531        mkvTrack.desc = (SampleDescriptionHandle) sndDesc;
532        err = MkvFinishSampleDescription(&kaxTrack, mkvTrack.desc, kToSampleDescription);
533        if (err) return err;
534       
535        err = QTSampleTableCreateMutable(NULL, GetMovieTimeScale(theMovie), NULL, &mkvTrack.sampleTable);
536        if (err) return err;
537       
538        err = QTSampleTableAddSampleDescription(mkvTrack.sampleTable, mkvTrack.desc, 0, &mkvTrack.qtSampleDesc);
539        return err;
540}
541
542ComponentResult MatroskaImport::AddSubtitleTrack(KaxTrackEntry &kaxTrack, MatroskaTrack &mkvTrack)
543{
544        Fixed trackWidth, trackHeight;
545        Rect movieBox;
546        MediaHandler mh;
547        ImageDescriptionHandle imgDesc = (ImageDescriptionHandle) NewHandleClear(sizeof(ImageDescription));
548        mkvTrack.desc = (SampleDescriptionHandle) imgDesc;
549       
550        // we assume that a video track has already been created, so we can set the subtitle track
551        // to have the same dimensions as it. Note that this doesn't work so well with multiple
552        // video tracks with different dimensions; but QuickTime doesn't expect that; how should we handle them?
553        GetMovieBox(theMovie, &movieBox);
554        trackWidth = IntToFixed(movieBox.right - movieBox.left);
555        trackHeight = IntToFixed(movieBox.bottom - movieBox.top);
556       
557        (*imgDesc)->idSize = sizeof(ImageDescription);
558        (*imgDesc)->cType = MkvGetFourCC(&kaxTrack);
559        (*imgDesc)->frameCount = 1;
560        (*imgDesc)->depth = 32;
561        (*imgDesc)->clutID = -1;
562       
563        if ((*imgDesc)->cType == kSubFormatVobSub) {
564                int width, height;
565               
566                // bitmap width & height is in the codec private in text format
567                KaxCodecPrivate & idxFile = GetChild<KaxCodecPrivate>(kaxTrack);
568                string idxFileStr((char *)idxFile.GetBuffer(), idxFile.GetSize());
569                string::size_type loc = idxFileStr.find("size:", 0);
570                if (loc == string::npos)
571                        return -1;
572               
573                sscanf(&idxFileStr.c_str()[loc], "size: %dx%d", &width, &height);
574                (*imgDesc)->width = width;
575                (*imgDesc)->height = height;
576               
577                if (trackWidth == 0 || trackHeight == 0) {
578                        trackWidth = IntToFixed(width);
579                        trackHeight = IntToFixed(height);
580                }
581               
582                mkvTrack.theTrack = NewMovieTrack(theMovie, trackWidth, trackHeight, kNoVolume);
583                if (mkvTrack.theTrack == NULL)
584                        return GetMoviesError();
585               
586                mkvTrack.theMedia = NewTrackMedia(mkvTrack.theTrack, 'vide', GetMovieTimeScale(theMovie), dataRef, dataRefType);
587                if (mkvTrack.theMedia == NULL)
588                        return GetMoviesError();
589               
590                // finally, say that we're transparent
591                mh = GetMediaHandler(mkvTrack.theMedia);
592                MediaSetGraphicsMode(mh, graphicsModePreBlackAlpha, NULL);
593               
594                // subtitle tracks should be above the video track, which should be layer 0
595                SetTrackLayer(mkvTrack.theTrack, -1);
596               
597                mkvTrack.is_vobsub = true;
598               
599        } else if ((*imgDesc)->cType == kSubFormatUTF8 || (*imgDesc)->cType == kSubFormatSSA || (*imgDesc)->cType == kSubFormatASS) {
600                if ((*imgDesc)->cType == kSubFormatASS) (*imgDesc)->cType = kSubFormatSSA; // no real reason to treat them differently
601                UInt32 emptyDataRefExtension[2]; // XXX the various uses of this bit of code should be unified
602                mkvTrack.subDataRefHandler = NewHandleClear(sizeof(Handle) + 1);
603                emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
604                emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
605               
606                PtrAndHand(&emptyDataRefExtension[0], mkvTrack.subDataRefHandler, sizeof(emptyDataRefExtension));
607               
608                mkvTrack.theTrack = CreatePlaintextSubTrack(theMovie, imgDesc, GetMovieTimeScale(theMovie), mkvTrack.subDataRefHandler, HandleDataHandlerSubType, (*imgDesc)->cType, NULL, movieBox);
609                if (mkvTrack.theTrack == NULL)
610                        return GetMoviesError();
611               
612                mkvTrack.theMedia = GetTrackMedia(mkvTrack.theTrack);
613                mkvTrack.is_vobsub = false;
614
615                BeginMediaEdits(mkvTrack.theMedia);
616        } else {
617                Codecprintf(NULL, "MKV: Unsupported subtitle type\n");
618                return noErr;
619        }
620       
621        // this sets up anything else needed in the description for the specific codec.
622        return MkvFinishSampleDescription(&kaxTrack, (SampleDescriptionHandle) imgDesc, kToSampleDescription);
623}
624
625ComponentResult MatroskaImport::ReadChapters(KaxChapters &chapterEntries)
626{
627        KaxEditionEntry & edition = GetChild<KaxEditionEntry>(chapterEntries);
628        UInt32 emptyDataRefExtension[2];
629       
630        if (seenChapters)
631                return noErr;
632       
633        chapterTrack = NewMovieTrack(theMovie, 0, 0, kNoVolume);
634        if (chapterTrack == NULL) {
635                Codecprintf(NULL, "MKV: Error creating chapter track %d\n", GetMoviesError());
636                return GetMoviesError();
637        }
638       
639        // we use a handle data reference here because I don't see any way to add textual
640        // sample references (TextMediaAddTextSample() will behave the same as AddSample()
641        // in that it modifies the original file if that's the data reference of the media)
642        Handle dataRef = NewHandleClear(sizeof(Handle) + 1);
643
644        emptyDataRefExtension[0] = EndianU32_NtoB(sizeof(UInt32)*2);
645        emptyDataRefExtension[1] = EndianU32_NtoB(kDataRefExtensionInitializationData);
646       
647        PtrAndHand(&emptyDataRefExtension[0], dataRef, sizeof(emptyDataRefExtension));
648       
649        Media chapterMedia = NewTrackMedia(chapterTrack, TextMediaType, GetMovieTimeScale(theMovie), 
650                                                                           dataRef, HandleDataHandlerSubType);
651        if (chapterMedia == NULL) {
652                OSErr err = GetMoviesError();
653                Codecprintf(NULL, "MKV: Error creating chapter media %d\n", err);
654                DisposeMovieTrack(chapterTrack);
655                return err;
656        }
657       
658        // Name the chapter track "Chapters" for easy distinguishing
659        QTMetaDataRef trackMetaData;
660        OSErr err = QTCopyTrackMetaData(chapterTrack, &trackMetaData);
661        if (err == noErr) {
662                OSType key = kUserDataName;
663                string chapterName("Chapters");
664                QTMetaDataAddItem(trackMetaData, kQTMetaDataStorageFormatUserData,
665                                                  kQTMetaDataKeyFormatUserData, (UInt8 *)&key, sizeof(key),
666                                                  (UInt8 *)chapterName.c_str(), chapterName.size(),
667                                                  kQTMetaDataTypeUTF8, NULL);
668                QTMetaDataRelease(trackMetaData);
669        }
670       
671        BeginMediaEdits(chapterMedia);
672       
673        // tell the text media handler the upcoming text samples are
674        // encoded in Unicode with a byte order mark (BOM)
675        MediaHandler mediaHandler = GetMediaHandler(chapterMedia);
676        SInt32 dataPtr = kTextEncodingUnicodeDefault;
677        TextMediaSetTextSampleData(mediaHandler, &dataPtr, kTXNTextEncodingAttribute);
678       
679        KaxChapterAtom *chapterAtom = FindChild<KaxChapterAtom>(edition);
680        while (chapterAtom && chapterAtom->GetSize() > 0) {
681                AddChapterAtom(chapterAtom, chapterTrack);
682                chapterAtom = &GetNextChild<KaxChapterAtom>(edition, *chapterAtom);
683        }
684       
685        EndMediaEdits(chapterMedia);
686        SetTrackEnabled(chapterTrack, false);
687        seenChapters = true;
688        return noErr;
689}
690
691void MatroskaImport::AddChapterAtom(KaxChapterAtom *atom, Track chapterTrack)
692{
693        KaxChapterAtom *subChapter = FindChild<KaxChapterAtom>(*atom);
694        bool addThisChapter = true;
695       
696        // since QuickTime only supports linear chapter tracks (no nesting), only add chapter leaves
697        if (subChapter && subChapter->GetSize() > 0) {
698                while (subChapter && subChapter->GetSize() > 0) {
699                        KaxChapterFlagHidden &hideChapter = GetChild<KaxChapterFlagHidden>(*subChapter);
700                       
701                        if (!uint8_t(hideChapter)) {
702                                AddChapterAtom(subChapter, chapterTrack);
703                                addThisChapter = false;
704                        }
705                        subChapter = &GetNextChild(*atom, *subChapter);
706                }
707        } 
708        if (addThisChapter) {
709                // add the chapter to the track if it has no children
710                KaxChapterTimeStart & startTime = GetChild<KaxChapterTimeStart>(*atom);
711                KaxChapterDisplay & chapDisplay = GetChild<KaxChapterDisplay>(*atom);
712                KaxChapterString & chapString = GetChild<KaxChapterString>(chapDisplay);
713                MediaHandler mh = GetMediaHandler(GetTrackMedia(chapterTrack));
714                TimeValue start = UInt64(startTime) / timecodeScale;
715
716                if (start > movieDuration) {
717                        Codecprintf(NULL, "MKV: Chapter time is beyond the end of the file\n");
718                        return;
719                }
720               
721                Rect bounds = {0, 0, 0, 0};
722                TimeValue inserted;
723                OSErr err = TextMediaAddTextSample(mh, 
724                                                                                   const_cast<Ptr>(UTFstring(chapString).GetUTF8().c_str()), 
725                                                                                   UTFstring(chapString).GetUTF8().size(), 
726                                                                                   0, 0, 0, NULL, NULL, 
727                                                                                   teCenter, &bounds, dfClipToTextBox, 
728                                                                                   0, 0, 0, NULL, 1, &inserted);
729                if (err)
730                        Codecprintf(NULL, "MKV: Error adding text sample %d\n", err);
731                else {
732                        InsertMediaIntoTrack(chapterTrack, start, inserted, 1, fixed1);
733                }
734        }
735}
736
737ComponentResult MatroskaImport::ReadAttachments(KaxAttachments &attachments)
738{
739        KaxAttached *attachedFile = FindChild<KaxAttached>(attachments);
740       
741        while (attachedFile && attachedFile->GetSize() > 0) {
742                string fileMimeType = GetChild<KaxMimeType>(*attachedFile);
743                /* The only attachments handled here are fonts, which currently can be truetype or opentype.
744                   application/x-* is probably not a permanent MIME type, but it is current practice... */
745                if (fileMimeType == "application/x-truetype-font" || fileMimeType == "application/x-font-otf") {
746                        KaxFileData & fontData = GetChild<KaxFileData>(*attachedFile);
747                       
748                        if (fontData.GetSize()) {
749                                ATSFontContainerRef container;
750                                ATSFontActivateFromMemory(fontData.GetBuffer(), fontData.GetSize(), kATSFontContextLocal, 
751                                                          kATSFontFormatUnspecified, NULL, kATSOptionFlagsDefault, &container);
752                        }
753                }
754               
755                attachedFile = &GetNextChild<KaxAttached>(attachments, *attachedFile);
756        }
757        return noErr;
758}
759
760ComponentResult MatroskaImport::ReadMetaSeek(KaxSeekHead &seekHead)
761{
762        ComponentResult err = noErr;
763        KaxSeek *seekEntry = FindChild<KaxSeek>(seekHead);
764       
765        // don't re-read a seek head that's already been read
766        uint64_t currPos = seekHead.GetElementPosition();
767        vector<MatroskaSeek>::iterator itr = levelOneElements.begin();
768        for (; itr != levelOneElements.end(); itr++) {
769                if (itr->GetID() == KaxSeekHead::ClassInfos.GlobalId && 
770                        itr->segmentPos + segmentOffset == currPos)
771                        return noErr;
772        }
773       
774        while (seekEntry && seekEntry->GetSize() > 0) {
775                MatroskaSeek newSeekEntry;
776                KaxSeekID & seekID = GetChild<KaxSeekID>(*seekEntry);
777                KaxSeekPosition & position = GetChild<KaxSeekPosition>(*seekEntry);
778                EbmlId elementID = EbmlId(seekID.GetBuffer(), seekID.GetSize());
779               
780                newSeekEntry.ebmlID = elementID.Value;
781                newSeekEntry.idLength = elementID.Length;
782                newSeekEntry.segmentPos = position;
783               
784                // recursively read seek heads that are pointed to by the current one
785                // as well as the level one elements we care about
786                if (elementID == KaxInfo::ClassInfos.GlobalId || 
787                        elementID == KaxTracks::ClassInfos.GlobalId || 
788                        elementID == KaxChapters::ClassInfos.GlobalId || 
789                        elementID == KaxAttachments::ClassInfos.GlobalId || 
790                        elementID == KaxSeekHead::ClassInfos.GlobalId) {
791                       
792                        MatroskaSeekContext savedContext = SaveContext();
793                        SetContext(newSeekEntry.GetSeekContext(segmentOffset));
794                        if (NextLevel1Element())
795                                err = ProcessLevel1Element();
796                       
797                        SetContext(savedContext);
798                        if (err) return err;
799                }
800               
801                levelOneElements.push_back(newSeekEntry);
802                seekEntry = &GetNextChild<KaxSeek>(seekHead, *seekEntry);
803        }
804       
805        sort(levelOneElements.begin(), levelOneElements.end());
806       
807        return noErr;
808}
809
810void MatroskaImport::ImportCluster(KaxCluster &cluster, bool addToTrack)
811{
812        KaxSegment & segment = *static_cast<KaxSegment *>(el_l0);
813        KaxClusterTimecode & clusterTime = GetChild<KaxClusterTimecode>(cluster);
814        CXXAutoreleasePool pool;
815                               
816        cluster.SetParent(segment);
817        cluster.InitTimecode(uint64(clusterTime), timecodeScale);
818       
819        for (int i = 0; i < cluster.ListSize(); i++) {
820                const EbmlId & elementID = EbmlId(*cluster[i]);
821                KaxInternalBlock *block = NULL;
822                uint32_t duration = 0;          // set to track's default duration in AddBlock if 0
823                short flags = 0;
824               
825                if (elementID == KaxBlockGroup::ClassInfos.GlobalId) {
826                        KaxBlockGroup & blockGroup = *static_cast<KaxBlockGroup *>(cluster[i]);
827                        KaxBlockDuration & blkDuration = GetChild<KaxBlockDuration>(blockGroup);
828                        block = &GetChild<KaxBlock>(blockGroup);
829                        if (blkDuration.ValueIsSet())
830                                duration = uint32(blkDuration);
831                        flags = blockGroup.ReferenceCount() > 0 ? mediaSampleNotSync : 0;
832                       
833                } else if (elementID == KaxSimpleBlock::ClassInfos.GlobalId) {
834                        KaxSimpleBlock & simpleBlock = *static_cast<KaxSimpleBlock *>(cluster[i]);
835                        block = &simpleBlock;
836                        if (!simpleBlock.IsKeyframe())
837                                flags |= mediaSampleNotSync;
838                        if (simpleBlock.IsDiscardable() && IsFrameDroppingEnabled())
839                                flags |= mediaSampleDroppable;
840                }
841               
842                if (block) {
843                        block->SetParent(cluster);
844                       
845                        for (int i = 0; i < tracks.size(); i++) {
846                                if (tracks[i].number == block->TrackNum()) {
847                                        tracks[i].AddBlock(*block, duration, flags);
848                                        break;
849                                }
850                        }
851                }
852        }
853       
854        if (addToTrack) {
855                for (int i = 0; i < tracks.size(); i++)
856                        tracks[i].AddSamplesToTrack();
857               
858                loadState = kMovieLoadStatePlayable;
859        }
860}
861
862MatroskaSeekContext MatroskaImport::SaveContext()
863{
864        MatroskaSeekContext ret = { el_l1, ioHandler->getFilePointer() };
865        el_l1 = NULL;
866        return ret;
867}
868
869void MatroskaImport::SetContext(MatroskaSeekContext context)
870{
871        if (el_l1)
872                delete el_l1;
873       
874        el_l1 = context.el_l1;
875        ioHandler->setFilePointer(context.position);
876}
877
878void MatroskaImport::PrerollSubtitleTracks()
879{
880        if (!seenTracks) return;
881               
882        for (int i = 0; i < tracks.size(); i++) {
883                MatroskaTrack *track = &tracks[i];
884               
885                if (track->type == track_subtitle) {
886                        Handle subtitleDescriptionExt;
887                        OSErr err = GetImageDescriptionExtension((ImageDescriptionHandle)track->desc, &subtitleDescriptionExt, kSubFormatSSA, 1);
888                       
889                        if (err || !subtitleDescriptionExt) continue;
890                       
891                        SubPrerollFromHeader(*subtitleDescriptionExt, GetHandleSize(subtitleDescriptionExt));
892                }
893        }
894}
895
896MatroskaTrack::MatroskaTrack()
897{
898        number = 0;
899        type = -1;
900        theTrack = NULL;
901        theMedia = NULL;
902        desc = NULL;
903        sampleTable = NULL;
904        qtSampleDesc = 0;
905        timecodeScale = 1000000;
906        maxLoadedTime = 0;
907        seenFirstBlock = false;
908        firstSample = -1;
909        numSamples = 0;
910        durationToAdd = 0;
911        displayOffsetSum = 0;
912        durationSinceZeroSum = 0;
913        subtitleSerializer = new CXXSubSerializer;
914        subDataRefHandler = NULL;
915        is_vobsub = false;
916        isEnabled = true;
917        defaultDuration = 0;
918        usesLacing = true;
919        currentFrame = 0;
920}
921
922MatroskaTrack::MatroskaTrack(const MatroskaTrack &copy)
923{
924        number = copy.number;
925        type = copy.type;
926        theTrack = copy.theTrack;
927        theMedia = copy.theMedia;
928       
929        if (copy.desc) {
930                desc = (SampleDescriptionHandle) NewHandle((*copy.desc)->descSize);
931                memcpy(*desc, *copy.desc, (*copy.desc)->descSize);
932        } else
933                desc = NULL;
934       
935        sampleTable = copy.sampleTable;
936        if (sampleTable)
937                QTSampleTableRetain(sampleTable);
938       
939        qtSampleDesc = copy.qtSampleDesc;
940        timecodeScale = copy.timecodeScale;
941        maxLoadedTime = copy.maxLoadedTime;
942       
943        for (int i = 0; i < copy.lastFrames.size(); i++)
944                lastFrames.push_back(copy.lastFrames[i]);
945       
946        seenFirstBlock = copy.seenFirstBlock;
947        firstSample = copy.firstSample;
948        numSamples = copy.numSamples;
949        durationToAdd = copy.durationToAdd;
950        displayOffsetSum = copy.displayOffsetSum;
951        durationSinceZeroSum = copy.durationSinceZeroSum;
952       
953        subtitleSerializer = copy.subtitleSerializer;
954        subtitleSerializer->retain();
955               
956        subDataRefHandler = copy.subDataRefHandler;
957       
958        is_vobsub = copy.is_vobsub;
959        isEnabled = copy.isEnabled;
960        defaultDuration = copy.defaultDuration;
961        usesLacing = copy.usesLacing;
962        currentFrame = copy.currentFrame;
963}
964
965MatroskaTrack::~MatroskaTrack()
966{
967        if (desc)
968                DisposeHandle((Handle) desc);
969       
970        if (sampleTable)
971                QTSampleTableRelease(sampleTable);
972       
973        if (subtitleSerializer)
974                subtitleSerializer->release();
975}
976
977void MatroskaTrack::ParseFirstBlock(KaxInternalBlock &block)
978{
979        AudioStreamBasicDescription asbd = {0};
980        AudioChannelLayout acl = {0};
981        bool replaceSoundDesc = false;
982       
983        if (desc) {
984                switch ((*desc)->dataFormat) {
985                        case kAudioFormatAC3:
986                                replaceSoundDesc = parse_ac3_bitstream(&asbd, &acl, block.GetBuffer(0).Buffer(), block.GetFrameSize(0));
987                                break;
988                }
989        }
990       
991        if (replaceSoundDesc) {
992                // successful in parsing, so the acl and asbd are more correct than what we generated in
993                // AddAudioTrack() so replace our sound description
994                SoundDescriptionHandle sndDesc = NULL;
995               
996                OSStatus err = QTSoundDescriptionCreate(&asbd, &acl, sizeof(AudioChannelLayout), NULL, 0, 
997                                                        kQTSoundDescriptionKind_Movie_LowestPossibleVersion, &sndDesc);
998                if (err == noErr) {
999                        DisposeHandle((Handle) desc);
1000                        desc = (SampleDescriptionHandle) sndDesc;
1001                        err = QTSampleTableAddSampleDescription(sampleTable, desc, 0, &qtSampleDesc);
1002                }
1003        }
1004}
1005
1006void MatroskaTrack::AddBlock(KaxInternalBlock &block, uint32 duration, short flags)
1007{
1008        if (!seenFirstBlock) {
1009                ParseFirstBlock(block);
1010                seenFirstBlock = true;
1011        }
1012       
1013        // tracks w/ lacing can't have b-frames, and neither can any known audio codec
1014        if (usesLacing || type != track_video) {
1015                // don't add the blocks until we get one with a new timecode
1016                TimeValue64 duration = 0;
1017                if (lastFrames.size() > 0)
1018                        duration = block.GlobalTimecode() / timecodeScale - lastFrames[0].pts;
1019               
1020                if (duration > 0) {
1021                        for (int i = 0; i < lastFrames.size(); i++) {
1022                                // since there can be multiple frames in one block, split the duration evenly between them
1023                                // giving the remainder to the latter blocks
1024                                int remainder = duration % lastFrames.size() >= lastFrames.size() - i ? 1 : 0;
1025                               
1026                                lastFrames[i].duration = duration / lastFrames.size() + remainder;
1027                               
1028                                AddFrame(lastFrames[i]);
1029                        }
1030                        lastFrames.clear();
1031                }
1032        } else if (ptsReorder.size() - currentFrame > MAX_DECODE_DELAY + 1) {
1033                map<TimeValue64, TimeValue>::iterator duration;
1034                MatroskaFrame &curr = lastFrames[currentFrame];
1035                MatroskaFrame &next = lastFrames[currentFrame+1];
1036                currentFrame++;
1037               
1038                // pts -> dts works this way: we assume that the first frame has correct dts
1039                // (we start at a keyframe, so pts = dts), and then we fill up a buffer with
1040                // frames until we have the frame whose pts is equal to the next dts
1041                // Then, we sort this buffer, extract the pts as dts, and calculate the duration.
1042               
1043                ptsReorder.sort();
1044                next.dts = *(++ptsReorder.begin());
1045                ptsReorder.pop_front();
1046               
1047                // Duration calculation has to be done between consecutive pts. Since we reorder
1048                // the pts into the dts, which have to be in order, we calculate the duration then
1049                // from the dts, then save it for the frame with the same pts.
1050               
1051                durationForPTS[curr.dts] = next.dts - curr.dts;
1052               
1053                duration = durationForPTS.find(lastFrames[0].pts);
1054                if (duration != durationForPTS.end()) {
1055                        lastFrames[0].duration = duration->second;
1056                        AddFrame(lastFrames[0]);
1057                        lastFrames.erase(lastFrames.begin());
1058                        durationForPTS.erase(duration);
1059                        currentFrame--;
1060                }
1061        }
1062       
1063        for (int i = 0; i < block.NumberFrames(); i++) {
1064                MatroskaFrame newFrame;
1065                newFrame.pts = block.GlobalTimecode() / timecodeScale;
1066                newFrame.dts = newFrame.pts;
1067                if (duration > 0)
1068                        newFrame.duration = duration;
1069                else
1070                        newFrame.duration = defaultDuration;
1071                newFrame.offset = block.GetDataPosition(i);
1072                newFrame.size = block.GetFrameSize(i);
1073                newFrame.flags = flags;
1074
1075                if (type == track_subtitle) {
1076                        newFrame.buffer = &block.GetBuffer(i);
1077                        AddFrame(newFrame);
1078                }
1079                else {
1080                        lastFrames.push_back(newFrame);
1081                        if (!usesLacing && type == track_video)
1082                                ptsReorder.push_back(newFrame.pts);
1083                }
1084               
1085                newFrame.buffer = NULL;
1086        }
1087}
1088
1089void MatroskaTrack::AddFrame(MatroskaFrame &frame)
1090{
1091        ComponentResult err = noErr;
1092        TimeValue sampleTime;
1093        TimeValue64 displayOffset = frame.pts - frame.dts;
1094       
1095        if (desc == NULL) return;
1096       
1097        if (type == track_subtitle && !is_vobsub) {
1098                const char *packet=NULL; size_t size=0; unsigned start=0, end=0;
1099               
1100                if (frame.size > 0)
1101                        subtitleSerializer->pushLine((const char*)frame.buffer->Buffer(), frame.buffer->Size(), frame.pts, frame.pts + frame.duration);
1102
1103                packet = subtitleSerializer->popPacket(&size, &start, &end);
1104                if (packet) {
1105                        Handle sampleH;
1106                        PtrToHand(packet, &sampleH, size);
1107                        err = AddMediaSample(theMedia, sampleH, 0, size, end - start, desc, 1, 0, &sampleTime);
1108                        if (err) {
1109                                Codecprintf(NULL, "MKV: error adding subtitle sample %d\n", err);
1110                                return;
1111                        }
1112                        DisposeHandle(sampleH);
1113                        frame.pts = start;
1114                        frame.duration = end - start;
1115                } else return;
1116        } else if (sampleTable) {
1117                SInt64 sampleNum;
1118               
1119                err = QTSampleTableAddSampleReferences(sampleTable, frame.offset, frame.size, frame.duration, 
1120                                                                                           displayOffset, 1, frame.flags, qtSampleDesc, &sampleNum);
1121                if (err) {
1122                        Codecprintf(NULL, "MKV: error adding sample reference to table %d\n", err);
1123                        return;
1124                }
1125               
1126                if (firstSample == -1)
1127                        firstSample = sampleNum;
1128                numSamples++;
1129        } else {
1130                SampleReference64Record sample;
1131                sample.dataOffset = SInt64ToWide(frame.offset);
1132                sample.dataSize = frame.size;
1133                sample.durationPerSample = frame.duration;
1134                sample.numberOfSamples = 1;
1135                sample.sampleFlags = frame.flags;
1136               
1137                err = AddMediaSampleReferences64(theMedia, desc, 1, &sample, &sampleTime);
1138                if (err) {
1139                        Codecprintf(NULL, "MKV: error adding sample reference to media %d\n", err);
1140                        return;
1141                }
1142        }
1143       
1144        // add to track immediately if subtitle, otherwise we let it be added elsewhere when we can do several at once
1145        if (type == track_subtitle) {
1146                err = InsertMediaIntoTrack(theTrack, frame.pts, sampleTime, frame.duration, fixed1);
1147                if (err) {
1148                        Codecprintf(NULL, "MKV: error adding subtitle media into track %d\n", err);
1149                        return;
1150                }
1151        } else {
1152                durationSinceZeroSum += frame.duration;
1153                displayOffsetSum += displayOffset;
1154                if (displayOffsetSum == 0) {
1155                        durationToAdd += durationSinceZeroSum;
1156                        durationSinceZeroSum = 0;
1157                }
1158        }
1159}
1160
1161void MatroskaTrack::AddSamplesToTrack()
1162{
1163        OSStatus err = noErr;
1164       
1165        if (type == track_subtitle)
1166                return;                 // handled in AddFrame()
1167
1168        if (durationToAdd == 0 && numSamples == 0)
1169                // nothing to add
1170                return;
1171       
1172        if (sampleTable) {
1173                if (firstSample == -1)
1174                        return;         // nothing to add
1175               
1176                err = AddSampleTableToMedia(theMedia, sampleTable, firstSample, numSamples, NULL);
1177                firstSample = -1;
1178                numSamples = 0;
1179                if (err) {
1180                        Codecprintf(NULL, "MKV: error adding sample table to the media %d\n", err);
1181                        durationToAdd = 0;
1182                        return;
1183                }
1184        }
1185       
1186        err = InsertMediaIntoTrack(theTrack, -1, maxLoadedTime, durationToAdd, fixed1);
1187        if (err)
1188                Codecprintf(NULL, "MKV: error inserting media into track %d\n", err);
1189        else
1190                maxLoadedTime += durationToAdd;
1191       
1192        durationToAdd = 0;
1193}
1194
1195void MatroskaTrack::FinishTrack()
1196{
1197        CXXAutoreleasePool pool;
1198       
1199        if (type == track_subtitle && !is_vobsub)
1200        {
1201                 subtitleSerializer->setFinished();
1202                 do {
1203                         MatroskaFrame fr = {0};
1204                         AddFrame(fr); // add empty frames to flush the subtitle packet queue
1205                 } while (!subtitleSerializer->empty());
1206                 EndMediaEdits(theMedia);
1207        } else {
1208                AddSamplesToTrack();
1209        }
1210}
Note: See TracBrowser for help on using the repository browser.