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 unsigned OggVorbisAudioStream::mMaxBlockSize = 4096 / sizeof(TInt16); // Seems to be the 'reference' value
00043         const unsigned OggVorbisAudioStream::mAnalysisWindowSize = 1024;
00044         
00045         OggVorbisAudioStream::OggVorbisAudioStream( const AudioFile& file )
00046                 : mFileHandle( NULL )
00047                 , mEncoding( false )
00048         {
00049                 mName = file.GetLocation();
00050                 mEncodedSampleRate = (int)file.GetHeader().GetSampleRate();
00051                 mChannels = (int)file.GetHeader().GetChannels();
00052                 mEncodeBuffer.resize( mChannels ); // as many stream buffers as channels
00053                 mBlockBuffer.resize( mMaxBlockSize );
00054         }
00055 
00056         OggVorbisAudioStream::~OggVorbisAudioStream()
00057         {
00058                 Dispose();
00059         }
00060 
00061         void OggVorbisAudioStream::PrepareReading()
00062         {
00063                 mFileHandle = fopen(mName.c_str(), "rb");
00064                 if (mFileHandle == NULL)
00065                 {
00066                         std::string msgString = "Could not open ";
00067                         msgString += mName;
00068                         msgString +=" for reading!";
00069                         CLAM_ASSERT( false, msgString.c_str() );
00070                 }
00071                 int error = ov_open(mFileHandle, &mNativeFileParams, NULL, 0);
00072                 if ( error < 0 )
00073                 {
00074                         fclose( mFileHandle );
00075                         mFileHandle=NULL;
00076                         std::string msgString = mName;
00077                         msgString += " is not a valid Ogg/Vorbis file!";
00078                         CLAM_ASSERT( false, msgString.c_str() );
00079                 }
00080 
00081                 vorbis_info* info = ov_info(&mNativeFileParams, -1);
00082                 CLAM_ASSERT(mChannels==unsigned(info->channels),
00083                         "OggVorbisAudioStream: channels info changed before opening");
00084 
00085                 mCurrentSection = 0;
00086                 mFramePosition = 0;
00087                 
00088                 // MRJ: Seen on Audacity sources. It seems that
00089                 // not all encoders respect the specs right: sometimes
00090                 // one might stumble on a file with poorly encoded headers
00091                 // having this the effect of reading several frames of zeros
00092                 // at the beginning
00093                 ov_pcm_seek( &mNativeFileParams, 0 );
00094         }
00095 
00096         void OggVorbisAudioStream::PrepareWriting()
00097         {
00098                 mFileHandle = fopen(mName.c_str(), "wb");
00099                 if ( mFileHandle==NULL )
00100                 {
00101                         std::string msgString = "Could not open ";
00102                         msgString += mName;
00103                         msgString +=" for writing!";
00104                         CLAM_ASSERT( false, msgString.c_str() );
00105                 }
00106                 VorbisI_EncoderSetup();
00107                 mEncoding = true;
00108         }
00109 
00110         void OggVorbisAudioStream::VorbisI_EncoderSetup()
00111         {
00112 
00113                 vorbis_info_init( &mStreamInfo );
00114                 // encoding mode choosing
00115                 int retValue = vorbis_encode_init_vbr(
00116                         &mStreamInfo, mChannels, mEncodedSampleRate, 0.5 );
00117 
00118                 CLAM_ASSERT( retValue == 0, "Error trying to initialize Vorbis encoder!" );
00119 
00120                 // We add to the comment section who we are
00121                 vorbis_comment_init( &mFileComments );
00122                 vorbis_comment_add_tag( &mFileComments, "ENCODER", "CLAM" );
00123 
00124                 // analysis state and auxiliary encoding state storage setup
00125                 vorbis_analysis_init( &mDSPState, &mStreamInfo );
00126                 vorbis_block_init( &mDSPState, &mVorbisBlock );
00127                 
00128                 // packet->stream encoder setup
00129                 // pick random serial number
00130                 // :TODO: this random number thing might be really important...
00131                 ogg_stream_init( &mOggStreamState, rand() );
00132 
00133                 WriteBitstreamHeader();
00134         }
00135 
00136         void OggVorbisAudioStream::WriteBitstreamHeader()
00137         {
00138                 // Every Vorbis stream begins with 3 headers:
00139                 //   + the initial header ( with codec setup params )
00140                 //   + the header with the comment fields
00141                 //   + the header with the code books
00142                 
00143                 ogg_packet header_codec_setup;
00144                 ogg_packet header_comments;
00145                 ogg_packet header_codebooks;
00146 
00147                 // We make the headers from the current Vorbis DSP module state
00148                 // and file comments
00149                 vorbis_analysis_headerout( &mDSPState, &mFileComments, 
00150                                            &header_codec_setup,
00151                                            &header_comments,
00152                                            &header_codebooks );
00153 
00154                 // We 'push' each header one at a time into the stream
00155                 ogg_stream_packetin( &mOggStreamState, &header_codec_setup );
00156                 ogg_stream_packetin( &mOggStreamState, &header_comments );
00157                 ogg_stream_packetin( &mOggStreamState, &header_codebooks );
00158 
00159                 // Now we ensure that the audio data will begin on a new
00160                 // 'page' as the specs require
00161 
00162                 while( ogg_stream_flush( &mOggStreamState, &mOggPage ) > 0 )
00163                 {
00164                         fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
00165                         fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
00166                 }
00167         }
00168 
00169         void OggVorbisAudioStream::Dispose()
00170         {
00171                 if ( mFileHandle == NULL) return;
00172                 if ( not mEncoding )
00173                 {
00174                         ov_clear( &mNativeFileParams );
00175                         mFileHandle = NULL;
00176                         return;
00177                 }
00178                 // Encoding dispose is more complex
00179                 // if there are yet samples to be processed we assure
00180                 // they are encoded
00181                 if ( !mEncodeBuffer[0].empty() )
00182                         DoVorbisAnalysis();
00183 
00184                 // We tell the Vorbis encoder that we are 
00185                 // finished with encoding frames
00186                 vorbis_analysis_wrote( &mDSPState, 0 );
00187                 // push blocks generated by the last call
00188                 // onto the Ogg stream
00189                 PushAnalysisBlocksOntoOggStream(); 
00190                 
00191                 // Encoder cleaning up
00192                 ogg_stream_clear( &mOggStreamState );
00193                 vorbis_block_clear( &mVorbisBlock );
00194                 vorbis_dsp_clear( &mDSPState );
00195                 vorbis_comment_clear( &mFileComments );
00196                 vorbis_info_clear( &mStreamInfo );
00197 
00198                 fclose( mFileHandle );
00199                 mFileHandle = NULL;
00200                 mEncoding = false;
00201         }
00202 
00203         void OggVorbisAudioStream::ConsumeDecodedSamples()
00204         {
00205                 unsigned nItems = mInterleavedData.size();
00206                 TData* pSamples = &mInterleavedData[0];
00207 
00208                 CLAM_ASSERT( mDecodeBuffer.size() >= nItems,
00209                              "This method cannot be called if the decode buffer"
00210                              " has less samples than requested by the upper level");
00211 
00212                 static const TData norm = 1.0 / 32768.0;
00213 
00214                 const TData* pSamplesEnd = pSamples + nItems;
00215                 typedef std::deque<TInt16> sampleDeque;
00216                 for( sampleDeque::iterator i = mDecodeBuffer.begin(); pSamples < pSamplesEnd; i++)
00217                         *pSamples++ = TData(*i)*norm;
00218 
00219                 mDecodeBuffer.erase(
00220                         mDecodeBuffer.begin(),
00221                         mDecodeBuffer.begin()+nItems );
00222 
00223                 unsigned nFrames = nItems / mChannels;
00224                 mFramePosition+=nFrames;
00225         }
00226 
00227         void OggVorbisAudioStream::DiskToMemoryTransfer()
00228         {
00229                 //Unused variable: TSize nBytes = 0;
00230                 unsigned samplesRead = 0;
00231 
00232                 while (mDecodeBuffer.size() < mInterleavedData.size())
00233                 {
00234                         mLastBytesRead = ov_read(
00235                                 &mNativeFileParams,
00236                                 (char*)&mBlockBuffer[0], 
00237                                 mMaxBlockSize*sizeof(TInt16),
00238                                 HOST_ENDIANESS,
00239                                 2, 1, &mCurrentSection );
00240                         
00241                         CLAM_ASSERT( mLastBytesRead >= 0, "Malformed OggVorbis file!" );
00242                         CLAM_ASSERT( mLastBytesRead % mChannels == 0, "BIG Whoops!" );
00243 
00244                         if ( mLastBytesRead == 0 ) break;
00245 
00246                         samplesRead = mLastBytesRead / sizeof(TInt16 );
00247 
00248                         mDecodeBuffer.insert(
00249                                 mDecodeBuffer.end(),
00250                                 mBlockBuffer.begin(),
00251                                 mBlockBuffer.begin() + samplesRead);
00252                 }
00253 
00254                 mFramesLastRead = mDecodeBuffer.size();
00255                 mEOFReached = ( mLastBytesRead == 0) && (mDecodeBuffer.empty());
00256 
00257                 if (mDecodeBuffer.empty()) return;
00258 
00259                 if (mDecodeBuffer.size() < mInterleavedData.size())
00260                 {
00261                         mDecodeBuffer.insert(
00262                                 mDecodeBuffer.end(),
00263                                 mInterleavedData.size() - mDecodeBuffer.size(),
00264                                 0);
00265                 }
00266                 ConsumeDecodedSamples();
00267         }
00268 
00269         void OggVorbisAudioStream::MemoryToDiskTransfer()
00270         {
00271                 // Yahoo! The vorbis encoder wants the samples
00272                 // as floats!
00273                 
00274                 // We expose the buffer for submitting data to the encoder
00275 
00276                 unsigned currentOffset = 0;
00277                 unsigned i;
00278                 do
00279                 {
00280                         for ( i = mEncodeBuffer[0].size(); 
00281                               i < mAnalysisWindowSize && currentOffset < mInterleavedData.size(); 
00282                               i++ )
00283                         {
00284                                 for (unsigned j=0; j<mChannels; j++)
00285                                         mEncodeBuffer[j].push_front( mInterleavedData[ currentOffset + j ] );
00286 
00287                                 currentOffset += mChannels;
00288                         }
00289 
00290                         if ( i == mAnalysisWindowSize ) // enough samples acquired
00291                                 DoVorbisAnalysis();
00292 
00293                 } while ( currentOffset < mInterleavedData.size() );
00294 
00295         }
00296 
00297         void OggVorbisAudioStream::PushAnalysisBlocksOntoOggStream()
00298         {
00299                 int eos = 0;
00300                 while( vorbis_analysis_blockout( &mDSPState, &mVorbisBlock ) == 1 && !eos )
00301                 {
00302                         // we assume we want bitrate management
00303                         vorbis_analysis( &mVorbisBlock, NULL );
00304                         vorbis_bitrate_addblock( &mVorbisBlock );
00305 
00306                         while( vorbis_bitrate_flushpacket( &mDSPState, &mOggPacket ) )
00307                         {
00308                                 // We push the packet into the bitstream
00309                                 ogg_stream_packetin( &mOggStreamState, &mOggPacket );
00310 
00311                                 // page writeout
00312                                 while( ogg_stream_pageout( &mOggStreamState, &mOggPage ) > 0 
00313                                         && !eos)
00314                                 {
00315                                         fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
00316                                         fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
00317                                         eos = ( ogg_page_eos( &mOggPage ) )? 1 : 0;
00318                                 }
00319                         }
00320                 }
00321         }
00322 
00323         void OggVorbisAudioStream::DoVorbisAnalysis()
00324         {
00325                 float** encBuffer = vorbis_analysis_buffer(
00326                         &mDSPState, mAnalysisWindowSize);
00327                 
00328                 for (unsigned j = 0; j < mChannels; j++ )
00329                 {
00330                         unsigned i = 0;
00331                         while( !mEncodeBuffer[j].empty() )
00332                         {
00333                                 encBuffer[j][i] = mEncodeBuffer[j].back();
00334                                 mEncodeBuffer[j].pop_back();                            
00335                                 i++;
00336                         }
00337 
00338                         // Zero padding
00339                         while( i < mAnalysisWindowSize )
00340                         {
00341                                 encBuffer[j][i] = 0.0;
00342                                 i++;
00343                         }
00344                 }
00345                 vorbis_analysis_wrote( &mDSPState, mAnalysisWindowSize );
00346                 PushAnalysisBlocksOntoOggStream();
00347         }
00348         void OggVorbisAudioStream::SeekTo(unsigned long framePosition)
00349         {
00350                 ov_pcm_seek( &mNativeFileParams, framePosition );
00351                 mFramePosition = ov_pcm_tell(&mNativeFileParams);
00352         }
00353 }       
00354 
00355 }
00356 
Generated by  doxygen 1.6.3