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 /* Later I should support loading and saving the user notes and cues, etc.. but I'm not even 22 * done defining my own, and the libFLAC++ documentation is a little hard to follow about metadata 23 * so I'll ommit this for now 24 */ 25 26 #include "CFLACSoundTranslator.h" 27 28 #if defined(HAVE_LIBFLACPP) 29 30 #include <unistd.h> 31 32 #include <string> 33 #include <stdexcept> 34 #include <utility> 35 36 #include <FLAC++/all.h> 37 38 #include <istring> 39 #include <CPath.h> 40 #include <TAutoBuffer.h> 41 42 #include "CSound.h" 43 #include "AStatusComm.h" 44 45 CFLACSoundTranslator::CFLACSoundTranslator() 46 { 47 } 48 49 CFLACSoundTranslator::~CFLACSoundTranslator() 50 { 51 } 52 53 54 55 56 class MyFLACDecoderFile : public FLAC::Decoder::File 57 { 58 public: 59 #define REALLOC_FILE_SIZE (1024*1024/4) 60 61 MyFLACDecoderFile(const string _filename,CSound *_sound) : 62 File(), 63 64 filename(_filename), 65 sound(_sound), 66 67 statusBar(_("Loading Sound"),0,CPath(filename).getSize(false),true), 68 69 sampleRate(0), 70 channelCount(0), 71 bitRate(0), 72 73 pos(0) 74 { 75 for(unsigned t=0;t<MAX_CHANNELS;t++) 76 accessers[t]=NULL; 77 78 //set_filename(filename.c_str()); 79 80 set_metadata_ignore_all(); 81 //set_metadata_respond(FLAC__METADATA_TYPE_VORBIS_COMMENT); 82 //set_metadata_respond(FLAC__METADATA_TYPE_CUESHEET); 83 84 FLAC__StreamDecoderInitStatus s=init(filename.c_str()); 85 if(s!=FLAC__STREAM_DECODER_INIT_STATUS_OK) 86 throw runtime_error(string(__func__)+" -- "+FLAC__StreamDecoderInitStatusString[s]); 87 } 88 89 virtual ~MyFLACDecoderFile() 90 { 91 finish(); 92 93 // remove space we didn't need to add (because we're adding in chunks) 94 sound->removeSpace(pos,sound->getLength()-pos); 95 96 for(unsigned t=0;t<channelCount;t++) 97 delete accessers[t]; 98 } 99 100 protected: 101 102 ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 *const buffer[]) 103 { 104 if(sound->isEmpty()) 105 { 106 #warning an exception thrown from here causes an abort because it is running in a thread??? why? I dunno, might want to talk to the FLAC guys 107 sampleRate=get_sample_rate(); 108 channelCount=get_channels(); 109 bitRate=get_bits_per_sample(); 110 111 #warning I need to translate from different bit rates that might come back from get_bits_per_sample ... I am not sure what they would be 112 //printf("br %d\n",bitRate); 113 //printf("cc %d\n",channelCount); 114 //printf("sr %d\n",sampleRate); 115 if(bitRate!=16 && bitRate!=24 && bitRate!=32) 116 { 117 Error("unhandled bitrate! (data will be incorrect): "+istring(bitRate)); 118 } 119 120 if(channelCount<=0 || channelCount>MAX_CHANNELS) // ??? could just ignore the extra channels 121 throw runtime_error(string(__func__)+" -- invalid number of channels in audio file: "+istring(channelCount)+" -- you could simply increase MAX_CHANNELS in CSound.h"); 122 123 if(sampleRate<100 || sampleRate>197000) 124 throw runtime_error(string(__func__)+" -- an unlikely sample rate of "+istring(sampleRate)); 125 126 sound->createWorkingPoolFile(filename,sampleRate,channelCount,REALLOC_FILE_SIZE); 127 128 for(unsigned t=0;t<channelCount;t++) 129 accessers[t]=new CRezPoolAccesser(sound->getAudio(t)); 130 } 131 132 133 const sample_pos_t sampleframes_read=frame->header.blocksize; 134 135 if((pos+sampleframes_read)>sound->getLength()) 136 sound->addSpace(sound->getLength(),REALLOC_FILE_SIZE); 137 138 for(unsigned i=0;i<channelCount;i++) 139 { 140 const FLAC__int32 *src=buffer[i]; 141 CRezPoolAccesser &dest=*(accessers[i]); 142 143 if(bitRate==16) 144 { 145 for(unsigned t=0;t<sampleframes_read;t++) 146 // the FLAC__int32 src seems to actually be 16bit (maybe this changes depending on the file?) 147 dest[pos+t]=convert_sample<int16_t,sample_t>(src[t]); 148 } 149 else if(bitRate==24) 150 { 151 for(unsigned t=0;t<sampleframes_read;t++) 152 { 153 int24_t sd; 154 sd.set(src[t]); 155 dest[pos+t]=convert_sample<int24_t,sample_t>(sd); 156 } 157 } 158 else if(bitRate==32) 159 { 160 for(unsigned t=0;t<sampleframes_read;t++) 161 dest[pos+t]=convert_sample<int32_t,sample_t>(src[t]); 162 } 163 else 164 { // warned user already 165 } 166 } 167 168 pos+=sampleframes_read; 169 170 171 // update status bar and detect user cancel 172 FLAC__uint64 filePosition; 173 FLAC__stream_decoder_get_decode_position(decoder_, &filePosition); 174 return statusBar.update(filePosition) ? FLAC__STREAM_DECODER_WRITE_STATUS_ABORT : FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; 175 } 176 177 void metadata_callback(const ::FLAC__StreamMetadata *metadata) 178 { 179 /* 180 if(sound->isEmpty()) 181 { 182 printf("ignoring metadata (until we know the parameters)\n"); 183 return; 184 } 185 186 printf("accepting metadata\n"); 187 */ 188 } 189 190 void error_callback(::FLAC__StreamDecoderErrorStatus status) 191 { 192 /* 193 printf("error\n"); 194 */ 195 } 196 197 198 private: 199 200 const string filename; 201 CSound *sound; 202 203 CStatusBar statusBar; 204 205 unsigned sampleRate; 206 unsigned channelCount; 207 unsigned bitRate; 208 209 210 sample_pos_t pos; 211 212 CRezPoolAccesser *accessers[MAX_CHANNELS]; 213 }; 214 215 bool CFLACSoundTranslator::onLoadSound(const string filename,CSound *sound) const 216 { 217 MyFLACDecoderFile f(filename,sound); 218 return f.process_until_end_of_stream(); 219 } 220 221 222 223 224 225 class MyFLACEncoderFile : public FLAC::Encoder::File 226 { 227 public: 228 CStatusBar statusBar; 229 bool cancelled; 230 231 MyFLACEncoderFile(sample_pos_t saveLength) : 232 File(), 233 statusBar(_("Saving Sound"),0,saveLength,true), 234 cancelled(false) 235 { 236 } 237 238 virtual ~MyFLACEncoderFile() 239 { 240 } 241 242 protected: 243 void progress_callback(FLAC__uint64 bytes_written, FLAC__uint64 samples_written, unsigned frames_written, unsigned total_frames_estimate) 244 { 245 cancelled|=statusBar.update(samples_written); 246 } 247 }; 248 249 bool CFLACSoundTranslator::onSaveSound(const string filename,const CSound *sound,const sample_pos_t saveStart,const sample_pos_t saveLength,bool useLastUserPrefs) const 250 { 251 int bitRate=0; 252 253 if(sound->getCueCount()>0 || sound->getUserNotes()!="") 254 { 255 if(Question(_("Saving user notes or cues is unimplemented for FLAC\nDo you wish to continue?"),yesnoQues)!=yesAns) 256 return false; 257 } 258 259 MyFLACEncoderFile f(saveLength); 260 261 //f.set_filename(filename.c_str()); 262 263 f.set_channels(sound->getChannelCount()); 264 265 /* ??? needs to be a user choice */ 266 f.set_bits_per_sample(16); 267 bitRate=16; 268 269 f.set_sample_rate(sound->getSampleRate()); 270 271 // calling set_do_mid_side_stereo set_loose_mid_side_stereo requires the sound to have only 2 channels 272 // maybe call set_loose_mid_side_stereo if it makes for better compression ??? 273 274 //f.set_metadata(...) // ??? to do to set cues and user notes, etc 275 276 277 FLAC__StreamEncoderInitStatus s=f.init(filename.c_str()); 278 if(s==FLAC__STREAM_ENCODER_INIT_STATUS_OK) 279 { 280 #define BUFFER_SIZE 65536 281 TAutoBuffer<FLAC__int32> buffers[MAX_CHANNELS]; 282 FLAC__int32 *_buffers[MAX_CHANNELS]; 283 284 for(unsigned t=0;t<sound->getChannelCount();t++) 285 { 286 buffers[t].setSize(BUFFER_SIZE); 287 _buffers[t]=buffers[t]; // get point of buffer[t] to be able to pass to f.process() 288 } 289 290 291 sample_pos_t pos=0; 292 while(pos<saveLength) 293 { 294 const sample_pos_t len=min((sample_pos_t)BUFFER_SIZE,saveLength-pos); 295 for(unsigned i=0;i<sound->getChannelCount();i++) 296 { 297 const CRezPoolAccesser src=sound->getAudio(i); 298 FLAC__int32 *dest=buffers[i]; 299 300 if(bitRate==16) 301 { 302 for(sample_pos_t t=0;t<len;t++) 303 dest[t]=convert_sample<sample_t,int16_t>(src[t+pos+saveStart]); 304 } 305 else 306 throw runtime_error(string(__func__)+" -- internal error -- unhandled bitRate: "+istring(bitRate)); 307 } 308 309 if(!f.process(_buffers,len)) 310 { 311 const int errNO=errno; 312 f.finish(); 313 throw runtime_error(string(__func__)+" -- error writing FLAC file -- "+strerror(errNO)); 314 } 315 316 if(f.cancelled) 317 { 318 f.finish(); 319 unlink(filename.c_str()); 320 return false; 321 } 322 323 pos+=len; 324 } 325 326 f.finish(); 327 328 return true; 329 } 330 else 331 throw runtime_error(string(__func__)+" -- error creating FLAC encoder -- "+FLAC__StreamEncoderInitStatusString[s]); 332 333 } 334 335 336 bool CFLACSoundTranslator::handlesExtension(const string extension,const string filename) const 337 { 338 return extension=="flac" || extension=="fla"; 339 } 340 341 bool CFLACSoundTranslator::supportsFormat(const string filename) const 342 { 343 // implementing this would not be convenient right now, I wish there were a simple function for it in the API 344 return false; 345 } 346 347 const vector<string> CFLACSoundTranslator::getFormatNames() const 348 { 349 vector<string> names; 350 351 names.push_back("FLAC"); 352 353 return names; 354 } 355 356 const vector<vector<string> > CFLACSoundTranslator::getFormatFileMasks() const 357 { 358 vector<vector<string> > list; 359 vector<string> fileMasks; 360 361 fileMasks.clear(); 362 fileMasks.push_back("*.flac"); 363 fileMasks.push_back("*.fla"); 364 list.push_back(fileMasks); 365 366 return list; 367 } 368 369 #endif // HAVE_LIBFLACPP 370