mirror of
				https://github.com/ElvishArtisan/rivendell.git
				synced 2025-10-31 06:03:51 +01:00 
			
		
		
		
	* Fixed bugs in the 'Import' Web API call that caused import of filenames containing multibyte UTF-8 characters to fail.
		
			
				
	
	
		
			2090 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			2090 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // rdaudioconvert.cpp
 | |
| //
 | |
| // Convert Audio File Formats
 | |
| //
 | |
| //   (C) Copyright 2010-2018 Fred Gleason <fredg@paravelsystems.com>
 | |
| //
 | |
| //   This program is free software; you can redistribute it and/or modify
 | |
| //   it under the terms of the GNU General Public License version 2 as
 | |
| //   published by the Free Software Foundation.
 | |
| //
 | |
| //   This program is distributed in the hope that it will be useful,
 | |
| //   but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
| //   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
| //   GNU General Public License for more details.
 | |
| //
 | |
| //   You should have received a copy of the GNU General Public
 | |
| //   License along with this program; if not, write to the Free Software
 | |
| //   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 | |
| //
 | |
| 
 | |
| #include <stdint.h>
 | |
| #include <stdlib.h>
 | |
| #include <sys/types.h>
 | |
| #include <sys/stat.h>
 | |
| #include <sys/wait.h>
 | |
| #include <fcntl.h>
 | |
| #include <unistd.h>
 | |
| #include <math.h>
 | |
| #include <dlfcn.h>
 | |
| #include <errno.h>
 | |
| #include <unistd.h>
 | |
| 
 | |
| #include <rdapplication.h>
 | |
| #include <rdaudioconvert.h>
 | |
| #include <rdcart.h>
 | |
| #include <rdconf.h>
 | |
| #include <rd.h>
 | |
| #include <rdtempdirectory.h>
 | |
| 
 | |
| #include <sndfile.h>
 | |
| #include <samplerate.h>
 | |
| #include <soundtouch/SoundTouch.h>
 | |
| #ifdef HAVE_VORBIS
 | |
| #include <ogg/ogg.h>
 | |
| #include <vorbis/vorbisenc.h>
 | |
| #endif  // HAVE_VORBIS
 | |
| #ifdef HAVE_FLAC
 | |
| #include <FLAC++/encoder.h>
 | |
| #include <rdflacdecode.h>
 | |
| #endif  // HAVE_FLAC
 | |
| #ifdef HAVE_MP4_LIBS
 | |
| #include <mp4v2/mp4v2.h>
 | |
| #include <neaacdec.h>
 | |
| #endif // HAVE_MP4_LIBS
 | |
| //#include <id3/tag.h>
 | |
| #include <id3v2tag.h>
 | |
| #include <textidentificationframe.h>
 | |
| #include <mpegfile.h>
 | |
| #include <qfile.h>
 | |
| 
 | |
| #define STAGE2_XFER_SIZE 2048
 | |
| #define STAGE2_BUFFER_SIZE 49152
 | |
| 
 | |
| RDAudioConvert::RDAudioConvert(QObject *parent)
 | |
|   : QObject(parent)
 | |
| {
 | |
|   conv_start_point=-1;
 | |
|   conv_end_point=-1;
 | |
|   conv_speed_ratio=1.0;
 | |
|   conv_peak_sample=0.0;
 | |
|   conv_settings=NULL;
 | |
|   conv_src_wavedata=new RDWaveData();
 | |
|   conv_dst_wavedata=NULL;
 | |
|   conv_src_converter=rda->libraryConf()->srcConverter();
 | |
|   conv_transcoding_delay=rda->config()->transcodingDelay();
 | |
| 
 | |
|   //
 | |
|   // Load MPEG Libraries
 | |
|   //
 | |
|   conv_mad_handle=dlopen("libmad.so.0",RTLD_LAZY);
 | |
|   conv_lame_handle=dlopen("libmp3lame.so.0",RTLD_LAZY);
 | |
|   conv_twolame_handle=dlopen("libtwolame.so.0",RTLD_LAZY);
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::~RDAudioConvert()
 | |
| {
 | |
|   delete conv_src_wavedata;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setSourceFile(const QString &filename)
 | |
| {
 | |
|   conv_src_filename=filename;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setDestinationFile(const QString &filename)
 | |
| {
 | |
|   conv_dst_filename=filename;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setDestinationSettings(RDSettings *settings)
 | |
| {
 | |
|   conv_settings=settings;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDWaveData *RDAudioConvert::sourceWaveData() const
 | |
| {
 | |
|   return conv_src_wavedata;  
 | |
| }
 | |
| 
 | |
| 
 | |
| QString RDAudioConvert::sourceRdxl() const
 | |
| {
 | |
|   return conv_src_rdxl;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setDestinationWaveData(RDWaveData *wavedata)
 | |
| {
 | |
|   conv_dst_wavedata=wavedata;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setDestinationRdxl(const QString &xml)
 | |
| {
 | |
|   conv_dst_rdxl=xml;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setRange(int start_pt,int end_pt)
 | |
| {
 | |
|   conv_start_point=start_pt;
 | |
|   conv_end_point=end_pt;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::setSpeedRatio(float ratio)
 | |
| {
 | |
|   conv_speed_ratio=ratio;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::convert()
 | |
| {
 | |
|   RDAudioConvert::ErrorCode err;
 | |
|   QString tmpfile1;
 | |
|   QString tmpfile2;
 | |
|   RDTempDirectory *temp_dir=NULL;
 | |
| 
 | |
|   //
 | |
|   // Make sure we're all set to go...
 | |
|   //
 | |
|   if(conv_settings==NULL) {
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
|   if(!RDAudioConvert::settingsValid(conv_settings)) {
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
|   struct stat stats;
 | |
|   memset(&stats,0,sizeof(stats));
 | |
|   if(stat((const char *)conv_src_filename.toUtf8(),&stats)!=0) {
 | |
|     return RDAudioConvert::ErrorNoSource;
 | |
|   }
 | |
|   if(conv_dst_filename.isEmpty()) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
|   if((conv_speed_ratio<RD_TIMESCALE_MIN)||(conv_speed_ratio>RD_TIMESCALE_MAX)) {
 | |
|     return RDAudioConvert::ErrorInvalidSpeed;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Generate Temporary Filenames
 | |
|   //
 | |
|   temp_dir=new RDTempDirectory("rdaudioconvert");
 | |
|   QString err_msg;
 | |
|   if(!temp_dir->create(&err_msg)) {
 | |
|     delete temp_dir;
 | |
|     rda->log(RDConfig::LogWarning,QString("Could not create "+err_msg));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   tmpfile1=QString(temp_dir->path())+"/signed32_1.wav";
 | |
|   tmpfile2=QString(temp_dir->path())+"/signed32_2.wav";
 | |
| 
 | |
|   //
 | |
|   // Stage One -- Convert Source Format to Signed 32 Bit Integer
 | |
|   //
 | |
|   if((err=Stage1Convert(conv_src_filename,tmpfile1))!=
 | |
|      RDAudioConvert::ErrorOk) {
 | |
|     delete temp_dir;
 | |
|     return err;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Stage Two -- Convert Levels, Sample Rate, Channelization, Speed
 | |
|   //
 | |
|   if((err=Stage2Convert(tmpfile1,tmpfile2))!=
 | |
|      RDAudioConvert::ErrorOk) {
 | |
|     delete temp_dir;
 | |
|     return err;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Stage Three -- Write Out Destination Format
 | |
|   //
 | |
|   if((err=Stage3Convert(tmpfile2,conv_dst_filename))!=
 | |
|      RDAudioConvert::ErrorOk) {
 | |
|     delete temp_dir;
 | |
|     return err;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   delete temp_dir;
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| }
 | |
| 
 | |
| 
 | |
| bool RDAudioConvert::settingsValid(RDSettings *settings)
 | |
| {
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| 
 | |
| QString RDAudioConvert::errorText(RDAudioConvert::ErrorCode err)
 | |
| {
 | |
|   QString ret=QString().sprintf("Unknown Error [%u]",err);
 | |
| 
 | |
|   switch(err) {
 | |
|   case RDAudioConvert::ErrorOk:
 | |
|     ret=tr("OK");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorInvalidSettings:
 | |
|     ret=tr("Invalid/Unsupported Settings");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorNoSource:
 | |
|     ret=tr("Unable to access source file");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorNoDestination:
 | |
|     ret=tr("Unable to create destination file");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorInvalidSource:
 | |
|     ret=tr("Unrecognized source format");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorInternal:
 | |
|     ret=tr("Internal Error");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorFormatNotSupported:
 | |
|     ret=tr("Unsupported Format");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorNoDisc:
 | |
|     ret=tr("No CD found in drive");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorNoTrack:
 | |
|     ret=tr("No such track on CD");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorInvalidSpeed:
 | |
|     ret=tr("Invalid speed ratio");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorFormatError:
 | |
|     ret=tr("Source format error");
 | |
|     break;
 | |
| 
 | |
|   case RDAudioConvert::ErrorNoSpace:
 | |
|     ret=tr("No space left on device");
 | |
|     break;
 | |
|   }
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1Convert(const QString &srcfile,
 | |
| 							const QString &dstfile)
 | |
| {
 | |
|   SNDFILE *sf_src=NULL;
 | |
|   SF_INFO sf_src_info;
 | |
|   RDWaveFile *wave=NULL;
 | |
|   RDAudioConvert::ErrorCode err=RDAudioConvert::ErrorInvalidSource;
 | |
| 
 | |
|   //
 | |
|   // Try RDWaveFile
 | |
|   //
 | |
|   wave=new RDWaveFile(srcfile);
 | |
|   if(wave->openWave(conv_src_wavedata)) {
 | |
|     switch(wave->type()) {
 | |
|     case RDWaveFile::Wave:
 | |
|       if(wave->getFormatTag()==WAVE_FORMAT_MPEG) {
 | |
| 	err=Stage1Mpeg(dstfile,wave);
 | |
| 	delete wave;
 | |
| 	return err;
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case RDWaveFile::Mpeg:
 | |
|     case RDWaveFile::Atx:
 | |
|     case RDWaveFile::Tmc:
 | |
|     case RDWaveFile::Ambos:
 | |
|       err=Stage1Mpeg(dstfile,wave);
 | |
|       delete wave;
 | |
|       return err;
 | |
| 
 | |
|     case RDWaveFile::Ogg:
 | |
|       err=Stage1Vorbis(dstfile,wave);
 | |
|       delete wave;
 | |
|       return err;
 | |
| 
 | |
|     case RDWaveFile::Flac:
 | |
|       err=Stage1Flac(dstfile,wave);
 | |
|       delete wave;
 | |
|       return err;
 | |
| 
 | |
|     case RDWaveFile::M4A:
 | |
|       err=Stage1M4A(dstfile,wave);
 | |
|       delete wave;
 | |
|       return err;
 | |
| 
 | |
|     case RDWaveFile::Aiff:
 | |
|     case RDWaveFile::Unknown:
 | |
|       break;
 | |
|     }
 | |
|   }
 | |
|   delete wave;
 | |
| 
 | |
|   //
 | |
|   // Try Libsndfile
 | |
|   //
 | |
|   memset(&sf_src_info,0,sizeof(sf_src_info));
 | |
|   if((sf_src=sf_open(srcfile.toUtf8(),SFM_READ,&sf_src_info))!=NULL) {
 | |
|     err=Stage1SndFile(dstfile,sf_src,&sf_src_info);
 | |
|     sf_close(sf_src);
 | |
|     return RDAudioConvert::ErrorOk;
 | |
|   }
 | |
| 
 | |
|   return err;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1Flac(const QString &dstfile,
 | |
| 						     RDWaveFile *wave)
 | |
| {
 | |
| #ifdef HAVE_FLAC
 | |
|   SNDFILE *sf_dst=NULL;
 | |
|   SF_INFO sf_dst_info;
 | |
|   RDFlacDecode *flac=NULL;
 | |
| 
 | |
|   //
 | |
|   // Open Destination
 | |
|   //
 | |
|   memset(&sf_dst_info,0,sizeof(sf_dst_info));
 | |
|   sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
 | |
|   sf_dst_info.channels=wave->getChannels();
 | |
|   sf_dst_info.samplerate=wave->getSamplesPerSec();
 | |
|   if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Decode
 | |
|   //
 | |
|   flac=new RDFlacDecode(sf_dst);
 | |
|   flac->setRange(conv_start_point,conv_end_point);
 | |
|   flac->decode(wave,&conv_peak_sample);
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   delete flac;
 | |
|   sf_close(sf_dst);
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_FLAC
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1Vorbis(const QString &dstfile,
 | |
| 						       RDWaveFile *wave)
 | |
| {
 | |
| #ifdef HAVE_VORBIS
 | |
|   SNDFILE *sf_dst=NULL;
 | |
|   SF_INFO sf_dst_info;
 | |
|   ogg_sync_state ogg_sync;
 | |
|   ogg_stream_state ogg_stream;
 | |
|   ogg_packet ogg_packet;
 | |
|   ogg_page ogg_page;
 | |
|   vorbis_info vorbis_info;
 | |
|   vorbis_comment vorbis_comment;
 | |
|   vorbis_dsp_state vorbis_dsp;
 | |
|   vorbis_block vorbis_block;
 | |
|   int fd;
 | |
|   ssize_t n;
 | |
|   long serialno=-1;
 | |
|   bool vorbis_ready=false;
 | |
|   int frames;
 | |
|   float **pcm;
 | |
|   float pcmbuf[32768];
 | |
|   sf_count_t start=0;
 | |
|   sf_count_t end=wave->getSampleLength();
 | |
|   sf_count_t total_frames=0;
 | |
| 
 | |
|   //
 | |
|   // Open Destination
 | |
|   //
 | |
|   memset(&sf_dst_info,0,sizeof(sf_dst_info));
 | |
|   sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
 | |
|   sf_dst_info.channels=wave->getChannels();
 | |
|   sf_dst_info.samplerate=wave->getSamplesPerSec();
 | |
|   if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Initialize Decoder
 | |
|   //
 | |
|   if((fd=open(wave->getName().toUtf8(),O_RDONLY))<0) {
 | |
|     sf_close(sf_dst);
 | |
|     return RDAudioConvert::ErrorNoSource;
 | |
|   }
 | |
|   ogg_sync_init(&ogg_sync);
 | |
|   vorbis_info_init(&vorbis_info);
 | |
|   vorbis_comment_init(&vorbis_comment);
 | |
| 
 | |
|   //
 | |
|   // Decode
 | |
|   //
 | |
|   if(conv_start_point>0) {
 | |
|     start=(double)conv_start_point*(double)wave->getSamplesPerSec()/1000.0;
 | |
|   }
 | |
|   if(conv_end_point>=0) {
 | |
|     end=(double)conv_end_point*(double)wave->getSamplesPerSec()/1000.0;
 | |
|   }
 | |
|   while((n=read(fd,ogg_sync_buffer(&ogg_sync,4096),4096))>0) {
 | |
|     ogg_sync_wrote(&ogg_sync,n);
 | |
|     while(ogg_sync_pageout(&ogg_sync,&ogg_page)==1) {
 | |
|       if(serialno<0) {
 | |
| 	serialno=ogg_page_serialno(&ogg_page);
 | |
| 	ogg_stream_init(&ogg_stream,serialno);
 | |
|       }
 | |
|       if(ogg_stream_pagein(&ogg_stream,&ogg_page)==0) {
 | |
| 	while(ogg_stream_packetout(&ogg_stream,&ogg_packet)==1) {
 | |
| 	  switch(ogg_packet.packetno) {
 | |
| 	  case 0:  // Start Packet
 | |
| 	  case 1:  // Comment Packet
 | |
| 	    vorbis_synthesis_headerin(&vorbis_info,&vorbis_comment,&ogg_packet);
 | |
| 	    break;
 | |
| 
 | |
| 	  case 2:  // Codebook Packet
 | |
| 	    vorbis_synthesis_headerin(&vorbis_info,&vorbis_comment,&ogg_packet);
 | |
| 	    vorbis_synthesis_init(&vorbis_dsp,&vorbis_info);
 | |
| 	    vorbis_block_init(&vorbis_dsp,&vorbis_block);
 | |
| 	    vorbis_ready=true;
 | |
| 	    break;
 | |
| 
 | |
| 	  default: // Audio Packets
 | |
| 	    if(vorbis_synthesis(&vorbis_block,&ogg_packet)==0) {
 | |
| 	      vorbis_synthesis_blockin(&vorbis_dsp,&vorbis_block);
 | |
| 	    }
 | |
| 	    while((frames=vorbis_synthesis_pcmout(&vorbis_dsp,&pcm))>0) {
 | |
| 	      for(int i=0;i<frames;i++) {
 | |
| 		for(int j=0;j<wave->getChannels();j++) {
 | |
| 		  pcmbuf[wave->getChannels()*i+j]=pcm[j][i];
 | |
| 		}
 | |
| 	      }
 | |
| 	      if(total_frames>=start) {
 | |
| 		if((total_frames+frames)<end) {    // Write entire buffer 
 | |
| 		  UpdatePeak(pcmbuf,frames*wave->getChannels());
 | |
| 		  sf_writef_float(sf_dst,pcmbuf,frames);
 | |
| 		}
 | |
| 		else {
 | |
| 		  if(total_frames<(total_frames+frames)) {  // Write start of buffer
 | |
| 		    UpdatePeak(pcmbuf,
 | |
| 			       (total_frames+frames-end)*wave->getChannels());
 | |
| 		    sf_writef_float(sf_dst,pcmbuf,total_frames+frames-end);
 | |
| 		    //
 | |
| 		    // Done -- no need to decode the rest
 | |
| 		    //
 | |
| 		    if(vorbis_ready) {
 | |
| 		      vorbis_block_clear(&vorbis_block);
 | |
| 		      vorbis_dsp_clear(&vorbis_dsp);
 | |
| 		    }
 | |
| 		    vorbis_info_clear(&vorbis_info);
 | |
| 		    vorbis_comment_clear(&vorbis_comment);
 | |
| 		    ogg_stream_clear(&ogg_stream);
 | |
| 		    ogg_sync_clear(&ogg_sync);
 | |
| 		    ::close(fd);
 | |
| 		    sf_close(sf_dst);
 | |
| 
 | |
| 		    return RDAudioConvert::ErrorOk;
 | |
| 		  }
 | |
| 		}
 | |
| 	      }
 | |
| 	      else {
 | |
| 		int diff=total_frames+frames-start;
 | |
| 		if(diff>0) {   // Write end of buffer
 | |
| 		  UpdatePeak(pcmbuf+diff,(frames-diff)*wave->getChannels());
 | |
| 		  sf_writef_float(sf_dst,pcmbuf+diff,frames-diff);
 | |
| 		}
 | |
| 	      }
 | |
| 	      total_frames+=frames;
 | |
| 	      vorbis_synthesis_read(&vorbis_dsp,frames);
 | |
| 	    }
 | |
| 	    break;
 | |
| 	  }
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   if(vorbis_ready) {
 | |
|     vorbis_block_clear(&vorbis_block);
 | |
|     vorbis_dsp_clear(&vorbis_dsp);
 | |
|   }
 | |
|   vorbis_info_clear(&vorbis_info);
 | |
|   vorbis_comment_clear(&vorbis_comment);
 | |
|   ogg_stream_clear(&ogg_stream);
 | |
|   ogg_sync_clear(&ogg_sync);
 | |
|   ::close(fd);
 | |
|   sf_close(sf_dst);
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_VORBIS
 | |
| }
 | |
| 
 | |
| #define STAGE1BUFSIZE 16384
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1Mpeg(const QString &dstfile,
 | |
| 						     RDWaveFile *wave)
 | |
| {
 | |
| #ifdef HAVE_MAD
 | |
|   SNDFILE *sf_dst=NULL;
 | |
|   SF_INFO sf_dst_info;
 | |
|   struct mad_stream mad_stream;
 | |
|   struct mad_frame mad_frame;
 | |
|   struct mad_synth mad_synth;
 | |
|   int left_over=0;
 | |
|   int fsize;
 | |
|   int n;
 | |
|   unsigned char buffer[STAGE1BUFSIZE];
 | |
|   float sf_buffer[1152*2];
 | |
|   sf_count_t start=0;
 | |
|   int64_t end=-1;
 | |
|   sf_count_t frames=0;
 | |
| 
 | |
|   //
 | |
|   // Load MAD
 | |
|   //
 | |
|   if(!LoadMad()) {
 | |
|     return RDAudioConvert::ErrorFormatNotSupported;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open Destination
 | |
|   //
 | |
|   memset(&sf_dst_info,0,sizeof(sf_dst_info));
 | |
|   sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
 | |
|   sf_dst_info.channels=wave->getChannels();
 | |
|   sf_dst_info.samplerate=wave->getSamplesPerSec();
 | |
|   if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
|   sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE);
 | |
| 
 | |
|   //
 | |
|   // Initialize Decoder
 | |
|   //
 | |
|   mad_stream_init(&mad_stream);
 | |
|   mad_frame_init(&mad_frame);
 | |
|   mad_synth_init(&mad_synth);
 | |
|   fsize=144*wave->getHeadBitRate()/wave->getSamplesPerSec();
 | |
| 
 | |
|   //
 | |
|   // Decode
 | |
|   //
 | |
|   if(conv_start_point>0) {
 | |
|     start=(double)conv_start_point*(double)wave->getSamplesPerSec()/1000.0;
 | |
|   }
 | |
|   if(conv_end_point>=0) {
 | |
|     end=(double)conv_end_point*(double)wave->getSamplesPerSec()/1000.0;
 | |
|   }
 | |
|   while((n=wave->readWave(buffer+left_over,fsize))>0) {
 | |
|     if((buffer[left_over]==0xff)&&(buffer[2+left_over]&0x02)!=0) {
 | |
|        n+=wave->readWave(buffer+left_over+n,1);  // Padding slot
 | |
|     }
 | |
|     mad_stream_buffer(&mad_stream,buffer,n+left_over);
 | |
|     //printf("mad err: %d\n",mad_stream.error);
 | |
|    while(1) {
 | |
| 
 | |
|       int thiserr=mad_frame_decode(&mad_frame,&mad_stream);
 | |
|       if(thiserr!=0) {
 | |
| 	if(!MAD_RECOVERABLE(mad_stream.error)) {
 | |
| 	  break;
 | |
| 	}
 | |
| 	else {
 | |
| 	  continue;
 | |
| 	}
 | |
|       }
 | |
|       mad_synth_frame(&mad_synth,&mad_frame);
 | |
|       for(int i=0;i<mad_synth.pcm.length;i++) {
 | |
| 	for(int j=0;j<mad_synth.pcm.channels;j++) {
 | |
| 	  sf_buffer[i*mad_synth.pcm.channels+j]=
 | |
| 	    (float)mad_f_todouble(mad_synth.pcm.samples[j][i]);
 | |
| 	}
 | |
|       }
 | |
|       if(frames>=start) {
 | |
| 	if((end<0)||((frames+mad_synth.pcm.length)<end)) { // Write full buffer 
 | |
| 	  UpdatePeak(sf_buffer,mad_synth.pcm.length*wave->getChannels());
 | |
| 	  sf_writef_float(sf_dst,sf_buffer,mad_synth.pcm.length);
 | |
| 	}
 | |
| 	else {
 | |
| 	  if(frames<(frames+mad_synth.pcm.length)) {  // Write start of buffer
 | |
| 	    UpdatePeak(sf_buffer,
 | |
| 		       (frames+mad_synth.pcm.length-end)*wave->getChannels());
 | |
| 	    sf_writef_float(sf_dst,sf_buffer,frames+mad_synth.pcm.length-end);
 | |
| 	    //
 | |
| 	    // Done -- no need to decode the rest
 | |
| 	    //
 | |
| 	    mad_synth_finish(&mad_synth);
 | |
| 	    mad_frame_finish(&mad_frame);
 | |
| 	    mad_stream_finish(&mad_stream);
 | |
| 	    wave->closeWave();
 | |
| 	    sf_close(sf_dst);
 | |
| 	    return RDAudioConvert::ErrorOk;
 | |
| 	  }
 | |
| 	}
 | |
|       }
 | |
|       else {
 | |
| 	int diff=frames+mad_synth.pcm.length-start;
 | |
| 	if(diff>0) {   // Write end of buffer
 | |
| 	  UpdatePeak(sf_buffer+diff,
 | |
| 		     (mad_synth.pcm.length-diff)*wave->getChannels());
 | |
| 	  sf_writef_float(sf_dst,sf_buffer+diff,mad_synth.pcm.length-diff);
 | |
| 	}
 | |
|       }
 | |
|       frames+=mad_synth.pcm.length;
 | |
| 
 | |
|     }
 | |
|     left_over=mad_stream.bufend-mad_stream.next_frame;
 | |
| 
 | |
|     // Prevent buffer overflow on malformed files.
 | |
|     // The amount checked for should match the maximum amount that may be read
 | |
|     // by the next top-of-loop wave->readWave call.
 | |
|     if(left_over + fsize + 1 > STAGE1BUFSIZE) {
 | |
|       return RDAudioConvert::ErrorFormatError;
 | |
|     }
 | |
|     memmove(buffer,mad_stream.next_frame,left_over);
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   memset(buffer+left_over,0,MAD_BUFFER_GUARD);
 | |
|   mad_stream_buffer(&mad_stream,buffer,MAD_BUFFER_GUARD+left_over);
 | |
|   if(mad_frame_decode(&mad_frame,&mad_stream)==0) {
 | |
|     mad_synth_frame(&mad_synth,&mad_frame);
 | |
|     for(int i=0;i<mad_synth.pcm.length;i++) {
 | |
|       for(int j=0;j<mad_synth.pcm.channels;j++) {
 | |
| 	sf_buffer[i*mad_synth.pcm.channels+j]=
 | |
| 	  (float)mad_f_todouble(mad_synth.pcm.samples[j][i]);
 | |
|       }
 | |
|     }
 | |
|     UpdatePeak(sf_buffer,mad_synth.pcm.length*wave->getChannels());
 | |
|     sf_writef_float(sf_dst,sf_buffer,mad_synth.pcm.length);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   mad_synth_finish(&mad_synth);
 | |
|   mad_frame_finish(&mad_frame);
 | |
|   mad_stream_finish(&mad_stream);
 | |
|   wave->closeWave();
 | |
| 
 | |
|   sf_close(sf_dst);
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_MAD
 | |
| }
 | |
| 
 | |
| // Based on libfaad's frontend/main.c, but using libmp4v2 for MP4 access.
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1M4A(const QString &dstfile,
 | |
| 						    RDWaveFile *wave) 
 | |
| {
 | |
| #ifdef HAVE_MP4_LIBS
 | |
|   SNDFILE *sf_dst=NULL;
 | |
|   SF_INFO sf_dst_info;
 | |
|   MP4FileHandle f;
 | |
|   MP4TrackId audioTrack;
 | |
|   MP4SampleId firstSample, lastSample;
 | |
|   uint32_t aacBufSize, aacConfigSize;
 | |
|   uint8_t *aacBuf, *aacConfigBuffer;
 | |
|   NeAACDecHandle hDecoder;
 | |
|   NeAACDecConfigurationPtr config;
 | |
|   unsigned long foundSampleRate;
 | |
|   unsigned char foundChannels;
 | |
|   RDAudioConvert::ErrorCode ret = RDAudioConvert::ErrorOk;
 | |
| 
 | |
|   if(!dlmp4.load()) {
 | |
|     return RDAudioConvert::ErrorFormatNotSupported;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open source
 | |
|   //
 | |
|   f = dlmp4.MP4Read(wave->getName().toUtf8());
 | |
|   if(f == MP4_INVALID_FILE_HANDLE)
 | |
|     return RDAudioConvert::ErrorNoSource;
 | |
| 
 | |
|   audioTrack = dlmp4.getMP4AACTrack(f);
 | |
|   firstSample = 1;
 | |
|   lastSample = dlmp4.MP4GetTrackNumberOfSamples(f, audioTrack);
 | |
|   if(conv_start_point > 0) {
 | |
|     
 | |
|     double startsecs = ((double)conv_start_point) / 1000;
 | |
|     MP4Timestamp startts = (MP4Timestamp)(startsecs * wave->getSamplesPerSec());
 | |
|     firstSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, startts, /*need_sync=*/false);
 | |
|     if(firstSample == MP4_INVALID_SAMPLE_ID) {
 | |
|       ret = RDAudioConvert::ErrorInvalidSource;
 | |
|       goto out_mp4;
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   if(conv_end_point > 0) {
 | |
| 
 | |
|     double stopsecs = ((double)conv_end_point) / 1000;
 | |
|     MP4Timestamp stopts = (MP4Timestamp)(stopsecs * wave->getSamplesPerSec());
 | |
|     lastSample = dlmp4.MP4GetSampleIdFromTime(f, audioTrack, stopts, /*need_sync=*/false);
 | |
|     if(lastSample == MP4_INVALID_SAMPLE_ID) {
 | |
|       ret = RDAudioConvert::ErrorInvalidSource;
 | |
|       goto out_mp4;
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   aacBufSize = dlmp4.MP4GetTrackMaxSampleSize(f, audioTrack);
 | |
|   aacBuf = (uint8_t*)malloc(aacBufSize);
 | |
|   if(!aacBufSize || !aacBuf) {
 | |
|     // Probably the source's fault for specifying a massive buffer.
 | |
|     ret = RDAudioConvert::ErrorInvalidSource;
 | |
|     goto out_mp4;
 | |
|   }
 | |
| 
 | |
|   dlmp4.MP4GetTrackESConfiguration(f, audioTrack, &aacConfigBuffer, &aacConfigSize);
 | |
|   if(!aacConfigBuffer) {
 | |
|     ret = RDAudioConvert::ErrorInvalidSource;
 | |
|     goto out_mp4_buf;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open Destination
 | |
|   //
 | |
|   
 | |
|   memset(&sf_dst_info,0,sizeof(sf_dst_info));
 | |
|   sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
 | |
|   sf_dst_info.channels=wave->getChannels();
 | |
|   sf_dst_info.samplerate=wave->getSamplesPerSec();
 | |
|   if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
 | |
|     ret = RDAudioConvert::ErrorNoDestination;
 | |
|     goto out_mp4_configbuf;
 | |
|   }
 | |
|   sf_command(sf_dst,SFC_SET_NORM_DOUBLE,NULL,SF_FALSE);
 | |
|   
 | |
|   //
 | |
|   // Initialize Decoder
 | |
|   //
 | |
|   hDecoder = dlmp4.NeAACDecOpen();
 | |
| 
 | |
|   config = dlmp4.NeAACDecGetCurrentConfiguration(hDecoder);
 | |
|   config->outputFormat = FAAD_FMT_FLOAT;
 | |
|   config->downMatrix = 1; // Downmix >2 channels to stereo.
 | |
|   if(!dlmp4.NeAACDecSetConfiguration(hDecoder, config)) {
 | |
|     ret = RDAudioConvert::ErrorInvalidSource;
 | |
|     goto out_decoder;
 | |
|   }
 | |
| 
 | |
|   if(dlmp4.NeAACDecInit2(hDecoder, aacConfigBuffer, aacConfigSize, &foundSampleRate, &foundChannels) < 0) {
 | |
|     ret = RDAudioConvert::ErrorInvalidSource;
 | |
|     goto out_decoder;
 | |
|   }
 | |
| 
 | |
|   if(foundSampleRate != wave->getSamplesPerSec() || foundChannels != wave->getChannels()) {
 | |
|     fprintf(stderr, "M4A header information inconsistent with actual file? Header: %u/%u; file: %lu/%u\n",
 | |
| 	    wave->getSamplesPerSec(), (unsigned)wave->getChannels(), foundSampleRate, (unsigned)foundChannels);
 | |
|     ret = RDAudioConvert::ErrorInvalidSource;
 | |
|     goto out_decoder;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Decode
 | |
|   //
 | |
|   for(MP4SampleId i = firstSample; i <= lastSample; ++i) {
 | |
| 
 | |
|     uint32_t aacBytes = aacBufSize;
 | |
|     if(!dlmp4.MP4ReadSample(f, audioTrack, i, &aacBuf, &aacBytes, 0, 0, 0, 0)) {
 | |
|       ret = RDAudioConvert::ErrorInvalidSource;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     NeAACDecFrameInfo frameInfo;
 | |
|     // The library docs are not clear about the lifetime or cleanup of sample_buffer.
 | |
|     // I hope it lives until the next NeAACDecDecode call, and is cleaned up by NeAACDecClose
 | |
|     void* sample_buffer = dlmp4.NeAACDecDecode(hDecoder, &frameInfo, aacBuf, aacBytes);
 | |
|     if(!sample_buffer) {
 | |
|       ret = RDAudioConvert::ErrorInvalidSource;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     UpdatePeak((const float*)sample_buffer, frameInfo.samples);
 | |
|     
 | |
|     if(sf_write_float(sf_dst, (const float*)sample_buffer, frameInfo.samples) != (sf_count_t)frameInfo.samples) {
 | |
|       rda->log(RDConfig::LogWarning,QString().sprintf("%s",sf_strerror(sf_dst)));
 | |
|       ret = RDAudioConvert::ErrorInternal;
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Cleanup
 | |
|   //
 | |
| 
 | |
|  out_decoder:
 | |
|   dlmp4.NeAACDecClose(hDecoder);
 | |
|   // out_sf: 
 | |
|   sf_close(sf_dst);
 | |
|  out_mp4_configbuf:
 | |
|   free(aacConfigBuffer);
 | |
|  out_mp4_buf:
 | |
|   free(aacBuf);
 | |
|  out_mp4:
 | |
|   dlmp4.MP4Close(f, 0);
 | |
| 
 | |
|   return ret;
 | |
|   
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif
 | |
| }
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage1SndFile(const QString &dstfile,
 | |
| 							SNDFILE *sf_src,
 | |
| 							SF_INFO *sf_src_info)
 | |
| {
 | |
|   SNDFILE *sf_dst=NULL;
 | |
|   SF_INFO sf_dst_info;
 | |
|   sf_count_t start=0;
 | |
|   sf_count_t end=sf_src_info->frames;
 | |
| 
 | |
|   //
 | |
|   // Open Destination
 | |
|   //
 | |
|   sf_dst_info=*sf_src_info;
 | |
|   sf_dst_info.format=SF_FORMAT_WAV|SF_FORMAT_FLOAT;
 | |
|   if((sf_dst=sf_open(dstfile,SFM_WRITE,&sf_dst_info))==NULL) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Transfer Data
 | |
|   //
 | |
|   sf_count_t buffer_size=2048/sf_src_info->channels;
 | |
|   float *buffer=new float[2048];
 | |
|   sf_count_t n=0;
 | |
|   if(conv_start_point>0) {
 | |
|     start=sf_seek(sf_src,(double)conv_start_point*
 | |
| 		  (double)sf_src_info->samplerate/1000.0,SEEK_SET);
 | |
|   }
 | |
|   if(conv_end_point>=0) {
 | |
|     end=(double)conv_end_point*(double)sf_src_info->samplerate/1000.0;
 | |
|   }
 | |
|   while((n=sf_readf_float(sf_src,buffer,buffer_size))>0) {
 | |
|     UpdatePeak(buffer,n*sf_src_info->channels);
 | |
|     sf_writef_float(sf_dst,buffer,n);
 | |
|     start+=n;
 | |
|     if((end-start)<buffer_size) {
 | |
|       buffer_size=end-start;
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   delete buffer;
 | |
|   sf_close(sf_dst);
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage2Convert(const QString &srcfile,
 | |
| 							const QString &dstfile)
 | |
| {
 | |
|   soundtouch::SoundTouch *st_conv=NULL;
 | |
|   SNDFILE *src_sf=NULL;
 | |
|   SNDFILE *dst_sf=NULL;
 | |
|   SF_INFO src_info;
 | |
|   SF_INFO dst_info;
 | |
|   SRC_STATE *src_state=NULL;
 | |
|   SRC_DATA src_data;
 | |
|   float *pcm[3]={NULL,NULL,NULL};
 | |
|   bool free_pcm[3]={false,false,false};
 | |
|   int err;
 | |
|   sf_count_t n;
 | |
|   float ratio=1.0;
 | |
| 
 | |
|   //
 | |
|   // Open Files
 | |
|   //
 | |
|   memset(&src_info,0,sizeof(src_info));
 | |
|   if((src_sf=sf_open(srcfile,SFM_READ,&src_info))==NULL) {
 | |
|     rda->log(RDConfig::LogWarning,QString().sprintf("Could not open %s",(const char *)srcfile));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   sf_command(src_sf,SFC_SET_NORM_FLOAT,NULL,SF_FALSE);
 | |
|   sf_command(dst_sf,SFC_SET_CLIPPING,NULL,SF_TRUE);
 | |
|   memset(&dst_info,0,sizeof(dst_info));
 | |
|   dst_info.format=SF_FORMAT_WAV|SF_FORMAT_PCM_32;
 | |
|   dst_info.channels=conv_settings->channels();
 | |
|   dst_info.samplerate=conv_settings->sampleRate();
 | |
|   if((dst_sf=sf_open(dstfile,SFM_WRITE,&dst_info))==NULL) {
 | |
|     sf_close(src_sf);
 | |
|     rda->log(RDConfig::LogWarning,QString().sprintf("Could not open %s",(const char *)dstfile));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Allocate Buffers
 | |
|   //
 | |
|   pcm[0]=new float[STAGE2_BUFFER_SIZE];
 | |
|   free_pcm[0]=true;
 | |
|   if(dst_info.samplerate!=src_info.samplerate) {
 | |
|     pcm[1]=new float[STAGE2_BUFFER_SIZE];
 | |
|     free_pcm[1]=true;
 | |
|     if(dst_info.channels!=src_info.channels) {
 | |
|       pcm[2]=new float[STAGE2_BUFFER_SIZE];
 | |
|       free_pcm[2]=true;
 | |
|     }
 | |
|     else {
 | |
|       pcm[2]=pcm[1];
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     pcm[1]=pcm[0];
 | |
|     if(dst_info.channels!=src_info.channels) {
 | |
|       pcm[2]=new float[STAGE2_BUFFER_SIZE];
 | |
|       free_pcm[2]=true;
 | |
|     }
 | |
|     else {
 | |
|       pcm[2]=pcm[0];
 | |
|     }
 | |
|   }  
 | |
| 
 | |
| 
 | |
|   //
 | |
|   // Initialize Rate Converter
 | |
|   //
 | |
|   if(dst_info.samplerate!=src_info.samplerate) {
 | |
|     if((src_state=src_new(conv_src_converter,src_info.channels,&err))==NULL) {
 | |
|       sf_close(src_sf);
 | |
|       sf_close(dst_sf);
 | |
|       rda->log(RDConfig::LogWarning,QString().sprintf("%s",src_strerror(err)));
 | |
|       return RDAudioConvert::ErrorInternal;
 | |
|     }
 | |
|     memset(&src_data,0,sizeof(src_data));
 | |
|     src_data.src_ratio=(double)dst_info.samplerate/(double)src_info.samplerate;
 | |
|     src_data.data_in=pcm[0];
 | |
|     src_data.data_out=pcm[1];
 | |
|     src_data.output_frames=STAGE2_XFER_SIZE*dst_info.samplerate/
 | |
|       src_info.samplerate+src_info.channels;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Initialize Speed Converter
 | |
|   //
 | |
|   if(conv_speed_ratio!=1.0) {
 | |
|     st_conv=new soundtouch::SoundTouch();
 | |
|     st_conv->setTempo(conv_speed_ratio);
 | |
|     st_conv->setSampleRate(dst_info.samplerate);
 | |
|     st_conv->setChannels(dst_info.channels);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Calculate Gain Ratio
 | |
|   //
 | |
|   if(conv_settings->normalizationLevel()!=0) {
 | |
|     float gain=
 | |
|       (float)conv_settings->normalizationLevel()-20.0*log10f(conv_peak_sample);
 | |
|     ratio=exp10f(gain/20.0);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Convert
 | |
|   //
 | |
|   while((n=sf_readf_float(src_sf,pcm[0],STAGE2_XFER_SIZE))>0) {
 | |
| 
 | |
|     //
 | |
|     // Levels
 | |
|     //
 | |
|     if(ratio!=1.0) {
 | |
|       for(unsigned i=0;i<(n*src_info.channels);i++) {
 | |
| 	pcm[0][i]=ratio*pcm[0][i];
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Sample Rate
 | |
|     //
 | |
|     if(src_state!=NULL) {
 | |
|       src_data.input_frames=n;
 | |
|       if((err=src_process(src_state,&src_data))!=0) {
 | |
|         fprintf(stderr,"SRC Error: %s\n",src_strerror(err));
 | |
|         rda->log(RDConfig::LogWarning,QString().sprintf("%s",src_strerror(err)));
 | |
|         return RDAudioConvert::ErrorInternal;
 | |
|       }
 | |
|       n=src_data.output_frames_gen;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Channelization
 | |
|     //
 | |
|     switch(src_info.channels) {
 | |
|     case 1:
 | |
|       switch(dst_info.channels) {
 | |
|       case 1:  // Nothing to do
 | |
| 	break;
 | |
| 
 | |
|       case 2:
 | |
| 	for(unsigned i=0;i<n;i++) {
 | |
| 	  pcm[2][2*i]=pcm[1][i];
 | |
| 	  pcm[2][2*i+1]=pcm[1][i];
 | |
| 	}
 | |
| 	break;
 | |
|       }
 | |
|       break;
 | |
| 
 | |
|     case 2:
 | |
|       switch(dst_info.channels) {
 | |
|       case 1:
 | |
| 	for(unsigned i=0;i<n;i++) {
 | |
| 	  pcm[2][i]=(pcm[1][2*i]+pcm[1][2*i+1])/2;
 | |
| 	}
 | |
| 	break;
 | |
| 
 | |
|       case 2:  // Nothing to do
 | |
| 	break;
 | |
|       }
 | |
|       break;
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Speed
 | |
|     //
 | |
|     if(st_conv!=NULL) {
 | |
|       st_conv->putSamples((soundtouch::SAMPLETYPE *)pcm[2],n);
 | |
|       n=st_conv->receiveSamples((soundtouch::SAMPLETYPE *)pcm[2],STAGE2_BUFFER_SIZE/dst_info.channels);
 | |
|     }
 | |
| 
 | |
|     //
 | |
|     // Write Output
 | |
|     //
 | |
|     if(sf_writef_float(dst_sf,pcm[2],n)!=n) {
 | |
|       for(unsigned i=0;i<3;i++) {
 | |
| 	if(free_pcm[i]) {
 | |
| 	  delete pcm[i];
 | |
| 	}
 | |
|       }
 | |
|       if(src_state!=NULL) {
 | |
| 	src_delete(src_state);
 | |
|       }
 | |
|       sf_close(src_sf);
 | |
|       sf_close(dst_sf);
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Finish Up Speed Conversion
 | |
|   //
 | |
|   if(st_conv!=NULL) {
 | |
|     st_conv->flush();
 | |
|     while((n=st_conv->
 | |
| 	   receiveSamples((soundtouch::SAMPLETYPE *)pcm[2],
 | |
| 			  STAGE2_BUFFER_SIZE/dst_info.channels))>0) {
 | |
|       if(sf_writef_float(dst_sf,pcm[2],n)!=n) {
 | |
| 	for(unsigned i=0;i<3;i++) {
 | |
| 	  if(free_pcm[i]) {
 | |
| 	    delete pcm[i];
 | |
| 	  }
 | |
| 	}
 | |
| 	if(src_state!=NULL) {
 | |
| 	  src_delete(src_state);
 | |
| 	}
 | |
| 	sf_close(src_sf);
 | |
| 	sf_close(dst_sf);
 | |
| 	return RDAudioConvert::ErrorNoSpace;
 | |
|       }
 | |
|       usleep(conv_transcoding_delay);
 | |
|     }
 | |
|     delete st_conv;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   for(unsigned i=0;i<3;i++) {
 | |
|     if(free_pcm[i]) {
 | |
|       delete pcm[i];
 | |
|     }
 | |
|   }
 | |
|   if(src_state!=NULL) {
 | |
|     src_delete(src_state);
 | |
|   }
 | |
|   sf_close(src_sf);
 | |
|   sf_close(dst_sf);
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Convert(const QString &srcfile,
 | |
| 							const QString &dstfile)
 | |
| {
 | |
|   SNDFILE *src_sf=NULL;
 | |
|   SF_INFO src_sf_info;
 | |
|   RDAudioConvert::ErrorCode ret;
 | |
| 
 | |
|   //
 | |
|   // Open Source File
 | |
|   //
 | |
|   if((src_sf=sf_open(srcfile,SFM_READ,&src_sf_info))==NULL) {
 | |
|     rda->log(RDConfig::LogWarning,QString().sprintf("%s",sf_strerror(NULL)));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
| 
 | |
|   switch(conv_settings->format()) {
 | |
|   case RDSettings::Pcm16:
 | |
|     ret=Stage3Pcm16(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::Pcm24:
 | |
|     ret=Stage3Pcm24(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::MpegL2:
 | |
|     ret=Stage3Layer2(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::MpegL2Wav:
 | |
|     ret=Stage3Layer2Wav(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::MpegL3:
 | |
|     ret=Stage3Layer3(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::Flac:
 | |
|     ret=Stage3Flac(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::OggVorbis:
 | |
|     ret=Stage3Vorbis(src_sf,&src_sf_info,dstfile);
 | |
|     break;
 | |
| 
 | |
|   case RDSettings::MpegL1:
 | |
|   default:
 | |
|     ret=RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   sf_close(src_sf);
 | |
|   return ret;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Flac(SNDFILE *src_sf,
 | |
| 						     SF_INFO *src_sf_info,
 | |
| 						     const QString &dstfile)
 | |
| {
 | |
| #ifdef HAVE_FLAC
 | |
|   sf_count_t n;
 | |
|   int32_t *pcm;
 | |
| 
 | |
|   //
 | |
|   // Initialize Encoder
 | |
|   //
 | |
|   FLAC::Encoder::File *flac=new FLAC::Encoder::File();
 | |
|   flac->set_channels(src_sf_info->channels);
 | |
|   flac->set_bits_per_sample(16);  // FIXME: Should vary by input file
 | |
|   flac->set_sample_rate(src_sf_info->samplerate);
 | |
|   //flac->set_compression_level(8);
 | |
|   flac->set_blocksize(0);
 | |
|   unlink(dstfile);
 | |
|   /*
 | |
|    * FLAC <1.2.x
 | |
|    *
 | |
|   flac->set_filename(dstfile.ascii());
 | |
|   switch(flac->init()) {
 | |
|     case 0:
 | |
|       break;
 | |
| 
 | |
|     default:
 | |
|       delete flac;
 | |
|       rda->log(RDConfig::LogWarning,QString("flac->init() failure"));
 | |
|       return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   */
 | |
|   /*
 | |
|    * FLAC 1.2.x
 | |
|    */
 | |
|   switch(flac->init(dstfile.ascii())) {
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_OK:
 | |
|     break;
 | |
| 
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_NUMBER_OF_CHANNELS:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_BITS_PER_SAMPLE:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_SAMPLE_RATE:
 | |
|     delete flac;
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
| 
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_ENCODER_ERROR:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_UNSUPPORTED_CONTAINER:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_MAX_LPC_ORDER:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_QLP_COEFF_PRECISION:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_BLOCK_SIZE_TOO_SMALL_FOR_LPC_ORDER:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_NOT_STREAMABLE:
 | |
|   case FLAC__STREAM_ENCODER_INIT_STATUS_INVALID_METADATA:
 | |
|   default:
 | |
|     delete flac;
 | |
|     rda->log(RDConfig::LogWarning,QString("flac->init() failure"));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
| 
 | |
|   pcm=new int32_t[2048*src_sf_info->channels];
 | |
| 
 | |
|   //
 | |
|   // Encode
 | |
|   //
 | |
|   while((n=sf_readf_int(src_sf,pcm,2048))>0) {
 | |
|     for(unsigned i=0;i<(n*src_sf_info->channels);i++) {
 | |
|       pcm[i]=pcm[i]>>16;
 | |
|     }
 | |
|     flac->process_interleaved(pcm,n);
 | |
|   }
 | |
|   flac->finish();
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   delete pcm;
 | |
|   delete flac;
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_FLAC
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Vorbis(SNDFILE *src_sf,
 | |
| 						       SF_INFO *src_sf_info,
 | |
| 						       const QString &dstfile)
 | |
| {
 | |
| #ifdef HAVE_VORBIS
 | |
|   ogg_stream_state ogg_stream;
 | |
|   ogg_page ogg_page;
 | |
|   ogg_packet header;
 | |
|   ogg_packet comment;
 | |
|   ogg_packet codebook;
 | |
|   ogg_packet ogg_packet;
 | |
|   vorbis_info vorbis_info;
 | |
|   vorbis_comment vorbis_comment;
 | |
|   vorbis_dsp_state vorbis_dsp;
 | |
|   vorbis_block vorbis_block;
 | |
|   float *pcm=NULL;
 | |
|   float **vorbis;
 | |
|   sf_count_t n;
 | |
|   int dst_fd=-1;
 | |
| 
 | |
|   //
 | |
|   // Open Destination File
 | |
|   //
 | |
|   unlink(dstfile);
 | |
|   if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
 | |
| 		  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   } 
 | |
| 
 | |
|   //
 | |
|   // Initialize the Encoder
 | |
|   //
 | |
|   vorbis_info_init(&vorbis_info);
 | |
|   switch(vorbis_encode_init_vbr(&vorbis_info,src_sf_info->channels,
 | |
| 				src_sf_info->samplerate,
 | |
| 				conv_settings->quality())) {
 | |
|   case OV_EFAULT:
 | |
|   default:
 | |
|     rda->log(RDConfig::LogWarning,QString("vorbis_encode_init_vbr() failure"));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
| 
 | |
|   case OV_EINVAL:
 | |
|   case OV_EIMPL:
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
| 
 | |
|   case 0:
 | |
|     break;
 | |
|   }
 | |
|   vorbis_comment_init(&vorbis_comment);
 | |
|   // Metadata stuff goes here...
 | |
|   vorbis_analysis_init(&vorbis_dsp,&vorbis_info);
 | |
|   vorbis_block_init(&vorbis_dsp,&vorbis_block);
 | |
|   vorbis_analysis_headerout(&vorbis_dsp,&vorbis_comment,
 | |
| 			    &header,&comment,&codebook);
 | |
|   ogg_stream_init(&ogg_stream,rand());
 | |
|   ogg_stream_packetin(&ogg_stream,&header);
 | |
|   ogg_stream_packetin(&ogg_stream,&comment);
 | |
|   ogg_stream_packetin(&ogg_stream,&codebook);
 | |
|   pcm=new float[2048*src_sf_info->channels];
 | |
| 
 | |
|   //
 | |
|   // Encode
 | |
|   //
 | |
|   while((n=sf_readf_float(src_sf,pcm,2048))>0) {
 | |
|     vorbis=vorbis_analysis_buffer(&vorbis_dsp,n);
 | |
|     for(unsigned i=0;i<n;i++) {
 | |
|       for(int j=0;j<src_sf_info->channels;j++) {
 | |
| 	vorbis[j][i]=pcm[src_sf_info->channels*i+j];
 | |
|       }
 | |
|     }
 | |
|     vorbis_analysis_wrote(&vorbis_dsp,n);
 | |
|     while(vorbis_analysis_blockout(&vorbis_dsp,&vorbis_block)>0) {
 | |
|       vorbis_analysis(&vorbis_block,&ogg_packet);
 | |
|       ogg_stream_packetin(&ogg_stream,&ogg_packet);
 | |
|       while(ogg_stream_pageout(&ogg_stream,&ogg_page)!=0) {
 | |
| 	if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
 | |
| 	   ogg_page.header_len) {
 | |
| 	  ::close(dst_fd);
 | |
| 	  delete pcm;
 | |
| 	  ogg_stream_clear(&ogg_stream);
 | |
| 	  vorbis_comment_clear(&vorbis_comment);
 | |
| 	  vorbis_info_clear(&vorbis_info);
 | |
| 	  return RDAudioConvert::ErrorNoSpace; 
 | |
| 	}
 | |
| 	if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
 | |
| 	   ogg_page.body_len) {
 | |
| 	  ::close(dst_fd);
 | |
| 	  delete pcm;
 | |
| 	  ogg_stream_clear(&ogg_stream);
 | |
| 	  vorbis_comment_clear(&vorbis_comment);
 | |
| 	  vorbis_info_clear(&vorbis_info);
 | |
| 	  return RDAudioConvert::ErrorNoSpace; 
 | |
| 	}
 | |
|       }
 | |
|     }
 | |
|     while(ogg_stream_flush(&ogg_stream,&ogg_page)!=0) {
 | |
|       if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
 | |
| 	 ogg_page.header_len) {
 | |
| 	  ::close(dst_fd);
 | |
| 	  delete pcm;
 | |
| 	  ogg_stream_clear(&ogg_stream);
 | |
| 	  vorbis_comment_clear(&vorbis_comment);
 | |
| 	  vorbis_info_clear(&vorbis_info);
 | |
| 	  return RDAudioConvert::ErrorNoSpace; 
 | |
| 	}
 | |
|       }
 | |
|     if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
 | |
|        ogg_page.body_len) {
 | |
|       ::close(dst_fd);
 | |
|       delete pcm;
 | |
|       ogg_stream_clear(&ogg_stream);
 | |
|       vorbis_comment_clear(&vorbis_comment);
 | |
|       vorbis_info_clear(&vorbis_info);
 | |
|       return RDAudioConvert::ErrorNoSpace; 
 | |
|     }
 | |
|   }
 | |
|   vorbis=vorbis_analysis_buffer(&vorbis_dsp,0);
 | |
|   vorbis_analysis_wrote(&vorbis_dsp,0);
 | |
|   while(vorbis_analysis_blockout(&vorbis_dsp,&vorbis_block)>0) {
 | |
|     vorbis_analysis(&vorbis_block,&ogg_packet);
 | |
|     ogg_stream_packetin(&ogg_stream,&ogg_packet);
 | |
|     while(ogg_stream_pageout(&ogg_stream,&ogg_page)!=0) {
 | |
|       if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
 | |
| 	 ogg_page.header_len) {
 | |
| 	::close(dst_fd);
 | |
| 	delete pcm;
 | |
| 	ogg_stream_clear(&ogg_stream);
 | |
| 	vorbis_comment_clear(&vorbis_comment);
 | |
| 	vorbis_info_clear(&vorbis_info);
 | |
| 	return RDAudioConvert::ErrorNoSpace; 
 | |
|       }
 | |
|       if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
 | |
| 	 ogg_page.body_len) {
 | |
| 	::close(dst_fd);
 | |
| 	delete pcm;
 | |
| 	ogg_stream_clear(&ogg_stream);
 | |
| 	vorbis_comment_clear(&vorbis_comment);
 | |
| 	vorbis_info_clear(&vorbis_info);
 | |
| 	return RDAudioConvert::ErrorNoSpace; 
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   while(ogg_stream_flush(&ogg_stream,&ogg_page)!=0) {
 | |
|     if(write(dst_fd,ogg_page.header,ogg_page.header_len)!=
 | |
|        ogg_page.header_len) {
 | |
|       ::close(dst_fd);
 | |
|       delete pcm;
 | |
|       ogg_stream_clear(&ogg_stream);
 | |
|       vorbis_comment_clear(&vorbis_comment);
 | |
|       vorbis_info_clear(&vorbis_info);
 | |
|       return RDAudioConvert::ErrorNoSpace; 
 | |
|     }
 | |
|     if(write(dst_fd,ogg_page.body,ogg_page.body_len)!=
 | |
|        ogg_page.body_len) {
 | |
|       ::close(dst_fd);
 | |
|       delete pcm;
 | |
|       ogg_stream_clear(&ogg_stream);
 | |
|       vorbis_comment_clear(&vorbis_comment);
 | |
|       vorbis_info_clear(&vorbis_info);
 | |
|       return RDAudioConvert::ErrorNoSpace; 
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   ::close(dst_fd);
 | |
|   delete pcm;
 | |
|   ogg_stream_clear(&ogg_stream);
 | |
|   vorbis_comment_clear(&vorbis_comment);
 | |
|   vorbis_info_clear(&vorbis_info);
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_VORBIS
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer3(SNDFILE *src_sf,
 | |
| 						       SF_INFO *src_sf_info,
 | |
| 						       const QString &dstfile)
 | |
| {
 | |
| #ifdef HAVE_LAME
 | |
|   MPEG_mode mpeg_mode=STEREO;
 | |
|   lame_global_flags *lameopts=NULL;
 | |
|   int dst_fd=-1;
 | |
|   int16_t pcm[2304];
 | |
|   unsigned char mpeg[2048];
 | |
|   sf_count_t n;
 | |
|   sf_count_t s;
 | |
| 
 | |
|   //
 | |
|   // Load LAME
 | |
|   //
 | |
|   if(!LoadLame()) {
 | |
|     return RDAudioConvert::ErrorFormatNotSupported;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Determine MPEG Mode
 | |
|   //
 | |
|   switch(src_sf_info->channels) {
 | |
|   case 1:
 | |
|     mpeg_mode=MONO;
 | |
|     break;
 | |
| 
 | |
|   case 2:
 | |
|     mpeg_mode=STEREO;    
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open Destination File
 | |
|   //
 | |
|   unlink(dstfile);
 | |
|   if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
 | |
| 		  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   } 
 | |
| 
 | |
|   //
 | |
|   // Initialize Encoder
 | |
|   //
 | |
|   if((lameopts=lame_init())==NULL) {
 | |
|     lame_close(lameopts);
 | |
|     ::close(dst_fd);
 | |
|     rda->log(RDConfig::LogWarning,QString("lame_init() failure"));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   lame_set_mode(lameopts,mpeg_mode);
 | |
|   lame_set_num_channels(lameopts,src_sf_info->channels);
 | |
|   lame_set_in_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   lame_set_out_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   lame_set_brate(lameopts,conv_settings->bitRate()/1000);
 | |
|   lame_set_bWriteVbrTag(lameopts,0);
 | |
|   if(lame_init_params(lameopts)!=0) {
 | |
|     lame_close(lameopts);
 | |
|     ::close(dst_fd);
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Encode
 | |
|   //
 | |
|   if(src_sf_info->channels==2) {
 | |
|     while((n=sf_readf_short(src_sf,pcm,1152))>0) {
 | |
|       if((s=lame_encode_buffer_interleaved(lameopts,pcm,n,mpeg,2048))>=0) {
 | |
| 	if(write(dst_fd,mpeg,s)!=s) {
 | |
| 	  lame_close(lameopts);
 | |
| 	  ::close(dst_fd);
 | |
| 	  return RDAudioConvert::ErrorNoSpace;
 | |
| 	}
 | |
|       }
 | |
|       usleep(conv_transcoding_delay);
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     while((n=sf_readf_short(src_sf,pcm,1152))>0) {
 | |
|       if((s=lame_encode_buffer(lameopts,pcm,NULL,n,mpeg,2048))>=0) {
 | |
| 	if(write(dst_fd,mpeg,s)!=s) {
 | |
| 	  lame_close(lameopts);
 | |
| 	  ::close(dst_fd);
 | |
| 	  return RDAudioConvert::ErrorNoSpace;
 | |
| 	}
 | |
| 	usleep(conv_transcoding_delay);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   if((s=lame_encode_flush(lameopts,mpeg,2048))>=0) {
 | |
|     if(write(dst_fd,mpeg,s)!=s) {
 | |
|       lame_close(lameopts);
 | |
|       ::close(dst_fd);
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   lame_close(lameopts);
 | |
|   ::close(dst_fd);
 | |
| 
 | |
|   //
 | |
|   // Apply Metadata
 | |
|   //
 | |
|   if(conv_dst_wavedata!=NULL) {
 | |
|     ApplyId3Tag(dstfile,conv_dst_wavedata);
 | |
|   }
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_LAME
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer2Wav(SNDFILE *src_sf,
 | |
| 							  SF_INFO *src_sf_info,
 | |
| 							  const QString &dstfile)
 | |
| {
 | |
| #ifdef HAVE_TWOLAME
 | |
|   sf_count_t n;
 | |
|   ssize_t s;
 | |
|   RDWaveFile *wave=NULL;
 | |
|   TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;
 | |
|   twolame_options *lameopts=NULL;
 | |
|   float pcm[2304];
 | |
|   unsigned char mpeg[2048];
 | |
| 
 | |
|   //
 | |
|   // Load TwoLAME
 | |
|   //
 | |
|   if(!LoadTwoLame()) {
 | |
|     return RDAudioConvert::ErrorFormatNotSupported;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Determine MPEG Mode
 | |
|   //
 | |
|   switch(src_sf_info->channels) {
 | |
|   case 1:
 | |
|     mpeg_mode=TWOLAME_MONO;
 | |
|     break;
 | |
| 
 | |
|   case 2:
 | |
|     mpeg_mode=TWOLAME_STEREO;    
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open Destination File
 | |
|   //
 | |
|   wave=new RDWaveFile(dstfile);
 | |
|   wave->setFormatTag(WAVE_FORMAT_MPEG);
 | |
|   wave->setChannels(src_sf_info->channels);
 | |
|   switch(src_sf_info->channels) {
 | |
|   case 1:
 | |
|     wave->setHeadMode(ACM_MPEG_SINGLECHANNEL);
 | |
|     break;
 | |
| 
 | |
|   case 2:
 | |
|     wave->setHeadMode(ACM_MPEG_STEREO);
 | |
|     break;
 | |
|   }
 | |
|   wave->setSamplesPerSec(src_sf_info->samplerate);
 | |
|   wave->setHeadLayer(2);
 | |
|   wave->setHeadBitRate(conv_settings->bitRate());
 | |
|   wave->setBextChunk(true);
 | |
|   wave->setMextChunk(true);
 | |
|   wave->setCartChunk(conv_dst_wavedata!=NULL);
 | |
|   wave->setLevlChunk(true);
 | |
|   wave->setRdxlContents(conv_dst_rdxl);
 | |
|   unlink(dstfile);
 | |
|   if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Initialize Encoder
 | |
|   //
 | |
|   if((lameopts=twolame_init())==NULL) {
 | |
|     wave->closeWave();
 | |
|     rda->log(RDConfig::LogWarning,QString("twolame_init() failure"));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   twolame_set_mode(lameopts,mpeg_mode);
 | |
|   twolame_set_num_channels(lameopts,src_sf_info->channels);
 | |
|   twolame_set_in_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   twolame_set_out_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   twolame_set_bitrate(lameopts,conv_settings->bitRate()/1000);
 | |
|   twolame_set_energy_levels(lameopts,1);
 | |
|   if(twolame_init_params(lameopts)!=0) {
 | |
|     twolame_close(&lameopts);
 | |
|     wave->closeWave();
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Encode
 | |
|   //
 | |
|   while((n=sf_readf_float(src_sf,pcm,1152))>0) {
 | |
|     if((s=twolame_encode_buffer_float32_interleaved(lameopts,
 | |
| 						    pcm,n,mpeg,2048))>=0) {
 | |
|       if(wave->writeWave(mpeg,s)!=s) {
 | |
| 	twolame_close(&lameopts);
 | |
| 	wave->closeWave(src_sf_info->frames);
 | |
| 	return RDAudioConvert::ErrorNoSpace;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       fprintf(stderr,"TwoLAME encode error\n");
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   if((s=twolame_encode_flush(lameopts,mpeg,2048))>=0) {
 | |
|     if(wave->writeWave(mpeg,s)!=s) {
 | |
|       twolame_close(&lameopts);
 | |
|       wave->closeWave(src_sf_info->frames);
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     fprintf(stderr,"TwoLAME encode error\n");
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   twolame_close(&lameopts);
 | |
|   wave->closeWave(src_sf_info->frames);
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_TWOLAME
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Layer2(SNDFILE *src_sf,
 | |
| 						       SF_INFO *src_sf_info,
 | |
| 						       const QString &dstfile)
 | |
| {
 | |
| #ifdef HAVE_TWOLAME
 | |
|   sf_count_t n;
 | |
|   ssize_t s;
 | |
|   int dst_fd=-1;
 | |
|   TWOLAME_MPEG_mode mpeg_mode=TWOLAME_STEREO;
 | |
|   twolame_options *lameopts=NULL;
 | |
|   float pcm[2304];
 | |
|   unsigned char mpeg[2048];
 | |
| 
 | |
|   if(!LoadTwoLame()) {
 | |
|     return RDAudioConvert::ErrorFormatNotSupported;
 | |
|   }
 | |
|   if((conv_settings->bitRate()>192000)&&(src_sf_info->channels<2)) {
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Determine MPEG Mode
 | |
|   //
 | |
|   switch(src_sf_info->channels) {
 | |
|   case 1:
 | |
|     mpeg_mode=TWOLAME_MONO;
 | |
|     break;
 | |
| 
 | |
|   case 2:
 | |
|     mpeg_mode=TWOLAME_STEREO;    
 | |
|     break;
 | |
| 
 | |
|   default:
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Open Destination File
 | |
|   //
 | |
|   unlink(dstfile);
 | |
|   if((dst_fd=open(dstfile,O_WRONLY|O_CREAT|O_TRUNC,
 | |
| 		  S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH))<0) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   } 
 | |
| 
 | |
|   //
 | |
|   // Initialize Encoder
 | |
|   //
 | |
|   if((lameopts=twolame_init())==NULL) {
 | |
|     ::close(dst_fd);
 | |
|     rda->log(RDConfig::LogWarning,QString("twolame_init() failure"));
 | |
|     return RDAudioConvert::ErrorInternal;
 | |
|   }
 | |
|   twolame_set_mode(lameopts,mpeg_mode);
 | |
|   twolame_set_num_channels(lameopts,src_sf_info->channels);
 | |
|   twolame_set_in_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   twolame_set_out_samplerate(lameopts,src_sf_info->samplerate);
 | |
|   twolame_set_bitrate(lameopts,conv_settings->bitRate()/1000);
 | |
|   if(twolame_init_params(lameopts)!=0) {
 | |
|     twolame_close(&lameopts);
 | |
|     ::close(dst_fd);
 | |
|     return RDAudioConvert::ErrorInvalidSettings;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Encode
 | |
|   //
 | |
|   while((n=sf_readf_float(src_sf,pcm,1152))>0) {
 | |
|     if((s=twolame_encode_buffer_float32_interleaved(lameopts,
 | |
| 						    pcm,n,mpeg,2048))>=0) {
 | |
|       if(write(dst_fd,mpeg,s)!=s) {
 | |
| 	twolame_close(&lameopts);
 | |
| 	::close(dst_fd);
 | |
| 	return RDAudioConvert::ErrorNoSpace;
 | |
|       }
 | |
|     }
 | |
|     else {
 | |
|       fprintf(stderr,"TwoLAME encode error\n");
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   if((s=twolame_encode_flush(lameopts,mpeg,2048))>=0) {
 | |
|     if(write(dst_fd,mpeg,s)!=s) {
 | |
|       twolame_close(&lameopts);
 | |
|       ::close(dst_fd);
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|   }
 | |
|   else {
 | |
|     fprintf(stderr,"TwoLAME encode error\n");
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Clean Up
 | |
|   //
 | |
|   twolame_close(&lameopts);
 | |
|   ::close(dst_fd);
 | |
| 
 | |
|   //
 | |
|   // Apply Metadata
 | |
|   //
 | |
|   if(conv_dst_wavedata!=NULL) {
 | |
|     ApplyId3Tag(dstfile,conv_dst_wavedata);
 | |
|   }
 | |
| 
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| #else
 | |
|   return RDAudioConvert::ErrorFormatNotSupported;
 | |
| #endif  // HAVE_TWOLAME
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Pcm16(SNDFILE *src_sf,
 | |
| 						      SF_INFO *src_sf_info,
 | |
| 						      const QString &dstfile)
 | |
| {
 | |
|   short *sf_buffer=NULL;
 | |
|   ssize_t n;
 | |
| 
 | |
|   RDWaveFile *wave=new RDWaveFile(dstfile);
 | |
|   wave->setFormatTag(WAVE_FORMAT_PCM);
 | |
|   wave->setChannels(src_sf_info->channels);
 | |
|   wave->setSamplesPerSec(src_sf_info->samplerate);
 | |
|   wave->setBitsPerSample(16);
 | |
|   wave->setBextChunk(true);
 | |
|   wave->setCartChunk(conv_dst_wavedata!=NULL);
 | |
|   wave->setRdxlContents(conv_dst_rdxl);
 | |
|   if((conv_dst_wavedata!=NULL)&&(conv_settings->normalizationLevel()!=0)) {
 | |
|     wave->setCartLevelRef(32768*
 | |
| 	      exp10((double)conv_settings->normalizationLevel()/20.0));
 | |
|   }
 | |
|   wave->setLevlChunk(true);
 | |
|   sf_buffer=new int16_t[2048*src_sf_info->channels];
 | |
|   unlink(dstfile);
 | |
|   if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
|   while((n=sf_readf_short(src_sf,sf_buffer,2048))>0) {
 | |
|     if((unsigned)wave->
 | |
|        writeWave(sf_buffer,n*sizeof(short)*src_sf_info->channels)!=
 | |
|        (n*sizeof(short)*src_sf_info->channels)) {
 | |
|       delete sf_buffer;
 | |
|       wave->closeWave();
 | |
|       delete wave;
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   delete sf_buffer;
 | |
|   wave->closeWave();
 | |
|   delete wave;
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| }
 | |
| 
 | |
| 
 | |
| RDAudioConvert::ErrorCode RDAudioConvert::Stage3Pcm24(SNDFILE *src_sf,
 | |
| 						      SF_INFO *src_sf_info,
 | |
| 						      const QString &dstfile)
 | |
| {
 | |
|   int *sf_buffer=NULL;
 | |
|   uint8_t *pcm24=NULL;
 | |
|   ssize_t n;
 | |
| 
 | |
|   RDWaveFile *wave=new RDWaveFile(dstfile);
 | |
|   wave->setFormatTag(WAVE_FORMAT_PCM);
 | |
|   wave->setChannels(src_sf_info->channels);
 | |
|   wave->setSamplesPerSec(src_sf_info->samplerate);
 | |
|   wave->setBitsPerSample(24);
 | |
|   wave->setBextChunk(true);
 | |
|   wave->setCartChunk(conv_dst_wavedata!=NULL);
 | |
|   wave->setRdxlContents(conv_dst_rdxl);
 | |
|   if((conv_dst_wavedata!=NULL)&&(conv_settings->normalizationLevel()!=0)) {
 | |
|     wave->setCartLevelRef(32768*
 | |
| 	      exp10((double)conv_settings->normalizationLevel()/20.0));
 | |
|   }
 | |
|   wave->setLevlChunk(true);
 | |
|   sf_buffer=new int[2048*src_sf_info->channels];
 | |
|   pcm24=new uint8_t[2048*src_sf_info->channels*sizeof(int)];
 | |
|   unlink(dstfile);
 | |
|   if(!wave->createWave(conv_dst_wavedata,conv_start_point)) {
 | |
|     return RDAudioConvert::ErrorNoDestination;
 | |
|   }
 | |
|   while((n=sf_readf_int(src_sf,sf_buffer,2048))>0) {
 | |
|     for(ssize_t i=0;i<(n*src_sf_info->channels);i++) {
 | |
|       pcm24[3*i]=0xFF&(sf_buffer[i]>>8);
 | |
|       pcm24[3*i+1]=0xFF&(sf_buffer[i]>>16);
 | |
|       pcm24[3*i+2]=0xFF&(sf_buffer[i]>>24);
 | |
|     }
 | |
|     if((unsigned)wave->writeWave(pcm24,n*3*src_sf_info->channels)!=
 | |
|        (n*3*src_sf_info->channels)) {
 | |
|       delete sf_buffer;
 | |
|       delete pcm24;
 | |
|       wave->closeWave();
 | |
|       delete wave;
 | |
|       return RDAudioConvert::ErrorNoSpace;
 | |
|     }
 | |
|     usleep(conv_transcoding_delay);
 | |
|   }
 | |
|   delete sf_buffer;
 | |
|   delete pcm24;
 | |
|   wave->closeWave();
 | |
|   delete wave;
 | |
|   return RDAudioConvert::ErrorOk;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::ApplyId3Tag(const QString &filename,RDWaveData *wavedata)
 | |
| {
 | |
|   TagLib::MPEG::File *file=new TagLib::MPEG::File(filename.toUtf8(),false);
 | |
|   TagLib::PropertyMap *map=new TagLib::PropertyMap();
 | |
|   TagLib::ID3v2::Tag *tag=file->ID3v2Tag();
 | |
| 
 | |
|   AddId3Property(map,"TITLE",wavedata->title());
 | |
|   if(!wavedata->artist().isEmpty()) {
 | |
|     AddId3Property(map,"ARTIST",wavedata->artist());
 | |
|   }
 | |
|   if(!wavedata->album().isEmpty()) {
 | |
|     AddId3Property(map,"ALBUM",wavedata->album());
 | |
|   }
 | |
|   if(!wavedata->label().isEmpty()) {
 | |
|     AddId3Property(map,"LABEL",wavedata->label());
 | |
|   }
 | |
|   if(!wavedata->conductor().isEmpty()) {
 | |
|     AddId3Property(map,"CONDUCTOR",wavedata->conductor());
 | |
|   }
 | |
|   if(!wavedata->composer().isEmpty()) {
 | |
|     AddId3Property(map,"COMPOSER",wavedata->composer());
 | |
|   }
 | |
|   if(!wavedata->publisher().isEmpty()) {
 | |
|     AddId3Property(map,"PUBLISHER",wavedata->publisher());
 | |
|   }
 | |
|   if(!wavedata->copyrightNotice().isEmpty()) {
 | |
|     AddId3Property(map,"COPYRIGHT",wavedata->copyrightNotice());
 | |
|   }
 | |
|   if(!wavedata->isrc().isEmpty()) {
 | |
|     AddId3Property(map,"ISRC",wavedata->isrc());
 | |
|   }
 | |
|   if(wavedata->releaseYear()>0) {
 | |
|     AddId3Property(map,"YEAR",QString().sprintf("%d",wavedata->releaseYear()));
 | |
|   }
 | |
|   if(wavedata->beatsPerMinute()>0) {
 | |
|     AddId3Property(map,"BPM",
 | |
| 		   QString().sprintf("%d",wavedata->beatsPerMinute()));
 | |
|   }
 | |
|   tag->setProperties(*map);
 | |
| 
 | |
|   RDCart *cart=new RDCart(wavedata->cartNumber());
 | |
|   if(cart->exists()) {
 | |
|     QString xml=
 | |
|       cart->xml(true,conv_start_point<0,conv_settings,wavedata->cutNumber());
 | |
|     TagLib::ID3v2::UserTextIdentificationFrame *frame=
 | |
|       new TagLib::ID3v2::UserTextIdentificationFrame(TagLib::String::UTF8);
 | |
|     frame->setDescription("rdxl");
 | |
|     frame->setText(TagLib::String((const char *)xml.toUtf8(),
 | |
|     				  TagLib::String::UTF8));
 | |
|     tag->addFrame(frame);
 | |
|   }
 | |
|   delete cart;
 | |
| 
 | |
|   file->save();
 | |
|   delete map;
 | |
|   delete file;
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::AddId3Property(TagLib::PropertyMap *map,const QString &key,
 | |
| 				    const QString &value) const
 | |
| {
 | |
|   TagLib::StringList args;
 | |
| 
 | |
|   args.
 | |
|     append(TagLib::String((const char *)value.toUtf8(),TagLib::String::UTF8));
 | |
|   map->insert((const char *)key.toUtf8(),args);
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::UpdatePeak(const float data[],ssize_t len)
 | |
| {
 | |
|   float peak;
 | |
| 
 | |
|   for(ssize_t i=0;i<len;i++) {
 | |
|     if((peak=fabsf(data[i]))>conv_peak_sample) {
 | |
|       conv_peak_sample=peak;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| void RDAudioConvert::UpdatePeak(const double data[],ssize_t len)
 | |
| {
 | |
|   float peak;
 | |
| 
 | |
|   for(ssize_t i=0;i<len;i++) {
 | |
|     if((peak=(float)fabsf(data[i]))>conv_peak_sample) {
 | |
|       conv_peak_sample=peak;
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| bool RDAudioConvert::LoadMad()
 | |
| {
 | |
| #ifdef HAVE_MAD
 | |
|   if(conv_mad_handle==NULL) {
 | |
|     return false;
 | |
|   }
 | |
|   *(void **)(&mad_stream_init)=
 | |
|     dlsym(conv_mad_handle,"mad_stream_init");
 | |
|   *(void **)(&mad_frame_init)=
 | |
|     dlsym(conv_mad_handle,"mad_frame_init");
 | |
|   *(void **)(&mad_synth_init)=
 | |
|     dlsym(conv_mad_handle,"mad_synth_init");
 | |
|   *(void **)(&mad_stream_buffer)=
 | |
|     dlsym(conv_mad_handle,"mad_stream_buffer");
 | |
|   *(void **)(&mad_frame_decode)=
 | |
|     dlsym(conv_mad_handle,"mad_frame_decode");
 | |
|   *(void **)(&mad_synth_frame)=
 | |
|     dlsym(conv_mad_handle,"mad_synth_frame");
 | |
|   *(void **)(&mad_frame_finish)=
 | |
|     dlsym(conv_mad_handle,"mad_frame_finish");
 | |
|   *(void **)(&mad_stream_finish)=
 | |
|     dlsym(conv_mad_handle,"mad_stream_finish");
 | |
|   return true;
 | |
| #else
 | |
|   return false;
 | |
| #endif  // HAVE_MAD
 | |
| }
 | |
| 
 | |
| 
 | |
| bool RDAudioConvert::LoadTwoLame()
 | |
| {
 | |
| #ifdef HAVE_TWOLAME
 | |
|   if(conv_twolame_handle==NULL) {
 | |
|     return false;
 | |
|   }
 | |
|   *(void **)(&twolame_init)=dlsym(conv_twolame_handle,"twolame_init");
 | |
|   *(void **)(&twolame_set_mode)=dlsym(conv_twolame_handle,"twolame_set_mode");
 | |
|   *(void **)(&twolame_set_num_channels)=
 | |
|     dlsym(conv_twolame_handle,"twolame_set_num_channels");
 | |
|   *(void **)(&twolame_set_in_samplerate)=
 | |
|     dlsym(conv_twolame_handle,"twolame_set_in_samplerate");
 | |
|   *(void **)(&twolame_set_out_samplerate)=
 | |
|     dlsym(conv_twolame_handle,"twolame_set_out_samplerate");
 | |
|   *(void **)(&twolame_set_bitrate)=
 | |
|     dlsym(conv_twolame_handle,"twolame_set_bitrate");
 | |
|   *(void **)(&twolame_init_params)=
 | |
|     dlsym(conv_twolame_handle,"twolame_init_params");
 | |
|   *(void **)(&twolame_close)=dlsym(conv_twolame_handle,"twolame_close");
 | |
|   *(void **)(&twolame_encode_buffer_float32_interleaved)=
 | |
|     dlsym(conv_twolame_handle,"twolame_encode_buffer_float32_interleaved");
 | |
|   *(void **)(&twolame_encode_flush)=
 | |
|     dlsym(conv_twolame_handle,"twolame_encode_flush");
 | |
|   *(void **)(&twolame_set_energy_levels)=
 | |
|     dlsym(conv_twolame_handle,"twolame_set_energy_levels");
 | |
|   return true;
 | |
| #else
 | |
|   return false;
 | |
| #endif  // HAVE_TWOLAME
 | |
| }
 | |
| 
 | |
| 
 | |
| bool RDAudioConvert::LoadLame()
 | |
| {
 | |
| #ifdef HAVE_LAME
 | |
|   if(conv_lame_handle==NULL) {
 | |
|     return false;
 | |
|   }
 | |
|   *(void **)(&lame_init)=dlsym(conv_lame_handle,"lame_init");
 | |
|   *(void **)(&lame_set_mode)=
 | |
|     dlsym(conv_lame_handle,"lame_set_mode");
 | |
|   *(void **)(&lame_set_num_channels)=
 | |
|     dlsym(conv_lame_handle,"lame_set_num_channels");
 | |
|   *(void **)(&lame_set_in_samplerate)=
 | |
|     dlsym(conv_lame_handle,"lame_set_in_samplerate");
 | |
|   *(void **)(&lame_set_out_samplerate)=
 | |
|     dlsym(conv_lame_handle,"lame_set_out_samplerate");
 | |
|   *(void **)(&lame_set_brate)=dlsym(conv_lame_handle,"lame_set_brate");
 | |
|   *(void **)(&lame_init_params)=dlsym(conv_lame_handle,"lame_init_params");
 | |
|   *(void **)(&lame_close)=dlsym(conv_lame_handle,"lame_close");
 | |
|   *(void **)(&lame_encode_buffer_interleaved)=
 | |
|     dlsym(conv_lame_handle,"lame_encode_buffer_interleaved");
 | |
|   *(void **)(&lame_encode_buffer)=
 | |
|     dlsym(conv_lame_handle,"lame_encode_buffer");
 | |
|   *(void **)(&lame_encode_flush)=dlsym(conv_lame_handle,"lame_encode_flush");
 | |
|   *(void **)(&lame_set_bWriteVbrTag)=
 | |
|     dlsym(conv_lame_handle,"lame_set_bWriteVbrTag");
 | |
|   return true;
 | |
| #else
 | |
|   return false;
 | |
| #endif  // HAVE_LAME
 | |
| }
 |