Audacity  2.2.2
ImportOGG.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3  Audacity: A Digital Audio Editor
4 
5  ImportOGG.cpp
6 
7  Joshua Haberman
8  Leland Lucius
9 
10 *//****************************************************************//****************************************************************//*******************************************************************/
30 
31 #include "../Audacity.h"
32 #include "ImportOGG.h"
33 
34 // For compilers that support precompilation, includes "wx/wx.h".
35 #include <wx/wxprec.h>
36 
37 #ifndef WX_PRECOMP
38 #include <wx/window.h>
39 #endif
40 
41 #include <wx/intl.h>
42 #include "../Prefs.h"
43 #include "../Internat.h"
44 #include "../Tags.h"
45 #include "../prefs/QualityPrefs.h"
46 
47 
48 #define DESC _("Ogg Vorbis files")
49 
50 static const wxChar *exts[] =
51 {
52  wxT("ogg")
53 };
54 
55 #ifndef USE_LIBVORBIS
56 /* BPF There is no real reason to compile without LIBVORBIS, but if you do, you will needs this header */
57 #include "ImportPlugin.h"
58 
59 void GetOGGImportPlugin(ImportPluginList &importPluginList,
60  UnusableImportPluginList &unusableImportPluginList)
61 {
62  unusableImportPluginList.push_back(
63  std::make_unique<UnusableImportPlugin>
64  (DESC, wxArrayString(WXSIZEOF(exts), exts))
65  );
66 }
67 
68 #else /* USE_LIBVORBIS */
69 
70 #include <wx/log.h>
71 #include <wx/string.h>
72 #include <wx/utils.h>
73 #include <wx/intl.h>
74 /* ffile.h must be included AFTER at least one other wx header that includes
75  * wx/setup.h, otherwise #ifdefs erronously collapse it to nothing. This is
76  * a bug in wxWidgets (ffile.h should itself include wx/setup.h), and it
77  * was a bitch to track down. */
78 #include <wx/ffile.h>
79 
80 #include <vorbis/vorbisfile.h>
81 
82 #include "../WaveTrack.h"
83 #include "ImportPlugin.h"
84 
85 class OggImportPlugin final : public ImportPlugin
86 {
87 public:
88  OggImportPlugin()
89  : ImportPlugin(wxArrayString(WXSIZEOF(exts), exts))
90  {
91  }
92 
93  ~OggImportPlugin() { }
94 
95  wxString GetPluginStringID() override { return wxT("liboggvorbis"); }
96  wxString GetPluginFormatDescription() override;
97  std::unique_ptr<ImportFileHandle> Open(const wxString &Filename) override;
98 };
99 
100 
101 class OggImportFileHandle final : public ImportFileHandle
102 {
103 public:
104  OggImportFileHandle(const wxString & filename,
105  std::unique_ptr<wxFFile> &&file,
106  std::unique_ptr<OggVorbis_File> &&vorbisFile)
107  : ImportFileHandle(filename),
108  mFile(std::move(file)),
109  mVorbisFile(std::move(vorbisFile))
110  , mStreamUsage{ static_cast<size_t>(mVorbisFile->links) }
111  {
113 
114  for (int i = 0; i < mVorbisFile->links; i++)
115  {
116  wxString strinfo;
117  strinfo.Printf(wxT("Index[%02x] Version[%d], Channels[%d], Rate[%ld]"), (unsigned int) i,mVorbisFile->vi[i].version,mVorbisFile->vi[i].channels,mVorbisFile->vi[i].rate);
118  mStreamInfo.Add(strinfo);
119  mStreamUsage[i] = 0;
120  }
121 
122  }
123  ~OggImportFileHandle();
124 
125  wxString GetFileDescription() override;
126  ByteCount GetFileUncompressedBytes() override;
127  ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks,
128  Tags *tags) override;
129 
130  wxInt32 GetStreamCount() override
131  {
132  if (mVorbisFile)
133  return mVorbisFile->links;
134  else
135  return 0;
136  }
137 
138  const wxArrayString &GetStreamInfo() override
139  {
140  return mStreamInfo;
141  }
142 
143  void SetStreamUsage(wxInt32 StreamID, bool Use) override
144  {
145  if (mVorbisFile)
146  {
147  if (StreamID < mVorbisFile->links)
148  mStreamUsage[StreamID] = (Use ? 1 : 0);
149  }
150  }
151 
152 private:
153  std::unique_ptr<wxFFile> mFile;
154  std::unique_ptr<OggVorbis_File> mVorbisFile;
155 
156  ArrayOf<int> mStreamUsage;
157  wxArrayString mStreamInfo;
158  std::list<TrackHolders> mChannels;
159 
160  sampleFormat mFormat;
161 };
162 
163 void GetOGGImportPlugin(ImportPluginList &importPluginList,
164  UnusableImportPluginList & WXUNUSED(unusableImportPluginList))
165 {
166  importPluginList.push_back( std::make_unique<OggImportPlugin>() );
167 }
168 
169 wxString OggImportPlugin::GetPluginFormatDescription()
170 {
171  return DESC;
172 }
173 
174 std::unique_ptr<ImportFileHandle> OggImportPlugin::Open(const wxString &filename)
175 {
176  // Suppress some compiler warnings about unused global variables in the library header
177  wxUnusedVar(OV_CALLBACKS_DEFAULT);
178  wxUnusedVar(OV_CALLBACKS_NOCLOSE);
179  wxUnusedVar(OV_CALLBACKS_STREAMONLY);
180  wxUnusedVar(OV_CALLBACKS_STREAMONLY_NOCLOSE);
181 
182  auto vorbisFile = std::make_unique<OggVorbis_File>();
183  auto file = std::make_unique<wxFFile>(filename, wxT("rb"));
184 
185  if (!file->IsOpened()) {
186  // No need for a message box, it's done automatically (but how?)
187  return nullptr;
188  }
189 
190  int err = ov_open(file->fp(), vorbisFile.get(), NULL, 0);
191 
192  if (err < 0) {
193  wxString message;
194 
195  switch (err) {
196  case OV_EREAD:
197  message = _("Media read error");
198  break;
199  case OV_ENOTVORBIS:
200  message = _("Not an Ogg Vorbis file");
201  break;
202  case OV_EVERSION:
203  message = _("Vorbis version mismatch");
204  break;
205  case OV_EBADHEADER:
206  message = _("Invalid Vorbis bitstream header");
207  break;
208  case OV_EFAULT:
209  message = _("Internal logic fault");
210  break;
211  }
212 
213  // what to do with message?
214  return nullptr;
215  }
216 
217  return std::make_unique<OggImportFileHandle>(filename, std::move(file), std::move(vorbisFile));
218 }
219 
220 wxString OggImportFileHandle::GetFileDescription()
221 {
222  return DESC;
223 }
224 
225 auto OggImportFileHandle::GetFileUncompressedBytes() -> ByteCount
226 {
227  // TODO:
228  return 0;
229 }
230 
231 ProgressResult OggImportFileHandle::Import(TrackFactory *trackFactory, TrackHolders &outTracks,
232  Tags *tags)
233 {
234  outTracks.clear();
235 
236  wxASSERT(mFile->IsOpened());
237 
238  CreateProgress();
239 
240  //Number of streams used may be less than mVorbisFile->links,
241  //but this way bitstream matches array index.
242  mChannels.resize(mVorbisFile->links);
243 
244  int i = -1;
245  for (auto &link : mChannels)
246  {
247  ++i;
248 
249  //Stream is not used
250  if (mStreamUsage[i] == 0)
251  {
252  //This is just a padding to keep bitstream number and
253  //array indices matched.
254  continue;
255  }
256 
257  vorbis_info *vi = ov_info(mVorbisFile.get(), i);
258 
259  link.resize(vi->channels);
260 
261  int c = -1;
262  for (auto &channel : link) {
263  ++c;
264 
265  channel = trackFactory->NewWaveTrack(mFormat, vi->rate);
266 
267  if (vi->channels == 2) {
268  switch (c) {
269  case 0:
270  channel->SetChannel(Track::LeftChannel);
271  channel->SetLinked(true);
272  break;
273  case 1:
274  channel->SetChannel(Track::RightChannel);
275  break;
276  }
277  }
278  else {
279  channel->SetChannel(Track::MonoChannel);
280  }
281  }
282  }
283 
284  /* The number of bytes to get from the codec in each run */
285 #define CODEC_TRANSFER_SIZE 4096u
286 
287  /* The number of samples to read between calls to the callback.
288  * Balance between responsiveness of the GUI and throughput of import. */
289 #define SAMPLES_PER_CALLBACK 100000
290 
291  auto updateResult = ProgressResult::Success;
292  long bytesRead = 0;
293  {
294  ArrayOf<short> mainBuffer{ CODEC_TRANSFER_SIZE };
295 
296  /* determine endianness (clever trick courtesy of Nicholas Devillard,
297  * (http://www.eso.org/~ndevilla/endian/) */
298  int testvar = 1, endian;
299  if (*(char *)&testvar)
300  endian = 0; // little endian
301  else
302  endian = 1; // big endian
303 
304  /* number of samples currently in each channel's buffer */
305  long samplesRead = 0;
306  int bitstream = 0;
307  int samplesSinceLastCallback = 0;
308 
309  // You would think that the stream would already be seeked to 0, and
310  // indeed it is if the file is legit. But I had several ogg files on
311  // my hard drive that have malformed headers, and this added call
312  // causes them to be read correctly. Otherwise they have lots of
313  // zeros inserted at the beginning
314  ov_pcm_seek(mVorbisFile.get(), 0);
315 
316  do {
317  /* get data from the decoder */
318  bytesRead = ov_read(mVorbisFile.get(), (char *)mainBuffer.get(),
319  CODEC_TRANSFER_SIZE,
320  endian,
321  2, // word length (2 for 16 bit samples)
322  1, // signed
323  &bitstream);
324 
325  if (bytesRead == OV_HOLE) {
326  wxFileName ff(mFilename);
327  wxLogError(wxT("Ogg Vorbis importer: file %s is malformed, ov_read() reported a hole"),
328  ff.GetFullName());
329  /* http://lists.xiph.org/pipermail/vorbis-dev/2001-February/003223.html
330  * is the justification for doing this - best effort for malformed file,
331  * hence the message.
332  */
333  continue;
334  }
335  else if (bytesRead < 0) {
336  /* Malformed Ogg Vorbis file. */
337  /* TODO: Return some sort of meaningful error. */
338  wxLogError(wxT("Ogg Vorbis importer: ov_read() returned error %i"),
339  bytesRead);
340  break;
341  }
342 
343  samplesRead = bytesRead / mVorbisFile->vi[bitstream].channels / sizeof(short);
344 
345  /* give the data to the wavetracks */
346  auto iter = mChannels.begin();
347  std::advance(iter, bitstream);
348  if (mStreamUsage[bitstream] != 0)
349  {
350  auto iter2 = iter->begin();
351  for (int c = 0; c < mVorbisFile->vi[bitstream].channels; ++iter2, ++c)
352  iter2->get()->Append((char *)(mainBuffer.get() + c),
353  int16Sample,
354  samplesRead,
355  mVorbisFile->vi[bitstream].channels);
356  }
357 
358  samplesSinceLastCallback += samplesRead;
359  if (samplesSinceLastCallback > SAMPLES_PER_CALLBACK) {
360  updateResult = mProgress->Update(ov_time_tell(mVorbisFile.get()),
361  ov_time_total(mVorbisFile.get(), bitstream));
362  samplesSinceLastCallback -= SAMPLES_PER_CALLBACK;
363  }
364  } while (updateResult == ProgressResult::Success && bytesRead != 0);
365  }
366 
367  auto res = updateResult;
368  if (bytesRead < 0)
370 
371  if (res == ProgressResult::Failed || res == ProgressResult::Cancelled) {
372  return res;
373  }
374 
375  for (auto &link : mChannels)
376  {
377  for (auto &channel : link) {
378  channel->Flush();
379  outTracks.push_back(std::move(channel));
380  }
381  }
382 
383  //\todo { Extract comments from each stream? }
384  if (mVorbisFile->vc[0].comments > 0) {
385  tags->Clear();
386  for (int c = 0; c < mVorbisFile->vc[0].comments; c++) {
387  wxString comment = UTF8CTOWX(mVorbisFile->vc[0].user_comments[c]);
388  wxString name = comment.BeforeFirst(wxT('='));
389  wxString value = comment.AfterFirst(wxT('='));
390  if (name.Upper() == wxT("DATE") && !tags->HasTag(TAG_YEAR)) {
391  long val;
392  if (value.Length() == 4 && value.ToLong(&val)) {
393  name = TAG_YEAR;
394  }
395  }
396  tags->SetTag(name, value);
397  }
398  }
399 
400  return res;
401 }
402 
403 OggImportFileHandle::~OggImportFileHandle()
404 {
405  ov_clear(mVorbisFile.get());
406  mFile->Detach(); // so that it doesn't try to close the file (ov_clear()
407  // did that already)
408 }
409 
410 #endif /* USE_LIBVORBIS */
void SetTag(const wxString &name, const wxString &value)
Definition: Tags.cpp:449
ProgressResult
An ImportFileHandle for data.
Definition: ImportPlugin.h:119
virtual void SetStreamUsage(wxInt32 StreamID, bool Use)=0
virtual wxString GetFileDescription()=0
virtual ProgressResult Import(TrackFactory *trackFactory, TrackHolders &outTracks, Tags *tags)=0
#define UTF8CTOWX(X)
Definition: Internat.h:179
Used to create a WaveTrack, or a LabelTrack.. Implementation of the functions of this class are dispe...
Definition: Track.h:862
virtual wxInt32 GetStreamCount()=0
static const wxChar * exts[]
Definition: ImportOGG.cpp:50
void Clear()
Definition: Tags.cpp:300
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...
#define DESC
Definition: ImportOGG.cpp:48
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
void GetOGGImportPlugin(ImportPluginList &importPluginList, UnusableImportPluginList &unusableImportPluginList)
Definition: ImportOGG.cpp:59
const wxChar * name
Definition: Distortion.cpp:94
An UnusableImportPlugin list.
An ImportPlugin list.
virtual ByteCount GetFileUncompressedBytes()=0
virtual wxString GetPluginFormatDescription()=0
static sampleFormat SampleFormatChoice()
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
#define TAG_YEAR
Definition: Tags.h:64
bool HasTag(const wxString &name) const
Definition: Tags.cpp:415