Audacity  2.2.2
ImportQT.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ImportQT.cpp
6 
7  Joshua Haberman
8 
9  On OS X, handles import of MPEG-4 audio including AAC and Apple
10  Lossless, import of audio from MPEG-4 video files, and import of
11  AIF(F)/AIFC files (AIF(F) might contain content libsndfile can't
12  handle). Could be modified to support QuickTime import on Windows.
13 
14 **********************************************************************/
15 
16 #include "../Audacity.h"
17 #include "ImportQT.h"
18 #include "ImportPlugin.h"
19 #include "../widgets/ErrorDialog.h"
20 
21 #define DESC _("QuickTime files")
22 
23 static const wxChar *exts[] =
24 {
25  wxT("aif"),
26  wxT("aifc"),
27  wxT("aiff"),
28  wxT("mov"),
29  wxT("aac"),
30  wxT("m4a"),
31  wxT("mp4")
32 };
33 
34 #if defined(__WXMAC__)
35 #undef USE_QUICKTIME
36 #endif
37 
38 #ifndef USE_QUICKTIME
39 
40 void GetQTImportPlugin(ImportPluginList &importPluginList,
41  UnusableImportPluginList &unusableImportPluginList)
42 {
43  unusableImportPluginList.push_back(
44  std::make_unique<UnusableImportPlugin>
45  (DESC, wxArrayString(WXSIZEOF(exts), exts))
46  );
47 }
48 
49 #else /* USE_QUICKTIME */
50 
51 // There's a name collision between our Track and QuickTime's...workaround it
52 #define Track XTrack
53 
54 #if defined(__WXMAC__)
55 // These get used when building under OSX
56  #include <Carbon/Carbon.h>
57  #include <QuickTime/QuickTime.h>
58 
59  #include <wx/osx/core/private.h>
60 #else
61 // These get used when building under Windows
62  #include <ConditionalMacros.h>
63  #include <Movies.h>
64  #include <QuickTimeComponents.h>
65  #include <Sound.h>
66  #include <Folders.h>
67  #include <ToolUtils.h>
68  #include <Gestalt.h>
69  #include <Navigation.h>
70 #endif
71 
72 // There's a name collision between our Track and QuickTime's...workaround it
73 #undef Track
74 
75 #include "../Internat.h"
76 #include "../Tags.h"
77 #include "../WaveTrack.h"
78 
79 #define kQTAudioPropertyID_MaxAudioSampleSize 'mssz'
80 
81 class QTImportPlugin final : public ImportPlugin
82 {
83  public:
84  QTImportPlugin()
85  : ImportPlugin(wxArrayString(WXSIZEOF(exts), exts)),
86  mInitialized(false)
87  {
88  OSErr err = noErr;
89 
90 #if defined(__WXMSW__)
91  err = ::InitializeQTML(0);
92 #endif
93 
94  if (err == noErr) {
95  err = ::EnterMovies();
96  if (err == noErr) {
97  mInitialized = true;
98  }
99  else {
100 #if defined(__WXMSW__)
101  TerminateQTML();
102 #endif
103  }
104  }
105  }
106 
107  ~QTImportPlugin()
108  {
109  if (mInitialized) {
110  ExitMovies();
111 #if defined(__WXMSW__)
112  TerminateQTML();
113 #endif
114  }
115 
116  mInitialized = false;
117  }
118 
119  wxString GetPluginStringID() { return wxT("quicktime"); }
120 
121  wxString GetPluginFormatDescription();
122  std::unique_ptr<ImportFileHandle> Open(const wxString & Filename) override;
123 
124  private:
125  bool mInitialized;
126 };
127 
128 class QTImportFileHandle final : public ImportFileHandle
129 {
130  public:
131  QTImportFileHandle(const wxString & name, Movie movie)
132  : ImportFileHandle(name)
133  {
134  mMovie = movie;
135  }
136 
137  virtual ~QTImportFileHandle()
138  {
139  if (mMovie) {
140  DisposeMovie(mMovie);
141  mMovie = NULL;
142  }
143  }
144 
145  wxString GetFileDescription() override;
146  ByteCount GetFileUncompressedBytes() override;
147 
148  wxInt32 GetStreamCount() override
149  {
150  return 1;
151  }
152 
153  const wxArrayString &GetStreamInfo() override
154  {
155  static wxArrayString empty;
156  return empty;
157  }
158 
159  void SetStreamUsage(wxInt32 StreamID, bool Use) override
160  {
161  }
162 
163  ProgressResult Import(TrackFactory *trackFactory,
164  TrackHolders &outTracks,
165  Tags *tags) override;
166 
167  private:
168  void AddMetadata(Tags *tags);
169 
170  Movie mMovie;
171 };
172 
173 void GetQTImportPlugin(ImportPluginList &importPluginList,
174  UnusableImportPluginList &unusableImportPluginList)
175 {
176  importPluginList.push_back( std::make_unique<QTImportPlugin>() );
177 }
178 
179 wxString QTImportPlugin::GetPluginFormatDescription()
180 {
181  return DESC;
182 }
183 
184 std::unique_ptr<ImportFileHandle> QTImportPlugin::Open(const wxString & Filename)
185 {
186  OSErr err;
187  FSRef inRef;
188  Movie theMovie = NULL;
189  Handle dataRef = NULL;
190  OSType dataRefType = 0;
191  short resID = 0;
192 
193 #if defined(__WXMAC__)
194  err = wxMacPathToFSRef(Filename, &inRef);
195 #else
196  // LLL: This will not work for pathnames with Unicode characters...find
197  // another method.
198  err = FSPathMakeRef((UInt8 *)OSFILENAME(Filename), &inRef, NULL);
199 #endif
200 
201  if (err != noErr) {
202  return nullptr;
203  }
204 
205  err = QTNewDataReferenceFromFSRef(&inRef, 0, &dataRef, &dataRefType);
206  if (err != noErr) {
207  return nullptr;
208  }
209 
210  // instantiate the movie
211  err = NewMovieFromDataRef(&theMovie,
212  newMovieActive | newMovieDontAskUnresolvedDataRefs,
213  &resID,
214  dataRef,
215  dataRefType);
216  DisposeHandle(dataRef);
217  if (err != noErr) {
218  return nullptr;
219  }
220 
221  return std::make_unique<QTImportFileHandle>(Filename, theMovie);
222 }
223 
224 
225 wxString QTImportFileHandle::GetFileDescription()
226 {
227  return DESC;
228 }
229 
230 auto QTImportFileHandle::GetFileUncompressedBytes() -> ByteCount
231 {
232  return 0;
233 }
234 
235 ProgressResult QTImportFileHandle::Import(TrackFactory *trackFactory,
236  TrackHolders &outTracks,
237  Tags *tags)
238 {
239  outTracks.clear();
240 
241  OSErr err = noErr;
242  MovieAudioExtractionRef maer = NULL;
243  auto updateResult = ProgressResult::Success;
244  auto totSamples =
245  (sampleCount) GetMovieDuration(mMovie); // convert from TimeValue
246  decltype(totSamples) numSamples = 0;
247  Boolean discrete = true;
248  UInt32 quality = kQTAudioRenderQuality_Max;
249  AudioStreamBasicDescription desc;
250  UInt32 maxSampleSize;
251  bool res = false;
252 
253  auto cleanup = finally( [&] {
254  if (maer) {
255  MovieAudioExtractionEnd(maer);
256  }
257  } );
258 
259  CreateProgress();
260 
261  do
262  {
263  err = MovieAudioExtractionBegin(mMovie, 0, &maer);
264  if (err != noErr) {
265  AudacityMessageBox(_("Unable to start QuickTime extraction"));
266  break;
267  }
268 
269  err = MovieAudioExtractionSetProperty(maer,
270  kQTPropertyClass_MovieAudioExtraction_Audio,
271  kQTMovieAudioExtractionAudioPropertyID_RenderQuality,
272  sizeof(quality),
273  &quality);
274  if (err != noErr) {
275  AudacityMessageBox(_("Unable to set QuickTime render quality"));
276  break;
277  }
278 
279  err = MovieAudioExtractionSetProperty(maer,
280  kQTPropertyClass_MovieAudioExtraction_Movie,
281  kQTMovieAudioExtractionMoviePropertyID_AllChannelsDiscrete,
282  sizeof(discrete),
283  &discrete);
284  if (err != noErr) {
285  AudacityMessageBox(_("Unable to set QuickTime discrete channels property"));
286  break;
287  }
288 
289  err = MovieAudioExtractionGetProperty(maer,
290  kQTPropertyClass_MovieAudioExtraction_Audio,
291  kQTAudioPropertyID_MaxAudioSampleSize,
292  sizeof(maxSampleSize),
293  &maxSampleSize,
294  NULL);
295  if (err != noErr) {
296  AudacityMessageBox(_("Unable to get QuickTime sample size property"));
297  break;
298  }
299 
300  err = MovieAudioExtractionGetProperty(maer,
301  kQTPropertyClass_MovieAudioExtraction_Audio,
302  kQTMovieAudioExtractionAudioPropertyID_AudioStreamBasicDescription,
303  sizeof(desc),
304  &desc,
305  NULL);
306  if (err != noErr) {
307  AudacityMessageBox(_("Unable to retrieve stream description"));
308  break;
309  }
310 
311  auto numchan = desc.mChannelsPerFrame;
312  const size_t bufsize = 5 * desc.mSampleRate;
313 
314  // determine sample format
316  switch (maxSampleSize)
317  {
318  case 16:
319  format = int16Sample;
320  break;
321 
322  case 24:
323  format = int24Sample;
324  break;
325 
326  default:
327  format = floatSample;
328  break;
329  }
330 
331  // Allocate an array of pointers, whose size is not known statically,
332  // and prefixed with the AudioBufferList structure.
334  static_cast< AudioBufferList * >(
335  calloc( 1, offsetof( AudioBufferList, mBuffers ) +
336  (sizeof(AudioBuffer) * numchan))) };
337  abl->mNumberBuffers = numchan;
338 
339  TrackHolders channels{ numchan };
340 
341  const auto size = sizeof(float) * bufsize;
342  ArraysOf<unsigned char> holders{ numchan, size };
343  for (size_t c = 0; c < numchan; c++) {
344  auto &buffer = abl->mBuffers[c];
345  auto &holder = holders[c];
346  auto &channel = channels[c];
347 
348  buffer.mNumberChannels = 1;
349  buffer.mDataByteSize = size;
350 
351  buffer.mData = holder.get();
352 
353  channel = trackFactory->NewWaveTrack( format );
354  channel->SetRate( desc.mSampleRate );
355 
356  if (numchan == 2) {
357  if (c == 0) {
358  channel->SetChannel(Track::LeftChannel);
359  channel->SetLinked(true);
360  }
361  else if (c == 1) {
362  channel->SetChannel(Track::RightChannel);
363  }
364  }
365  }
366 
367  do {
368  UInt32 flags = 0;
369  UInt32 numFrames = bufsize;
370 
371  err = MovieAudioExtractionFillBuffer(maer,
372  &numFrames,
373  abl.get(),
374  &flags);
375  if (err != noErr) {
376  AudacityMessageBox(_("Unable to get fill buffer"));
377  break;
378  }
379 
380  for (size_t c = 0; c < numchan; c++) {
381  channels[c]->Append((char *) abl->mBuffers[c].mData, floatSample, numFrames);
382  }
383 
384  numSamples += numFrames;
385 
386  updateResult = mProgress->Update(
387  numSamples.as_long_long(),
388  totSamples.as_long_long() );
389 
390  if (numFrames == 0 || flags & kQTMovieAudioExtractionComplete) {
391  break;
392  }
393  } while (updateResult == ProgressResult::Success);
394 
395  res = (updateResult == ProgressResult::Success && err == noErr);
396 
397  if (res) {
398  for (const auto &channel: channels) {
399  channel->Flush();
400  }
401 
402  outTracks.swap(channels);
403  }
404 
405  //
406  // Extract any metadata
407  //
408  if (res) {
409  AddMetadata(tags);
410  }
411  } while (false);
412 
413 // done:
414 
416 }
417 
418 static const struct
419 {
420  const OSType key;
421  const wxChar *name;
422 }
423 names[] =
424 {
425  { kQTMetaDataCommonKeyAuthor, wxT("Author") },
426  { kQTMetaDataCommonKeyComment, TAG_COMMENTS },
427  { kQTMetaDataCommonKeyCopyright, TAG_COPYRIGHT },
428  { kQTMetaDataCommonKeyDirector, wxT("Director") },
429  { kQTMetaDataCommonKeyDisplayName, wxT("Full Name") },
430  { kQTMetaDataCommonKeyInformation, wxT("Information") },
431  { kQTMetaDataCommonKeyKeywords, wxT("Keywords") },
432  { kQTMetaDataCommonKeyProducer, wxT("Producer") },
433  { kQTMetaDataCommonKeyAlbum, TAG_ALBUM },
434  { kQTMetaDataCommonKeyArtist, TAG_ARTIST },
435  { kQTMetaDataCommonKeyChapterName, wxT("Chapter") },
436  { kQTMetaDataCommonKeyComposer, wxT("Composer") },
437  { kQTMetaDataCommonKeyDescription, wxT("Description") },
438  { kQTMetaDataCommonKeyGenre, TAG_GENRE },
439  { kQTMetaDataCommonKeyOriginalFormat, wxT("Original Format") },
440  { kQTMetaDataCommonKeyOriginalSource, wxT("Original Source") },
441  { kQTMetaDataCommonKeyPerformers, wxT("Performers") },
442  { kQTMetaDataCommonKeySoftware, TAG_SOFTWARE },
443  { kQTMetaDataCommonKeyWriter, wxT("Writer") },
444 };
445 
446 void QTImportFileHandle::AddMetadata(Tags *tags)
447 {
448  QTMetaDataRef metaDataRef = NULL;
449  auto cleanup = finally( [&] {
450  // we are done so release our metadata object
451  if ( metaDataRef )
452  QTMetaDataRelease(metaDataRef);
453  } );
454 
455  OSErr err;
456 
457  err = QTCopyMovieMetaData(mMovie, &metaDataRef);
458  if (err != noErr) {
459  return;
460  }
461 
462  for (int i = 0; i < WXSIZEOF(names); i++) {
463  QTMetaDataItem item = kQTMetaDataItemUninitialized;
464  // OSType key = names[i].key;
465 
466  err = QTMetaDataGetNextItem(metaDataRef,
467  kQTMetaDataStorageFormatWildcard,
468  kQTMetaDataItemUninitialized,
469  kQTMetaDataKeyFormatCommon,
470  (const UInt8 *) &names[i].key,
471  sizeof(names[i].key),
472  &item);
473  if (err != noErr) {
474  continue;
475  }
476 
477  if (item == kQTMetaDataItemUninitialized) {
478  continue;
479  }
480 
481  QTPropertyValueType outPropType;
482  ::ByteCount outPropValueSize;
483  ::ByteCount outPropValueSizeUsed = 0;
484  UInt32 outPropFlags;
485  UInt32 dataType;
486 
487  // Get data type
488  err = QTMetaDataGetItemProperty(metaDataRef,
489  item,
490  kPropertyClass_MetaDataItem,
491  kQTMetaDataItemPropertyID_DataType,
492  sizeof(dataType),
493  &dataType,
494  &outPropValueSizeUsed);
495  if (err != noErr) {
496  continue;
497  }
498 
499  // Get the data length
500  err = QTMetaDataGetItemPropertyInfo(metaDataRef,
501  item,
502  kPropertyClass_MetaDataItem,
503  kQTMetaDataItemPropertyID_Value,
504  &outPropType,
505  &outPropValueSize,
506  &outPropFlags );
507  if (err != noErr) {
508  continue;
509  }
510 
511  // Alloc memory for it
512  ArrayOf<char> outVals{ outPropValueSize };
513 
514  // Retrieve the data
515  err = QTMetaDataGetItemProperty(metaDataRef,
516  item,
517  kPropertyClass_MetaDataItem,
518  kQTMetaDataItemPropertyID_Value,
519  outPropValueSize,
520  outVals.get(),
521  &outPropValueSizeUsed);
522  if (err != noErr)
523  continue;
524 
525  wxString v = wxT("");
526 
527  switch (dataType)
528  {
529  case kQTMetaDataTypeUTF8:
530  v = wxString(outVals.get(), wxConvUTF8);
531  break;
532  case kQTMetaDataTypeUTF16BE:
533  {
534  wxMBConvUTF16BE conv;
535  v = wxString(outVals.get(), conv);
536  }
537  break;
538  }
539 
540  if (!v.IsEmpty()) {
541  tags->SetTag(names[i].name, v);
542  }
543  }
544 
545  return;
546 }
547 
548 #endif
void SetTag(const wxString &name, const wxString &value)
Definition: Tags.cpp:449
static wxArrayString names()
Definition: Tags.cpp:697
memory.h template class for making an array of arrays.
Definition: MemoryX.h:137
ProgressResult
An ImportFileHandle for data.
Definition: ImportPlugin.h:119
virtual void SetStreamUsage(wxInt32 StreamID, bool Use)=0
virtual wxString GetFileDescription()=0
const wxChar * desc
Definition: ExportPCM.cpp:58
virtual ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags)=0
#define DESC
Definition: ImportQT.cpp:21
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
#define OSFILENAME(X)
Definition: Internat.h:173
#define TAG_ARTIST
Definition: Tags.h:61
static const wxChar * exts[]
Definition: ImportQT.cpp:23
Used to create a WaveTrack, or a LabelTrack.. Implementation of the functions of this class are dispe...
Definition: Track.h:862
#define TAG_SOFTWARE
Definition: Tags.h:67
virtual wxInt32 GetStreamCount()=0
Final_action< F > finally(F f)
Definition: MemoryX.h:453
int format
Definition: ExportPCM.cpp:56
void GetQTImportPlugin(ImportPluginList &importPluginList, UnusableImportPluginList &unusableImportPluginList)
Definition: ImportQT.cpp:40
virtual const wxArrayString & GetStreamInfo()=0
sampleFormat
Definition: Types.h:188
The interface that all file import "plugins" (if you want to call them that) must implement...
ID3 Tags (for MP3)
Definition: Tags.h:70
std::unique_ptr< WaveTrack > NewWaveTrack(sampleFormat format=(sampleFormat) 0, double rate=0)
Definition: WaveTrack.cpp:78
Base class for FlacImportPlugin, LOFImportPlugin, MP3ImportPlugin, OggImportPlugin and PCMImportPlugi...
Definition: ImportPlugin.h:73
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
#define TAG_COPYRIGHT
Definition: Tags.h:68
const wxChar * name
Definition: Distortion.cpp:94
#define TAG_COMMENTS
Definition: Tags.h:66
#define TAG_GENRE
Definition: Tags.h:65
An UnusableImportPlugin list.
An ImportPlugin list.
virtual ByteCount GetFileUncompressedBytes()=0
virtual wxString GetPluginFormatDescription()=0
virtual std::unique_ptr< ImportFileHandle > Open(const wxString &Filename)=0
virtual wxString GetPluginStringID()=0
std::vector< std::unique_ptr< WaveTrack >> TrackHolders
Definition: ImportRaw.h:24
std::unique_ptr< T, freer > MallocPtr
Definition: MemoryX.h:411
#define TAG_ALBUM
Definition: Tags.h:62