Re: vorbis decoder stuffs

Ed Sweetman <[email protected]>
Newsgroups gmane.comp.audio.zinf.devel
Message-ID <[email protected]>
Ed Sweetman wrote:
> Well, the vorbis mailing list wasn't any help... for some reason 
> everyone went to sleep at the same time there.
> 
> Anyways i was able to figure out what was wrong with the decoder and 
> fixed it.
> 
> 
> The decoder is still rough around the edges, i need to clean up seeking 
> and clean up a couple places where error handling has to be done 
> properly,  but it's a far superior decoder (minus seeking) than the 
> current decoder.  Much quicker.
> 
> 
> Should be up and ready to go this weekend.
> 


I want to give anyone who's had  vorbis decoder experience some chance 
to look at this to see if they can tell me why my seeking doesn't work. 
  Just pop these in place of the current vorbis files.

Note: this is a rough work in progress so there are some unused 
variables, no comments and crappy error handling.

It should be able to do slow track selection, stop, start, pause, 
unpause, play till end of stream and goto next file.

Basically it should be able to do everything but seek and fast track at 
the moment.  Anyone with vorbis decoder experience who can see what my 
seeking code isn't doing correctly would be much welcomed.

In the meantime i'll be looking into it too.   I still think it'll be 
ready sometime this weekend.
vorbislmc.cpp (text/x-c++src, 15 KB)
/*____________________________________________________________________________
   
   Zinf - Zinf Is Not FreeA*p (The Free MP3 Player)

   Portions Copyright (C) 2000 Monty

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, Write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   
   $Id: vorbislmc.cpp,v 1.16 2003/03/20 22:25:33 kgk Exp $
____________________________________________________________________________*/

/* system headers */
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string>
#include <vector>
#include <math.h>

using namespace std;
#include "config.h"
#include "errors.h"
#include "pmoevent.h"
#include "eventbuffer.h"
#include "event.h"
#include "eventdata.h"
#include "mutex.h"
#include "semaphore.h"
#include "preferences.h"
#include "lmc.h"
#include "facontext.h"
#include "log.h"
#include "debug.h"

#include "vorbislmc.h" 

#define DB printf("%s:%d\n",  __FILE__, __LINE__);

extern    "C"
{
   LogicalMediaConverter *Initialize(FAContext *context)
   {
      return new VorbisLMC(context);
   }
}

const int iDecodeBlockSize = 8192;
const int iFramesPerSecond = 10;
const int iBitrateLoopsPerUpdate = iFramesPerSecond * 3;
const int iInitialOutputBufferSize = 65536; 
//const char *szFailRead = N_("Cannot read vorbis data from input plugin.");
const char *szFailWrite = N_("Cannot write audio data to output buffer.");
const char *szCannotDecode = N_("Skipped corrupted file.");

VorbisLMC::VorbisLMC(FAContext *context) :
         LogicalMediaConverter(context),m_pPmi(NULL),m_pPmo(NULL),
	 m_decoderThread(NULL),m_szUrl(NULL),m_szError(NULL)
{
   m_pContext = context;
   m_bInit = false;
   m_newPos = -1;
   m_decodeInfo.sendInfo = true;
   pBuffer = NULL;
   pOutBuffer = NULL;
   tbuffer = NULL;
   iMaxFrameSize = 8192;
   iReadSize = 0;
   seeked = false;
}

VorbisLMC::~VorbisLMC()
{
   if (m_decoderThread)
   {
      ClrDecode();
      m_bExit = true;
      m_pPauseSem->Signal();
      m_pSleepSem->Signal();
      delete [] tbuffer;
      m_decoderThread->Join();
      delete m_decoderThread;
   }
}

Error VorbisLMC::Prepare(PullBuffer *pInputBuffer, PullBuffer *&pOutBuffer)
{
   m_pInputBuffer = pInputBuffer;


   m_pOutputBuffer = new EventBuffer(iInitialOutputBufferSize, 0, 
                                    m_pContext);
   if (!m_decoderThread){
      m_decoderThread = Thread::CreateThread();
      if (!m_decoderThread){
         return kError_CreateThreadFailed;
      }
      m_decoderThread->Create(VorbisLMC::DecodeWorkerThreadFunc, this);
   }

   pOutBuffer =  m_pOutputBuffer;

   m_pInputBuffer->SetName("Input");
   m_pOutputBuffer->SetName("Output");

   return kError_NoErr;
}

void VorbisLMC::Clear()
{
   if (m_pOutputBuffer)
      ((EventBuffer *)m_pOutputBuffer)->Clear();
}

vector<string> *VorbisLMC::GetExtensions(void)
{
   vector<string> *extList = new vector<string>;

   extList->push_back("OGG");

   return extList;
}


void  VorbisLMC::ClrDecode()
{
    ogg_stream_clear(&os);
    vorbis_block_clear(&vb);
    vorbis_dsp_clear(&vd);
    vorbis_comment_clear(&vc);
    vorbis_info_clear(&vi);
    ogg_sync_clear(&oy);
}    

Error VorbisLMC::CanDecode()
{
   Error err;

   if (!m_bInit){
       err = InitDecoder();
       if (err != kError_NoErr)
           return err;
   }

   return kError_NoErr;
}

Error VorbisLMC::InitDecoder()
{
    int            result;
    int              iNewSize,bytes;
    int 	m_iMaxWriteSize;
    OutputInfo *info;
    int headers = 0;
    Error Err = kError_PluginNotInitialized;
    ogg_packet op;
    if (!m_pTarget || !m_pPmi || !m_pPmo || !m_pInputBuffer || !m_pOutputBuffer)
    {
	return kError_PluginNotInitialized;
    }
    if (m_bExit) return kError_Interrupt;
    cerr << "Initializing vorbis decoder\n";
    ogg_sync_init(&oy);
    if (iMaxFrameSize > (int)m_pInputBuffer->GetNumBytesInBuffer() &&     
			m_pInputBuffer->GetNumBytesInBuffer() > 0 &&                      
			m_pInputBuffer->IsEndOfStream())                                  
	iReadSize = m_pInputBuffer->GetNumBytesInBuffer();                
    else                                                                  
	iReadSize = iMaxFrameSize;
    (char*)pOutBuffer = ogg_sync_buffer(&oy, iReadSize);
    while((Err = m_pInputBuffer->BeginRead(pBuffer,iReadSize)) == kError_NoDataAvail){
	    m_pPmi->Wake();
	    if(Sleep())
		return kError_Interrupt;
    }
    bytes = iReadSize;
    memcpy(pOutBuffer,pBuffer,iReadSize);
    ogg_sync_wrote(&oy,bytes);
    if(ogg_sync_pageout(&oy,&og) != 1){
	return (kError_PluginNotInitialized);
    }
    serialno = ogg_page_serialno(&og);
    ogg_stream_init(&os, serialno);
    vorbis_info_init(&vi);
    vorbis_comment_init(&vc);
    if(ogg_stream_pagein(&os,&og) < 0){
	return (kError_PluginNotInitialized);
    }
    if(ogg_stream_packetout(&os,&op) != 1){
	return (kError_PluginNotInitialized);
    }
    if(vorbis_synthesis_headerin(&vi,&vc,&op) < 0){
	return (kError_PluginNotInitialized);
    }
    headers++;
    while(headers < 3) {
	while(headers < 3){
	    if((result = ogg_sync_pageout(&oy,&og)) == 0)
		break;
	    if(result == 1){
		ogg_stream_pagein(&os,&og);
		while(headers < 3){
		    if((result = ogg_stream_packetout(&os,&op)) == 0)
			break;
		    if(result < 0){
			return (kError_PluginNotInitialized);
		    }
		    vorbis_synthesis_headerin(&vi,&vc,&op);
		    headers++;
		}
	    }
	}
	if (iMaxFrameSize > (int)m_pInputBuffer->GetNumBytesInBuffer() &&     
			m_pInputBuffer->GetNumBytesInBuffer() > 0 &&                      
			m_pInputBuffer->IsEndOfStream())                                  
	    iReadSize = m_pInputBuffer->GetNumBytesInBuffer();                
	else                                                                  
	    iReadSize = iMaxFrameSize; 
	pOutBuffer = ogg_sync_buffer(&oy,iReadSize);
	m_pInputBuffer->EndRead(iReadSize);
	while((Err = m_pInputBuffer->BeginRead(pBuffer,iReadSize)) == kError_NoDataAvail){
	    m_pPmi->Wake();
	    if(Sleep())
		return kError_Interrupt;
	}
	bytes = iReadSize;
	memcpy(pOutBuffer,pBuffer,iReadSize);
	if(Err == kError_EndOfStream){
	    return kError_PluginNotInitialized;
	}
	if(bytes == 0 && headers < 3){
	    return (kError_PluginNotInitialized);
	}
	ogg_sync_wrote(&oy,bytes);
    }
    cerr << "all headers read\n";
    m_channels = vi.channels;
    m_rate = vi.rate;
    m_section = -1;
    vorbis_synthesis_init(&vd,&vi);
    vorbis_block_init(&vd,&vb);
    info = new OutputInfo;
    info->bits_per_sample = 16;
    info->number_of_channels = 2;
    info->samples_per_second = vi.rate;
    m_iMaxWriteSize = info->number_of_channels * (info->bits_per_sample/8) *
			4096;
    info->samples_per_frame = 512;
    info->max_buffer_size = m_iMaxWriteSize;
    m_pContext->prefs->GetPrefInt32(kOutputBufferSizePref, &iNewSize);
    iNewSize = max(iNewSize, iMinimumOutputBufferSize);
    iNewSize *= 1024;
    result = m_pOutputBuffer->Resize(iNewSize, iNewSize / 6);
    if (IsError((Error)result)){
	ReportError(_("Internal buffer sizing error occurred."));
	m_pContext->log->Error("Resize output buffer failed.");
	return (Error)result;
    }   
    ((EventBuffer *)m_pOutputBuffer)->AcceptEvent(new PMOInitEvent(info));
    m_bInit = true;
    cerr << "Initialization complete\n";
    return kError_NoErr;
}
 
Error VorbisLMC::ExtractMediaInfo()
{
   Error           err;
   float           totalSeconds;
   int32_t	  filesize;
   MediaInfoEvent *pMIE;

   if (!m_bInit){
       err = InitDecoder();
       if (err != kError_NoErr)
           return err;
   }

    if (m_bExit) {
	return kError_Interrupt;
    }
   
    if(m_pPmi->IsStreaming())
	totalSeconds = (double)m_pPmi->Tell(filesize) / 
					((double)vi.bitrate_nominal/8.0);
    else {
	err = m_pPmi->GetLength((size_t)filesize);
	if(err == kError_NoErr)
	    totalSeconds = (double)filesize / ((double)vi.bitrate_nominal/8.0);
	else {
	    return (err);
	}
    }
    pMIE = new MediaInfoEvent(m_pPmi->Url().c_str(), totalSeconds);
    if (!pMIE) {
	return kError_OutOfMemory;
    }

    VorbisInfoEvent *mie = new VorbisInfoEvent(vi.bitrate_nominal,
                                              vi.channels, 
                                              vi.rate, 
                                              1. / (float)iFramesPerSecond);
    if (mie){
	pMIE->AddChildEvent((Event *) mie);
	mie = NULL;
    } else {
	return kError_OutOfMemory;
    }
    if (m_pTarget)
	m_pTarget->AcceptEvent(pMIE);
	
    return kError_NoErr;
}

uint32_t VorbisLMC::CalculateSongLength(const char *url)
{
    int totalSeconds;
    size_t filesize;
    m_pPmi->GetLength(filesize);
    totalSeconds = (double)filesize / ((double)vi.bitrate_nominal/8.0);
    
    return (totalSeconds);
}

void VorbisLMC::DecodeWorkerThreadFunc(void *pxlmc)
{
   if (pxlmc){
      VorbisLMC  *xlmc = (VorbisLMC *) pxlmc;
      xlmc->DecodeWork();
   }
}

void VorbisLMC::DecodeWork()
{
    Error          Err;
    int32_t          iValue;
    int32_t          section, ret;
    OutputInfo    *info;
    uint32_t         bytesCopied, bytesPerFrame;
    int            bitrateLoops = 0;
    int i;
    int val;
    int bout;
    
    int	  eos = 0;

    assert(m_pPmi);
    assert(m_pPmo);

    m_pSleepSem->Wait();
    m_pPmi->Wake();
    
    char convbuffer[4096];
    tbuffer = new char[8192];
    
    if (!m_bInit){
	Err = InitDecoder();
	if (Err == kError_Interrupt)
	    return;
	if (IsError(Err)){
	    if(m_decodeInfo.sendInfo){
		ReportStatus(szCannotDecode);
		m_pTarget->AcceptEvent(new Event(INFO_DoneOutputtingDueToError));
	    }else{
		((EventBuffer *)m_pOutputBuffer)->AcceptEvent(new PMOErrorEvent());
	    }
	    return;
	}
    }
   
    Err = ExtractMediaInfo();
    if (Err == kError_Interrupt)
	return;
    if (IsError(Err)){
	m_pContext->log->Error("ExtractMediaInfo failed: %d\n", Err);
	if (m_decodeInfo.sendInfo){
	    ReportStatus(szCannotDecode);
	    m_pTarget->AcceptEvent(new Event(INFO_DoneOutputtingDueToError));
	} else
	    ((EventBuffer *)m_pOutputBuffer)->AcceptEvent(new PMOErrorEvent());
	return;
    }

    m_pContext->prefs->GetPrefInt32(kDecoderThreadPriorityPref, &iValue);
    m_decoderThread->SetPriority(iValue);
    m_frameCounter = 0;
    cerr << "Decoding...\n";
    left = 8192;
    m_pInputBuffer->EndRead(iReadSize);
    while(!eos && !m_bExit){
	while(!eos && !m_bExit){
	    ((EventBuffer *)m_pOutputBuffer)->AcceptEvent(
			new PMOTimeInfoEvent(m_frameCounter));
	    ogg_packet op;
	    if(m_bPause){
		m_pPauseSem->Wait();
		if(m_bExit)
		    break;
	    }
	    ret = ogg_sync_pageout(&oy,&og);
	    if(!ret){
		break;
	    } else {
		ogg_stream_pagein(&os,&og);
		while(1){
		    ret = ogg_stream_packetout(&os,&op);
		    if(!ret){
			break;
		    } else {
			float **pcm;
			int samples;
			if(!vorbis_synthesis(&vb,&op))
			    vorbis_synthesis_blockin(&vd,&vb);
			while((samples=vorbis_synthesis_pcmout(&vd,&pcm))>0){
			    int j;
			    int clipflag=0;
			    int bout=(samples<(left/4)?samples:(left/4));
			    for(i=0;i<vi.channels;i++){
				ogg_int16_t *ptr=((ogg_int16_t*)convbuffer)+i;
				float *mono=pcm[i];
				for(j=0;j<bout;j++){
				    int val= mono[j]*32767.f;
				    if(val>32767){
					val = 32767;
					clipflag=1;
				    }
				    if(val<-32768){
					val = -32768;
					clipflag=1;
				    }
				    *ptr=val;
				    ptr+=vi.channels;
				}
			    }
			    memcpy(((char*)tbuffer+(8192-left)), convbuffer,bout*2*vi.channels);				
			    left = left - (bout * 2 * vi.channels);
			    vorbis_synthesis_read(&vd,bout);
			    m_frameCounter++;
			    if(left ==0 || iReadSize < 8192){
				while((Err = m_pOutputBuffer->BeginWrite(tbuffer,8192))==kError_BufferTooSmall){
				    if(Sleep())
					break;
				    continue;
				}
				if(m_pOutputBuffer){
				    if(Err == kError_NoErr)
					Err = m_pOutputBuffer->EndWrite(8192);
				}
				left = 8192;
			    } 
			}
		    }
		}
		if(ogg_page_eos(&og))
		    eos=1;
	    }
	}
	if(!eos){
	    if (iMaxFrameSize > (int)m_pInputBuffer->GetNumBytesInBuffer() &&     
				m_pInputBuffer->GetNumBytesInBuffer() > 0 &&                      
				m_pInputBuffer->IsEndOfStream())                                  
		iReadSize = m_pInputBuffer->GetNumBytesInBuffer();                
	    else                                                                  
		iReadSize = iMaxFrameSize;
	    pOutBuffer = ogg_sync_buffer(&oy,left);
	    while((Err = m_pInputBuffer->BeginRead(pBuffer,left)) == kError_NoDataAvail){
		m_pPmi->Wake();
		if(Sleep())
		    return;
	    }
	    if(Err == kError_EndOfStream){
		((EventBuffer *)m_pOutputBuffer)->AcceptEvent(new PMOQuitEvent());
		while(!m_bExit && m_pInputBuffer->IsEndOfStream())                
		    Sleep();                                                      
		if (!m_pInputBuffer->IsEndOfStream()) {                           
                    break;                                                         
		}                                                                                               
                return;                                                           
	    }          				          
	    memcpy(pOutBuffer,pBuffer,left);
	    ogg_sync_wrote(&oy,left);
	    if(Err == kError_NoErr)
		m_pInputBuffer->EndRead(left);		
	    
	    seeked = false;
	    if(!iReadSize)
		eos=1;
	}
	
    }
}


Error VorbisLMC::ChangePosition(int32_t position)
{
    int32_t dummy;
    uint32_t ISeekTo;
    size_t length;
    Error Err;    
    m_pPmi->GetLength(length);
    m_pOutputBuffer->DiscardBytes();
    m_pInputBuffer->EndRead(0);
    ISeekTo = position * 512 *4;
    m_pPmi->Seek(dummy,ISeekTo, SEEK_FROM_START);
    m_frameCounter = position;
    vorbis_synthesis_read(&vd,left);
    seeked = true;
   return kError_NoErr;
}

Error VorbisLMC::SetDecodeInfo(DecodeInfo &info)
{
    m_decodeInfo = info;
    return kError_NoErr;
}

const string VorbisLMC::ConvertToISO(const char *utf8)
{
   unsigned char *in, *buf;
   unsigned char *out, *end;
   string               ret;

   in = (unsigned char *)utf8;
   buf = out = new unsigned char[strlen(utf8) + 1];
   end = in + strlen(utf8);
   for(;*in != 0x00 && in <= end; in++, out++)
   {
       if (*in < 0x80)
       {  /* lower 7-bits unchanged */
          *out = *in;
       }
       else
       if (*in > 0xC3)
       { /* discard anything above 0xFF */
          *out = '?';
       }
       else
       if (*in & 0xC0)
       { /* parse upper 7-bits */
          if (in >= end)
            *out = 0;
          else
          {
            *out = (((*in) & 0x1F) << 6) | (0x3F & (*(++in)));
          }
       }
       else
       {
          *out = '?';  /* this should never happen */
       }
   }
   *out = 0x00; /* append null */
   ret = string((char *)buf);
   delete[] buf;

   return ret;
}
vorbislmc.h (text/x-chdr, 3.1 KB)
/*____________________________________________________________________________
   
   Zinf - Zinf Is Not FreeA*p (The Free MP3 Player)
   Portions Copyright (C) 1998-1999 EMusic.com

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   
   $Id: vorbislmc.h,v 1.4 2003/02/09 19:28:03 rottmann Exp $

____________________________________________________________________________*/

#ifndef INCLUDED_VORBISLMC_H_
#define INCLUDED_VORBISLMC_H_

/* system headers */
#include <stdlib.h>
#include <time.h>
#include <vorbis/vorbisfile.h>
#include <string>
#include <vector>

/* project headers */
#include "config.h"

#include "pmi.h"
#include "pmo.h"
#include "mutex.h"
#include "event.h"
#include "lmc.h"
#include "thread.h"
#include "mutex.h"
#include "queue.h"
#include "semaphore.h"

class VorbisLMC : public LogicalMediaConverter
{

   public:
            VorbisLMC(FAContext *context);
   virtual ~VorbisLMC();

   virtual uint32_t CalculateSongLength(const char *url);

   virtual Error ChangePosition(int32_t position);

   virtual Error CanDecode();
   virtual void  Clear();
   virtual Error ExtractMediaInfo();

   virtual void  SetPMI(PhysicalMediaInput *pmi) { m_pPmi = pmi; };
   virtual void  SetPMO(PhysicalMediaOutput *pmo) { m_pPmo = pmo; };
   virtual Error Prepare(PullBuffer *pInputBuffer, PullBuffer *&pOutBuffer);
   virtual Error InitDecoder();

   virtual std::vector<std::string> *GetExtensions(void);

   virtual Error SetEQData(float *f, float) { return kError_YouScrewedUp; };
   virtual Error SetEQData(bool b) { return kError_YouScrewedUp; };
  
   virtual Error SetDecodeInfo(DecodeInfo &info);
 
 private:
 
   const std::string ConvertToISO(const char *utf8);

   static void          DecodeWorkerThreadFunc(void *);
   void			ClrDecode();
   void                 DecodeWork();

   PhysicalMediaInput  *m_pPmi;
   PhysicalMediaOutput *m_pPmo;

   Thread              *m_decoderThread;

   char                *m_szUrl;
   const char          *m_szError;
   bool                 m_bInit;
   int                  m_channels, m_section, m_rate;
   long                 m_frameCounter, m_newPos;
    ogg_sync_state oy;
    ogg_stream_state os;
    ogg_page og;
//    ogg_packet op;
    vorbis_dsp_state vd;
    vorbis_block vb;
    vorbis_info vi;
    vorbis_comment vc;
    long serialno;
    void *pBuffer;
    void *pOutBuffer;
    void *tbuffer;
//    void *convbuffer;
    int iMaxFrameSize;
    int iReadSize;    
    int left;
    bool seeked;
};

#endif
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.