|
CLAM-Development
1.3
|
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
1.7.6.1