![]() |
Mixxx
|
00001 /*************************************************************************** 00002 enginerecord.cpp - class to record the mix 00003 ------------------- 00004 copyright : (C) 2007 by John Sully 00005 copyright : (C) 2010 by Tobias Rafreider 00006 email : 00007 ***************************************************************************/ 00008 00009 /*************************************************************************** 00010 * * 00011 * This program is free software; you can redistribute it and/or modify * 00012 * it under the terms of the GNU General Public License as published by * 00013 * the Free Software Foundation; either version 2 of the License, or * 00014 * (at your option) any later version. * 00015 * * 00016 ***************************************************************************/ 00017 00018 #include "enginerecord.h" 00019 #include "defs_recording.h" 00020 #include "controllogpotmeter.h" 00021 #include "configobject.h" 00022 #include "controlobjectthread.h" 00023 #include "controlobject.h" 00024 #include "trackinfoobject.h" 00025 #include "dlgprefrecord.h" 00026 #ifdef __SHOUTCAST__ 00027 #include "encodervorbis.h" 00028 #include "encodermp3.h" 00029 #endif 00030 00031 /*************************************************************************** 00032 * * 00033 * Notice To Future Developpers: * 00034 * There is code here to write the file in a seperate thread * 00035 * however it is unstable and has been abondoned. Its only use * 00036 * was to support low priority recording, however I don't think its * 00037 * worth the trouble. * 00038 * * 00039 ***************************************************************************/ 00040 00041 EngineRecord::EngineRecord(ConfigObject<ConfigValue> * _config) 00042 { 00043 m_config = _config; 00044 m_encoder = NULL; 00045 m_sndfile = NULL; 00046 00047 m_recReady = new ControlObjectThread( 00048 ControlObject::getControl(ConfigKey("[Master]", "Record"))); 00049 m_samplerate = new ControlObjectThread(ControlObject::getControl(ConfigKey("[Master]", "samplerate"))); 00050 00051 m_iMetaDataLife = 0; 00052 } 00053 00054 EngineRecord::~EngineRecord() 00055 { 00056 closeCueFile(); 00057 closeFile(); 00058 if(m_recReady) delete m_recReady; 00059 if(m_samplerate) delete m_samplerate; 00060 } 00061 00062 void EngineRecord::updateFromPreferences() 00063 { 00064 m_Encoding = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY,"Encoding")).toLatin1(); 00065 //returns a number from 1 .. 10 00066 m_OGGquality = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY,"OGG_Quality")).toLatin1(); 00067 m_MP3quality = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY,"MP3_Quality")).toLatin1(); 00068 m_filename = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY,"Path")); 00069 m_baTitle = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Title")).toLatin1(); 00070 m_baAuthor = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Author")).toLatin1(); 00071 m_baAlbum = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "Album")).toLatin1(); 00072 m_cuefilename = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CuePath")).toLatin1(); 00073 m_bCueIsEnabled = m_config->getValueString(ConfigKey(RECORDING_PREF_KEY, "CueEnabled")).toInt(); 00074 00075 if(m_encoder){ 00076 delete m_encoder; //delete m_encoder if it has been initalized (with maybe) different bitrate 00077 m_encoder = NULL; 00078 } 00079 00080 if(m_Encoding == ENCODING_MP3){ 00081 #ifdef __SHOUTCAST__ 00082 m_encoder = new EncoderMp3(this); 00083 m_encoder->updateMetaData(m_baAuthor.data(),m_baTitle.data(),m_baAlbum.data()); 00084 00085 if(m_encoder->initEncoder(Encoder::convertToBitrate(m_MP3quality.toInt())) < 0){ 00086 delete m_encoder; 00087 m_encoder = NULL; 00088 qDebug() << "MP3 recording is not supported. Lame could not be initialized"; 00089 } 00090 #else 00091 qDebug() << "MP3 recording requires Mixxx to build with shoutcast support"; 00092 #endif 00093 00094 } 00095 if(m_Encoding == ENCODING_OGG){ 00096 #ifdef __SHOUTCAST__ 00097 m_encoder = new EncoderVorbis(this); 00098 m_encoder->updateMetaData(m_baAuthor.data(),m_baTitle.data(),m_baAlbum.data()); 00099 00100 if(m_encoder->initEncoder(Encoder::convertToBitrate(m_OGGquality.toInt())) < 0){ 00101 delete m_encoder; 00102 m_encoder = NULL; 00103 qDebug() << "OGG recording is not supported. OGG/Vorbis library could not be initialized"; 00104 00105 } 00106 #else 00107 qDebug() << "OGG recording requires Mixxx to build with shoutcast support"; 00108 #endif 00109 00110 } 00111 /* 00112 * If we use WAVE OR AIFF 00113 * the encoder will be NULL at all times 00114 * 00115 */ 00116 00117 } 00118 00119 /* 00120 * Check if the metadata has changed since the previous check. 00121 * We also check when was the last check performed to avoid using 00122 * too much CPU and as well to avoid changing the metadata during 00123 * scratches. 00124 */ 00125 bool EngineRecord::metaDataHasChanged() 00126 { 00127 TrackPointer pTrack; 00128 00129 if ( m_iMetaDataLife < 16 ) { 00130 m_iMetaDataLife++; 00131 return false; 00132 } 00133 m_iMetaDataLife = 0; 00134 00135 pTrack = PlayerInfo::Instance().getCurrentPlayingTrack(); 00136 if ( !pTrack ) 00137 return false; 00138 00139 if ( m_pCurrentTrack ) { 00140 if ((pTrack->getId() == -1) || (m_pCurrentTrack->getId() == -1)) { 00141 if ((pTrack->getArtist() == m_pCurrentTrack->getArtist()) && 00142 (pTrack->getTitle() == m_pCurrentTrack->getArtist())) { 00143 return false; 00144 } 00145 } 00146 else if (pTrack->getId() == m_pCurrentTrack->getId()) { 00147 return false; 00148 } 00149 } 00150 00151 m_pCurrentTrack = pTrack; 00152 return true; 00153 } 00154 00155 void EngineRecord::process(const CSAMPLE * pIn, const CSAMPLE * pOut, const int iBufferSize) { 00156 Q_UNUSED(pOut); 00157 // Calculate the latency of this buffer 00158 m_dLatency = (double)iBufferSize / m_samplerate->get(); 00159 00160 //if recording is disabled 00161 if (m_recReady->get() == RECORD_OFF) { 00162 //qDebug("Setting record flag to: OFF"); 00163 if (fileOpen()) { 00164 closeFile(); //close file and free encoder 00165 emit(isRecording(false)); 00166 } 00167 } 00168 //if we are ready for recording, i.e, the output file has been selected, we open a new file 00169 if (m_recReady->get() == RECORD_READY) { 00170 updateFromPreferences(); //update file location from pref 00171 if (openFile()) { 00172 qDebug("Setting record flag to: ON"); 00173 m_recReady->slotSet(RECORD_ON); 00174 emit(isRecording(true)); //will notify the RecordingManager 00175 00176 if (m_bCueIsEnabled) { 00177 openCueFile(); 00178 m_cuesamplepos = 0; 00179 m_cuetrack = 0; 00180 } 00181 } else{ //Maybe the encoder could not be initialized 00182 qDebug("Setting record flag to: OFF"); 00183 m_recReady->slotSet(RECORD_OFF); 00184 emit(isRecording(false)); 00185 } 00186 } 00187 //If recording is enabled process audio to compressed or uncompressed data. 00188 if (m_recReady->get() == RECORD_ON) { 00189 if (m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF) { 00190 if (m_sndfile != NULL) { 00191 sf_write_float(m_sndfile, pIn, iBufferSize); 00192 emit(bytesRecorded(iBufferSize)); 00193 } 00194 } else { 00195 if (m_encoder) { 00196 //Compress audio. Encoder will call method 'write()' below to write a file stream 00197 m_encoder->encodeBuffer(pIn, iBufferSize); 00198 } 00199 } 00200 00201 if (m_bCueIsEnabled) { 00202 if (metaDataHasChanged()) { 00203 m_cuetrack++; 00204 writeCueLine(); 00205 m_cuefile.flush(); 00206 } 00207 m_cuesamplepos += iBufferSize; 00208 } 00209 } 00210 } 00211 00212 void EngineRecord::writeCueLine() { 00213 // account for multiple channels 00214 unsigned long samplerate = m_samplerate->get() * 2; 00215 // CDDA is specified as having 75 frames a second 00216 unsigned long frames = ((unsigned long) 00217 ((m_cuesamplepos / (samplerate / 75))) 00218 % 75); 00219 00220 unsigned long seconds = ((unsigned long) 00221 (m_cuesamplepos / samplerate) 00222 % 60 ); 00223 00224 unsigned long minutes = m_cuesamplepos / (samplerate * 60); 00225 00226 m_cuefile.write(QString(" TRACK %1 AUDIO\n") 00227 .arg((double)m_cuetrack, 2, 'f', 0, '0') 00228 .toLatin1() 00229 ); 00230 00231 m_cuefile.write(QString(" TITLE %1\n") 00232 .arg(m_pCurrentTrack->getTitle()).toLatin1()); 00233 m_cuefile.write(QString(" PERFORMER %1\n") 00234 .arg(m_pCurrentTrack->getArtist()).toLatin1()); 00235 00236 // Woefully inaccurate (at the seconds level anyways). 00237 // We'd need a signal fired state tracker 00238 // for the track detection code. 00239 m_cuefile.write(QString(" INDEX 01 %1:%2:%3\n") 00240 .arg((double)minutes, 2, 'f', 0, '0') 00241 .arg((double)seconds, 2, 'f', 0, '0') 00242 .arg((double)frames, 2, 'f', 0, '0') 00243 .toLatin1() 00244 ); 00245 } 00246 00248 void EngineRecord::write(unsigned char *header, unsigned char *body, 00249 int headerLen, int bodyLen) 00250 { 00251 if (!fileOpen()) { 00252 return; 00253 } 00254 //Relevant for OGG 00255 if (headerLen > 0) { 00256 m_datastream.writeRawData((const char*) header, headerLen); 00257 } 00258 //always write body 00259 m_datastream.writeRawData((const char*) body, bodyLen); 00260 emit(bytesRecorded((headerLen+bodyLen))); 00261 00262 } 00263 00264 bool EngineRecord::fileOpen() { 00265 // Both encoder and file must be initalized 00266 00267 if (m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF) { 00268 return (m_sndfile != NULL); 00269 } else { 00270 return (m_file.handle() != -1); 00271 } 00272 } 00273 00274 //Creates a new MP3 file 00275 bool EngineRecord::openFile() { 00276 //Unfortunately, we cannot use QFile for writing WAV and AIFF audio 00277 if(m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF){ 00278 unsigned long samplerate = m_samplerate->get(); 00279 //set sfInfo 00280 m_sfInfo.samplerate = samplerate; 00281 m_sfInfo.channels = 2; 00282 00283 if (m_Encoding == ENCODING_WAVE) 00284 m_sfInfo.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; 00285 else 00286 m_sfInfo.format = SF_FORMAT_AIFF | SF_FORMAT_PCM_16; 00287 00288 //creates a new WAVE or AIFF file and write header information 00289 m_sndfile = sf_open(m_filename.toLocal8Bit(), SFM_WRITE, &m_sfInfo); 00290 if (m_sndfile) { 00291 sf_command(m_sndfile, SFC_SET_NORM_FLOAT, NULL, SF_FALSE) ; 00292 //set meta data 00293 int ret; 00294 00295 ret = sf_set_string(m_sndfile, SF_STR_TITLE, m_baTitle.data()); 00296 if(ret != 0) 00297 qDebug("libsndfile: %s", sf_error_number(ret)); 00298 00299 ret = sf_set_string(m_sndfile, SF_STR_ARTIST, m_baAuthor.data()); 00300 if(ret != 0) 00301 qDebug("libsndfile: %s", sf_error_number(ret)); 00302 00303 ret = sf_set_string(m_sndfile, SF_STR_COMMENT, m_baAlbum.data()); 00304 if(ret != 0) 00305 qDebug("libsndfile: %s", sf_error_number(ret)); 00306 00307 } 00308 } else { 00309 //we can use a QFile to write compressed audio 00310 if (m_encoder) { 00311 m_file.setFileName(m_filename); 00312 m_file.open(QIODevice::WriteOnly); 00313 if (m_file.handle() != -1) { 00314 m_datastream.setDevice(&m_file); 00315 } 00316 } else { 00317 return false; 00318 } 00319 } 00320 //check if file are really open 00321 if (!fileOpen()) { 00322 ErrorDialogProperties* props = ErrorDialogHandler::instance()->newDialogProperties(); 00323 props->setType(DLG_WARNING); 00324 props->setTitle(tr("Recording")); 00325 props->setText(tr("<html>Could not create audio file for recording!<p><br>Maybe you do not have enough free disk space or file permissions.</html>")); 00326 ErrorDialogHandler::instance()->requestErrorDialog(props); 00327 return false; 00328 } 00329 return true; 00330 } 00331 00332 bool EngineRecord::openCueFile() { 00333 if (m_cuefilename.length() <= 0) { 00334 return false; 00335 } 00336 00337 qDebug() << "Opening Cue File:" << m_cuefilename; 00338 m_cuefile.setFileName(m_cuefilename); 00339 m_cuefile.open(QIODevice::WriteOnly); 00340 00341 if (m_baAuthor.length() > 0) { 00342 m_cuefile.write(QString("PERFORMER \"%1\"\n") 00343 .arg(QString(m_baAuthor).replace(QString("\""), QString("\\\""))) 00344 .toLatin1()); 00345 } 00346 00347 if (m_baTitle.length() > 0) { 00348 m_cuefile.write(QString("TITLE \"%1\"\n") 00349 .arg(QString(m_baTitle).replace(QString("\""), QString("\\\""))) 00350 .toLatin1()); 00351 } 00352 00353 m_cuefile.write(QString("FILE \"%1\" %2%3\n").arg( 00354 QString(m_filename).replace(QString("\""), QString("\\\"")), 00355 QString(m_Encoding).toUpper(), 00356 (m_Encoding == ENCODING_WAVE ? "E" : " ")).toLatin1()); 00357 return true; 00358 } 00359 00360 void EngineRecord::closeFile() { 00361 if (m_Encoding == ENCODING_WAVE || m_Encoding == ENCODING_AIFF) { 00362 if (m_sndfile != NULL) { 00363 sf_close(m_sndfile); 00364 m_sndfile = NULL; 00365 } 00366 } else if (m_file.handle() != -1) { 00367 // close QFile and encoder, if open 00368 if (m_encoder) { 00369 m_encoder->flush(); 00370 delete m_encoder; 00371 m_encoder = NULL; 00372 } 00373 m_file.close(); 00374 } 00375 } 00376 00377 void EngineRecord::closeCueFile() { 00378 if ( m_cuefile.handle() != -1) { 00379 m_cuefile.close(); 00380 } 00381 }