Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | File List | Namespace Members | Class Members | File Members | Related Pages

wvoggvorbis.cc

Go to the documentation of this file.
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  *
00005  * Provides a WvEncoder abstraction for Ogg vorbis files.
00006  */
00007 #include "wvoggvorbis.h"
00008 #include <ogg/ogg.h>
00009 #include <vorbis/codec.h>
00010 #include <vorbis/vorbisenc.h>
00011 #include <unistd.h>
00012 
00013 #define OGG_VORBIS_ENCODER_BUF_SIZE 1024  // 1k samples for Vorbis
00014 #define OGG_VORBIS_DECODER_BUF_SIZE 16384 // at least 8k for Vorbis
00015 
00016 /***** WvOggVorbisEncoder *****/
00017 
00018 WvOggVorbisEncoder::WvOggVorbisEncoder(
00019     const BitrateSpec &bitratespec,
00020     int samplingrate, int channels, long serialno) :
00021     oggstream(NULL), ovinfo(NULL), ovcomment(NULL),
00022     ovdsp(NULL), ovblock(NULL), wrote_header(false)
00023 {
00024     assert(channels == 1 || ! "stereo not supported yet");
00025 
00026     // pick a serial number
00027     if (serialno == RANDOM_SERIALNO)
00028     {
00029         serialno = rand();
00030     }
00031 
00032     // init ogg bitstream layer
00033     int retval;
00034     oggstream = new ogg_stream_state;
00035     if ((retval = ogg_stream_init(oggstream, serialno)) != 0)
00036     {
00037         seterror("error %s during ogg_stream_init", retval);
00038         return;
00039     }
00040     
00041     // init vorbis codec layer
00042     ovinfo = new vorbis_info;
00043     vorbis_info_init(ovinfo);
00044     
00045     ovcomment = new vorbis_comment;
00046     vorbis_comment_init(ovcomment);
00047 
00048     // init vorbis bitrate management
00049     switch (bitratespec.mode)
00050     {
00051         case BitrateSpec::VBR_QUALITY:
00052             if ((retval = vorbis_encode_init_vbr(ovinfo, channels,
00053                 samplingrate, bitratespec.quality_index)) < 0)
00054             {
00055                 seterror("error %s during vorbis_encode_init_vbr", retval);
00056                 return;
00057             }
00058             break;
00059         
00060         case BitrateSpec::VBR_BITRATE:
00061             if ((retval = vorbis_encode_init(ovinfo, channels, samplingrate,
00062                 bitratespec.max_bitrate, bitratespec.nominal_bitrate,
00063                 bitratespec.min_bitrate)) < 0)
00064             {
00065                 seterror("error %s during vorbis_encode_init", retval);
00066                 return;
00067             }
00068             break;
00069     }
00070 
00071     // init vorbis dsp layer
00072     ovdsp = new vorbis_dsp_state;
00073     if ((retval = vorbis_analysis_init(ovdsp, ovinfo)) != 0)
00074     {
00075         seterror("error %s during vorbis_analysis_init", retval);
00076         return;
00077     }
00078     ovblock = new vorbis_block;
00079     if ((retval = vorbis_block_init(ovdsp, ovblock)) != 0)
00080     {
00081         seterror("error %s during vorbis_block_init", retval);
00082         return;
00083     }
00084 }
00085 
00086 
00087 WvOggVorbisEncoder::~WvOggVorbisEncoder()
00088 {
00089     // destroy vorbis dsp layer
00090     if (ovblock)
00091     {
00092         vorbis_block_clear(ovblock);
00093         delete ovblock;
00094     }
00095     if (ovdsp)
00096     {
00097         vorbis_dsp_clear(ovdsp);
00098         delete ovdsp;
00099     }
00100 
00101     // destroy vorbis codec layer
00102     if (ovcomment)
00103     {
00104         vorbis_comment_clear(ovcomment);
00105         delete ovcomment;
00106     }
00107     if (ovinfo)
00108     {
00109         vorbis_info_clear(ovinfo);
00110         delete ovinfo;
00111     }
00112 
00113     // destroy ogg bitstream layer
00114     if (oggstream)
00115     {
00116         ogg_stream_clear(oggstream);
00117         delete oggstream;
00118     }
00119 }
00120 
00121 void WvOggVorbisEncoder::add_comment(WvStringParm comment)
00122 {
00123     if (! comment) return;
00124     // Ogg Vorbis missing const qualifier in function prototype!
00125     char *str = const_cast<char *>(comment.cstr());
00126     vorbis_comment_add(ovcomment, str);
00127 }
00128 
00129 
00130 void WvOggVorbisEncoder::add_tag(WvStringParm tag, WvStringParm value)
00131 {
00132     if (! tag || ! value) return;
00133     // Ogg Vorbis missing const qualifier in function prototype!
00134     char *tagstr = const_cast<char *>(tag.cstr());
00135     char *valuestr = const_cast<char *>(value.cstr());
00136     vorbis_comment_add_tag(ovcomment, tagstr, valuestr);
00137 }
00138 
00139 
00140 bool WvOggVorbisEncoder::_typedencode(IBuffer &inbuf, OBuffer &outbuf,
00141     bool flush)
00142 {
00143     // write header pages if needed
00144     if (! wrote_header)
00145     {
00146         if (! write_header(outbuf))
00147             return false;
00148         wrote_header = true;
00149     }
00150 
00151     // write compressed audio pages
00152     for (;;)
00153     {
00154         // read in more data
00155         size_t ovsamples = inbuf.used();
00156         if (ovsamples == 0)
00157         {
00158             // no more data
00159             if (flush)
00160                 if (! write_stream(outbuf, true))
00161                     return false;
00162             return true;
00163         }
00164         if (ovsamples > OGG_VORBIS_ENCODER_BUF_SIZE)
00165             ovsamples = OGG_VORBIS_ENCODER_BUF_SIZE;
00166                 
00167         float **ovbuf = vorbis_analysis_buffer(ovdsp, ovsamples);
00168         if (ovbuf == NULL)
00169         {
00170             seterror("error allocating vorbis analysis buffer");
00171             return false;
00172         }
00173         inbuf.move(ovbuf[0], ovsamples);
00174         vorbis_analysis_wrote(ovdsp, ovsamples);
00175         process_audio(outbuf);
00176     }
00177 }
00178 
00179 
00180 bool WvOggVorbisEncoder::_typedfinish(OBuffer &outbuf)
00181 {
00182     // write header pages if needed
00183     if (! wrote_header)
00184     {
00185         if (! write_header(outbuf))
00186             return false;
00187         wrote_header = true;
00188     }
00189 
00190     // write EOF mark
00191     vorbis_analysis_wrote(ovdsp, 0);
00192     process_audio(outbuf);
00193     return true;
00194 }
00195 
00196 
00197 bool WvOggVorbisEncoder::write_header(OBuffer &outbuf)
00198 {
00199     // generate headers
00200     ogg_packet headers[3];
00201     int retval;
00202     if ((retval = vorbis_analysis_headerout(ovdsp, ovcomment,
00203         & headers[0], & headers[1], & headers[2])) != 0)
00204     {
00205         seterror("error %s during vorbis_analysis_headerout", retval);
00206         return false;
00207     }
00208         
00209     // push headers to ogg stream
00210     for (int i = 0; i < 3; ++i)
00211         ogg_stream_packetin(oggstream, & headers[i]); // always succeeds
00212         
00213     // flush to ensure next data packet is in its own page
00214     return write_stream(outbuf, true /*flush*/);
00215 }
00216 
00217 
00218 bool WvOggVorbisEncoder::write_stream(OBuffer &outbuf, bool flush)
00219 {
00220     ogg_page oggpage;
00221     for (;;)
00222     {
00223         if (flush)
00224         {
00225             int retval = ogg_stream_flush(oggstream, & oggpage);
00226             if (retval == 0)
00227                 break; // no remaining data
00228             else if (retval < 0)
00229             {
00230                 seterror("error %s during ogg_stream_flush", retval);
00231                 return false;
00232             }
00233         }
00234         else
00235         {
00236             int retval = ogg_stream_pageout(oggstream, & oggpage);
00237             if (retval == 0)
00238                 break; // not enough data
00239             else if (retval < 0)
00240             {
00241                 seterror("error %s during ogg_stream_pageout", retval);
00242                 return false;
00243             }
00244         }
00245         outbuf.put(oggpage.header, oggpage.header_len);
00246         outbuf.put(oggpage.body, oggpage.body_len);
00247     }
00248     return true;
00249 }
00250 
00251 
00252 bool WvOggVorbisEncoder::process_audio(OBuffer &outbuf)
00253 {
00254     while (vorbis_analysis_blockout(ovdsp, ovblock) == 1)
00255     {
00256         // we got a block!
00257         int retval = vorbis_analysis(ovblock, NULL);
00258         if (retval < 0)
00259         {
00260             seterror("error %s during vorbis_analysis", retval);
00261             return false;
00262         }
00263         retval = vorbis_bitrate_addblock(ovblock);
00264         if (retval < 0)
00265         {
00266             seterror("error %s during vorbis_bitrate_addblock", retval);
00267             return false;
00268         }
00269     
00270         // write out packets
00271         ogg_packet oggpacket;
00272         while (vorbis_bitrate_flushpacket(ovdsp, & oggpacket) > 0)
00273         {
00274             ogg_stream_packetin(oggstream, & oggpacket); // always succeeds
00275             if (! write_stream(outbuf, false))
00276                 return false;
00277         }
00278     }
00279     return true;
00280 }
00281 
00282 
00283 
00284 /***** WvOggVorbisDecoder *****/
00285 
00286 WvOggVorbisDecoder::WvOggVorbisDecoder() :
00287     oggsync(NULL), oggstream(NULL), ovinfo(NULL), ovcomment(NULL),
00288     ovdsp(NULL), ovblock(NULL), need_serialno(true), need_headers(3)
00289 {
00290     int retval;
00291     
00292     // init ogg sync layer
00293     oggsync = new ogg_sync_state;
00294     if ((retval = ogg_sync_init(oggsync)) != 0)
00295     {
00296         seterror("error %s during ogg_sync_init", retval);
00297         return;
00298     }
00299     oggpage = new ogg_page;
00300 }
00301 
00302 
00303 WvOggVorbisDecoder::~WvOggVorbisDecoder()
00304 {
00305     // destroy vorbis dsp layer
00306     if (ovblock)
00307     {
00308         vorbis_block_clear(ovblock);
00309         delete ovblock;
00310     }
00311     if (ovdsp)
00312     {
00313         vorbis_dsp_clear(ovdsp);
00314         delete ovdsp;
00315     }
00316     
00317     // destroy vorbis codec layer
00318     if (ovcomment)
00319     {
00320         vorbis_comment_clear(ovcomment);
00321         delete ovcomment;
00322     }
00323     if (ovinfo)
00324     {
00325         vorbis_info_clear(ovinfo);
00326         delete ovinfo;
00327     }
00328 
00329     // destroy ogg bitstream layer
00330     if (oggstream)
00331     {
00332         ogg_stream_clear(oggstream);
00333         delete oggstream;
00334     }
00335     
00336     // destroy ogg sync layer
00337     delete oggpage;
00338     ogg_sync_clear(oggsync);
00339     delete oggsync;
00340 }
00341 
00342 
00343 bool WvOggVorbisDecoder::isheaderok() const
00344 {
00345     return need_headers == 0;
00346 }
00347 
00348 
00349 bool WvOggVorbisDecoder::_typedencode(IBuffer &inbuf, OBuffer &outbuf,
00350     bool flush)
00351 {
00352     bool checkheaderok = ! isheaderok() && ! flush;
00353     for (;;)
00354     {
00355         if (oggstream)
00356         {
00357             // extract packets from the bitstream
00358             ogg_packet oggpacket;
00359             while (ogg_stream_packetout(oggstream, & oggpacket) > 0)
00360             {
00361                 if (! process_packet(& oggpacket, outbuf))
00362                     return false;
00363             }
00364             
00365             // detect end of stream
00366             if (oggstream->e_o_s)
00367             {
00368                 setfinished();
00369                 return true;
00370             }
00371         }
00372 
00373         // get more pages
00374         while (ogg_sync_pageseek(oggsync, oggpage) <= 0)
00375         {
00376             // read in more data
00377             size_t oggbufsize = inbuf.used();
00378             if (oggbufsize == 0)
00379             {
00380                 // no more data
00381                 if (flush && oggsync->fill != 0)
00382                     return false;
00383                 return true;
00384             }
00385             if (oggbufsize > OGG_VORBIS_DECODER_BUF_SIZE)
00386                 oggbufsize = OGG_VORBIS_DECODER_BUF_SIZE;
00387                 
00388             char *oggbuf = ogg_sync_buffer(oggsync, oggbufsize);
00389             if (oggbuf == NULL)
00390             {
00391                 seterror("error allocating ogg sync buffer");
00392                 return false;
00393             }
00394             inbuf.move(oggbuf, oggbufsize);
00395             ogg_sync_wrote(oggsync, oggbufsize);
00396         }
00397         // we got a page!
00398         if (! process_page(oggpage, outbuf))
00399             return false;
00400         
00401         // return immediately after we see the header if not flushing
00402         // guarantee no data has been decoded yet since Ogg Vorbis
00403         // spec says that the audio data must begin on a fresh page
00404         // following the headers
00405         if (checkheaderok && isheaderok())
00406             return true;
00407     }
00408 }
00409 
00410 
00411 bool WvOggVorbisDecoder::_typedfinish(OBuffer &outbuf)
00412 {
00413     if (! isheaderok())
00414     {
00415         seterror("failed to detect an Ogg Vorbis stream");
00416         return false;
00417     }
00418     return true;
00419 }
00420 
00421 
00422 bool WvOggVorbisDecoder::process_page(ogg_page *oggpage,
00423     OBuffer &outbuf)
00424 {       
00425     if (need_serialno)
00426     {
00427         // attach to the first bitstream we find
00428         long serialno = ogg_page_serialno(oggpage);
00429         if (! prepare_stream(serialno))
00430             return false;
00431         need_serialno = false;
00432     }
00433     // submit the page to the bitstream
00434     if (ogg_stream_pagein(oggstream, oggpage) != 0)
00435     {
00436         // this page was bad, or did not match the stream's
00437         // serial number exactly, skip it
00438         return true;
00439     }
00440     return true;
00441 }
00442 
00443 
00444 bool WvOggVorbisDecoder::process_packet(ogg_packet *oggpacket,
00445     OBuffer &outbuf)
00446 {
00447     if (need_headers > 0)
00448     {
00449         // read headers
00450         int result = vorbis_synthesis_headerin(ovinfo,
00451             ovcomment, oggpacket);
00452         if (result != 0)
00453         {
00454             seterror("error %s reading vorbis headers "
00455                 "(not a vorbis stream?)", result);
00456             return false;
00457         }
00458         if (--need_headers == 0)
00459             return prepare_dsp();
00460     }
00461     else
00462     {
00463         // process a block of Vorbis data
00464         if (vorbis_synthesis(ovblock, oggpacket) != 0)
00465         {
00466             // bad data? skip it!
00467             return true;
00468         }
00469         vorbis_synthesis_blockin(ovdsp, ovblock);
00470 
00471         // synthesize PCM audio
00472         for (;;) {
00473             float **pcm;
00474             long samples = vorbis_synthesis_pcmout(ovdsp, &pcm);
00475             if (samples == 0) break;
00476             
00477             outbuf.put(pcm[0], samples);
00478             vorbis_synthesis_read(ovdsp, samples);
00479         }
00480     }
00481     return true;
00482 }
00483 
00484 
00485 bool WvOggVorbisDecoder::prepare_dsp()
00486 {
00487     // extract comments
00488     for (int i = 0; i < ovcomment->comments; ++i)
00489     {
00490         char *c = ovcomment->user_comments[i];
00491         if (c)
00492             comment_list.append(new WvString(c), true);
00493     }
00494 
00495     // prepare OggVorbis dsp state
00496     int retval;
00497     ovdsp = new vorbis_dsp_state;
00498     if ((retval = vorbis_synthesis_init(ovdsp, ovinfo)) != 0)
00499     {
00500         seterror("error %s during vorbis_synthesis_init", retval);
00501         return false;
00502     }
00503     ovblock = new vorbis_block;
00504     if ((retval = vorbis_block_init(ovdsp, ovblock)) != 0)
00505     {
00506         seterror("error %s during vorbis_block_init", retval);
00507         return false;
00508     }
00509     return true;
00510 }
00511 
00512 
00513 bool WvOggVorbisDecoder::prepare_stream(long serialno)
00514 {
00515     // init ogg bitstream layer
00516     oggstream = new ogg_stream_state;
00517     int retval;
00518     if ((retval = ogg_stream_init(oggstream, serialno)) != 0)
00519     {
00520         seterror("error %s during ogg_stream_init", retval);
00521         return false;
00522     }
00523 
00524     // init vorbis codec layer
00525     ovinfo = new vorbis_info;
00526     vorbis_info_init(ovinfo); // cannot fail
00527     ovcomment = new vorbis_comment;
00528     vorbis_comment_init(ovcomment); // cannot fail
00529     return true;
00530 }

Generated on Sat Feb 21 21:05:31 2004 for WvStreams by doxygen 1.3.5