OggVorbisAudioStream.cxx

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2004 MUSIC TECHNOLOGY GROUP (MTG)
00003  *                         UNIVERSITAT POMPEU FABRA
00004  *
00005  *
00006  * This program is free software; you can redistribute it and/or modify
00007  * it under the terms of the GNU General Public License as published by
00008  * the Free Software Foundation; either version 2 of the License, or
00009  * (at your option) any later version.
00010  *
00011  * This program is distributed in the hope that it will be useful,
00012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  * GNU General Public License for more details.
00015  *
00016  * You should have received a copy of the GNU General Public License
00017  * along with this program; if not, write to the Free Software
00018  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
00019  *
00020  */
00021 
00022 #include "OggVorbisAudioStream.hxx"
00023 #include "AudioFile.hxx"
00024 #include <cstdio>
00025 #include <ctime>
00026 #include <cstdlib>
00027 #include <vorbis/codec.h>
00028 #include <iostream>
00029 #include <algorithm>
00030 
00031 #if defined ( __powerpc__ ) || defined ( __POWERPC__ )
00032 #define HOST_ENDIANESS 1
00033 #else
00034 #define HOST_ENDIANESS 0
00035 #endif
00036 
00037 namespace CLAM
00038 {
00039 
00040 namespace AudioCodecs
00041 {
00042         const TSize OggVorbisAudioStream::mMaxBlockSize = 4096 / sizeof(TInt16); // Seems to be the 'reference' value
00043         const TSize OggVorbisAudioStream::mAnalysisWindowSize = 1024;
00044         
00045         OggVorbisAudioStream::OggVorbisAudioStream()
00046                 : mFileHandle( NULL ), mValidFileParams( false ), mEncoding( false )
00047         {
00048                 mBlockBuffer.Resize( mMaxBlockSize );
00049                 mBlockBuffer.SetSize( mMaxBlockSize );
00050 
00051         }
00052 
00053         OggVorbisAudioStream::OggVorbisAudioStream( const AudioFile& file )
00054                 : mFileHandle( NULL ), mValidFileParams( false ), mEncoding( false )
00055         {
00056                 SetFOI( file );
00057                 mBlockBuffer.Resize( mMaxBlockSize );
00058                 mBlockBuffer.SetSize( mMaxBlockSize );
00059 
00060         }
00061 
00062         OggVorbisAudioStream::~OggVorbisAudioStream()
00063         {
00064                 if ( mValidFileParams )
00065                         Dispose();
00066         }
00067 
00068         void OggVorbisAudioStream::SetFOI( const AudioFile& file )
00069         {
00070                 if ( mValidFileParams )
00071                         Dispose();
00072                 AudioFileToNative( file );
00073                 
00074         }
00075 
00076         void OggVorbisAudioStream::AudioFileToNative( const AudioFile& file )
00077         {
00078                 mName = file.GetLocation();
00079                 mEncodedSampleRate = (int)file.GetHeader().GetSampleRate();
00080                 mEncodedChannels = (int)file.GetHeader().GetChannels();
00081                 
00082                 mEncodeBuffer.resize( mEncodedChannels ); // as many stream buffers as channels
00083 
00084         }
00085 
00086 
00087         void OggVorbisAudioStream::PrepareReading()
00088         {
00089                 if ( ( mFileHandle = fopen( mName.c_str(), "rb" ) ) == NULL )
00090                 {
00091                         std::string msgString = "Could not open ";
00092                         msgString += mName;
00093                         msgString +=" for reading!";
00094                         CLAM_ASSERT( false, msgString.c_str() );
00095                 }
00096 
00097                 if ( ov_open( mFileHandle, &mNativeFileParams, NULL, 0 ) < 0 )          
00098                 {
00099                         fclose( mFileHandle );                  
00100                         std::string msgString = mName;
00101                         msgString += " is not a valid Ogg/Vorbis file!";
00102                         
00103                         CLAM_ASSERT( false, msgString.c_str() );
00104                 }
00105 
00106                 vorbis_info* info = ov_info( &mNativeFileParams, -1 );
00107                 
00108                 SetChannels( info->channels );
00109                 MarkAllChannelsAsConsumed();
00110 
00111                 mValidFileParams = true;
00112                 mCurrentSection = 0;
00113                 
00114                 // MRJ: Seen on Audacity sources. It seems that
00115                 // not all encoders respect the specs right: sometimes
00116                 // one might stumble on a file with poorly encoded headers
00117                 // having this the effect of reading several frames of zeros
00118                 // at the beginning
00119                 ov_pcm_seek( &mNativeFileParams, 0 );           
00120         }
00121 
00122         void OggVorbisAudioStream::PrepareWriting()
00123         {
00124                 if ( ( mFileHandle = fopen( mName.c_str(), "wb" ) ) == NULL )
00125                 {
00126                         std::string msgString = "Could not open ";
00127                         msgString += mName;
00128                         msgString +=" for writing!";
00129                         CLAM_ASSERT( false, msgString.c_str() );
00130                 }
00131                 
00132                 VorbisI_EncoderSetup();
00133                 MarkAllChannelsAsProduced();
00134                 mEncoding = true;
00135 
00136         }
00137 
00138         void OggVorbisAudioStream::VorbisI_EncoderSetup()
00139         {
00140 
00141                 vorbis_info_init( &mStreamInfo );
00142 
00143                 // encoding mode choosing
00144 
00145                 int retValue = vorbis_encode_init_vbr( &mStreamInfo, 
00146                                                        mEncodedChannels,
00147                                                        mEncodedSampleRate,
00148                                                        0.5 );
00149 
00150                 CLAM_ASSERT( retValue == 0, "Error trying to initialize Vorbis encoder!" );
00151 
00152                 // We add to the comment section who we are
00153                 vorbis_comment_init( &mFileComments );
00154                 vorbis_comment_add_tag( &mFileComments, "ENCODER", "CLAM" );
00155 
00156                 // analysis state and auxiliary encoding state storage setup
00157                 vorbis_analysis_init( &mDSPState, &mStreamInfo );
00158                 vorbis_block_init( &mDSPState, &mVorbisBlock );
00159                 
00160                 // packet->stream encoder setup
00161                 // pick random serial number
00162                 // :TODO: this random number thing might be really important...
00163                 ogg_stream_init( &mOggStreamState, rand() );
00164 
00165                 WriteBitstreamHeader();
00166 
00167                 SetChannels( mEncodedChannels );
00168         }
00169 
00170         void OggVorbisAudioStream::WriteBitstreamHeader()
00171         {
00172                 // Every Vorbis stream begins with 3 headers:
00173                 //   + the initial header ( with codec setup params )
00174                 //   + the header with the comment fields
00175                 //   + the header with the code books
00176                 
00177                 ogg_packet header_codec_setup;
00178                 ogg_packet header_comments;
00179                 ogg_packet header_codebooks;
00180 
00181                 // We make the headers from the current Vorbis DSP module state
00182                 // and file comments
00183                 vorbis_analysis_headerout( &mDSPState, &mFileComments, 
00184                                            &header_codec_setup,
00185                                            &header_comments,
00186                                            &header_codebooks );
00187 
00188                 // We 'push' each header one at a time into the stream
00189                 ogg_stream_packetin( &mOggStreamState, &header_codec_setup );
00190                 ogg_stream_packetin( &mOggStreamState, &header_comments );
00191                 ogg_stream_packetin( &mOggStreamState, &header_codebooks );
00192 
00193                 // Now we ensure that the audio data will begin on a new
00194                 // 'page' as the specs require
00195 
00196                 while( ogg_stream_flush( &mOggStreamState, &mOggPage ) > 0 )
00197                 {
00198                         fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
00199                         fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
00200                 }
00201 
00202 
00203         }
00204 
00205         void OggVorbisAudioStream::PrepareReadWrite()
00206         {
00207                 CLAM_ASSERT( false, "Cannot be done!" );
00208         }
00209 
00210         void OggVorbisAudioStream::Dispose()
00211         {
00212                 if ( !mEncoding )
00213                 {
00214                         ov_clear( &mNativeFileParams );
00215                         mValidFileParams = false;
00216                 }
00217                 else
00218                 {
00219                         // if there are yet samples to be processed we assure
00220                         // they are encoded
00221                         if ( !mEncodeBuffer[0].empty() )
00222                                 DoVorbisAnalysis();
00223 
00224                         // We tell the Vorbis encoder that we are 
00225                         // finished with encoding frames
00226                         vorbis_analysis_wrote( &mDSPState, 0 );
00227                         // push blocks generated by the last call
00228                         // onto the Ogg stream
00229                         PushAnalysisBlocksOntoOggStream(); 
00230                         
00231                         // Encoder cleaning up
00232                         ogg_stream_clear( &mOggStreamState );
00233                         vorbis_block_clear( &mVorbisBlock );
00234                         vorbis_dsp_clear( &mDSPState );
00235                         vorbis_comment_clear( &mFileComments );
00236                         vorbis_info_clear( &mStreamInfo );
00237 
00238                         fclose( mFileHandle );
00239                         
00240                         mEncoding = false;
00241 
00242                 }
00243         }
00244 
00245         void OggVorbisAudioStream::ConsumeDecodedSamples()
00246         {
00247                 CLAM_ASSERT( mDecodeBuffer.size() >= unsigned(mInterleavedData.Size()),
00248                              "This method cannot be called if the decode buffer"
00249                              " has less samples than requested by the upper level");
00250 
00251                 static const TData norm = 1.0 / 32768.0;
00252 
00253                 TData* pSamples = mInterleavedData.GetPtr();
00254                 const TData* pSamplesEnd = mInterleavedData.GetPtr() + mInterleavedData.Size();
00255                 for( std::deque<TInt16>::iterator i = mDecodeBuffer.begin();
00256                      pSamples < pSamplesEnd; i++, pSamples++ )
00257                         *pSamples = TData(*i)*norm;             
00258                 
00259                 mDecodeBuffer.erase( mDecodeBuffer.begin(),
00260                                      mDecodeBuffer.begin()+mInterleavedData.Size() );
00261                 
00262         }
00263 
00264         void OggVorbisAudioStream::DiskToMemoryTransfer()
00265         {
00266 
00267                 //Unused variable: TSize nBytes = 0;
00268                 unsigned samplesRead = 0;
00269 
00270                 while ( mDecodeBuffer.size() < unsigned(mInterleavedData.Size()) )
00271                 {
00272                         mLastBytesRead = ov_read( &mNativeFileParams, 
00273                                                   (char*)mBlockBuffer.GetPtr(), 
00274                                                   mMaxBlockSize*sizeof(TInt16),
00275                                                   HOST_ENDIANESS,
00276                                                   2, 1, &mCurrentSection );
00277                         
00278                         CLAM_ASSERT( mLastBytesRead >= 0, "Malformed OggVorbis file!" );
00279                         CLAM_ASSERT( mLastBytesRead % mEncodedChannels == 0, "BIG Whoops!" );
00280 
00281                         if ( mLastBytesRead == 0 ) break;
00282 
00283                         samplesRead = mLastBytesRead / sizeof(TInt16 );
00284 
00285                         
00286                         mDecodeBuffer.insert( mDecodeBuffer.end(),
00287                                               mBlockBuffer.GetPtr(),
00288                                               mBlockBuffer.GetPtr() + samplesRead);                             
00289                 }
00290 
00291                 mFramesLastRead = mDecodeBuffer.size();
00292 
00293                 if ( !mDecodeBuffer.empty() )
00294                 {
00295 
00296                         if ( mDecodeBuffer.size() < unsigned(mInterleavedData.Size()) )
00297                         {
00298                                 mDecodeBuffer.insert( mDecodeBuffer.end(),
00299                                                       mInterleavedData.Size() - mDecodeBuffer.size(),
00300                                                       0);
00301                         }
00302                         
00303                         ConsumeDecodedSamples();
00304                 }
00305 
00306                 mEOFReached = ( mLastBytesRead == 0) && (mDecodeBuffer.empty());
00307         
00308         }
00309 
00310         void OggVorbisAudioStream::MemoryToDiskTransfer()
00311         {
00312                 // Yahoo! The vorbis encoder wants the samples
00313                 // as floats!
00314                 
00315                 // We expose the buffer for submitting data to the encoder
00316 
00317                 TIndex currentOffset = 0;
00318                 int i;
00319 
00320                 do
00321                 {
00322                         for ( i = mEncodeBuffer[0].size(); 
00323                               i < mAnalysisWindowSize && currentOffset < mInterleavedDataOut.Size(); 
00324                               i++ )
00325                         {
00326                                 for ( int j = 0; j < mEncodedChannels; j++ )
00327                                         mEncodeBuffer[j].push_front( mInterleavedDataOut[ currentOffset + j ] );
00328 
00329                                 currentOffset += mEncodedChannels;
00330                         }
00331 
00332                         if ( i == mAnalysisWindowSize ) // enough samples acquired
00333                                 DoVorbisAnalysis();
00334                         
00335                 } while ( currentOffset < mInterleavedDataOut.Size() );
00336 
00337         }
00338 
00339         void OggVorbisAudioStream::PushAnalysisBlocksOntoOggStream()
00340         {
00341                 int eos = 0;
00342 
00343                 while( vorbis_analysis_blockout( &mDSPState, &mVorbisBlock ) == 1 && !eos )
00344                 {
00345                         // we assume we want bitrate management
00346 
00347                         vorbis_analysis( &mVorbisBlock, NULL );
00348                         vorbis_bitrate_addblock( &mVorbisBlock );
00349 
00350                         while( vorbis_bitrate_flushpacket( &mDSPState, &mOggPacket ) )
00351                         {
00352                                 // We push the packet into the bitstream
00353                                 ogg_stream_packetin( &mOggStreamState, &mOggPacket );                           
00354 
00355                                 // page writeout
00356 
00357                                 
00358                                 while( ogg_stream_pageout( &mOggStreamState, &mOggPage ) > 0 
00359                                         && !eos)
00360                                 {
00361                                         fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
00362                                         fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
00363                                         
00364                                         eos = ( ogg_page_eos( &mOggPage ) )? 1 : 0;
00365 
00366                                 }
00367                         
00368                         }
00369                 }
00370 
00371         }
00372 
00373         void OggVorbisAudioStream::DoVorbisAnalysis()
00374         {
00375 
00376                 float** encBuffer = vorbis_analysis_buffer( &mDSPState, 
00377                                                             mAnalysisWindowSize);
00378                 
00379                 //Unused variable: int samplesWrote = 0;
00380 
00381                 for ( int j = 0; j < mEncodedChannels; j++ )
00382                 {
00383                         int i = 0;
00384 
00385                         while( !mEncodeBuffer[j].empty() )
00386                         {
00387 
00388                                 encBuffer[j][i] = mEncodeBuffer[j].back();
00389                                 mEncodeBuffer[j].pop_back();                            
00390                                 i++;
00391                         }
00392 
00393                         // Zero padding
00394                         while( i < mAnalysisWindowSize )
00395                         {
00396                                 encBuffer[j][i] = 0.0;
00397                                 i++;
00398                         }
00399 
00400                 }
00401 
00402                 vorbis_analysis_wrote( &mDSPState, mAnalysisWindowSize );
00403                 
00404                 PushAnalysisBlocksOntoOggStream();
00405         
00406 
00407         }
00408 }       
00409 
00410 }
00411 

Generated on Tue Aug 12 22:33:43 2008 for CLAM by  doxygen 1.5.5