Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/recording/enginerecord.cpp

Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines