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,&current_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(),&current_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