Files
recycled-ni-neutrino/src/driver/audiodec/oggdec.cpp
[CST] Focus 8e1d04b4dd driver/audiodec: make sure driver/netfile.h included as last header
Origin commit data
------------------
Branch: ni/coolstream
Commit: 5a66719583
Author: [CST] Focus <focus.cst@gmail.com>
Date: 2012-04-17 (Tue, 17 Apr 2012)


------------------
No further description and justification available within origin commit message!

------------------
This commit was generated by Migit
2012-04-17 11:01:02 +04:00

356 lines
9.4 KiB
C++

/*
Neutrino-GUI - DBoxII-Project
Copyright (C) 2004 Sania, Zwen
Homepage: http://www.dbox2.info/
Kommentar:
ogg vorbis audio decoder
uses tremor libvorbisidec
License: GPL
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.
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <endian.h>
#include <errno.h>
#include <linux/soundcard.h>
#include <algorithm>
#include <sstream>
#include <zapit/include/audio.h>
#include "oggdec.h"
#include <driver/netfile.h>
extern cAudio * audioDecoder;
#define ProgName "OggDec"
// nr of msecs to skip in ff/rev mode
#define MSECS_TO_SKIP 3000
// nr of msecs to play in ff/rev mode
#define MSECS_TO_PLAY 200
/* at first, define our own callback functions used in */
/* tremor to access the data. These functions are simple mappers */
size_t ogg_read(void *buf, size_t size, size_t nmemb, void *data)
{
return fread(buf, size, nmemb, (FILE*)data);
}
int ogg_seek(void *data, ogg_int64_t offset, int whence)
{
return fseek((FILE*)data, (long)offset, whence);
}
int ogg_close(void */*data*/)
{
return 0;
}
long ogg_tell(void *data)
{
return ftell((FILE*)data);
}
#define PCMBUFFER 4096 //4096 max for libtremor
#define MAX_OUTPUT_SAMPLES 2048 /* AVIA_GT_PCM_MAX_SAMPLES-1 */
CBaseDec::RetCode COggDec::Decoder(FILE *in, const int OutputFd, State* const state, CAudioMetaData* meta_data, time_t* const time_played, unsigned int* const secondsToSkip)
{
OggVorbis_File vf;
int bitstream, rval;
ogg_int64_t jumptime=0;
Status=OK;
mOutputFd = OutputFd;
mState = state;
mTimePlayed=time_played;
if (!Open(in, &vf))
{
Status=DATA_ERR;
return Status;
}
SetMetaData(&vf, meta_data);
#if __BYTE_ORDER == __LITTLE_ENDIAN || USE_TREMOR
audioDecoder->PrepareClipPlay(ov_info(&vf,0)->channels, ov_info(&vf,0)->rate, 16, 1);
#else
audioDecoder->PrepareClipPlay(ov_info(&vf,0)->channels, ov_info(&vf,0)->rate, 16, 0);
#endif
/* up and away ... */
mSlotSize = MAX_OUTPUT_SAMPLES * 2 * ov_info(&vf,0)->channels;
for(int i = 0 ; i < DECODE_SLOTS ; i++)
{
if ((mPcmSlots[i] = (char*) malloc(mSlotSize)) == NULL)
{
for (int j = i - 1; j >= 0; j--)
{
free(mPcmSlots[j]);
}
Status=INTERNAL_ERR;
return Status;
}
mSlotTime[i]=0;
}
mReadSlot=mWriteSlot=0;
pthread_t OutputThread;
if (pthread_create (&OutputThread, 0, OutputDsp, (void *) this) != 0 )
{
for(int i = 0 ; i < DECODE_SLOTS ; i++)
free(mPcmSlots[i]);
Status=INTERNAL_ERR;
return Status;
}
int bytes;
State oldstate=*state;
do
{
// clear buffer on state change
if(oldstate!=*state)
{
if(*state!=PAUSE && (*state!=PLAY || oldstate!=PAUSE))
{
mWriteSlot=mReadSlot=0;
oldstate=*state;
}
}
while((mWriteSlot+1)%DECODE_SLOTS == mReadSlot)
{
usleep(10000);
}
bytes=0;
if(mSeekable)
#ifdef USE_TREMOR
mSlotTime[mWriteSlot] = ov_time_tell(&vf);
#else
mSlotTime[mWriteSlot] = (ogg_int64_t)(1000 * ov_time_tell(&vf));
#endif
do
{
#ifdef USE_TREMOR
rval = ov_read(&vf, mPcmSlots[mWriteSlot]+bytes, mSlotSize-bytes, &bitstream);
#else
rval = ov_read(&vf, mPcmSlots[mWriteSlot]+bytes, mSlotSize-bytes, 0, 2, 1, &bitstream);
#endif
bytes+=rval;
//printf("Ogg: read buf 0x%x size %d / %d done %d\n", mPcmSlots[mWriteSlot]+bytes, rval, mSlotSize, bytes);
} while (rval > 0 && bytes !=mSlotSize);
//printf("\n");
int actMSecsToSkip = (*secondsToSkip != 0) ? *secondsToSkip * 1000 : MSECS_TO_SKIP;
if((*state==FF || *state==REV) && mSeekable )
{
if((std::abs((long int)( mSlotTime[mWriteSlot]-jumptime))) > MSECS_TO_PLAY)
{
if(*state==FF)
{
ov_time_seek_page(&vf, mSlotTime[mWriteSlot] + actMSecsToSkip);
jumptime=mSlotTime[mWriteSlot]+actMSecsToSkip;
}
else
{
if(mSlotTime[mWriteSlot] < actMSecsToSkip)
{
ov_time_seek(&vf, 0);
*state=PLAY;
}
else
{
ov_time_seek_page(&vf, mSlotTime[mWriteSlot] - actMSecsToSkip);
jumptime=mSlotTime[mWriteSlot]-actMSecsToSkip;
}
}
}
if (*secondsToSkip != 0) {
*state=PLAY;
}
}
if(bytes == mSlotSize)
mWriteSlot=(mWriteSlot+1) % DECODE_SLOTS;
} while (rval != 0 && *state!=STOP_REQ && Status==OK);
//printf("COggDec::Decoder: read loop stop, rval %d state %d status %d\n", rval, *state, Status);
// let buffer run dry
while(rval==0 && *state!=STOP_REQ && Status==OK && mReadSlot != mWriteSlot)
usleep(100000);
//pthread_cancel(OutputThread);
//printf("COggDec::Decoder: OutputThread join\n");
Status = WRITE_ERR;
pthread_join(OutputThread, NULL);
//printf("COggDec::Decoder: OutputThread join done\n");
audioDecoder->StopClip();
for(int i = 0 ; i < DECODE_SLOTS ; i++)
free(mPcmSlots[i]);
/* clean up the junk from the party */
ov_clear(&vf);
/* and drive home ;) */
return Status;
}
void* COggDec::OutputDsp(void * arg)
{
COggDec* dec = (COggDec*) arg;
while(dec->Status == OK/*true*/)
{
while(dec->mReadSlot==dec->mWriteSlot || *dec->mState==PAUSE)
{
if(dec->Status != OK)
return NULL;
usleep(10000);
}
//if (write(dec->mOutputFd, dec->mPcmSlots[dec->mReadSlot], dec->mSlotSize) != dec->mSlotSize)
if (audioDecoder->WriteClip((unsigned char *)dec->mPcmSlots[dec->mReadSlot], dec->mSlotSize) != dec->mSlotSize)
{
fprintf(stderr,"%s: PCM write error (%s).\n", ProgName, strerror(errno));
dec->Status=WRITE_ERR;
break;
}
*dec->mTimePlayed = (int)(dec->mSlotTime[dec->mReadSlot]/1000);
dec->mReadSlot=(dec->mReadSlot+1)%DECODE_SLOTS;
}
return NULL;
}
bool COggDec::GetMetaData(FILE *in, const bool /*nice*/, CAudioMetaData* m)
{
OggVorbis_File vf;
if (!Open(in, &vf))
{
return false;
}
SetMetaData(&vf, m);
ov_clear(&vf);
return true;
}
COggDec* COggDec::getInstance()
{
static COggDec* OggDec = NULL;
if(OggDec == NULL)
{
OggDec = new COggDec();
}
return OggDec;
}
void COggDec::ParseUserComments(vorbis_comment* vc, CAudioMetaData* m)
{
for(int i=0; i < vc->comments ; i++)
{
char* search;
if((search=strstr(vc->user_comments[i],"Artist"))!=NULL ||
(search=strstr(vc->user_comments[i],"ARTIST"))!=NULL)
m->artist = search+7;
else if((search=strstr(vc->user_comments[i],"Album"))!=NULL ||
(search=strstr(vc->user_comments[i],"ALBUM"))!=NULL)
m->album = search+6;
else if((search=strstr(vc->user_comments[i],"Title"))!=NULL ||
(search=strstr(vc->user_comments[i],"TITLE"))!=NULL)
m->title = search+6;
else if((search=strstr(vc->user_comments[i],"Genre"))!=NULL ||
(search=strstr(vc->user_comments[i],"GENRE"))!=NULL)
m->genre = search+6;
else if((search=strstr(vc->user_comments[i],"Date"))!=NULL ||
(search=strstr(vc->user_comments[i],"DATE"))!=NULL)
m->date = search+5;
else if((search=strstr(vc->user_comments[i],"TrackNumber"))!=NULL ||
(search=strstr(vc->user_comments[i],"TRACKNUMBER"))!=NULL)
m->track = search+12;
}
}
void COggDec::SetMetaData(OggVorbis_File* vf, CAudioMetaData* m)
{
/* Set Metadata */
m->type = CAudioMetaData::OGG;
m->bitrate = ov_info(vf,0)->bitrate_nominal;
m->samplerate = ov_info(vf,0)->rate;
if(mSeekable)
#ifdef USE_TREMOR
m->total_time = (time_t) ov_time_total(vf, 0) / 1000;
#else
m->total_time = (time_t) ov_time_total(vf, 0);
#endif
std::stringstream ss;
ss << "OGG V." << ov_info(vf,0)->version << " / " << ov_info(vf,0)->channels << "channel(s)";
m->type_info = ss.str();
ParseUserComments(ov_comment(vf, 0), m);
m->changed=true;
}
bool COggDec::Open(FILE* in, OggVorbis_File* vf)
{
int rval;
ov_callbacks cb;
/* we need to use our own functions, because we have */
/* the netfile layer hooked in here. If we would not */
/* provide callbacks, the tremor lib and the netfile */
/* layer would clash and steal each other the data */
/* from the stream ! */
cb.read_func = ogg_read;
cb.seek_func = ogg_seek;
cb.close_func = ogg_close;
cb.tell_func = ogg_tell;
/* test the dope ... */
//rval = ov_test_callbacks((void*)in, vf, NULL, 0, cb);
rval = ov_open_callbacks(in, vf, NULL, 0, cb);
/* and tell our friends about the quality of the stuff */
// initialize the sound device here
if(rval<0)
{
switch(rval)
{
/* err_txt from netfile.cpp */
case OV_EREAD: sprintf(err_txt, "media read error"); break;
case OV_ENOTVORBIS: sprintf(err_txt, "no vorbis stream"); break;
case OV_EVERSION: sprintf(err_txt, "incompatible vorbis version"); break;
case OV_EBADHEADER: sprintf(err_txt, "invalid bvorbis bitstream header"); break;
case OV_EFAULT: sprintf(err_txt, "internal logic fault (tremor)"); break;
default: sprintf(err_txt, "unknown error, code: %d", rval);
}
fprintf(stderr,"%s: %s\n", ProgName, err_txt);
return false;
}
/* finish the opening and ignite the joint */
//ov_test_open(vf);
if(ov_seekable(vf))
mSeekable = true;
else
mSeekable = false;
return true;
}