This repository has been archived by the owner on Jul 30, 2022. It is now read-only.
mirrored from https://chromium.googlesource.com/webm/webmquicktime
-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathWebMImport.cpp
1715 lines (1448 loc) · 58.7 KB
/
WebMImport.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2010 The WebM project authors. All Rights Reserved.
//
// Use of this source code is governed by a BSD-style license
// that can be found in the LICENSE file in the root of the source
// tree. An additional intellectual property rights grant can be found
// in the file PATENTS. All contributing project authors may
// be found in the AUTHORS file in the root of the source tree.
//
// Quicktime Movie Import Commponent ('eat ') for the WebM format.
// See www.webmproject.org for more info.
//
// The MkvBufferedReaderQT class uses asynchronous io to read WebM
// data into a buffer. The "libwebm" project's libmkvparser.a library
// is used to parse the WebM data (which is similar to Matroska).
// This implements an "idling" importer, and for every call to the
// Idle() routine we import one WebM Cluster. Audio and video samples
// are collected in STL containers and then added to QuickTime Media
// and Track objects. When the resulting QuickTime movie is played,
// the VP8 video data is passed to the QuickTime Video Decompressor
// component ('imdc') to be decoded. Audio data is sent to a separate
// Vorbis decoder component.
// When the "WebM.component" is installed in /Library/QuickTime/
// folder, it enables playback of the WebM format in QuickTime Player
// 7, Safari, and other QuickTime applications on the Mac. The vorbis
// decoder, XiphQT.component, must also be installed for audio
// support.
//
// TODO([email protected]): Consider using precise-width integer
// types from <stdint.h> for all the code that's not directly
// interfacing with QuickTime or other OSX frameworks.
#include "WebMImport.hpp"
#include <Carbon/Carbon.h>
#include <QuickTime/QuickTime.h>
#include <vector>
#include "mkvparser.hpp"
#include "mkvreaderqt.hpp"
#include "keystone_util.h"
#include "log.h"
typedef std::vector<SampleReferenceRecord> SampleRefVec;
typedef std::vector<long long> SampleTimeVec;
// WebM Import Component Globals structure
typedef struct {
ComponentInstance self;
IdleManager idleManager;
mkvparser::Segment* webmSegment;
const mkvparser::Cluster* webmCluster;
const mkvparser::Tracks* webmTracks;
long long segmentDuration;
::Track movieVideoTrack, movieAudioTrack;
::Media movieVideoMedia, movieAudioMedia;
::Movie movie;
ImageDescriptionHandle vp8DescHand;
SoundDescriptionHandle audioDescHand;
Handle dataRef;
OSType dataRefType;
SampleRefVec videoSamples;
SampleTimeVec videoTimes;
SampleRefVec audioSamples;
SampleTimeVec audioTimes;
// Total count of video blocks added to Media.
long videoCount;
// Total count of audio blocks added to Media.
long audioCount;
// Number of tracks added to Movie.
long trackCount;
// One of: kMovieLoadStateLoading, ... kMovieLoadStateComplete.
long loadState;
// Total duration of video blocks already inserted into QT Track,
// expressed in video media's time scale.
long videoMaxLoaded;
// Total duration of audio blocks already inserted into QT Track,
// expressed in audio media's time scale.
long audioMaxLoaded;
// Last time we added samples, (in ~60/s ticks).
unsigned long addSamplesLast;
// Ticks at import start.
unsigned long startTicks;
// Is the component importing in idling mode?
Boolean usingIdles;
// Placeholder track for visualizing progressive importing.
Track placeholderTrack;
// Number of bytes successfully parsed so far.
long long parsed_bytes;
// Offset into the current partially parsed cluster.
long long parsed_cluster_offset;
// Timestamp of the first cluster in the file.
long long first_cluster_time_offset;
// Number of media timescale units to subtract from media sample timestamps.
TimeValue video_media_offset;
TimeValue audio_media_offset;
// Current import process state.
int import_state;
// Reader object passed to libwebm's mkvparser.
MkvBufferedReaderQT* reader;
} WebMImportGlobalsRec, *WebMImportGlobals;
namespace {
// Track types.
enum { VIDEO_TRACK = 1, AUDIO_TRACK = 2 };
// WebM data parsing status codes.
enum {
kNoError = 0,
kNeedMoreData = 1,
kParseError = 2,
kEOS = 3,
};
// Importer data processing states.
enum {
kImportStateParseHeaders = 0,
kImportStateParseClusters = 1,
kImportStateFinished = 2,
};
// Number of nanoseconds in a second.
const long long ns_per_sec = 1000000000;
// Minimum number of seconds between playhead and the end of added
// data we should maintain.
const long kMinPlayheadDistance = 5;
// Minimum interval for considering adding samples (in ~60/s ticks).
const long kAddSamplesCheckTicks = 30;
// Normal interval for adding samples (in ~60/s ticks).
const long kAddSamplesNormalTicks = 90;
// Last sample end time not known.
const long kNoEndTime = 0;
} // namespace
#define MOVIEIMPORT_BASENAME() WebMImport
#define MOVIEIMPORT_GLOBALS() WebMImportGlobals storage
#define CALLCOMPONENT_BASENAME() MOVIEIMPORT_BASENAME()
#define CALLCOMPONENT_GLOBALS() MOVIEIMPORT_GLOBALS()
#define COMPONENT_DISPATCH_FILE "WebMImportDispatch.h"
#define COMPONENT_UPP_SELECT_ROOT() MovieImport
extern "C" {
#include <CoreServices/Components.k.h>
#include <QuickTime/QuickTimeComponents.k.h>
#include <QuickTime/ImageCompression.k.h> // for ComponentProperty selectors
#include <QuickTime/ComponentDispatchHelper.c>
}
#pragma mark-
extern "C" {
pascal ComponentResult WebMImportOpen(WebMImportGlobals store,
ComponentInstance self);
//...
}
static void ResetState(WebMImportGlobals store);
static int ParseDataHeaders(WebMImportGlobals store);
static int ParseDataCluster(WebMImportGlobals store);
OSErr CreateVP8ImageDescription(long width, long height,
ImageDescriptionHandle *descOut);
OSErr CreateAudioDescription(SoundDescriptionHandle *descOut,
const mkvparser::AudioTrack* webmAudioTrack);
OSErr CreateCookieFromCodecPrivate(const mkvparser::AudioTrack* webmAudioTrack,
Handle* cookie);
OSErr AddAudioBlock(WebMImportGlobals store, const mkvparser::Block* webmBlock,
long long blockTime_ns);
OSErr AddAudioSampleRefsToQTMedia(WebMImportGlobals store,
long long lastTime_ns);
OSErr AddVideoBlock(WebMImportGlobals store, const mkvparser::Block* webmBlock,
long long blockTime_ns);
OSErr AddVideoSampleRefsToQTMedia(WebMImportGlobals store,
long long lastTime_ns);
static int CreateQTTracksAndMedia(WebMImportGlobals store);
static int ProcessCluster(WebMImportGlobals store);
static int ProcessData(WebMImportGlobals store);
static ComponentResult SetupDataHandler(WebMImportGlobals store,
Handle data_ref, OSType data_ref_type);
static ComponentResult InitIdleImport(WebMImportGlobals store);
static ComponentResult NonIdleImport(WebMImportGlobals store);
static Boolean ShouldAddSamples(WebMImportGlobals store);
static ComponentResult CreatePlaceholderTrack(WebMImportGlobals store);
static void RemovePlaceholderTrack(WebMImportGlobals store);
static ComponentResult NotifyMovieChanged(WebMImportGlobals store);
void DumpWebMDebugInfo(mkvparser::EBMLHeader* ebmlHeader,
const mkvparser::SegmentInfo* webmSegmentInfo,
const mkvparser::Tracks* webmTracks);
void DumpWebMGlobals(WebMImportGlobals store);
#pragma mark -
//-----------------------------------------------------------------------------
// Component Open Request - Required
pascal ComponentResult WebMImportOpen(WebMImportGlobals store,
ComponentInstance self) {
OSErr err;
dbg_printf("[WebM Import] >> [%08lx] :: Open()\n", (UInt32) store);
TouchActivityFile();
store = (WebMImportGlobals) NewPtrClear(sizeof(WebMImportGlobalsRec));
if ((err = MemError()) == noErr) {
store->self = self;
SetComponentInstanceStorage(self, (Handle)store);
}
dbg_printf("[WebM Import] < [%08lx] :: Open()\n", (UInt32) store);
return err;
}
//-----------------------------------------------------------------------------
// Component Close Request - Required
pascal ComponentResult WebMImportClose(WebMImportGlobals store,
ComponentInstance self) {
dbg_printf("[WebM Import] >> [%08lx] :: Close()\n", (UInt32) store);
if (store) {
ResetState(store);
DisposePtr((Ptr)store);
}
dbg_printf("[WebM Import] < [%08lx] :: Close()\n", (UInt32) store);
return noErr;
}
//-----------------------------------------------------------------------------
// Component Version Request - Required
pascal ComponentResult WebMImportVersion(WebMImportGlobals store) {
dbg_printf("[WebM Import] >> [%08lx] :: Version()\n", (UInt32) store);
dbg_printf("[WebM Import] < [%08lx] :: Version()\n", (UInt32) store);
return kWebMImportVersion;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportFile(WebMImportGlobals store,
const FSSpec *theFile,
Movie theMovie, Track targetTrack,
Track *usedTrack,
TimeValue atTime,
TimeValue *durationAdded,
long inFlags, long *outFlags) {
OSErr err = noErr;
AliasHandle alias = NULL;
*outFlags = 0;
dbg_printf("[WebM Import] >> [%08lx] :: FromFile(%d, %ld)\n",
(UInt32) store, targetTrack != NULL, atTime);
err = NewAliasMinimal(theFile, &alias);
if (!err) {
err = MovieImportDataRef(store->self, (Handle)alias, rAliasType, theMovie,
targetTrack, usedTrack, atTime, durationAdded,
inFlags, outFlags);
} else {
if (alias)
DisposeHandle((Handle)alias);
}
dbg_printf("[WebM Import] < [%08lx] :: FromFile()\n", (UInt32) store);
return err;
}
//-----------------------------------------------------------------------------
// Parse given file and import it into QuickTime Movie data structures.
//
pascal ComponentResult WebMImportDataRef(WebMImportGlobals store,
Handle dataRef, OSType dataRefType,
Movie theMovie, Track targetTrack,
Track* usedTrack, TimeValue atTime,
TimeValue* durationAdded,
long inFlags, long* outFlags) {
ComponentResult status = noErr;
ResetState(store);
store->movie = theMovie;
store->loadState = kMovieLoadStateLoading;
store->import_state = kImportStateParseHeaders;
*outFlags = 0;
*durationAdded = 0;
status = SetupDataHandler(store, dataRef, dataRefType);
if (status) {
*outFlags |= movieImportResultComplete;
return status;
}
if (inFlags & movieImportWithIdle) {
status = InitIdleImport(store);
if (!status) {
*outFlags |= movieImportResultNeedIdles;
} else {
*outFlags |= movieImportResultComplete;
}
} else {
status = NonIdleImport(store);
*outFlags |= movieImportResultComplete;
if (status)
return status;
if (store->trackCount > 0) {
if (durationAdded) {
const Track track = store->movieVideoTrack ?
store->movieVideoTrack : store->movieAudioTrack;
*durationAdded = GetTrackDuration(track) - atTime;
}
if (store->trackCount == 1 && usedTrack) {
*usedTrack = store->movieVideoTrack ?
store->movieVideoTrack : store->movieAudioTrack;
}
}
}
return status;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportGetMIMETypeList(WebMImportGlobals store,
QTAtomContainer *outMimeInfo) {
dbg_printf("[WebM Import] >> [%08lx] :: GetMIMETypeList()\n",
(UInt32) store);
OSErr err = GetComponentResource((Component)store->self, 'mime', 263,
(Handle *)outMimeInfo);
if (err != noErr) {
dbg_printf("GetMIMETypeList FAILED");
}
dbg_printf("[WebM Import] << [%08lx] :: GetMIMETypeList()\n",
(UInt32) store);
return err;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportValidateDataRef(WebMImportGlobals store,
Handle dataRef,
OSType dataRefType,
UInt8 *valid) {
OSErr err = noErr;
dbg_printf("[WebM Import] >> [%08lx] :: ValidateDataRef()\n",
(UInt32) store);
MkvReaderQT* reader = (MkvReaderQT*) new (std::nothrow) MkvReaderQT;
if (!reader) {
err = notEnoughMemoryErr;
} else {
// Just open the reader component, don't query the size.
const int status = reader->Open(dataRef, dataRefType, false);
if (!status) {
// From the reference documentation: "... the |valid| parameter
// for this function is a value that can be interpreted as the
// degree to which the importer can interpret the file's
// contents. (...) For now, it is best to return either 0 or 128
// only."
*valid = 128;
err = noErr;
} else {
dbg_printf("[WebM Import] ValidateDataRef() FAIL... err = %d\n", err);
err = invalidDataRef;
}
delete reader;
}
dbg_printf("[WebM Import] << [%08lx] :: ValidateDataRef()\n",
(UInt32) store);
return err;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportValidate(WebMImportGlobals store,
const FSSpec *theFile,
Handle theData, Boolean *valid) {
OSErr err = noErr;
dbg_printf("[WebM Import] >> [%08lx] :: Validate()\n", (UInt32) store);
FSRef fileFSRef;
AliasHandle fileAlias = NULL;
*valid = false;
if ((err = FSpMakeFSRef(theFile, &fileFSRef) == noErr) &&
(err = FSNewAliasMinimal(&fileFSRef, &fileAlias) == noErr)) {
err = MovieImportValidateDataRef(store->self, (Handle)fileAlias,
rAliasType, (UInt8*) valid);
}
if (*valid == false) {
dbg_printf("[WebM Import] Validate() FAIL... \n");
}
dbg_printf("[WebM Import] << [%08lx] :: Validate()\n", (UInt32) store);
return err;
}
//-----------------------------------------------------------------------------
// QuickTime will call this repeatedly to give our component some CPU
// time to do a smallish bit of importing, until we indicate we are
// done.
//
pascal ComponentResult WebMImportIdle(WebMImportGlobals store,
long inFlags, long* outFlags) {
dbg_printf("WebMImportIdle()\n");
DumpWebMGlobals(store);
// Give the reader's data handling component some CPU time.
store->reader->TaskDataHandler();
if (store->reader->RequestPending()) {
// Fill buffer request still pending, nothing more to parse at the
// moment. If the data is coming from network then delay idling by
// 1/10 second to save CPU time.
if (store->reader->is_streaming())
QTIdleManagerSetNextIdleTimeDelta(store->idleManager, 1, 10);
return noErr;
}
int status = 0;
do {
status = ProcessData(store);
} while (!status);
if (status == kNeedMoreData) {
status = store->reader->RequestFillBuffer();
if (!status)
QTIdleManagerSetNextIdleTimeNow(store->idleManager);
else if (status == MkvBufferedReaderQT::kFillBufferNotEnoughSpace)
status = internalComponentErr;
} else if (status == kEOS) {
// Skip to finishing-up below.
status = 0;
} else if (status == kParseError) {
*outFlags |= movieImportResultComplete;
status = invalidDataRef;
}
if (status) {
*outFlags |= movieImportResultComplete;
return status;
}
if (store->import_state == kImportStateFinished) {
// Add any remaining samples from cache.
AddVideoSampleRefsToQTMedia(store, store->segmentDuration);
AddAudioSampleRefsToQTMedia(store, store->segmentDuration);
RemovePlaceholderTrack(store);
// set flags to indicate we are done.
*outFlags |= movieImportResultComplete;
store->loadState = kMovieLoadStateComplete;
NotifyMovieChanged(store);
DumpWebMGlobals(store);
}
return noErr;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportSetIdleManager(WebMImportGlobals store,
IdleManager idleMgr) {
store->idleManager = idleMgr;
return noErr;
}
//-----------------------------------------------------------------------------
pascal ComponentResult WebMImportGetMaxLoadedTime(WebMImportGlobals store,
TimeValue *time) {
// time arg is in movie's timescale so return video maxloaded
*time = store->videoMaxLoaded;
return noErr;
}
//-----------------------------------------------------------------------------
// Returns the asynchronous load state for a movie.
pascal ComponentResult WebMImportGetLoadState(WebMImportGlobals store,
long* importerLoadState) {
*importerLoadState = store->loadState;
return noErr;
}
//-----------------------------------------------------------------------------
// Estimates the remaining time to fully import the file
// (seems to be needed to properly visualize progressive imports)
//
pascal ComponentResult WebMImportEstimateCompletionTime(
WebMImportGlobals store, TimeRecord *time) {
if (time == NULL) {
return paramErr;
}
if (store->segmentDuration <= 0) {
time->value = SInt64ToWide(S64Set(0));
time->scale = 0;
} else {
TimeScale media_timescale;
long loaded;
unsigned long timeUsed = TickCount() - store->startTicks;
if (store->movieVideoTrack) {
media_timescale = GetMediaTimeScale(store->movieVideoMedia);
loaded = store->videoMaxLoaded;
} else {
media_timescale = GetMediaTimeScale(store->movieAudioMedia);
loaded = store->audioMaxLoaded;
}
long duration = (double)store->segmentDuration / ns_per_sec *
media_timescale;
long estimate = timeUsed * (duration - loaded) / loaded;
dbg_printf("EstimateCompletionTime(): %ld (%.3f)\n", estimate,
(double)estimate / 60);
time->value = SInt64ToWide(S64Set(estimate));
time->scale = 60; // roughly TickCount()'s resolution
}
time->base = NULL; // no time base, this time record is a duration
return noErr;
}
#pragma mark -
//-----------------------------------------------------------------------------
// Bring the component member variables to a clean, initial state,
// releasing any dynamically alocated objects as necessary.
//
static void ResetState(WebMImportGlobals store) {
store->idleManager = NULL;
if (store->webmSegment) {
delete store->webmSegment;
store->webmSegment = NULL;
}
store->movieVideoTrack = NULL;
store->movieAudioMedia = NULL;
store->movieVideoMedia = NULL;
store->movieAudioMedia = NULL;
store->movie = NULL;
if (store->vp8DescHand) {
DisposeHandle((Handle) store->vp8DescHand);
store->vp8DescHand = NULL;
}
if (store->audioDescHand) {
DisposeHandle((Handle) store->audioDescHand);
store->audioDescHand = NULL;
}
if (store->dataRef) {
DisposeHandle(store->dataRef);
store->dataRef = NULL;
}
store->dataRefType = 0;
store->videoCount = 0;
store->audioCount = 0;
store->trackCount = 0;
store->loadState = 0;
store->videoMaxLoaded = 0;
store->audioMaxLoaded = 0;
store->addSamplesLast = 0;
store->startTicks = 0;
store->usingIdles = false;
if (store->placeholderTrack) {
DisposeMovieTrack(store->placeholderTrack);
store->placeholderTrack = NULL;
}
store->parsed_bytes = 0;
store->parsed_cluster_offset = 0;
store->first_cluster_time_offset = -1;
store->import_state = kImportStateParseHeaders;
store->video_media_offset = 0;
store->audio_media_offset = 0;
if (store->reader) {
store->reader->Close();
delete store->reader;
store->reader = NULL;
}
}
//-----------------------------------------------------------------------------
// Attempt to parse the WebM file header info.
// Will return kNeedMoreData if not enough data is currently
// available, and can be called again when more data becomes
// available. Will set the corresponding component member variables
// when the header structures are successfully parsed.
static int ParseDataHeaders(WebMImportGlobals store) {
long long parse_status = 0;
long long pos = 0;
mkvparser::EBMLHeader ebml_header;
pos = 0;
parse_status = ebml_header.Parse(store->reader, pos);
if (parse_status)
return kNeedMoreData;
dbg_printf("ParseDataHeaders: EBMLHeader parsed (pos = %lld)\n", pos);
mkvparser::Segment* segment;
parse_status = mkvparser::Segment::CreateInstance(store->reader, pos,
segment);
if (parse_status)
return kNeedMoreData;
if (store->webmSegment)
delete store->webmSegment;
store->webmSegment = segment;
dbg_printf("ParseDataHeaders: segment parsed (pos = %lld)\n", pos);
parse_status = segment->ParseHeaders();
if (parse_status)
return kNeedMoreData;
dbg_printf("ParseDataHeaders: after ParseHeaders()\n");
const mkvparser::SegmentInfo* segment_info = segment->GetInfo();
if (!segment_info)
return kParseError;
dbg_printf("ParseDataHeaders: SegmentInfo parsed\n");
const mkvparser::Tracks* tracks = segment->GetTracks();
if (!tracks)
return kParseError;
dbg_printf("ParseDataHeaders: Tracks parsed\n");
store->webmTracks = tracks;
store->segmentDuration = segment_info->GetDuration();
store->parsed_bytes = tracks->m_element_start + tracks->m_element_size;
DumpWebMDebugInfo(&ebml_header, segment_info, tracks);
return kNoError;
}
//-----------------------------------------------------------------------------
// Attempt to parse one complete WebM cluster, indicating when more
// data is needed or parse errors occured.
//
static int ParseDataCluster(WebMImportGlobals store) {
int status = 0;
long size = 0;
if (!store->webmCluster) {
long long pos = store->parsed_bytes;
status = store->webmSegment->LoadCluster(pos, size);
if (status)
return kNeedMoreData;
const mkvparser::Cluster* cluster = store->webmSegment->GetLast();
if (!cluster || cluster->EOS())
return kNeedMoreData;
store->parsed_cluster_offset = pos;
store->webmCluster = cluster;
}
for (;;) {
status = store->webmCluster->Parse(store->parsed_cluster_offset, size);
if (status == 1) {
break;
} else if (status == mkvparser::E_BUFFER_NOT_FULL) {
return kNeedMoreData;
} else if (status != 0) {
return kParseError;
}
}
const long long element_size = store->webmCluster->GetElementSize();
store->parsed_bytes = store->webmCluster->m_element_start + element_size;
store->parsed_cluster_offset = 0;
dbg_printf("Parsed cluster @ %8lld (size %8lld)\n",
store->webmCluster->m_element_start, element_size);
return kNoError;
}
//-----------------------------------------------------------------------------
// Create image description for a VP8 video frame so that QT can find
// codec component to decode it.
// see ImageCompression.h
OSErr CreateVP8ImageDescription(long width, long height,
ImageDescriptionHandle *descOut) {
OSErr err = noErr;
ImageDescriptionHandle descHand = NULL;
ImageDescriptionPtr descPtr;
descHand = (ImageDescriptionHandle)NewHandleClear(sizeof(ImageDescription));
if (err = MemError()) goto bail;
descPtr = *descHand;
descPtr->idSize = sizeof(ImageDescription);
descPtr->cType = kVP8CodecFormatType;
descPtr->vendor = kGoogManufacturer;
descPtr->frameCount = 1;
descPtr->depth = codecInfoDepth24;
descPtr->clutID = -1; // color table not used
// Horizontal and vertical resolutions in dpi:
descPtr->hRes = 72L << 16;
descPtr->vRes = 72L << 16;
descPtr->width = width;
descPtr->height = height;
descPtr->version = 0;
bail:
if (descHand && err) {
DisposeHandle((Handle)descHand);
descHand = NULL;
}
*descOut = descHand;
return err;
}
enum {
kAudioFormatVorbis = 'XiVs'
};
//-----------------------------------------------------------------------------
// Create audio sample description for Vorbis.
OSErr CreateAudioDescription(SoundDescriptionHandle *descOut,
const mkvparser::AudioTrack* webmAudioTrack) {
OSErr err = noErr;
SoundDescriptionHandle descHand = NULL;
unsigned long cookieSize = 0;
Handle cookieHand = NULL;
Ptr cookie = NULL;
err = CreateCookieFromCodecPrivate(webmAudioTrack, &cookieHand);
if ((err == noErr) && (cookieHand)) {
cookie = *cookieHand;
cookieSize = GetHandleSize(cookieHand);
}
// In all fields, a value of 0 indicates that the field is either
// unknown, not applicable or otherwise is inapproprate for the
// format and should be ignored.
AudioStreamBasicDescription asbd;
asbd.mFormatID = kAudioFormatVorbis;
asbd.mSampleRate = webmAudioTrack->GetSamplingRate();
asbd.mFormatFlags = kAudioFormatFlagIsBigEndian |
kAudioFormatFlagIsSignedInteger |
kAudioFormatFlagIsPacked;
asbd.mChannelsPerFrame = webmAudioTrack->GetChannels();
asbd.mBitsPerChannel = 0; // 24; // or webmAudioTrack->GetBitDepth() ?
asbd.mFramesPerPacket = 0; // 1;
asbd.mBytesPerFrame = 0; // 3;
asbd.mBytesPerPacket = 0; // 3;
dbg_printf("WebMImport CreateAudioDescription() - asbd.mSampleRate"
" = %7.3f\n", asbd.mSampleRate);
err = QTSoundDescriptionCreate(
&asbd, // description of the format
NULL, // no AudioChannelLayout
0, // 0 size: no AudioChannelLayout
(void*)cookie,
cookieSize,
kQTSoundDescriptionKind_Movie_LowestPossibleVersion,
&descHand);
if (err == noErr) {
*descOut = descHand;
}
// Note: SoundDescription.sampleRate is of type UnsignedFixed.
dbg_printf("WebMImport CreateAudioDescription() - descHand.sampleRate"
" = %ld\n", (*descHand)->sampleRate >> 16);
return err;
}
enum {
kCookieTypeVorbisHeader = 'vCtH',
kCookieTypeVorbisComments = 'vCt#',
kCookieTypeVorbisCodebooks = 'vCtC',
kCookieTypeVorbisFirstPageNo = 'vCtN'
};
struct CookieAtomHeader {
long size;
long type;
unsigned char data[1];
};
typedef struct CookieAtomHeader CookieAtomHeader;
//-----------------------------------------------------------------------------
// The Matroska spec
// <http://www.matroska.org/technical/specs/codecid/index.html>
// describes the Vorbis codec private data as: The private data
// contains the first three Vorbis packet in order. The lengths of the
// packets precedes them. The actual layout is:
// Byte 1: number of distinct packets '#p' minus one inside the
// CodecPrivate block. This should be '2' for current Vorbis
// headers.
// Bytes 2..n: lengths of the first '#p' packets, coded in Xiph-style
// lacing. The length of the last packet is the length of the
// CodecPrivate block minus the lengths coded in these bytes minus
// one.
// Bytes n+1..: The Vorbis identification header, followed by the
// Vorbis comment header followed by the codec setup header.
//
OSErr CreateCookieFromCodecPrivate(const mkvparser::AudioTrack* webmAudioTrack,
Handle* cookie) {
OSErr err = noErr;
Handle cookieHand = NULL;
size_t vorbisCodecPrivateSize;
const unsigned char* vorbisCodecPrivateData =
webmAudioTrack->GetCodecPrivate(vorbisCodecPrivateSize);
dbg_printf("Vorbis Codec Private Data = %p, Vorbis Codec Private Size"
" = %ld\n", vorbisCodecPrivateData, vorbisCodecPrivateSize);
int numPackets = vorbisCodecPrivateData[0] + 1;
const unsigned long idHeaderSize = vorbisCodecPrivateData[1];
const unsigned long commentHeaderSize = vorbisCodecPrivateData[2];
const unsigned char* idHeader = &vorbisCodecPrivateData[3];
const unsigned char* commentHeader = idHeader + idHeaderSize;
const unsigned char* setupHeader = commentHeader + commentHeaderSize;
// Size of third packet = total size minus length of other two
// packets, minus the two length bytes, minus 1 for the "numPackets"
// byte.
const unsigned long setupHeaderSize =
vorbisCodecPrivateSize - idHeaderSize - commentHeaderSize - 2 - 1;
if ((3 + idHeaderSize + commentHeaderSize + setupHeaderSize) !=
vorbisCodecPrivateSize) {
dbg_printf("Error. Codec Private header sizes don't add up."
" 3 + id (%ld) + comment (%ld) + setup (%ld) != total (%ld)\n",
idHeaderSize, commentHeaderSize, setupHeaderSize,
vorbisCodecPrivateSize);
return -1;
}
// Note: CookieAtomHeader is
// {long size; long type; unsigned char data[1]; }
// see WebMAudioStream.c
// first packet - id header
uint32_t atomhead1[2] = { EndianU32_NtoB(idHeaderSize + 2*4),
EndianU32_NtoB(kCookieTypeVorbisHeader) };
PtrToHand(atomhead1, &cookieHand, sizeof(atomhead1));
PtrAndHand(idHeader, cookieHand, idHeaderSize);
// second packet - comment header
uint32_t atomhead2[2] = { EndianU32_NtoB(commentHeaderSize +
sizeof(atomhead2)),
EndianU32_NtoB(kCookieTypeVorbisComments) };
PtrAndHand(atomhead2, cookieHand, sizeof(atomhead2));
PtrAndHand(commentHeader, cookieHand, commentHeaderSize);
// third packet - setup header
uint32_t atomhead3[2] = { EndianU32_NtoB(setupHeaderSize + sizeof(atomhead3)),
EndianU32_NtoB(kCookieTypeVorbisCodebooks) };
PtrAndHand(atomhead3, cookieHand, sizeof(atomhead3));
PtrAndHand(setupHeader, cookieHand, setupHeaderSize);
*cookie = cookieHand;
return noErr;
}
//-----------------------------------------------------------------------------
// Add one audio block to cache of sample references. Call this for
// each block in a section (maybe a cluster) to collect sample
// references first, and then call AddMediaSampleReferences() later
// for the section, or once at the end of file.
//
// Lacing means there could be multiple Frames per Block, so iterate
// and add all Frames here.
//
OSErr AddAudioBlock(WebMImportGlobals store, const mkvparser::Block* webmBlock,
long long blockTime_ns) {
OSErr err = noErr;
dbg_printf("Audio Block\n");
long frameCount = webmBlock->GetFrameCount();
for (long fi = 0; fi < frameCount; fi++) {
const mkvparser::Block::Frame& webmFrame = webmBlock->GetFrame(fi);
dbg_printf("\tFrame:\tpos:%15lld, len:%15ld\n", webmFrame.pos,
webmFrame.len);
SampleReferencePtr srp;
srp = (SampleReferencePtr)malloc(sizeof(SampleReferenceRecord));
srp->dataOffset = webmFrame.pos;
srp->dataSize = webmFrame.len;
srp->durationPerSample = 0; // Will calculate this later for all samples.
srp->numberOfSamples = 1;
srp->sampleFlags = 0;
store->audioSamples.push_back(*srp);
store->audioTimes.push_back(blockTime_ns);
}
return err;
}
//-----------------------------------------------------------------------------
// Divide given duration equally between given samples.
//
// Also, set mediaSampleNotSync on all but the first sample in the
// given range.
// Note, the inclusive [first; last] range of samples to operate on
// should contain at least two elements.
//
static void InterpolateDurations(SampleRefVec &samples, TimeValue duration,
long first, long last) {
const long inter_duration = duration / (last - first + 1);
samples[first].durationPerSample = inter_duration;
for (long i = first + 1; i < last; ++i) {
samples[i].durationPerSample = inter_duration;
samples[i].sampleFlags |= mediaSampleNotSync;
}
samples[last].durationPerSample = duration - (last - first) * inter_duration;
samples[last].sampleFlags |= mediaSampleNotSync;
}
//-----------------------------------------------------------------------------
// Add some sample references to the given QuickTime media and track.
//
// As QuickTime requires the duration of all sample references be
// positive values, here we'll calculate any missing durations (from
// the timestamp of the next-in-queue block or the last_time
// argument).
// The runs of samples with the same timestamp (e.g. multiple frames
// in blocks using lacing) will have durations approximated as if they
// were spread equidistantly between the two samples with known
// different timestamps at the ends of the run; "not sync" flag set on
// all the samples in the run but first to avoid A/V desynchronization
// when seeking.
// A positive value for the argument last_time should be passed if the
// timestamp of the _end_ of the last block (in the samples+times pair
// of vectors) is known, otherwise adding of the last block might be
// deferred until more blocks are available or positive last_time is
// given.
//
static OSErr AddSampleRefsToQTMedia(Media media, Track track, Movie movie,
SampleDescriptionHandle sample_description,
SampleRefVec &samples,
SampleTimeVec ×, long long last_time,
TimeValue * const media_offset,
long * const added_count,
long * const added_duration) {
OSErr err = noErr;
TimeValue media_duration = 0, sample_duration = 0;
const double scaling_factor = (double) GetMediaTimeScale(media) / ns_per_sec;
long num_samples = samples.size() - 1;
long last_known_index = -1;
if (num_samples < 0)
return err;
////
// Calculate block durations.
for (long i = 0; i < num_samples; ++i) {
sample_duration =
lround(times[i + 1] * scaling_factor) -
lround(times[i] * scaling_factor);
if (sample_duration == 0) {
if (last_known_index == -1)
last_known_index = i;
} else if (last_known_index != -1) {
InterpolateDurations(samples, sample_duration, last_known_index, i);
media_duration += sample_duration;
last_known_index = -1;
} else {
samples[i].durationPerSample = sample_duration;
media_duration += sample_duration;
}
}
if (last_time > 0) {
sample_duration =
lround(last_time * scaling_factor) -
lround(times[num_samples] * scaling_factor);
if (sample_duration > 0) {
if (last_known_index != -1) {