Mixxx

/home/maxime/Projets/Mixxx/1.10/mixxx/src/soundsourcemp3.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002                           soundsourcemp3.cpp  -  description
00003                              -------------------
00004     copyright            : (C) 2002 by Tue and Ken Haste Andersen
00005     email                :
00006 ***************************************************************************/
00007 
00008 /***************************************************************************
00009 *                                                                         *
00010 *   This program is free software; you can redistribute it and/or modify  *
00011 *   it under the terms of the GNU General Public License as published by  *
00012 *   the Free Software Foundation; either version 2 of the License, or     *
00013 *   (at your option) any later version.                                   *
00014 *                                                                         *
00015 ***************************************************************************/
00016 
00017 #include <taglib/mpegfile.h>
00018 
00019 #include "soundsourcemp3.h"
00020 #include <QtDebug>
00021 
00022 
00023 SoundSourceMp3::SoundSourceMp3(QString qFilename) :
00024         Mixxx::SoundSource(qFilename),
00025         m_file(qFilename)
00026 {
00027     inputbuf = NULL;
00028     Stream = new mad_stream;
00029     mad_stream_init(Stream);
00030     Synth = new mad_synth;
00031     mad_synth_init(Synth);
00032     Frame = new mad_frame;
00033     mad_frame_init(Frame);
00034 
00035     m_currentSeekFrameIndex = 0;
00036     m_iAvgFrameSize = 0;
00037     m_iChannels = 0;
00038     rest = 0;
00039 }
00040 
00041 SoundSourceMp3::~SoundSourceMp3()
00042 {
00043     mad_stream_finish(Stream);
00044     delete Stream;
00045 
00046     mad_frame_finish(Frame);
00047     delete Frame;
00048 
00049     mad_synth_finish(Synth);
00050     delete Synth;
00051 
00052     // Unmap inputbuf.
00053     m_file.unmap(inputbuf);
00054     inputbuf = NULL;
00055     m_file.close();
00056 
00057     //Free the pointers in our seek list, LIBERATE THEM!!!
00058     for (int i = 0; i < m_qSeekList.count(); i++)
00059     {
00060         delete m_qSeekList[i];
00061     }
00062     m_qSeekList.clear();
00063 }
00064 
00065 QList<QString> SoundSourceMp3::supportedFileExtensions()
00066 {
00067     QList<QString> list;
00068     list.push_back("mp3");
00069     return list;
00070 }
00071 
00072 int SoundSourceMp3::open()
00073 {
00074     m_file.setFileName(m_qFilename);
00075     if (!m_file.open(QIODevice::ReadOnly)) {
00076         //qDebug() << "MAD: Open failed:" << m_qFilename;
00077         return ERR;
00078     }
00079 
00080     // Get a pointer to the file using memory mapped IO
00081     inputbuf_len = m_file.size();
00082     inputbuf = m_file.map(0, inputbuf_len);
00083 
00084     // Transfer it to the mad stream-buffer:
00085     mad_stream_init(Stream);
00086     mad_stream_options(Stream, MAD_OPTION_IGNORECRC);
00087     mad_stream_buffer(Stream, inputbuf, inputbuf_len);
00088 
00089     /*
00090        Decode all the headers, and fill in stats:
00091      */
00092     mad_header Header;
00093     mad_header_init(&Header);
00094     filelength = mad_timer_zero;
00095     bitrate = 0;
00096     currentframe = 0;
00097     pos = mad_timer_zero;
00098 
00099     while ((Stream->bufend - Stream->this_frame) > 0)
00100     {
00101         if (mad_header_decode (&Header, Stream) == -1) {
00102             if (!MAD_RECOVERABLE (Stream->error))
00103                 break;
00104             if (Stream->error == MAD_ERROR_LOSTSYNC) {
00105                 // ignore LOSTSYNC due to ID3 tags
00106                 int tagsize = id3_tag_query (Stream->this_frame,Stream->bufend - Stream->this_frame);
00107                 if (tagsize > 0) {
00108                     //qDebug() << "SSMP3::SSMP3() : skipping ID3 tag size " << tagsize;
00109                     mad_stream_skip (Stream, tagsize);
00110                     continue;
00111                 }
00112             }
00113 
00114             // qDebug() << "MAD: ERR decoding header "
00115             //          << currentframe << ": "
00116             //          << mad_stream_errorstr(Stream)
00117             //          << " (len=" << mad_timer_count(filelength,MAD_UNITS_MILLISECONDS)
00118             //          << ")";
00119             continue;
00120         }
00121 
00122         // Grab data from Header
00123 
00124         // This warns us only when the reported sample rate changes. (and when
00125         // it is first set)
00126         if (m_iSampleRate == 0 && Header.samplerate > 0) {
00127             setSampleRate(Header.samplerate);
00128         } else if (m_iSampleRate != Header.samplerate) {
00129             qDebug() << "SSMP3: file has differing samplerate in some headers:"
00130                      << m_qFilename
00131                      << m_iSampleRate << "vs" << Header.samplerate;
00132         }
00133 
00134         m_iChannels = MAD_NCHANNELS(&Header);
00135         mad_timer_add (&filelength, Header.duration);
00136         bitrate += Header.bitrate;
00137 
00138         // Add frame to list of frames
00139         MadSeekFrameType * p = new MadSeekFrameType;
00140         p->m_pStreamPos = (unsigned char *)Stream->this_frame;
00141         p->pos = length();
00142         m_qSeekList.append(p);
00143         currentframe++;
00144     }
00145     //qDebug() << "channels " << m_iChannels;
00146 
00147     mad_header_finish (&Header); // This is a macro for nothing.
00148 
00149     // This is not a working MP3 file.
00150     if (currentframe == 0) {
00151         return ERR;
00152     }
00153 
00154     // Find average frame size
00155     m_iAvgFrameSize = (currentframe == 0) ? 0 : length()/currentframe;
00156     // And average bitrate
00157     bitrate = (currentframe == 0) ? 0 : bitrate / currentframe;
00158     framecount = currentframe;
00159     currentframe = 0;
00160 
00161     //Recalculate the duration by using the average frame size. Our first guess at
00162     //the duration of VBR MP3s in parseHeader() goes for speed over accuracy
00163     //since it runs during a library scan. When we open() an MP3 for playback,
00164     //we had to seek through the entire thing to build a seek table, so we've
00165     //also counted the number of frames in it. We need that to better estimate
00166     //the length of VBR MP3s.
00167     if (getSampleRate() > 0 && m_iChannels > 0) //protect again divide by zero
00168     {
00169         //qDebug() << "SSMP3::open() - Setting duration to:" << framecount * m_iAvgFrameSize / getSampleRate() / m_iChannels;
00170         setDuration(framecount * m_iAvgFrameSize / getSampleRate() / m_iChannels);
00171     }
00172 
00173     //TODO: Emit metadata updated signal?
00174 
00175 /*
00176     qDebug() << "length  = " << filelength.seconds << "d sec.";
00177     qDebug() << "frames  = " << framecount;
00178     qDebug() << "bitrate = " << bitrate/1000;
00179     qDebug() << "Size    = " << length();
00180  */
00181 
00182     // Re-init buffer:
00183     seek(0);
00184 
00185     return OK;
00186 }
00187 
00188 bool SoundSourceMp3::isValid() const {
00189     return framecount > 0;
00190 }
00191 
00192 MadSeekFrameType* SoundSourceMp3::getSeekFrame(long frameIndex) const {
00193     if (frameIndex < 0 || frameIndex >= m_qSeekList.size()) {
00194         return NULL;
00195     }
00196     return m_qSeekList.at(frameIndex);
00197 }
00198 
00199 long SoundSourceMp3::seek(long filepos) {
00200     // Ensure that we are seeking to an even filepos
00201     Q_ASSERT(filepos%2==0);
00202 
00203     if (!isValid()) {
00204         return 0;
00205     }
00206 
00207 //     qDebug() << "SEEK " << filepos;
00208 
00209     MadSeekFrameType* cur = NULL;
00210 
00211     if (filepos==0)
00212     {
00213         // Seek to beginning of file
00214 
00215         // Re-init buffer:
00216         mad_stream_finish(Stream);
00217         mad_stream_init(Stream);
00218         mad_stream_options(Stream, MAD_OPTION_IGNORECRC);
00219         mad_stream_buffer(Stream, (unsigned char *) inputbuf, inputbuf_len);
00220         mad_frame_init(Frame);
00221         mad_synth_init(Synth);
00222         rest=-1;
00223 
00224         m_currentSeekFrameIndex = 0;
00225         cur = getSeekFrame(0);
00226         //frameIterator.toFront(); //Might not need to do this -- Albert June 19/2010 (during Qt3 purge)
00227     }
00228     else
00229     {
00230         //qDebug() << "seek precise";
00231         // Perform precise seek accomplished by using a frame in the seek list
00232 
00233         // Find the frame to seek to in the list
00234         /*
00235            MadSeekFrameType *cur = m_qSeekList.last();
00236            int k=0;
00237            while (cur!=0 && cur->pos>filepos)
00238            {
00239             cur = m_qSeekList.prev();
00240          ++k;
00241            }
00242          */
00243 
00244         int framePos = findFrame(filepos);
00245         if (framePos==0 || framePos>filepos || m_currentSeekFrameIndex < 5)
00246         {
00247             //qDebug() << "Problem finding good seek frame (wanted " << filepos << ", got " << framePos << "), starting from 0";
00248 
00249             // Re-init buffer:
00250             mad_stream_finish(Stream);
00251             mad_stream_init(Stream);
00252             mad_stream_options(Stream, MAD_OPTION_IGNORECRC);
00253             mad_stream_buffer(Stream, (unsigned char *) inputbuf, inputbuf_len);
00254             mad_frame_init(Frame);
00255             mad_synth_init(Synth);
00256             rest = -1;
00257             m_currentSeekFrameIndex = 0;
00258             cur = getSeekFrame(m_currentSeekFrameIndex);
00259         }
00260         else
00261         {
00262 //             qDebug() << "frame pos " << cur->pos;
00263 
00264             // Start four frame before wanted frame to get in sync...
00265             m_currentSeekFrameIndex -= 4;
00266             cur = getSeekFrame(m_currentSeekFrameIndex);
00267             if (cur != NULL) {
00268                 // Start from the new frame
00269                 mad_stream_finish(Stream);
00270                 mad_stream_init(Stream);
00271                 mad_stream_options(Stream, MAD_OPTION_IGNORECRC);
00272                 //        qDebug() << "mp3 restore " << cur->m_pStreamPos;
00273                 mad_stream_buffer(Stream, (const unsigned char *)cur->m_pStreamPos,
00274                                   inputbuf_len-(long int)(cur->m_pStreamPos-(unsigned char *)inputbuf));
00275 
00276                 // Mute'ing is done here to eliminate potential pops/clicks from skipping
00277                 // Rob Leslie explains why here:
00278                 // http://www.mars.org/mailman/public/mad-dev/2001-August/000321.html
00279                 mad_synth_mute(Synth);
00280                 mad_frame_mute(Frame);
00281 
00282                 // Decode the three frames before
00283                 mad_frame_decode(Frame,Stream);
00284                 mad_frame_decode(Frame,Stream);
00285                 mad_frame_decode(Frame,Stream);
00286                 mad_frame_decode(Frame,Stream);
00287 
00288                 // this is also explained in the above mad-dev post
00289                 mad_synth_frame(Synth, Frame);
00290 
00291                 // Set current position
00292                 rest = -1;
00293                 m_currentSeekFrameIndex += 4;
00294                 cur = getSeekFrame(m_currentSeekFrameIndex);
00295             }
00296         }
00297 
00298         // Synthesize the the samples from the frame which should be discard to reach the requested position
00299         if (cur != NULL) //the "if" prevents crashes on bad files.
00300             discard(filepos-cur->pos);
00301     }
00302 /*
00303     else
00304     {
00305         qDebug() << "seek unprecise";
00306         // Perform seek which is can not be done precise because no frames is in the seek list
00307 
00308         int newpos = (int)(inputbuf_len * ((float)filepos/(float)length()));
00309    //        qDebug() << "Seek to " << filepos << " " << inputbuf_len << " " << newpos;
00310 
00311         // Go to an approximate position:
00312         mad_stream_buffer(Stream, (unsigned char *) (inputbuf+newpos), inputbuf_len-newpos);
00313         mad_synth_mute(Synth);
00314         mad_frame_mute(Frame);
00315 
00316         // Decode a few (possible wrong) buffers:
00317         int no = 0;
00318         int succesfull = 0;
00319         while ((no<10) && (succesfull<2))
00320         {
00321             if (!mad_frame_decode(Frame, Stream))
00322             succesfull ++;
00323             no ++;
00324         }
00325 
00326         // Discard the first synth:
00327         mad_synth_frame(Synth, Frame);
00328 
00329         // Remaining samples in buffer are useless
00330         rest = -1;
00331 
00332         // Reset seek frame list
00333         m_qSeekList.clear();
00334         MadSeekFrameType *p = new MadSeekFrameType;
00335         p->m_pStreamPos = (unsigned char*)Stream->this_frame;
00336         p->pos = filepos;
00337         m_qSeekList.append(p);
00338         m_iSeekListMinPos = filepos;
00339         m_iSeekListMaxPos = filepos;
00340         m_iCurFramePos = filepos;
00341     }
00342  */
00343 
00344     // Unfortunately we don't know the exact fileposition. The returned position is thus an
00345     // approximation only:
00346     return filepos;
00347 
00348 }
00349 
00350 inline long unsigned SoundSourceMp3::length() {
00351     enum mad_units units;
00352 
00353     switch (m_iSampleRate)
00354     {
00355     case 8000:
00356         units = MAD_UNITS_8000_HZ;
00357         break;
00358     case 11025:
00359         units = MAD_UNITS_11025_HZ;
00360         break;
00361     case 12000:
00362         units = MAD_UNITS_12000_HZ;
00363         break;
00364     case 16000:
00365         units = MAD_UNITS_16000_HZ;
00366         break;
00367     case 22050:
00368         units = MAD_UNITS_22050_HZ;
00369         break;
00370     case 24000:
00371         units = MAD_UNITS_24000_HZ;
00372         break;
00373     case 32000:
00374         units = MAD_UNITS_32000_HZ;
00375         break;
00376     case 44100:
00377         units = MAD_UNITS_44100_HZ;
00378         break;
00379     case 48000:
00380         units = MAD_UNITS_48000_HZ;
00381         break;
00382     default:             //By the MP3 specs, an MP3 _has_ to have one of the above samplerates...
00383         units = MAD_UNITS_44100_HZ;
00384         qWarning() << "MP3 with corrupt samplerate (" << m_iSampleRate << "), defaulting to 44100";
00385 
00386         m_iSampleRate = 44100; //Prevents division by zero errors.
00387     }
00388 
00389     return (long unsigned) 2 *mad_timer_count(filelength, units);
00390 }
00391 
00392 /*
00393   decode the chosen number of samples and discard
00394 */
00395 
00396 unsigned long SoundSourceMp3::discard(unsigned long samples_wanted)
00397 {
00398     unsigned long Total_samples_decoded = 0;
00399     int no;
00400 
00401     if(rest > 0)
00402         Total_samples_decoded += 2*(Synth->pcm.length-rest);
00403 
00404     while (Total_samples_decoded < samples_wanted)
00405     {
00406         if(mad_frame_decode(Frame,Stream))
00407         {
00408             if(MAD_RECOVERABLE(Stream->error))
00409             {
00410                 continue;
00411             } else if(Stream->error==MAD_ERROR_BUFLEN) {
00412                 break;
00413             } else {
00414                 break;
00415             }
00416         }
00417         mad_synth_frame(Synth,Frame);
00418         no = math_min(Synth->pcm.length,(samples_wanted-Total_samples_decoded)/2);
00419         Total_samples_decoded += 2*no;
00420     }
00421 
00422     if (Synth->pcm.length > no)
00423         rest = no;
00424     else
00425         rest = -1;
00426 
00427 
00428     return Total_samples_decoded;
00429 }
00430 
00431 /*
00432    read <size> samples into <destination>, and return the number of
00433    samples actually read.
00434  */
00435 unsigned SoundSourceMp3::read(unsigned long samples_wanted, const SAMPLE * _destination)
00436 {
00437     if (!isValid())
00438         return 0;
00439 
00440     // Ensure that we are reading an even number of samples. Otherwise this function may
00441     // go into an infinite loop
00442     Q_ASSERT(samples_wanted%2==0);
00443 //     qDebug() << "frame list " << m_qSeekList.count();
00444 
00445     SAMPLE * destination = (SAMPLE *)_destination;
00446     unsigned Total_samples_decoded = 0;
00447     int i;
00448 
00449     // If samples are left from previous read, then copy them to start of destination
00450     // Make sure to take into account the case where there are more samples left over
00451     // from the previous read than the client requested.
00452     if (rest > 0)
00453     {
00454         for (i=rest; i<Synth->pcm.length && Total_samples_decoded < samples_wanted; i++)
00455         {
00456             // Left channel
00457             *(destination++) = madScale(Synth->pcm.samples[0][i]);
00458 
00459             /* Right channel. If the decoded stream is monophonic then
00460             * the right output channel is the same as the left one. */
00461             if (m_iChannels>1)
00462                 *(destination++) = madScale(Synth->pcm.samples[1][i]);
00463             else
00464                 *(destination++) = madScale(Synth->pcm.samples[0][i]);
00465 
00466             // This is safe because we have Q_ASSERTed that samples_wanted is even.
00467             Total_samples_decoded += 2;
00468 
00469         }
00470 
00471         if(Total_samples_decoded >= samples_wanted) {
00472             if(i < Synth->pcm.length)
00473                 rest = i;
00474             else
00475                 rest = -1;
00476             return Total_samples_decoded;
00477         }
00478 
00479     }
00480 
00481 //     qDebug() << "Decoding";
00482     int no = 0;
00483     unsigned int frames = 0;
00484     while (Total_samples_decoded < samples_wanted)
00485     {
00486         // qDebug() << "no " << Total_samples_decoded;
00487         if(mad_frame_decode(Frame,Stream))
00488         {
00489             if(MAD_RECOVERABLE(Stream->error))
00490             {
00491                 if(Stream->error == MAD_ERROR_LOSTSYNC) {
00492                     // Ignore LOSTSYNC due to ID3 tags
00493                     int tagsize = id3_tag_query(Stream->this_frame, Stream->bufend - Stream->this_frame);
00494                     if(tagsize > 0) {
00495                         //qDebug() << "SSMP3::Read Skipping ID3 tag size: " << tagsize;
00496                         mad_stream_skip(Stream, tagsize);
00497                     }
00498                     continue;
00499                 }
00500                 //qDebug() << "MAD: Recoverable frame level ERR (" << mad_stream_errorstr(Stream) << ")";
00501                 continue;
00502             } else if(Stream->error==MAD_ERROR_BUFLEN) {
00503                 // qDebug() << "MAD: buflen ERR";
00504                 break;
00505             } else {
00506                 // qDebug() << "MAD: Unrecoverable frame level ERR (" << mad_stream_errorstr(Stream) << ").";
00507                 break;
00508             }
00509         }
00510 
00511         ++frames;
00512 
00513         /* Once decoded the frame is synthesized to PCM samples. No ERRs
00514          * are reported by mad_synth_frame();
00515          */
00516         mad_synth_frame(Synth,Frame);
00517 
00518         // Number of channels in frame
00519         //ch = MAD_NCHANNELS(&Frame->header);
00520 
00521         /* Synthesized samples must be converted from mad's fixed
00522          * point number to the consumer format (16 bit). Integer samples
00523          * are temporarily stored in a buffer that is flushed when
00524          * full.
00525          */
00526 
00527 
00528 //         qDebug() << "synthlen " << Synth->pcm.length << ", remain " << (samples_wanted-Total_samples_decoded);
00529         no = math_min(Synth->pcm.length,(samples_wanted-Total_samples_decoded)/2);
00530         for (i=0; i<no; i++)
00531         {
00532             // Left channel
00533             *(destination++) = madScale(Synth->pcm.samples[0][i]);
00534 
00535             /* Right channel. If the decoded stream is monophonic then
00536             * the right output channel is the same as the left one. */
00537             if (m_iChannels==2)
00538                 *(destination++) = madScale(Synth->pcm.samples[1][i]);
00539             else
00540                 *(destination++) = madScale(Synth->pcm.samples[0][i]);
00541         }
00542         Total_samples_decoded += 2*no;
00543 
00544         // qDebug() << "decoded: " << Total_samples_decoded << ", wanted: " << samples_wanted;
00545     }
00546 
00547     // If samples are still left in buffer, set rest to the index of the unused samples
00548     if (Synth->pcm.length > no)
00549         rest = no;
00550     else
00551         rest = -1;
00552 
00553     // qDebug() << "decoded " << Total_samples_decoded << " samples in " << frames << " frames, rest: " << rest << ", chan " << m_iChannels;
00554     return Total_samples_decoded;
00555 }
00556 
00557 int SoundSourceMp3::parseHeader()
00558 {
00559     setType("mp3");
00560 #ifdef __WINDOWS__
00561     /* From Tobias: A Utf-8 string did not work on my Windows XP (German edition)
00562      * If you try this conversion, f.isValid() will return false in many cases
00563      * and processTaglibFile() will fail
00564      *
00565      * The method toLocal8Bit() returns the local 8-bit representation of the string as a QByteArray.
00566      * The returned byte array is undefined if the string contains characters not supported
00567      * by the local 8-bit encoding.
00568      */
00569     QByteArray qBAFilename = m_qFilename.toLocal8Bit();
00570 #else
00571     QByteArray qBAFilename = m_qFilename.toUtf8();
00572 #endif
00573     TagLib::MPEG::File f(qBAFilename.constData());
00574 
00575     // Takes care of all the default metadata
00576     bool result = processTaglibFile(f);
00577 
00578     // Now look for MP3 specific metadata (e.g. BPM)
00579     TagLib::ID3v2::Tag* id3v2 = f.ID3v2Tag();
00580     if (id3v2) {
00581         processID3v2Tag(id3v2);
00582     }
00583 
00584     TagLib::APE::Tag *ape = f.APETag();
00585     if (ape) {
00586         processAPETag(ape);
00587     }
00588 
00589     if (result)
00590         return OK;
00591     return ERR;
00592 }
00593 
00594 int SoundSourceMp3::findFrame(int pos)
00595 {
00596     // Guess position of frame in m_qSeekList based on average frame size
00597     m_currentSeekFrameIndex = math_min(m_qSeekList.count()-1,
00598                                        m_iAvgFrameSize ? (unsigned int)(pos/m_iAvgFrameSize) : 0);
00599     MadSeekFrameType* temp = getSeekFrame(m_currentSeekFrameIndex);
00600 
00601 /*
00602     if (temp!=0)
00603         qDebug() << "find " << pos << ", got " << temp->pos;
00604     else
00605         qDebug() << "find " << pos << ", tried idx " << math_min(m_qSeekList.count()-1 << ", total " << pos/m_iAvgFrameSize);
00606  */
00607 
00608     // Ensure that the list element is not at a greater position than pos
00609     while (temp != NULL && temp->pos > pos)
00610     {
00611         m_currentSeekFrameIndex--;
00612         temp = getSeekFrame(m_currentSeekFrameIndex);
00613 //        if (temp!=0) qDebug() << "backing " << pos << ", got " << temp->pos;
00614     }
00615 
00616     // Ensure that the following position is also not smaller than pos
00617     if (temp != NULL)
00618     {
00619         temp = getSeekFrame(m_currentSeekFrameIndex);
00620         while (temp != NULL && temp->pos < pos)
00621         {
00622             m_currentSeekFrameIndex++;
00623             temp = getSeekFrame(m_currentSeekFrameIndex);
00624 //            if (temp!=0) qDebug() << "fwd'ing " << pos << ", got " << temp->pos;
00625         }
00626 
00627         if (temp == NULL)
00628             m_currentSeekFrameIndex = m_qSeekList.count()-1;
00629         else
00630             m_currentSeekFrameIndex--;
00631 
00632         temp = getSeekFrame(m_currentSeekFrameIndex);
00633     }
00634 
00635     if (temp != NULL)
00636     {
00637 //        qDebug() << "ended at " << pos << ", got " << temp->pos;
00638         return temp->pos;
00639     }
00640     else
00641     {
00642 //        qDebug() << "ended at 0";
00643         return 0;
00644     }
00645 }
00646 
00647 inline signed int SoundSourceMp3::madScale(mad_fixed_t sample)
00648 {
00649     sample += (1L << (MAD_F_FRACBITS - 16));
00650 
00651     if (sample >= MAD_F_ONE)
00652         sample = MAD_F_ONE - 1;
00653     else if (sample < -MAD_F_ONE)
00654         sample = -MAD_F_ONE;
00655 
00656     return sample >> (MAD_F_FRACBITS + 1 - 16);
00657 }
00658 
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Defines