1 /*
2 * Copyright (C) 2002 - David W. Durham
3 *
4 * This file is part of ReZound, an audio editing application.
5 *
6 * ReZound is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 2 of the License,
9 * or (at your option) any later version.
10 *
11 * ReZound is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19 */
20
21 /*
22 * Much of this source code is adapted from the ogg/vorbis example source code files
23 *
24 * Something I found to be a bad thing is that if you load and save the same ogg file
25 * over and over with a bit of a low bit rate compression, then the artifacts of the
26 * compression noise become quite louder and louder.
27 *
28 */
29
30 #include "ClibvorbisSoundTranslator.h"
31
32 #if defined(HAVE_LIBVORBIS) && defined(HAVE_LIBOGG)
33
34 #include <stdio.h> // for fopen/fclose
35 #include <errno.h>
36 #include <string.h> // for strerror()
37 #include <ctype.h>
38 #include <unistd.h> // for unlink
39
40 #include <time.h> // for time()
41 #include <stdlib.h> // for rand() and atoll
42
43 #include "vorbis/codec.h"
44 #include "vorbis/vorbisfile.h"
45 #include "vorbis/vorbisenc.h"
46
47
48 #include <stdexcept>
49 #include <vector>
50
51 #include <istring>
52 #include <TAutoBuffer.h>
53
54 #include <CPath.h>
55
56 #include "CSound.h"
57 #include "AStatusComm.h"
58 #include "AFrontendHooks.h"
59
60
61 #ifdef WORDS_BIGENDIAN
62 #define ENDIAN 1
63 #else
64 #define ENDIAN 0
65 #endif
66
ClibvorbisSoundTranslator()67 ClibvorbisSoundTranslator::ClibvorbisSoundTranslator()
68 {
69 }
70
~ClibvorbisSoundTranslator()71 ClibvorbisSoundTranslator::~ClibvorbisSoundTranslator()
72 {
73 }
74
OVstrerror(int e)75 static const string OVstrerror(int e)
76 {
77 switch(e)
78 {
79 case OV_FALSE:
80 return "not true, or no data available";
81 case OV_HOLE:
82 return "vorbisfile encoutered missing or corrupt data in the bitstream. Recovery is normally automatic and this return code is for informational purposes only";
83 case OV_EREAD:
84 return "read error while fetching compressed data for decode";
85 case OV_EFAULT:
86 return "internal inconsistency in decode state. Continuing is likely not possible.";
87 case OV_EIMPL:
88 return "feature not implemented";
89 case OV_EINVAL:
90 return "either an invalid argument, or incompletely initialized argument passed to libvorbisfile call";
91 case OV_ENOTVORBIS:
92 return "the given file/data was not recognized as Ogg Vorbis data";
93 case OV_EBADHEADER:
94 return "the file/data is apparently an Ogg Vorbis stream, but contains a corrupted or undecipherable header";
95 case OV_EVERSION:
96 return "the bitstream format revision of the given stream is not supported.";
97 case OV_EBADLINK:
98 return "the given link exists in the Vorbis data stream, but is not decipherable due to garbacge or corruption";
99 case OV_ENOSEEK:
100 return "the given stream is not seekable";
101 default:
102 return "undocumented/unknown Ogg/Vorbis error code: "+istring(e);
103 }
104 }
105
106
107 // ??? could just return a CSound object an have used the one constructor that takes the meta info
108 // ??? but, then how would I be able to have createWorkingPoolFileIfExists
onLoadSound(const string filename,CSound * sound) const109 bool ClibvorbisSoundTranslator::onLoadSound(const string filename,CSound *sound) const
110 {
111 #ifdef HAVE_LIBVORBIS
112 bool ret=true;
113
114 int e;
115 FILE *f=fopen(filename.c_str(),"rb");
116 int err=errno;
117 if(f==NULL)
118 throw runtime_error(string(__func__)+" -- error opening '"+filename+"' -- "+strerror(err));
119
120 OggVorbis_File vf;
121 if((e=ov_open(f, &vf, NULL, 0))<0)
122 {
123 fclose(f);
124 throw runtime_error(string(__func__)+" -- error opening ogg file or may not be an Ogg bitstream -- "+OVstrerror(e));
125 }
126
127 CRezPoolAccesser *accessers[MAX_CHANNELS]={0};
128 try
129 {
130
131 vorbis_info *vi=ov_info(&vf,-1);
132
133 unsigned channelCount=vi->channels;
134 if(channelCount<=0 || channelCount>MAX_CHANNELS) // ??? could just ignore the extra channels
135 throw runtime_error(string(__func__)+" -- invalid number of channels in audio file: "+istring(channelCount)+" -- you could simply increase MAX_CHANNELS in CSound.h");
136
137 unsigned sampleRate=vi->rate;
138 if(sampleRate<4000 || sampleRate>96000)
139 throw runtime_error(string(__func__)+" -- an unlikely sample rate of "+istring(sampleRate));
140
141 #define REALLOC_FILE_SIZE (1024*1024/4)
142
143 // ??? make sure it's not more than MAX_LENGTH
144 // ??? just truncate the length
145 sample_pos_t size=REALLOC_FILE_SIZE; // start with an initial size unless there's a way to get the final length from vorbisfile
146 if(size<0)
147 throw runtime_error(string(__func__)+" -- libvorbis reports the data length as "+istring(size));
148
149 sound->createWorkingPoolFile(filename,sampleRate,channelCount,size);
150
151 /*
152 * As best as I can tell, the ogg stream format for user comments
153 * is simply an array of strings. Now the API presets an interface
154 * for supporting 'tags' but it looked to me like all this is is a
155 * tag name follow by an '=' constitutes a tag a value (which
156 * follows the '='). So I will use these 'tags' to loading and
157 * saving of cues, and the usernotes will be made up of any tags in
158 * the user comments that aren't my cue tags.
159 */
160
161 vorbis_comment *oc=ov_comment(&vf,-1);
162 char **commentsArray;
163 vector<int> commentsThatWereCues;
164
165 // load the cues
166 commentsArray=oc->user_comments;
167 sound->clearCues();
168 for(int t=0;t<oc->comments;t++)
169 {
170 if(strncmp(*commentsArray,"CUE=",4)==0)
171 {
172 string cuePos;
173 string cueName;
174 bool isAnchored=false;
175
176 // parse "[0-9]+[+-][A-Z]*\0" which should follow the "CUE="
177 // which is the sample position, +(is anchored) -(is not), cue name
178 char *cueDesc=*commentsArray+4;
179 int state=0;
180 for(;;)
181 {
182 const char c=*cueDesc;
183 cueDesc++;
184 if(c==0)
185 break;
186
187 switch(state)
188 {
189 case 0:
190 if(isdigit(c))
191 {
192 cuePos.append(&c,1);
193 state=1;
194 }
195 else
196 goto notACue;
197 break;
198
199 case 1:
200 if(isdigit(c))
201 cuePos.append(&c,1);
202 else if(c=='-')
203 {
204 isAnchored=false;
205 state=2;
206 }
207 else if(c=='+')
208 {
209 isAnchored=true;
210 state=2;
211 }
212 else
213 goto notACue;
214 break;
215
216 case 2:
217 cueName.append(&c,1);
218 break;
219
220 default:
221 goto notACue;
222 }
223 }
224
225 if(cueName=="")
226 cueName=sound->getUnusedCueName("cue"); // give it a unique name
227
228 try
229 {
230 sound->addCue(cueName,(sample_pos_t)atoll(cuePos.c_str()),isAnchored);
231 }
232 catch(exception &e)
233 { // don't make an error adding a cue cause the whole file not to load
234 Warning("file: '"+filename+"' -- "+e.what());
235 }
236
237 commentsThatWereCues.push_back(t);
238 }
239
240 notACue:
241
242 commentsArray++;
243 }
244
245 // load the user notes
246 commentsArray=oc->user_comments;
247 string userNotes;
248 size_t ctwcPos=0;
249 for(int t=0;t<oc->comments;t++)
250 {
251 if(commentsThatWereCues.empty() || commentsThatWereCues[ctwcPos]!=t)
252 { // comment was not previously found to be a cue definition
253 userNotes+=(*commentsArray);
254 userNotes+="\n";
255 }
256 else // ignore comment if it was a cue definition
257 ctwcPos++;
258
259 commentsArray++;
260 }
261 sound->setUserNotes(userNotes);
262
263
264 // load the audio data
265 for(unsigned t=0;t<channelCount;t++)
266 accessers[t]=new CRezPoolAccesser(sound->getAudio(t));
267
268 unsigned long count=CPath(filename).getSize();
269 CStatusBar statusBar("Loading Sound",ftell(f),count,true);
270
271 // ??? if sample_t is not the bit type that ogg is going to return I should write some convertion functions... that are overloaded to go to and from several types to and from sample_t
272 // ??? this needs to match the type of sample_t
273 // ??? float is supported by ov_read_float
274 #define BIT_RATE 16
275
276 TAutoBuffer<sample_t> mem_buffer(4096);
277 sample_pos_t pos=0;
278
279 int eof=0;
280 int current_section;
281 for(;;)
282 {
283 #if defined(SAMPLE_TYPE_S16)
284 sample_t * const buffer=mem_buffer;
285 const long read_ret=ov_read(&vf,(char *)buffer,mem_buffer.getSize()*sizeof(sample_t),ENDIAN,sizeof(sample_t),1,¤t_section);
286 const int readLength=read_ret/(sizeof(sample_t)*channelCount);
287
288 #elif defined(SAMPLE_TYPE_FLOAT)
289 float **buffer;
290 const long read_ret=ov_read_float(&vf,&buffer,mem_buffer.getSize(),¤t_section);
291 const int readLength=read_ret;
292
293 #else
294 #error unhandle SAMPLE_TYPE_xxx define
295 #endif
296
297 if(read_ret==0)
298 break;
299 else if(read_ret<0)
300 { // error in the stream... may not be fatal however
301 fprintf(stderr,"error returned from ov_read_xxx -- continuing to read -- skip may exist in audio now -- %s\n",OVstrerror(read_ret).c_str());
302 }
303 else // if(read_ret>0)
304 {
305 if((pos+REALLOC_FILE_SIZE)>sound->getLength())
306 sound->addSpace(sound->getLength(),REALLOC_FILE_SIZE);
307
308 for(unsigned c=0;c<channelCount;c++)
309 {
310 CRezPoolAccesser &accesser=*(accessers[c]);
311 #if defined(SAMPLE_TYPE_S16)
312 // buffer points to frames of audio
313 for(int i=0;i<readLength;i++)
314 accesser[pos+i]=buffer[i*channelCount+c];
315 #elif defined(SAMPLE_TYPE_FLOAT)
316 // buffer points to arrays of samples of audio (1 array for each channel)
317 const float * const _buffer=buffer[c];
318 for(int i=0;i<readLength;i++)
319 accesser[pos+i]=_buffer[i];
320 #else
321 #error unhandle SAMPLE_TYPE_xxx define
322 #endif
323 }
324
325 pos+=readLength;
326 }
327
328 if(statusBar.update(ftell(f)))
329 { // cancelled
330 ret=false;
331 goto cancelled;
332 }
333 }
334
335 // remove any extra allocated space
336 if(sound->getLength()>pos)
337 sound->removeSpace(pos,sound->getLength()-pos);
338
339 cancelled:
340
341 for(unsigned t=0;t<MAX_CHANNELS;t++)
342 {
343 delete accessers[t];
344 accessers[t]=NULL;
345 }
346 }
347 catch(...)
348 {
349 ov_clear(&vf); // closes file too
350
351 for(unsigned t=0;t<MAX_CHANNELS;t++)
352 delete accessers[t];
353 throw;
354 }
355
356 ov_clear(&vf); // closes file too
357
358 return ret;
359 #else
360 throw runtime_error(string(__func__)+" -- loading Ogg Vorbis is not enabled -- missing libvorbisfile");
361 #endif
362 }
363
onSaveSound(const string filename,const CSound * sound,const sample_pos_t saveStart,const sample_pos_t saveLength,bool useLastUserPrefs) const364 bool ClibvorbisSoundTranslator::onSaveSound(const string filename,const CSound *sound,const sample_pos_t saveStart,const sample_pos_t saveLength,bool useLastUserPrefs) const
365 {
366 #ifdef HAVE_LIBVORBIS
367 bool ret=true;
368
369 vorbis_info vi;
370
371
372 int e;
373 const unsigned channelCount=sound->getChannelCount();
374 const unsigned sampleRate=sound->getSampleRate();
375
376 vorbis_info_init(&vi);
377
378 // get user preferences for saving the ogg
379 static bool parametersGotten=false;
380 static AFrontendHooks::OggCompressionParameters parameters;
381 useLastUserPrefs&=parametersGotten;
382 if(!useLastUserPrefs)
383 {
384 if(!gFrontendHooks->promptForOggCompressionParameters(parameters))
385 return false;
386 parametersGotten=true;
387 }
388
389 if(parameters.method==AFrontendHooks::OggCompressionParameters::brVBR)
390 {
391 if((e=vorbis_encode_init(&vi,channelCount,sampleRate,parameters.maxBitRate,parameters.normBitRate,parameters.minBitRate))<0)
392 throw runtime_error(string(__func__)+" -- error initializing the Ogg Vorbis encoder engine; perhaps try different compression parameters -- "+OVstrerror(e));
393 }
394 else if(parameters.method==AFrontendHooks::OggCompressionParameters::brQuality)
395 {
396 if((e=vorbis_encode_init_vbr(&vi,channelCount,sampleRate,parameters.quality))<0)
397 throw runtime_error(string(__func__)+" -- error initializing the Ogg Vorbis encoder engine -- "+OVstrerror(e));
398 }
399 else
400 throw runtime_error(string(__func__)+" -- internal error -- unhandle bit rate method "+istring((int)parameters.method));
401
402 vorbis_comment vc;
403 vorbis_comment_init(&vc);
404
405
406 // save the cues
407 for(size_t t=0;t<sound->getCueCount();t++)
408 {
409 if(sound->getCueTime(t)>=saveStart && sound->getCueTime(t)<(saveStart+saveLength))
410 {
411 const string cueDesc="CUE="+istring(sound->getCueTime(t)-saveStart)+(sound->isCueAnchored(t) ? "+" : "-")+sound->getCueName(t);
412 vorbis_comment_add(&vc,(char *)cueDesc.c_str());
413 }
414 }
415
416
417 // save user notes
418 // since the user notes may contain "tagname=value" then I break each line in the user notes string into a separate comment
419 const string userNotes=sound->getUserNotes();
420 string comment;
421 for(size_t t=0;t<userNotes.length();t++)
422 {
423 const char c=userNotes[t];
424 if(c=='\n')
425 {
426 vorbis_comment_add(&vc,(char *)comment.c_str());
427 comment="";
428 }
429 else
430 comment.append(&c,1);
431 }
432 if(comment!="")
433 vorbis_comment_add(&vc,(char *)comment.c_str());
434
435
436 /* set up the analysis state and auxiliary encoding storage */
437 vorbis_dsp_state vd;
438 vorbis_block vb;
439 vorbis_analysis_init(&vd,&vi);
440 vorbis_block_init(&vd,&vb);
441
442 ogg_page og; // one raw packet of data for decode
443
444 /* set up our packet->stream encoder */
445 /* pick a random serial number; that way we can more likely build chained streams just by concatenation */
446 ogg_stream_state os; // take physical pages, weld into a logical stream of packets
447 srand(time(NULL));
448 ogg_stream_init(&os,rand());
449
450 FILE *f=fopen(filename.c_str(),"wb");
451 int err=errno;
452 if(f==NULL)
453 throw runtime_error(string(__func__)+" -- error opening '"+filename+"' -- "+strerror(err));
454
455 /*
456 * Vorbis streams begin with three headers; the initial header
457 * (with most of the codec setup parameters) which is mandated
458 * by the Ogg bitstream spec. The second header holds any
459 * comment fields. The third header holds the bitstream
460 * codebook. We merely need to make the headers, then pass
461 * them to libvorbis one at a time; libvorbis handles the
462 * additional Ogg bitstream constraints
463 */
464 {
465 ogg_packet header;
466 ogg_packet header_comm;
467 ogg_packet header_code;
468
469 vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
470 ogg_stream_packetin(&os,&header); /* automatically placed in its own page */
471 ogg_stream_packetin(&os,&header_comm);
472 ogg_stream_packetin(&os,&header_code);
473
474 /*
475 * We don't have to write out here, but doing so makes streaming
476 * much easier, so we do, flushing ALL pages. This ensures the actual
477 * audio data will start on a new page
478 */
479 for(;;)
480 {
481 if(ogg_stream_flush(&os,&og)==0)
482 break;
483 fwrite(og.header,1,og.header_len,f);
484 fwrite(og.body,1,og.body_len,f);
485 }
486 }
487
488
489 const CRezPoolAccesser *accessers[MAX_CHANNELS]={0};
490 for(unsigned t=0;t<channelCount;t++)
491 accessers[t]=new CRezPoolAccesser(sound->getAudio(t));
492
493 try
494 {
495 #define CHUNK_SIZE 4096
496
497 ogg_packet op; // one Ogg bitstream page. Vorbis packets are inside
498
499 sample_pos_t pos=0;
500
501 const sample_pos_t chunkCount= (saveLength/CHUNK_SIZE) + ((saveLength%CHUNK_SIZE)!=0 ? 1 : 0);
502
503 CStatusBar statusBar("Saving Sound",0,chunkCount,true);
504
505 for(sample_pos_t t=0;t<=chunkCount;t++)
506 {
507 if(t==chunkCount)
508 { // after last chunk
509 /*
510 * Tell the library we're at end of stream so that it can handle
511 * the last frame and mark end of stream in the output properly
512 */
513 vorbis_analysis_wrote(&vd,0);
514 }
515 else
516 { // give some data to the encoder
517
518 // get the buffer to submit data
519 float **buffer=vorbis_analysis_buffer(&vd,CHUNK_SIZE);
520
521 // copy data into buffer
522 const unsigned chunkSize= (t==chunkCount-1) ? (saveLength%CHUNK_SIZE) : CHUNK_SIZE;
523 for(unsigned c=0;c<channelCount;c++)
524 {
525 const CRezPoolAccesser &accesser=*(accessers[c]);
526 for(unsigned i=0;i<chunkSize;i++)
527 buffer[c][i]=convert_sample<sample_t,float>(accesser[pos+i+saveStart]);
528 }
529 pos+=chunkSize;
530
531 // tell the library how much we actually submitted
532 vorbis_analysis_wrote(&vd,chunkSize);
533 }
534
535 /*
536 * vorbis does some data preanalysis, then divvies up blocks for
537 * more involved (potentially parallel) processing. Get a single
538 * block for encoding now
539 */
540 while(vorbis_analysis_blockout(&vd,&vb)==1)
541 {
542 // analysis, assume we want to use bitrate management
543 vorbis_analysis(&vb,NULL);
544 vorbis_bitrate_addblock(&vb);
545
546 while(vorbis_bitrate_flushpacket(&vd,&op))
547 {
548 // weld the packet into the bitstream
549 ogg_stream_packetin(&os,&op);
550
551 // write out pages (if any)
552 for(;;)
553 {
554 if(ogg_stream_pageout(&os,&og)==0)
555 break;
556 fwrite(og.header,1,og.header_len,f);
557 fwrite(og.body,1,og.body_len,f);
558 if(ferror(f))
559 {
560 const int errNO=errno;
561 throw runtime_error(string(__func__)+" -- error writing to file: "+filename+" -- "+strerror(errNO));
562 }
563
564 if(ogg_page_eos(&og))
565 break;
566 }
567 }
568 }
569
570 if(statusBar.update(t))
571 {
572 ret=false;
573 goto cancelled;
574 }
575 }
576
577 cancelled:
578
579 for(unsigned t=0;t<MAX_CHANNELS;t++)
580 {
581 delete accessers[t];
582 accessers[t]=NULL;
583 }
584
585 /* clean up. vorbis_info_clear() must be called last */
586 ogg_stream_clear(&os);
587 vorbis_block_clear(&vb);
588 vorbis_dsp_clear(&vd);
589 vorbis_comment_clear(&vc);
590 vorbis_info_clear(&vi);
591
592 fclose(f);
593 }
594 catch(...)
595 {
596 ogg_stream_clear(&os);
597 vorbis_block_clear(&vb);
598 vorbis_dsp_clear(&vd);
599 vorbis_comment_clear(&vc);
600 vorbis_info_clear(&vi);
601
602 fclose(f);
603
604 for(unsigned t=0;t<MAX_CHANNELS;t++)
605 delete accessers[t];
606
607 throw;
608 }
609
610 if(!ret)
611 unlink(filename.c_str()); // remove the cancelled file
612
613 return ret;
614 #else
615 throw runtime_error(string(__func__)+" -- saving Ogg Vorbis is not enabled -- missing libvorbisenc");
616 #endif
617 }
618
619
handlesExtension(const string extension,const string filename) const620 bool ClibvorbisSoundTranslator::handlesExtension(const string extension,const string filename) const
621 {
622 return extension=="ogg";
623 }
624
supportsFormat(const string filename) const625 bool ClibvorbisSoundTranslator::supportsFormat(const string filename) const
626 {
627 #ifdef HAVE_LIBVORBIS
628 FILE *f=fopen(filename.c_str(),"rb");
629 if(f==NULL)
630 return false;
631
632 OggVorbis_File vf;
633 if(ov_open(f, &vf, NULL, 0) < 0)
634 {
635 fclose(f);
636 return false;
637 }
638
639 ov_clear(&vf);
640 return true;
641 #else
642 return false;
643 #endif
644 }
645
getFormatNames() const646 const vector<string> ClibvorbisSoundTranslator::getFormatNames() const
647 {
648 vector<string> names;
649
650 names.push_back("Ogg Vorbis");
651
652 return names;
653 }
654
getFormatFileMasks() const655 const vector<vector<string> > ClibvorbisSoundTranslator::getFormatFileMasks() const
656 {
657 vector<vector<string> > list;
658 vector<string> fileMasks;
659
660 fileMasks.clear();
661 fileMasks.push_back("*.ogg");
662 list.push_back(fileMasks);
663
664 return list;
665 }
666
667 #endif // HAVE_LIBVORBIS && HAVE_LIBOGG
668