CLAM-Development  1.4.0
OggVorbisAudioStream.cxx
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2004 MUSIC TECHNOLOGY GROUP (MTG)
3  * UNIVERSITAT POMPEU FABRA
4  *
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19  *
20  */
21 
22 #include "OggVorbisAudioStream.hxx"
23 #include "AudioFile.hxx"
24 #include <cstdio>
25 #include <ctime>
26 #include <cstdlib>
27 #include <vorbis/codec.h>
28 #include <iostream>
29 #include <algorithm>
30 
31 #if defined ( __powerpc__ ) || defined ( __POWERPC__ )
32 #define HOST_ENDIANESS 1
33 #else
34 #define HOST_ENDIANESS 0
35 #endif
36 
37 namespace CLAM
38 {
39 
40 namespace AudioCodecs
41 {
42  const unsigned OggVorbisAudioStream::mMaxBlockSize = 4096 / sizeof(TInt16); // Seems to be the 'reference' value
43  const unsigned OggVorbisAudioStream::mAnalysisWindowSize = 1024;
44 
46  : mFileHandle( NULL )
47  , mEncoding( false )
48  {
49  mName = file.GetLocation();
50  mEncodedSampleRate = (int)file.GetHeader().GetSampleRate();
51  mChannels = (int)file.GetHeader().GetChannels();
52  mEncodeBuffer.resize( mChannels ); // as many stream buffers as channels
53  mBlockBuffer.resize( mMaxBlockSize );
54  }
55 
57  {
58  Dispose();
59  }
60 
62  {
63  mFileHandle = fopen(mName.c_str(), "rb");
64  if (mFileHandle == NULL)
65  {
66  std::string msgString = "Could not open ";
67  msgString += mName;
68  msgString +=" for reading!";
69  CLAM_ASSERT( false, msgString.c_str() );
70  }
71  int error = ov_open(mFileHandle, &mNativeFileParams, NULL, 0);
72  if ( error < 0 )
73  {
74  fclose( mFileHandle );
76  std::string msgString = mName;
77  msgString += " is not a valid Ogg/Vorbis file!";
78  CLAM_ASSERT( false, msgString.c_str() );
79  }
80 
81  vorbis_info* info = ov_info(&mNativeFileParams, -1);
82  CLAM_ASSERT(mChannels==unsigned(info->channels),
83  "OggVorbisAudioStream: channels info changed before opening");
84 
85  mCurrentSection = 0;
86  mFramePosition = 0;
87 
88  // MRJ: Seen on Audacity sources. It seems that
89  // not all encoders respect the specs right: sometimes
90  // one might stumble on a file with poorly encoded headers
91  // having this the effect of reading several frames of zeros
92  // at the beginning
93  ov_pcm_seek( &mNativeFileParams, 0 );
94  }
95 
97  {
98  mFileHandle = fopen(mName.c_str(), "wb");
99  if ( mFileHandle==NULL )
100  {
101  std::string msgString = "Could not open ";
102  msgString += mName;
103  msgString +=" for writing!";
104  CLAM_ASSERT( false, msgString.c_str() );
105  }
106  VorbisI_EncoderSetup();
107  mEncoding = true;
108  }
109 
110  void OggVorbisAudioStream::VorbisI_EncoderSetup()
111  {
112 
113  vorbis_info_init( &mStreamInfo );
114  // encoding mode choosing
115  int retValue = vorbis_encode_init_vbr(
117 
118  CLAM_ASSERT( retValue == 0, "Error trying to initialize Vorbis encoder!" );
119 
120  // We add to the comment section who we are
121  vorbis_comment_init( &mFileComments );
122  vorbis_comment_add_tag( &mFileComments, "ENCODER", "CLAM" );
123 
124  // analysis state and auxiliary encoding state storage setup
125  vorbis_analysis_init( &mDSPState, &mStreamInfo );
126  vorbis_block_init( &mDSPState, &mVorbisBlock );
127 
128  // packet->stream encoder setup
129  // pick random serial number
130  // :TODO: this random number thing might be really important...
131  ogg_stream_init( &mOggStreamState, rand() );
132 
133  WriteBitstreamHeader();
134  }
135 
136  void OggVorbisAudioStream::WriteBitstreamHeader()
137  {
138  // Every Vorbis stream begins with 3 headers:
139  // + the initial header ( with codec setup params )
140  // + the header with the comment fields
141  // + the header with the code books
142 
143  ogg_packet header_codec_setup;
144  ogg_packet header_comments;
145  ogg_packet header_codebooks;
146 
147  // We make the headers from the current Vorbis DSP module state
148  // and file comments
149  vorbis_analysis_headerout( &mDSPState, &mFileComments,
150  &header_codec_setup,
151  &header_comments,
152  &header_codebooks );
153 
154  // We 'push' each header one at a time into the stream
155  ogg_stream_packetin( &mOggStreamState, &header_codec_setup );
156  ogg_stream_packetin( &mOggStreamState, &header_comments );
157  ogg_stream_packetin( &mOggStreamState, &header_codebooks );
158 
159  // Now we ensure that the audio data will begin on a new
160  // 'page' as the specs require
161 
162  while( ogg_stream_flush( &mOggStreamState, &mOggPage ) > 0 )
163  {
164  fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
165  fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
166  }
167  }
168 
170  {
171  if ( mFileHandle == NULL) return;
172  if ( not mEncoding )
173  {
174  ov_clear( &mNativeFileParams );
175  mFileHandle = NULL;
176  return;
177  }
178  // Encoding dispose is more complex
179  // if there are yet samples to be processed we assure
180  // they are encoded
181  if ( !mEncodeBuffer[0].empty() )
182  DoVorbisAnalysis();
183 
184  // We tell the Vorbis encoder that we are
185  // finished with encoding frames
186  vorbis_analysis_wrote( &mDSPState, 0 );
187  // push blocks generated by the last call
188  // onto the Ogg stream
189  PushAnalysisBlocksOntoOggStream();
190 
191  // Encoder cleaning up
192  ogg_stream_clear( &mOggStreamState );
193  vorbis_block_clear( &mVorbisBlock );
194  vorbis_dsp_clear( &mDSPState );
195  vorbis_comment_clear( &mFileComments );
196  vorbis_info_clear( &mStreamInfo );
197 
198  fclose( mFileHandle );
199  mFileHandle = NULL;
200  mEncoding = false;
201  }
202 
203  void OggVorbisAudioStream::ConsumeDecodedSamples()
204  {
205  unsigned nItems = mInterleavedData.size();
206  TData* pSamples = &mInterleavedData[0];
207 
208  CLAM_ASSERT( mDecodeBuffer.size() >= nItems,
209  "This method cannot be called if the decode buffer"
210  " has less samples than requested by the upper level");
211 
212  static const TData norm = 1.0 / 32768.0;
213 
214  const TData* pSamplesEnd = pSamples + nItems;
215  typedef std::deque<TInt16> sampleDeque;
216  for( sampleDeque::iterator i = mDecodeBuffer.begin(); pSamples < pSamplesEnd; i++)
217  *pSamples++ = TData(*i)*norm;
218 
219  mDecodeBuffer.erase(
220  mDecodeBuffer.begin(),
221  mDecodeBuffer.begin()+nItems );
222 
223  unsigned nFrames = nItems / mChannels;
224  mFramePosition+=nFrames;
225  }
226 
228  {
229  //Unused variable: TSize nBytes = 0;
230  unsigned samplesRead = 0;
231 
232  while (mDecodeBuffer.size() < mInterleavedData.size())
233  {
234  mLastBytesRead = ov_read(
236  (char*)&mBlockBuffer[0],
237  mMaxBlockSize*sizeof(TInt16),
239  2, 1, &mCurrentSection );
240 
241  CLAM_ASSERT( mLastBytesRead >= 0, "Malformed OggVorbis file!" );
242  CLAM_ASSERT( mLastBytesRead % mChannels == 0, "BIG Whoops!" );
243 
244  if ( mLastBytesRead == 0 ) break;
245 
246  samplesRead = mLastBytesRead / sizeof(TInt16 );
247 
248  mDecodeBuffer.insert(
249  mDecodeBuffer.end(),
250  mBlockBuffer.begin(),
251  mBlockBuffer.begin() + samplesRead);
252  }
253 
255  mEOFReached = ( mLastBytesRead == 0) && (mDecodeBuffer.empty());
256 
257  if (mDecodeBuffer.empty()) return;
258 
259  if (mDecodeBuffer.size() < mInterleavedData.size())
260  {
261  mDecodeBuffer.insert(
262  mDecodeBuffer.end(),
263  mInterleavedData.size() - mDecodeBuffer.size(),
264  0);
265  }
266  ConsumeDecodedSamples();
267  }
268 
270  {
271  // Yahoo! The vorbis encoder wants the samples
272  // as floats!
273 
274  // We expose the buffer for submitting data to the encoder
275 
276  unsigned currentOffset = 0;
277  unsigned i;
278  do
279  {
280  for ( i = mEncodeBuffer[0].size();
281  i < mAnalysisWindowSize && currentOffset < mInterleavedData.size();
282  i++ )
283  {
284  for (unsigned j=0; j<mChannels; j++)
285  mEncodeBuffer[j].push_front( mInterleavedData[ currentOffset + j ] );
286 
287  currentOffset += mChannels;
288  }
289 
290  if ( i == mAnalysisWindowSize ) // enough samples acquired
291  DoVorbisAnalysis();
292 
293  } while ( currentOffset < mInterleavedData.size() );
294 
295  }
296 
297  void OggVorbisAudioStream::PushAnalysisBlocksOntoOggStream()
298  {
299  int eos = 0;
300  while( vorbis_analysis_blockout( &mDSPState, &mVorbisBlock ) == 1 && !eos )
301  {
302  // we assume we want bitrate management
303  vorbis_analysis( &mVorbisBlock, NULL );
304  vorbis_bitrate_addblock( &mVorbisBlock );
305 
306  while( vorbis_bitrate_flushpacket( &mDSPState, &mOggPacket ) )
307  {
308  // We push the packet into the bitstream
309  ogg_stream_packetin( &mOggStreamState, &mOggPacket );
310 
311  // page writeout
312  while( ogg_stream_pageout( &mOggStreamState, &mOggPage ) > 0
313  && !eos)
314  {
315  fwrite( mOggPage.header, 1, mOggPage.header_len, mFileHandle );
316  fwrite( mOggPage.body, 1, mOggPage.body_len, mFileHandle );
317  eos = ( ogg_page_eos( &mOggPage ) )? 1 : 0;
318  }
319  }
320  }
321  }
322 
323  void OggVorbisAudioStream::DoVorbisAnalysis()
324  {
325  float** encBuffer = vorbis_analysis_buffer(
327 
328  for (unsigned j = 0; j < mChannels; j++ )
329  {
330  unsigned i = 0;
331  while( !mEncodeBuffer[j].empty() )
332  {
333  encBuffer[j][i] = mEncodeBuffer[j].back();
334  mEncodeBuffer[j].pop_back();
335  i++;
336  }
337 
338  // Zero padding
339  while( i < mAnalysisWindowSize )
340  {
341  encBuffer[j][i] = 0.0;
342  i++;
343  }
344  }
345  vorbis_analysis_wrote( &mDSPState, mAnalysisWindowSize );
346  PushAnalysisBlocksOntoOggStream();
347  }
348  void OggVorbisAudioStream::SeekTo(unsigned long framePosition)
349  {
350  ov_pcm_seek( &mNativeFileParams, framePosition );
351  mFramePosition = ov_pcm_tell(&mNativeFileParams);
352  }
353 }
354 
355 }
356