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