From: Chris Tallon Date: Sun, 15 Jul 2007 16:56:59 +0000 (+0000) Subject: Media Player From Andreas Vogel X-Git-Url: https://git.vomp.tv/gitweb/?a=commitdiff_plain;h=ace408c37bec26438d603832c907fca069a8d491;p=vompclient-marten.git Media Player From Andreas Vogel --- diff --git a/audio.h b/audio.h index 3eba642..340110c 100644 --- a/audio.h +++ b/audio.h @@ -70,6 +70,7 @@ class Audio : public DrainTarget // Audio stream type // FIXME these are MVP specific (probably!) const static UCHAR MPEG2_PES = 2; const static UCHAR MPEG1_PES = 3; // unused + const static UCHAR MP3 = 0; //media player #ifdef DEV virtual int test()=0; diff --git a/audioplayer.cc b/audioplayer.cc new file mode 100644 index 0000000..0524362 --- /dev/null +++ b/audioplayer.cc @@ -0,0 +1,734 @@ +/* + Copyright 2004-2006 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "audioplayer.h" +#include "vaudioplayer.h" +#include "demuxeraudio.h" +#include "timers.h" + +AudioPlayer * AudioPlayer::instance=NULL; + +AudioPlayer * AudioPlayer::getInstance(View * parent,bool create) { +// Log::getInstance()->log("AudioPlayer",Log::DEBUG,"getInstance for view %p, instance=%p",parent,instance); + AudioPlayer *np=instance; + if (! np && ! create) return NULL; + if (! np) { + np=new AudioPlayer(parent); + np->run(); + instance=np; + } + instance->threadLock(); + instance->frontend=parent; + instance->threadUnlock(); + return instance; +} + +AudioPlayer::AudioPlayer(View *parent) : afeed(this) +{ + frontend=parent; + audio = Audio::getInstance(); + logger = Log::getInstance(); + vdr = VDR::getInstance(); + logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorI"); + running=true; + playerRunnig=false; + streampos = 0; + bytesWritten=0; + state = S_STOP; + requestState=S_STOP; + feederState=FEEDER_STOP; + + threadBuffer = NULL; + + Video::getInstance()->turnVideoOff(); + requestedSequence=0; + sequence=0; + playSequence=0; + currentPlaySequence=-1; + filename=NULL; + demuxer=new DemuxerAudio(); + logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorII"); + if (!demuxer->init(this, audio, NULL, 0, DemuxerAudio::PACKET_SIZE+200)) + { + logger->log("AudioPlayer", Log::ERR, "Demuxer failed to init"); + shutdown(); + return ; + } + logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorIII"); + afeed.init(); + audio->reset(); + lenInBytes=0; + logger->log("AudioPlayer", Log::DEBUG, "Audio player created"); +} + +AudioPlayer::~AudioPlayer() +{ + if (threadBuffer) free(threadBuffer); + Timers::getInstance()->cancelTimer(this,1); + controlFeeder(FEEDER_STOP); + audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); + delete demuxer; + demuxer=NULL; + delete filename; +} + +void AudioPlayer::controlFeeder(int feederAction) { + logger->log("AudioPlayer",Log::DEBUG,"control feeder old=%d, new=%d",feederState,feederAction); + switch (feederAction) { + case FEEDER_START: + case FEEDER_UNPAUSE: + if (feederState == FEEDER_STOP) + afeed.start(); + afeed.enable(); + break; + case FEEDER_STOP: + if (feederState != FEEDER_STOP) + afeed.stop(); + break; + case FEEDER_PAUSE: + afeed.disable(); + break; + } + feederState=feederAction; +} + + +void AudioPlayer::run() { + if (playerRunnig) return; + playerRunnig=true; + threadStart(); +} + + +void AudioPlayer::shutdown() +{ + running=false; + if (playerRunnig) { + threadSignalNoLock(); + //wait for the player thread to stop + logger->log("AudioPlayer",Log::DEBUG,"shutdown - warting for player thread to stop"); + //at most wait 10s + for (int loopcount=200;playerRunnig && loopcount > 0;loopcount--) { + MILLISLEEP(50); + } + if (playerRunnig) { + logger->log("AudioPlayer",Log::ERR,"shutdown - unable to stop player within 10s"); + } + } + instance=NULL; + delete this; +} + +bool AudioPlayer::isPlayerRunning() { + return playerRunnig; +} + +int AudioPlayer::setRequestedState(UCHAR rstate) { + int rt=0; + threadLock(); + requestState=rstate; + requestedSequence++; + rt=requestedSequence; + threadUnlock(); + return rt; +} + + +UCHAR AudioPlayer::getState() { + UCHAR rt=0; + threadLock(); + rt=state; + threadUnlock(); + return rt; +} + +void AudioPlayer::setState(UCHAR s) { + threadLock(); + state=s; + threadUnlock(); +} + +//------------------- externally called functions -------------------------- +int AudioPlayer::play(const char * fn) +{ + logger->log("AudioPlayer", Log::DEBUG, "play request for %s", fn); + int rt=0; + threadLock(); + if (filename) delete filename; + filename=new char[strlen(fn)+1]; + strcpy(filename,fn); + requestState=S_PLAY; + requestedSequence++; + rt=requestedSequence; + playSequence++; + threadUnlock(); + threadSignalNoLock(); + return rt; +} + +int AudioPlayer::stop() +{ + int rt= setRequestedState(S_STOP); + threadSignalNoLock(); + return rt; +} + +int AudioPlayer::pause() +{ + int rt= setRequestedState(S_PAUSE); + threadSignalNoLock(); + return rt; +} +int AudioPlayer::unpause() +{ + int rt= setRequestedState(S_PLAY); + threadSignalNoLock(); + return rt; +} + +int AudioPlayer::fastForward(){ + int rt=setRequestedState(S_FF); + threadSignalNoLock(); + return rt; +} + +int AudioPlayer::fastBackward(){ + int rt=setRequestedState(S_BACK); + threadSignalNoLock(); + return rt; +} +int AudioPlayer::jumpToPercent(double percent){ + threadLock(); + ULONG fsec=demuxer->getSecondsFromLen(lenInBytes); + ULONG npos=streampos; + if (fsec != 0) { + fsec=(ULONG)(((double)fsec*percent)/(double)100); + npos=demuxer->positionFromSeconds(fsec); + logger->log("AudioPlayer",Log::DEBUG,"new pos %ld from demux",npos); + } + if (npos == 0) { + //the demuxer cannot help us + npos=(ULONG)(((double)lenInBytes*percent)/(double)100); + logger->log("AudioPlayer",Log::DEBUG,"new pos %ld without demux",npos); + } + if (npos > lenInBytes) npos=lenInBytes-1; + requestedStreampos=npos; + requestState=S_POSITION; + requestedSequence++; + threadUnlock(); + //no need to wait here... + return 0; +} + +int AudioPlayer::skipForward(int seconds) { + threadLock(); + ULONG curr=demuxer->getSecondsFromLen(streampos); + ULONG dest=demuxer->positionFromSeconds(curr+(UINT)seconds); + if (dest != 0) { + logger->log("AudioPlayer",Log::DEBUG,"new pos %ld skip %ds",dest,seconds); + requestedStreampos=dest; + } + requestState=S_POSITION; + requestedSequence++; + threadUnlock(); + return 0; +} +int AudioPlayer::skipBackward(int seconds) { + threadLock(); + ULONG curr=demuxer->getSecondsFromLen(streampos); + if (curr > (UINT)seconds) { + ULONG dest=demuxer->positionFromSeconds(curr-(UINT)seconds); + if (dest != 0) { + logger->log("AudioPlayer",Log::DEBUG,"new pos %ld skip %ds",dest,seconds); + requestedStreampos=dest; + requestState=S_POSITION; + requestedSequence++; + } + } + threadUnlock(); + return 0; +} + + + +// ----------------------------------- Internal functions + + +void AudioPlayer::sendFrontendMessage(ULONG para) +{ + logger->log("AudioPlayer", Log::DEBUG, "sending frontend message %ld",para); + Message* m = new Message(); + threadLock(); + m->to = frontend; + threadUnlock(); + m->from = this; + m->message = Message::PLAYER_EVENT; + m->parameter = para; + Command::getInstance()->postMessageFromOuterSpace(m); +} + +void AudioPlayer::handleVDRerror(){ + if (!vdr->isConnected()) + { + logger->log("AudioPlayer", Log::ERR, "disconnect"); + sendFrontendMessage(CONNECTION_LOST); + setState(S_ERROR); + } +} +//open a new file +//called within the thread! +int AudioPlayer::openFile() { + char * fn=NULL; + threadLock(); + if (filename) { + fn=new char[strlen(filename)+1]; + strcpy(fn,filename); + } + threadUnlock(); + demuxer->reset(); + streampos=0; + bytesWritten=0; + lenInBytes=vdr->loadImage(fn,0,0); + Log::getInstance()->log("Audioplayer", Log::DEBUG, "request file rt=%d file=%s",lenInBytes,fn); + handleVDRerror(); + if (lenInBytes <= 0) { + return 1; + } + UINT rsize=0; + UCHAR *idbuf=vdr->getImageBlock(0,demuxer->headerBytes(),&rsize); + handleVDRerror(); + if (rsize < demuxer->headerBytes() || idbuf == NULL) { + if (idbuf) free(idbuf); + Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get header for file %s",fn); + return 0; + } + threadLock(); + int hdrpos=demuxer->checkStart(idbuf,rsize); + threadUnlock(); + if (hdrpos >= 0) { + streampos=hdrpos; + } + if (idbuf) free(idbuf); + idbuf=NULL; + if (demuxer->getId3Tag() == NULL) { + //OK - look at the end + idbuf=vdr->getImageBlock(lenInBytes-demuxer->footerBytes(),demuxer->footerBytes(),&rsize); + handleVDRerror(); + if (rsize < demuxer->footerBytes() || idbuf == NULL) { + if (idbuf) free(idbuf); + Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get footer for file %s",fn); + return 0; + } + threadLock(); + hdrpos=demuxer->checkID3(idbuf,rsize); + threadUnlock(); + if (hdrpos < 0) { + Log::getInstance()->log("VAudioplayer", Log::DEBUG, "no ID3 in footer for file %s",fn); + } + free(idbuf); + } + return 0; +} +//method called by the playing thread to handle +//"commands" by the frontend +UCHAR AudioPlayer::checkState() +{ + threadLock(); + UCHAR rstate=requestState; + UCHAR cstate=state; + int rseq=requestedSequence; + int cseq=sequence; + int fseq=playSequence; + threadUnlock(); + //flag to decide which message to send + //to frontend + bool newFile=false; + + if ( rseq > cseq) { + logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u", cstate, rstate); + switch(rstate) + { + case S_PAUSE: + if (cstate != S_PLAY && cstate != S_FF) rstate=cstate; //ignore request + else { + skipfactor=0; + demuxer->setSkipFactor(0); + audio->mute(); + audio->pause(); + } + break; + case S_PLAY: // to S_PLAY + skipfactor=0; + demuxer->setSkipFactor(0); + if (fseq != currentPlaySequence || cstate == S_STOP || cstate == S_ERROR || cstate == S_DONE) { + //this is a new play request interrupting the current + logger->log("AudioPlayer", Log::DEBUG, "replay from start fseq=%d, startseq=%d", fseq, currentPlaySequence); + threadLock(); + playSequence=fseq; + threadUnlock(); + currentPlaySequence=fseq; + newFile=true; + if (cstate != S_DONE) audio->mute(); + int rt=openFile(); + if (rt != 0) { + rstate=S_ERROR; + } + else { + audio->unPause(); + if (cstate != S_DONE) { + //flush only if we are not coming from stream end + thisRead=0; + thisWrite=0; + if (threadBuffer) free(threadBuffer); + threadBuffer=NULL; + controlFeeder(FEEDER_STOP); + demuxer->flush(); + controlFeeder(FEEDER_START); + audio->reset(); + audio->systemMuteOff(); + audio->setStreamType(Audio::MP3); + } + audio->unMute(); + audio->play(); + } + } + else if (cstate == S_PAUSE){ + newFile=true; + audio->unPause(); + audio->unMute(); + } + else if (cstate == S_FF || S_BACK) { + ; + } + else { + rstate=cstate; + } + break; + case S_DONE: + Timers::getInstance()->setTimerD(this,1,4); + //inform the frontend + break; + case S_FF: + if (cstate != S_PLAY && cstate != S_PAUSE && cstate != S_FF) { + rstate=cstate; + } + else { + if (skipfactor == 0) skipfactor=2; + else skipfactor=skipfactor<<1; + if (skipfactor > 16 ) skipfactor=2; + demuxer->setSkipFactor(skipfactor); + } + if (cstate == S_PAUSE) { + audio->unPause(); + audio->unMute(); + } + break; + case S_POSITION: + if (cstate != S_PLAY && cstate != S_PAUSE) { + rstate=cstate; + } + else { + audio->mute(); + audio->unPause(); + controlFeeder(FEEDER_STOP); + demuxer->flush(); + thisRead=0; + thisWrite=0; + if (threadBuffer) free(threadBuffer); + threadBuffer=NULL; + streampos=requestedStreampos; + bytesWritten=streampos; + audio->reset(); + audio->setStreamType(Audio::MP3); + controlFeeder(FEEDER_START); + audio->unMute(); + audio->play(); + rstate=S_PLAY; + } + break; + default: // to S_STOP + rstate=S_STOP; + audio->mute(); + audio->stop(); + audio->unPause(); + controlFeeder(FEEDER_STOP); + demuxer->flush(); + thisRead=0; + thisWrite=0; + if (threadBuffer) free(threadBuffer); + threadBuffer=NULL; + logger->log("AudioPlayer", Log::DEBUG, "stop handled fseq: %d startseq %d completed", playSequence, currentPlaySequence); + break; + } + threadLock(); + state=rstate; + sequence=rseq; + threadUnlock(); + if (newFile) sendFrontendMessage(NEW_SONG); + else if (cstate != rstate && rstate != S_DONE ) { + sendFrontendMessage(STATUS_CHANGE); + //any change after done cancels the "done" timer + Timers::getInstance()->cancelTimer(this,1); + } + logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u completed nf=%s", cstate, rstate,newFile?"true":"false"); + //we return the newly set state + return rstate; + } + //rstate could be different but no new request - so return cstate + return cstate; +} + + + + +// ----------------------------------- Feed thread + +void AudioPlayer::waitTimed(int ms) { + threadLock(); + struct timeval ct; + gettimeofday(&ct,NULL); + struct timespec nt; + int sec=ms/1000; + int us=1000*(ms - 1000*sec); + nt.tv_sec=ct.tv_sec+sec; + nt.tv_nsec=1000*us+1000*ct.tv_usec; + threadWaitForSignalTimed(&nt); + threadUnlock(); +} + +void AudioPlayer::threadMethod() +{ + logger->log("AudioPlayer", Log::DEBUG, "player thread started"); + thisWrite=0; + thisRead=0; + while(1) + { + UCHAR cstate=checkState(); + if (! running) { + break; + } + if (cstate != S_PLAY && cstate != S_FF && cstate != S_BACK) { + waitTimed(500); + continue; + } + threadCheckExit(); + + if (thisWrite == thisRead) { + //TODO: use normal blocks... + thisRead=0; + thisWrite=0; + threadBuffer = vdr->getImageBlock(streampos, BUFLEN , &thisRead); + handleVDRerror(); + if (!threadBuffer || thisRead == 0) { + //OK we count this as end of stream + //hmm --- we should be able to detect if the audio has gone... + logger->log("AudioPlayer", Log::DEBUG, "stream end"); + setRequestedState(S_DONE); + continue; + } + //logger->log("AudioPlayer", Log::DEBUG, "read %ld bytes at pos %ld",thisRead,streampos); + streampos+=thisRead; + } + + threadCheckExit(); + /* + MediaPacket p; + memset(&p,sizeof(p),0); + p.pos_buffer=0; + p.length=thisRead; + MediaPacketList pl; + pl.push_back(p); + audio->PrepareMediaSample(pl,0); + UINT bytesWritten=0; + UINT rt=audio->DeliverMediaSample(threadBuffer,&bytesWritten); + ULONG written=thisRead; + if (rt == 0) + written=bytesWritten; + */ + ULONG written= demuxer->put(threadBuffer + thisWrite, thisRead - thisWrite); + thisWrite+=written; + bytesWritten+=written; + if (thisWrite < thisRead) { + if (written == 0) { + // demuxer is full and can't take anymore + waitTimed(200); + } + } + else { + //logger->log("AudioPlayer", Log::DEBUG, "block written %d", thisWrite); + thisWrite=0; + thisRead=0; + free(threadBuffer); + threadBuffer = NULL; + } + + } + + logger->log("AudioPlayer", Log::DEBUG, "finished"); + playerRunnig=false; + return; +} + +int AudioPlayer::waitForSequence(int timeout, int seq) { + time_t starttime=time(NULL)+timeout; + time_t curtime=0; + logger->log("AudioPlayer", Log::DEBUG, "waiting for sequence %d",seq); + while ((curtime=time(NULL)) < starttime) { + int cseq=getSequence(); + if (cseq >= seq) return cseq; + MILLISLEEP(10); + } + return -1; +} + +int AudioPlayer::getSequence() { + int rt=0; + threadLock(); + rt=sequence; + threadUnlock(); + return rt; +} + + + +void AudioPlayer::threadPostStopCleanup() +{ + if (threadBuffer) + { + delete(threadBuffer); + threadBuffer = NULL; + } + playerRunnig=false; +} + +void AudioPlayer::call(void *) { + threadSignalNoLock(); +} + +void AudioPlayer::timercall(int ref) { + if (ref == 1) { + logger->log("AudioPlayer", Log::DEBUG, "stream end - informing frontend"); + sendFrontendMessage(STREAM_END); + } +} + +//--------------------------- info functions ------------------- + +char * AudioPlayer::getTitle() { + logger->log("AudioPlayer", Log::DEBUG, "getTitle"); + threadLock(); + const id3_tag * tag=demuxer->getId3Tag(); + const char * title=NULL; + char *rt=NULL; + if (tag != NULL) { + title=tag->title; + } + if (title && strlen(title) != 0) { + rt=new char[strlen(title)+1]; + strcpy(rt,title); + rt[strlen(title)]=0; + } + //let the frontend fill in something + threadUnlock(); + return rt; +} + +char * AudioPlayer::getID3Info() { + logger->log("AudioPlayer", Log::DEBUG, "getID3Info"); + threadLock(); + int len=0; + const id3_tag * tag=demuxer->getId3Tag(); + int taglen=0; + if (tag) taglen=tag->stringlen(false); + len+=taglen; + const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo(); + if (info) len+=30; + char * rt=NULL; + if (len > 0) { + char bitrateType='C'; + if (info && info->avrBitrate != info->bitRate) bitrateType='V'; + rt=new char[len]; + if (!tag && info) { + snprintf(rt,len-1,"%s: %s/L%d %cBR,SR=%dk,%s\n",tr("MpegInfo"), + info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000, + info->info); + } + else if (tag && info){ + char tmp[taglen+1]; + snprintf(rt,len-1,"%s\n" + "%s: %s/L%d %cBR,SR=%dk,%s\n", + tag->toString(tmp,taglen,false), + tr("MpegInfo"), + info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000, + info->info); + } + else if (tag && !info){ + char tmp[taglen+1]; + snprintf(rt,len-1,"%s\n", + tag->toString(tmp,taglen,false)); + } + rt[len-1]=0; + } + threadUnlock(); + logger->log("AudioPlayer", Log::DEBUG, "getID3Info returns %s",rt); + return rt; +} + +ULONG AudioPlayer::getCurrentTimes(){ + ULONG rt=0; + threadLock(); + if (streampos != 0){ + rt=demuxer->getSecondsFromLen(bytesWritten); + if (rt == 0) { + //we can only guess + rt= bytesWritten/DEFAULT_BITRATE; + } + } + threadUnlock(); + return rt; +} + +ULONG AudioPlayer::getSonglen(){ + ULONG rt=0; + threadLock(); + const DemuxerAudio::vbrInfo * vbr=demuxer->getVBRINfo(); + if (vbr) rt=vbr->fileSeconds; + else { + if (lenInBytes != 0) { + rt=demuxer->getSecondsFromLen(lenInBytes); + if (rt == 0) { + //we can only guess + rt= lenInBytes/DEFAULT_BITRATE; + } + } + } + threadUnlock(); + return rt; +} + +int AudioPlayer::getCurrentBitrate(){ + int rt=DEFAULT_BITRATE; + threadLock(); + const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo(); + if (info) rt=info->bitRate; + threadUnlock(); + return rt; +} diff --git a/audioplayer.h b/audioplayer.h new file mode 100644 index 0000000..727e035 --- /dev/null +++ b/audioplayer.h @@ -0,0 +1,203 @@ +/* + Copyright 2004-2006 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef AUDIOPLAYER_H +#define AUDIOPLAYER_H + +#include +#include +#ifndef WIN32 +#include +#endif +#include + +#include "audio.h" +#include "remote.h" +#include "vdr.h" +#include "callback.h" +#include "message.h" +#include "messagequeue.h" +#include "thread.h" +#include "afeed.h" +#include "timerreceiver.h" + +#ifdef WIN32 +#include "threadwin.h" +#else +#include "threadp.h" +#endif + + + +class View; +class DemuxerAudio; + + +class AudioPlayer : public Thread_TYPE, public Callback, public TimerReceiver +{ + public: + //the instance method to create the + //player instance - only call thie for + //the first time in the main thread (no lock!) + //later you can change the view the player is connect with + //a view that "goes" should call getInstance(NULL,false) to detach + //the last media view should shutdown the player + static AudioPlayer * getInstance(View * frontend, bool create=true); + //start the player thread + void run(); + + //is the player still running? + bool isPlayerRunning(); + + void shutdown(); + + //each of the commands works as a request + //only after getSequence returned the same sequence as those commands this is + //handled by the player and getError is valid + int play(const char * filename); + //stop the player without shutting it down + int stop(); + int pause(); + int unpause(); + int fastForward(); + int fastBackward(); + int jumpToPercent(double percent); + int skipForward(int sec); + int skipBackward(int sec); + + //info functions for frontend + //delete provided String afterwards + //get the played title from ID3 Tag - or NULL + char * getTitle(); + //get info + //multi line String containing ID3 infos + char * getID3Info(); + + //wait for a particular sequence to be handled + //timeout in s, returnes current sequence, -1 on timeout + int waitForSequence(int timeout, int sequence); + int getSequence(); + + //info functions + + //get current position in s + ULONG getCurrentTimes(); + //get song len in s + ULONG getSonglen(); + //current bitrate + int getCurrentBitrate(); + + + virtual void call(void * caller); + + UCHAR getState() ; + + const static UCHAR S_PLAY = 1; + const static UCHAR S_PAUSE = 2; + const static UCHAR S_POSITION = 3; + //player finished a song - no reset, next will follow + const static UCHAR S_DONE=5; + const static UCHAR S_STOP = 6; + const static UCHAR S_ERROR = 8; + const static UCHAR S_FF = 9; + const static UCHAR S_BACK = 10; + + //message parameters for frontend messages + const static ULONG CONNECTION_LOST=1; + const static ULONG STREAM_END=2; + const static ULONG STREAM_ERR=3; + const static ULONG STATUS_CHANGE=4; //some info has been changed + const static ULONG NEW_SONG=5; //some info has been changed + const static ULONG SHORT_UPDATE=6; //timer info update + + virtual void timercall(int reference); + + protected: + void threadMethod(); + void threadPostStopCleanup(); + + private: + //to guess lengthes if the demux does not know + const static ULONG DEFAULT_BITRATE=128000; + AudioPlayer(View *frontend); + virtual ~AudioPlayer(); + static AudioPlayer * instance; + bool playerRunnig; + Audio* audio; + VDR* vdr; + Log* logger; + + DemuxerAudio *demuxer; + AFeed afeed; + + //feeder control + const static int FEEDER_START=1; + const static int FEEDER_STOP=2; + const static int FEEDER_PAUSE=3; + const static int FEEDER_UNPAUSE=4; + void controlFeeder(int action) ; + int feederState; + + //synchronized get/set methods for states + int setRequestedState(UCHAR st); + void setState(UCHAR st); + + //to be called from within the thread + UCHAR checkState(); + + //variables used by the thread + UINT thisWrite; + UINT thisRead; + bool running; + + UCHAR *threadBuffer; + UCHAR state; + UCHAR requestState; + ULONG streampos; + ULONG lenInBytes; + ULONG bytesWritten; + ULONG requestedStreampos; + + int skipfactor; + //the buffer len in bytes + const static int BUFLEN=50*1024; + View *frontend; + //requested sequence + int requestedSequence; + //handled sequence + int sequence; + //startplay sequence + int currentPlaySequence; + //sequence that is changed for each new filename + int playSequence; + + char * filename; + int openFile(); + void handleVDRerror(); + + void sendFrontendMessage(ULONG para); + + void waitTimed(int ms); + + +}; + +#endif + diff --git a/demuxeraudio.cc b/demuxeraudio.cc new file mode 100644 index 0000000..e2f5244 --- /dev/null +++ b/demuxeraudio.cc @@ -0,0 +1,1223 @@ +/* + Copyright 2006 Mark Calderbank, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "demuxeraudio.h" +#include "audio.h" +#include "i18n.h" + +#define HDRBYTE1 0xff +#define HDRBYTE2 0xe0 +#define HDRBYTE2MASK 0xe0 + + + +class PacketBuffer { + + public: + PacketBuffer(Stream *as,UCHAR strtype) { + log=Log::getInstance(); + audio=as; + streamtype=strtype; + newStream(); + } + //just handle the data (do not deal with headers) + int putInternal(UCHAR* buf,int len); + void reset(){ + partPacket=0; + bytesWritten=0; + framelen=DemuxerAudio::PACKET_SIZE; + } + void newStream() { + reset(); + numpackets=0; + numbytes=0; + skipfactor=0; + numskip=0; + } + bool bufferFull() { + return (partPacket>=framelen); + } + //can we write a new packet? + bool bufferEmpty() { + return partPacket==0; + } + //only set this, if buffer empty + //otherwise ignored! + bool setFramelen(int len) { + if (! bufferEmpty() ) return false; + if (len > (int)DemuxerAudio::PACKET_SIZE) return false; + framelen=len; + return true; + } + //how much bytes do we need to fill the packet? + int bytesMissing() { + return framelen-partPacket; + } + int getFramelen() { + return framelen; + } + void setSkipFactor(int factor) { + skipfactor=factor; + numskip=0; + } + private: + void packetWritten() { + numbytes+=framelen; + //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written packet %ld l=%d, bytes %ld",numpackets,framelen,numbytes); + numpackets++; + reset(); + } + bool doSkip(); + UCHAR store[DemuxerAudio::PACKET_SIZE]; // Storage for partial packets + int partPacket; // Length of partial packet stored from previous put() + int bytesWritten; //if they are !=0 and != framelength the stream is full... + int framelen; + Log * log; + Stream * audio; + UCHAR streamtype; + //global counters + ULONG numpackets; + ULONG numbytes; + int skipfactor; + int numskip; +}; + + +DemuxerAudio::DemuxerAudio(int p_vID, int p_aID) +{ + inSync=false; + isStarting=true; + log=Log::getInstance(); + readHeaders=0; + streamtype=Audio::MP3; + buffer=new PacketBuffer(&audiostream,streamtype); +// buffer=new PacketBuffer(&teststream,streamtype); + globalBytesWritten=0; + id3=NULL; + info=NULL; + vbr=NULL; + reset(); +} + +DemuxerAudio::~DemuxerAudio() { + delete buffer; + if(info) delete info; + if(id3) delete id3; + if (vbr) delete vbr; +} + +void DemuxerAudio::flush() +{ + Demuxer::flushAudio(); + buffer->newStream(); + tmpFill=0; +} + +void DemuxerAudio::reset() { + buffer->newStream(); + tmpFill=0; + readHeaders=0; + outOfSync=0; + globalBytesWritten=0; + if (id3) delete id3; + id3=NULL; + if (info) delete info; + info=NULL; + if (vbr) delete vbr; + vbr=NULL; + inSync=false; + hasHdrInfo=false; + hasVBRInfo=false; + isStarting=true; + hdrBitrate=128000; + hdrSamplingRate=44100; + avrBitrate=0; + hdrFramelen=0; + isStarting=true; +} + +int DemuxerAudio::scan(UCHAR *buf, int len) +{ + //no differend pids here + return 0; +} + +void DemuxerAudio::setVID(int p_vID) +{ +} + +void DemuxerAudio::setAID(int p_aID) +{ +} + +static char * id3_1_genre[] = { + "Blueshhh", + "Classic Rock", + "Country", + "Dance", + "Disco", + "Funk", + "Grunge", + "Hip-Hop", + "Jazz", + "Metal", + "New Age", + "Oldies", + "Other", + "Pop", + "R&B", + "Rap", + "Reggae", + "Rock", + "Techno", + "Industrial", + "Alternative", + "Ska", + "Death Metal", + "Pranks", + "Soundtrack", + "Euro-Techno", + "Ambient", + "Trip-Hop", + "Vocal", + "Jazz+Funk", + "Fusion", + "Trance", + "Classical", + "Instrumental", + "Acid", + "House", + "Game", + "Sound Clip", + "Gospel", + "Noise", + "AlternRock", + "Bass", + "Soul", + "Punk", + "Space", + "Meditative", + "Instrumental Pop", + "Instrumental Rock", + "Ethnic", + "Gothic", + "Darkwave", + "Techno-Industrial", + "Electronic", + "Pop-Folk", + "Eurodance", + "Dream", + "Southern Rock", + "Comedy", + "Cult", + "Gangsta", + "Top 40", + "Christian Rap", + "Pop/Funk", + "Jungle", + "Native American", + "Cabaret", + "New Wave", + "Psychadelic", + "Rave", + "Showtunes", + "Trailer", + "Lo-Fi", + "Tribal", + "Acid Punk", + "Acid Jazz", + "Polka", + "Retro", + "Musical", + "Rock & Roll", + "Hard Rock" +}; + + + +static int bitrateTable[16][5]={ +/* L1,L2,L3,2L1,2L2 */ +/*0000*/ {-1,-1,-1,-1,-1}, +/*0001*/ {32,32,32,32,8}, +/*0010*/ {64,48,40,48,16}, +/*0011*/ {96,56,48,56,24}, +/*0100*/ {128,64,56,64,32}, +/*0101*/ {160,80,64,80,40}, +/*0110*/ {192,96,80,96,48}, +/*0111*/ {224,112,96,112,56}, +/*1000*/ {256,128,112,128,64}, +/*1001*/ {288,160,128,144,80}, +/*1010*/ {320,192,160,160,96}, +/*1011*/ {352,224,192,176,112}, +/*1100*/ {384,256,224,192,128}, +/*1101*/ {416,320,256,224,144}, +/*1110*/ {448,384,320,256,160}, +/*1111*/ {-1,-1,-1,-1,-1} }; + +static int samplingRateTable[4][3]={ +/*00*/ {44100,22050,11025}, +/*01*/ {48000,24000,12000}, +/*10*/ {32000,16000,8000}, +/*11*/ {-1,-1,-1}}; + +//max 7 char! +static const char * mpegString(UCHAR code) { + switch(code) { + case 0: + return "MPEG2.5"; + case 1: + return "RESERV"; + case 2: + return "MPEG 2"; + case 3: + return "MPEG 1"; + } + return "UNKNOWN"; +} + +static const char * layerString(UCHAR code) { + switch(code) { + case 0: + return "Layer reserved"; + case 1: + return "Layer III"; + case 2: + return "Layer II"; + case 3: + return "Layer I"; + } + return "Layer UNKNOWN"; +} +/** + * parse an id3 Header + * provided by Brian Walton + * @returns -1 of nothing found + */ + +int DemuxerAudio::id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame) +{ + if (buf[0] < 0x20 || buf[1] < 0x20 || buf [2] < 0x20 ) return -1; + frame->size = (buf[4] & 0x7F) << 21 | (buf[5] & 0x7F) << 14 | (buf[6] & 0x7F) << 7 | (buf[7] & 0x7F); + if (frame->size == 0) return -1; + //TODO. clearify flags against: + //http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b + frame->flags.tagAlterPreserv = (buf[8] & 0x80) >> 7; + frame->flags.filelterPreserv = (buf[8] & 0x40) >> 6; + frame->flags.readOnly = (buf[8] & 0x20) >> 5; + frame->flags.groupId = (buf[9] & 0x20) >> 5; + frame->flags.compression = (buf[9] & 0x80) >> 7; + frame->flags.encryption = (buf[9] & 0x40) >> 6; + frame->flags.unsync = 0; + frame->flags.dataLen = 0; + return 0; +} + + /** + * parse an id3 Header + * provided by Brian Walton + * @returns -1 of nothing found + */ + +int DemuxerAudio::id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame) +{ + if (buf[0] < 0x20 || buf[1] < 0x20 || buf[2] < 0x20) return -1; + frame->size = (buf[3] & 0x7F) << 14 | (buf[4] & 0x7F) << 7 | (buf[5] & 0x7F); + if (frame->size == 0) return -1; + return 0; +} + + +//fill an id3tag from a frame payload +//http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b +//http://id3.org/id3v2-00 +static struct tagid { + const char * bytes; + int index; +} knownFrames[]= { + //ID3V2.3 + {"TIT2",1}, //title + {"TPE1",2}, //artist + {"TCON",3}, //genre + {"TRCK",6}, //track + {"TYER",4}, //year + {"TALB",5}, //album + {"TCOM",7}, //composer + {"COMM",8}, //comment + //Text encoding $xx + //Language $xx xx xx + //Short content descrip. $00 (00) + //The actual text + //ID3V2.0 + {"TT2",1 }, + {"TP1",2 }, + {"TCM",7 }, + {"TCO",3 }, //(genreNumber) + {"TAL",5 }, + {"TRK",6 }, + {"TYE",4 }, + {"COM",8 } +}; +#define NUMKNOWN (sizeof(knownFrames)/sizeof(knownFrames[0])) + +/*fill in infos + from an ID3 V2.x, V2.3 frame into the tags structure + frameData must point to the header + framelen is the len without header (10 Bytes for V23, 6 Bytes for v2x) + */ + +#define MAXLEN(tagtype) ((UINT)frameLentagtype)-1?(UINT)frameLen:sizeof(tag->tagtype)-1) +bool DemuxerAudio::fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23) { + int tl=v23?4:3; + int tagIndex=-1; + if (tag == NULL) return false; + if (frameLen < 2) return false; + for (UINT i=0;i< NUMKNOWN;i++) { + if(strncmp((char *)frameData,knownFrames[i].bytes,tl) == 0) { + tagIndex=knownFrames[i].index; + break; + } + } + if (tagIndex < 0) return false; + UCHAR encoding=*(frameData+dataOffset); + dataOffset++; + frameLen--; + if (encoding != 0) { + log->log("DemuxerAudio",Log::DEBUG,"unknown encoding for tag %d, tagid %s",encoding, + knownFrames[tagIndex].bytes); + return false; + } + switch(tagIndex) { + case 1: //title + strncpy(tag->title,(char*)(frameData+dataOffset),MAXLEN(title)); + tag->title[MAXLEN(title)]=0; + break; + case 2: //artist + strncpy(tag->artist,(char*)(frameData+dataOffset),MAXLEN(artist)); + tag->artist[MAXLEN(artist)]=0; + break; + case 3: //genre + { + UCHAR * st=frameData+dataOffset; + int genre=0; + if (*st=='(') { + genre=atoi((const char *)(st+1)) && 31; + st=(UCHAR *)id3_1_genre[genre]; + } + strncpy(tag->genre,(char*)st,MAXLEN(genre)); + tag->genre[MAXLEN(genre)]=0; + break; + } + case 4: //year + strncpy(tag->year,(char *)(frameData+dataOffset),MAXLEN(year)); + tag->year[MAXLEN(year)]=0; + break; + case 5: //album + strncpy(tag->album,(char *)(frameData+dataOffset),MAXLEN(album)); + tag->album[MAXLEN(album)]=0; + break; + case 6: //track + strncpy(tag->track,(char *)(frameData+dataOffset),MAXLEN(track)); + tag->track[MAXLEN(track)]=0; + break; + case 7: //composer + strncpy(tag->composer,(char *)(frameData+dataOffset),MAXLEN(composer)); + tag->composer[MAXLEN(composer)]=0; + break; + case 8: //comment + strncpy(tag->comment,(char *)(frameData+dataOffset),MAXLEN(comment)); + tag->comment[MAXLEN(comment)]=0; + break; + default: + return false; + } + + return true; +} + +/** + * parse an id3 Header + * based on code provided by Brian Walton + * @returns -1 of nothing found + * otherwise the id3 info is filled + */ + +int DemuxerAudio::parseID3V2(UCHAR *data, int len) { + int debug=0; + UCHAR * start=data; + id3_header id3header; + id3_frame id3frame; + id3_tag * id3tag=NULL; + //len = read(fd, data, 10); + if (len < 10) { + delete id3tag; + return -1; + } + len-=10; + if(data[0]=='I' && data[1]=='D' && data[2]=='3') + { + id3tag=new id3_tag(); + id3header.major = data[3]; + id3header.minor = data[4]; + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 V2.%d.%d found\n", id3header.major, id3header.minor); + id3header.flags.unsynchronisation = (data[5] & 0x80)>>7; + id3header.flags.extended_header = (data[5] & 0x40)>>6; + id3header.flags.experimental = (data[5] & 0x20)>>5; + id3header.flags.footer = (data[5] & 0x10)>>4; + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Unsynchronisation flag: %d\n", id3header.flags.unsynchronisation); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header flag: %d\n", id3header.flags.extended_header); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Experimental indicator flag: %d\n", id3header.flags.experimental); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Footer present flag: %d\n", id3header.flags.footer); + id3header.size = (data[6] & 0x7F) << 21 | (data[7] & 0x7F) << 14 | (data[8] & 0x7F) << 7 | (data[9] & 0x7F); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 Size: %d\n", id3header.size); + data=start+10; + if (len <= id3header.size) { + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"header size to big %d, only %d bytes available\n",id3header.size,len); + delete id3tag; + return -1; + } + if (id3header.flags.extended_header) + { + int extended_hdr_hdr=4; //still to be discussed (id3.org...) + //read extended header size + if (len < extended_hdr_hdr) { + if (debug) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended header found but cannot read\n"); + delete id3tag; + return -1; + } + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"remaining %d chars after extended hdr hdr\n", len); + id3header.extended_header.size = (data[0] & 0x7F) << 21 | (data[1] & 0x7F) << 14 | (data[2] & 0x7F) << 7 | (data[3] & 0x7F); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header size: %d\n", id3header.extended_header.size); + if (len <= id3header.extended_header.size+extended_hdr_hdr) { + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended Header to big, only %d bytes available\n",len); + delete id3tag; + return -1; + } + //lseek(fd, id3header.extended_header.size - 6, SEEK_CUR); + data+=id3header.extended_header.size+extended_hdr_hdr; + len-=id3header.extended_header.size+extended_hdr_hdr; + } + //set the end of the header + UCHAR * eob=start+id3header.size+10; + bool readNext=true; + while (data < eob && readNext) + { + //skip over some padding - found this in lame MCDI tag... + if (*data == 0) { + data++; + continue; + } + readNext=false; + switch(id3header.major) + { + case 2: //ID3 V2.2.x + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.2 frame, %d : %c %c %c\n", data-start,*data,*(data+1),*(data+2)); + if (data + 6 >= eob) + { + break; + } + if (id3_2_2_FrameParse(data, &id3frame) < 0) + { + break; + } + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame size: %d\n", id3frame.size); + fillId3Tag(id3tag,data,id3frame.size,6,false); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + 6 +1); + data+=6+id3frame.size; + readNext=true; + break; + case 3: //ID3 V2.3.x + { + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.3 frame, %d : %c %c %c %c\n", data-start, + *data,*(data+1),*(data+2),*(data+3)); + if (data + 10 >= eob) + { + break; + } + if (id3_2_3_FrameParse(data, &id3frame) <0) + { + break; + } + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame size: %d\n", id3frame.size); + int dataOffset=10; + if (id3frame.flags.groupId) + { + dataOffset++; + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame group: %d\n", data[dataOffset]); + } + if (id3frame.flags.compression) + { + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame compressed: %d\n", id3frame.flags.compression); + } + if (id3frame.flags.encryption) + { + dataOffset+=1; + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame encryption method: %d\n", data[dataOffset]); + } + fillId3Tag(id3tag,data,id3frame.size-dataOffset+10,dataOffset,true); + if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + dataOffset +1); + data+=10+id3frame.size; + readNext=true; + break; + } + default: + //don't support this version + delete id3tag; + return -1; + } + } + + data=eob; + //store the found tag + if (id3) delete id3; + id3=id3tag; + return data-start; + } + return -1; +} + +/** + * parse an id3v1 Header + * based on code provided by Brian Walton + * @returns -1 of nothing found + * otherwise the id3 info is filled + */ +#define MEMCPY(type,len,offset) {int type##max=sizeof(tag->type)-1type)-1:len;strncpy(tag->type,(char*)&data[offset],type##max);tag->type[type##max]=0;} + +int DemuxerAudio::parseID3V1(UCHAR *data, int len) { + int debug=1; + if (len < 128) return -1; + if(data[0]=='T' && data[1]=='A' && data[2]=='G') + { + id3_tag * tag=new id3_tag(); + if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1 tag found\n"); + MEMCPY(title,30,3); + MEMCPY(artist,30,33); + MEMCPY(album,30,63); + MEMCPY(year,4,93); + if (data[125]==0 && data[126]!=0) + { //ID3 V1.1 + if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.1 tag\n"); + MEMCPY(comment,29,97); + sprintf(tag->track, "%d", data[126]); + } else { + if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.0 tag\n"); + MEMCPY(comment,30,97); + } + if (data[127] < sizeof(id3_1_genre)/sizeof(id3_1_genre[0])) + { + sprintf(tag->genre, id3_1_genre[data[127]]); + } + if (id3) delete id3; + id3=tag; + return 0; + } + return -1; +} + +//infos from http://www.multiweb.cz/twoinches/MP3inside.htm +int DemuxerAudio::parseVBR(UCHAR *data, int len) { + UCHAR *hdr=findHeader(data,len); + //we expect the header exactly here + if (hdr != data) return -1; + const static char * VBRHDR="Xing"; + int vbridpos=36; + UCHAR mpgtype=(data[1] & 0x18)>>3; + UCHAR chmode=(data[2] & 0xc0) >> 6; + UCHAR layer=(data[2] & 0x06) >>1; + if ( mpgtype == 3 && chmode == 11) vbridpos=21; + if ( mpgtype != 3 && chmode != 11) vbridpos=21; + if ( mpgtype != 3 && chmode == 11) vbridpos=13; + //check for the header ID + if (vbridpos+(int)strlen(VBRHDR)+4 >= len) { + Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header %d",len); + return -1; + } + for (int i=4;ilog("DemuxerAudio::parseVBR",Log::DEBUG,"garbage when searching VBR header at pos %d",i); + return -1; + } + } + if ( strncmp((char *)&data[vbridpos],VBRHDR,strlen(VBRHDR)) != 0) { + Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"no VBR header at pos %d",vbridpos); + return -1; + } + int framedata=vbridpos+strlen(VBRHDR); + int expectedLen=0; + //OK we should now have a valid vbr header + bool hasFramenum=data[framedata+3] & 1; + bool hasBytes=data[framedata+3] & 0x2; + bool hasTOC=data[framedata+3] & 0x4; + expectedLen+=8; + if (hasTOC) expectedLen+=100; + if (framedata+expectedLen > len) { + Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header data %d, expected %d", + len,framedata+expectedLen); + return -1; + } + if (!hasFramenum || ! hasBytes || ! hasTOC) { + //not usefull for us.. + Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"not all parts in VBR header - ignore"); + return -1; + } + framedata+=4; + if (vbr) delete vbr; + vbr=new vbrInfo(); + vbr->numFrames=data[framedata] << 24 | data[framedata+1]<<16| + data[framedata+2]<<8 |data[framedata+3]; + framedata+=4; + vbr->numBytes=data[framedata] << 24 | data[framedata+1]<<16| + data[framedata+2]<<8 |data[framedata+3]; + framedata+=4; + for (int ti=0;ti<100;ti++) { + vbr->table[ti]=data[framedata+ti]; + } + //compute file size in seconds + //should be (#of frames -1) *samplesPerFrame / sampleRate + //TODO: difference for Mono? + ULONG samplesPerFrame=384; //layer1 + if (layer != 3) samplesPerFrame=1152; + vbr->fileSeconds=(vbr->numFrames-1)*samplesPerFrame/hdrSamplingRate; + Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"successfully read VBR %ldbytes, %ld frames, %ldsec", + vbr->numBytes,vbr->numFrames,vbr->fileSeconds); + return hdrFramelen; +} + + + + + + + +UCHAR * DemuxerAudio::findHeader(UCHAR *buf, int len, bool writeInfo) { + while (len >= 3) //assume hdr+crc + { + UCHAR pattern=*buf; + buf++; len--; + if (pattern != HDRBYTE1 ) continue; + if ((*buf & HDRBYTE2MASK) != HDRBYTE2) continue; + if (readHeader((buf-1),4,writeInfo) != 0) continue; + return buf-1; + } + return NULL; +} + + + +int DemuxerAudio::readHeader(UCHAR * hbuf,int len,bool writeInfo) { + int curFramelen=0; + int curBitrate=0; + int curSamplingRate=0; + if (*hbuf != HDRBYTE1) return -1; + hbuf++; + if ((*hbuf & HDRBYTE2MASK) != HDRBYTE2) return -1; + UCHAR mpgtype=(*hbuf & 0x18)>>3; + if (mpgtype == 1) { + log->log("DemuxerAudio",Log::DEBUG,"header invalid mpgtype %s %i %i %i", + mpegString(mpgtype),*hbuf,*(hbuf+1),*(hbuf+2)); + return 1; + } + UCHAR layer=(*hbuf & 0x06) >>1; + //bool hasCRC=!(*hbuf & 1); + hbuf++; + UCHAR bitrateCode=(*hbuf & 0xf0) >>4; + UCHAR samplingCode=(*hbuf & 0x0c) >> 2; + bool padding=*hbuf & 0x02; + hbuf++; + //0 Stereo, 1 JointStereo, 2 Dual, 3 Mono + UCHAR chmode=(*hbuf & 0xc0) >> 6; + //UCHAR extension=(*hbuf & 0x30) >> 4; + + //layercode: 1-L3, 2-L2, 3-L1 + //columns 0,1,2 for MPEG1 + UCHAR bitrateColumn=3-layer; + if (bitrateColumn > 2) { + log->log("DemuxerAudio",Log::DEBUG,"header invalid layer %s %i %i %i", + layerString(layer),*(hbuf-2),*(hbuf-1),*hbuf); + return 1; + } + if (mpgtype != 3) bitrateColumn+=3; + if (bitrateColumn>4) bitrateColumn=4; + curBitrate=1000*bitrateTable[bitrateCode][bitrateColumn]; + UCHAR sampleRateColumn=0; + if (mpgtype == 10) sampleRateColumn=1; + if (mpgtype == 0) sampleRateColumn=2; + curSamplingRate=samplingRateTable[samplingCode][sampleRateColumn]; + if (curSamplingRate < 0 || curBitrate < 0) { + log->log("DemuxerAudio",Log::DEBUG,"header invalid rates br=%d sr=%d %i %i %i", + curBitrate,curSamplingRate,*(hbuf-2),*(hbuf-1),*hbuf); + return 1; + } + int padbytes=0; + if (padding) { + if (layer == 3) padbytes=4; + else padbytes=1; + } + if (layer == 3) { + //Layer 1 + //FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4 + curFramelen=(12*curBitrate/curSamplingRate+padbytes) * 4; + } + else { + //Layer 2/3 + //FrameLengthInBytes = 144 * BitRate / SampleRate + Padding + curFramelen=144*curBitrate/curSamplingRate+padbytes; + } + //the header itself + if (curFramelen < 32) { + log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d-invalid %i %i %i", + readHeaders,mpegString(mpgtype),layerString(layer), + curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf); + return 1; + } + if (writeInfo || isStarting){ + log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d %i %i %i", + readHeaders,mpegString(mpgtype),layerString(layer), + curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf); + if (info) delete info; + info=new mpegInfo(); + strcpy(info->mpegVersion,mpegString(mpgtype)); + info->mpegLayer=4-layer; + info->bitRate=curBitrate; + info->avrBitrate=curBitrate; + info->sampleRate=curSamplingRate; + const char *chmodStr=tr("Stereo"); + switch (chmode) { + case 1: chmodStr=tr("JointStero");break; + case 2: chmodStr=tr("Dual");break; + case 3: chmodStr=tr("Mono");break; + } + snprintf(info->info,sizeof(info->info)-1,"%s",chmodStr); + } + if (isStarting) avrBitrate=curBitrate; + isStarting=false; + readHeaders++; + //moving average F=0.005 + avrBitrate=avrBitrate+((5*(curBitrate-avrBitrate))/1024); + hdrBitrate=curBitrate; + hdrFramelen=curFramelen; + hdrSamplingRate=curSamplingRate; + hasHdrInfo=true; + return 0; +} + +int DemuxerAudio::findPTS(UCHAR* buf, int len, ULLONG* dest) +{ + //we have no PTS number ... + *dest=0; + return (findHeader(buf,len) != NULL)?1:0; +} + +bool PacketBuffer::doSkip() { + if (!bufferFull()) return false; + if (skipfactor == 0) return false; + numskip++; + if (numskip >= skipfactor) { + //sent at least always 2 packets + if (numskip > skipfactor) numskip=0; + return false; + } + packetWritten(); + return true; +} + +// just handle the real stream without dealing with the header +int PacketBuffer::putInternal(UCHAR * wbuf, int len) +{ + if (bufferFull()) { + if (doSkip()) return 0; + //we are still full - so try to write + int sent=audio->put(store+bytesWritten,framelen-bytesWritten,streamtype); + //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream (still full) pp=%d, framelen=%d, written=%d",sent,partPacket,framelen, bytesWritten ); + if (sent < (framelen - bytesWritten)) { + //packet still not written + bytesWritten+=sent; + return 0; + } + packetWritten(); + //let the demuxer come back with the rest - need to check header first + return 0; + } + if (partPacket+len >= framelen) { + //now we have at least a complete packet + int bytesConsumed=framelen-partPacket; + memcpy(store+partPacket,wbuf,bytesConsumed); + partPacket=framelen; + //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"stored packet %ld, length %d (last %d) for stream %p",numpackets,framelen,bytesConsumed,audio ); + if (doSkip()) return bytesConsumed; + int sent=audio->put(store,framelen,streamtype); + bytesWritten+=sent; + //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream",sent ); + if (bytesWritten < framelen) { + //still not completely written + return bytesConsumed; + } + packetWritten(); + //let the player come back... + return bytesConsumed; + } + //OK packet still not complete + if (len == 0) return 0; + int bytesConsumed=len; + memcpy(store+partPacket,wbuf,bytesConsumed); + partPacket+=bytesConsumed; + return bytesConsumed; +} + +/** + major entry for data from a player + the demuxer is either in the state headerSearch (packet written or + just at the beginning), writing garbage (inSync=false) or + handling data (none set) + A header is expected at the first byte after the previous packet - + otherwise we switch to garbage where we always search for a header + (but anyway provide the data to the underlying device - it's probably + more intelligent then we are... + We only loose a correct position display. + */ +int DemuxerAudio::put(UCHAR* wbuf, int len) +{ + //return audiostream.put(wbuf,len,streamtype); + int framelen=PACKET_SIZE; + UCHAR *hdr; + int bytesConsumed=0; + int oldBytes=0; + if (tmpFill != 0 || (buffer->bufferEmpty() && len < HDRLEN)) { + //OK we have to copy everything to the tmp buffer + int cp=(UINT)len<(PACKET_SIZE-tmpFill)?(UINT)len:(PACKET_SIZE-tmpFill); + memcpy(&tmpBuffer[tmpFill],wbuf,cp); + oldBytes=tmpFill; + tmpFill+=cp; + wbuf=tmpBuffer; + len=tmpFill; + //if there is no header here and our buffer + //is empty - just wait for the next header + if (len < HDRLEN && buffer->bufferEmpty()) { + log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten); + return cp; + } + } + while (bytesConsumed < len ) { + if (buffer->bufferFull()) { + //if this is the first part of the loop, try to write to the stream + if (bytesConsumed == 0) buffer->putInternal(wbuf,0); + //if the buffer is full, no need to continue + if (buffer->bufferFull()) break; + } + //either we are in a packet (buffer != full && buffer != empty) + //or we are searching a header + if (buffer->bufferEmpty()) { + if (len-bytesConsumed < HDRLEN) { + // we cannot still search + if (tmpFill != 0) { + //we are already working at the buffer + break; + } + memcpy(tmpBuffer,wbuf,len-bytesConsumed); + tmpFill=len-bytesConsumed; + log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten); + return len; + } + + int lastFramelen=hdrFramelen; + //if the header has been valid before and we are searching + //it should be here + int maxsearch=((len-bytesConsumed) < (int)PACKET_SIZE)?len-bytesConsumed:(int)PACKET_SIZE; + hdr=findHeader(wbuf,maxsearch); + if (hdr != NULL) { + //log->log("DemuxerAudio",Log::DEBUG,"header found at offset %d",hdr-wbuf); + } + //hdr now points to the new header + if (hdr != wbuf) { + //still at least bytes before the header + inSync=false; + outOfSync++; + int garbageBytes=(hdr!=NULL)?(hdr-wbuf):maxsearch; + //we consider the garbage now as an own packet + //we can consume at most our buffer + //basically the buffer should be empty here (we are + //in header search - and we fill up each garbage + + int nframesize=garbageBytes; + if (nframesize > (int)PACKET_SIZE) { + nframesize=(int)PACKET_SIZE; + } + if (! buffer->bufferEmpty()) { + log->log("DemuxerAudio",Log::WARN,"buffer not empty when having garbage to store"); + //at the end no big problem, we only write the remaining bytes that we have... + } + else { + buffer->setFramelen(nframesize); + } + log->log("DemuxerAudio",Log::DEBUG,"garbage found at packet %ld (bytes %ld) of length %d, " + "framelen set to %d (last fl=%d)", + readHeaders,globalBytesWritten,garbageBytes,buffer->getFramelen(),lastFramelen); + //hmm - we assume that he low level driver is more intelligent + //and give him the data "as is" + int written=buffer->putInternal(wbuf,garbageBytes); + globalBytesWritten+=written; + bytesConsumed+=written; + if (written != garbageBytes || hdr == NULL ) { + break; + } + //OK either all the "garbage" is written or + //we found the next header as expected + //continue with the next package + wbuf=hdr; + } + //we have to wait now until the buffer is + //free for the next package + if ( ! buffer->bufferEmpty()) return bytesConsumed; + //this is the place where we start a new packet + framelen=hdrFramelen; + if ( !buffer->setFramelen(framelen) ) { + log->log("DemuxerAudio",Log::DEBUG,"unable to set framelen should=%d, current=%d", + framelen,buffer->getFramelen()); + } + inSync=true; + } + //now we are surely within a packet + int written=buffer->putInternal(wbuf,len-bytesConsumed); + //update the status + globalBytesWritten+=written; + wbuf+=written; + bytesConsumed+=written; + if (written == 0) { + //stream is full + break; + } + //OK - handle the rest + } + if (bytesConsumed >= oldBytes) { + //if we consumed more than the old bytes - OK the buffer + //can be thrown away + tmpFill=0; + return bytesConsumed-oldBytes; + } + else { + //only consumed bytes from the buffer + if (bytesConsumed == 0) { + //restore the buffer + tmpFill=oldBytes; + return 0; + } + //shift bytes in buffer + for (int i=0;iavrBitrate=avrBitrate; + info->bitRate=hdrBitrate; + return info; +} + +const DemuxerAudio::vbrInfo * DemuxerAudio::getVBRINfo() { + return vbr; +} + +int DemuxerAudio::checkStart(UCHAR *b, int len) { + UCHAR *start=b; + int id3len=parseID3V2(b,len); + if (id3len > 0) { + char * str=id3->toString(); + log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 %d bytes of %d",id3len,len); + log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 found:%s",str); + delete str; + b+=id3len; + len-=id3len; + } + int vbrlen=parseVBR(b,len); + if (vbrlen > 0 ) { + hasVBRInfo=true; + b+=vbrlen; + len-=vbrlen; + } + UCHAR * hdr=findHeader(b,len,true); + if (hdr == NULL) return -1; + return hdr-start; +} + +int DemuxerAudio::checkID3(UCHAR *b, int len) { + if (len != 128) return -1; + int rt=parseID3V1(b,len); + if (rt >= 0) { + char * str=id3->toString(); + log->log("DemuxerAudio",Log::DEBUG,"parseID3V1 found:%s",str); + delete str; + } + return rt; +} + +bool DemuxerAudio::isSync() { + return inSync; +} + +UINT DemuxerAudio::getSyncErrors() { + return outOfSync; +} + +ULONG DemuxerAudio::getBytesPerSecond() +{ + ULONG bps=hdrBitrate; + if (! hasHdrInfo) return 0; + if (hdrBitrate != avrBitrate) { + //we seem to have vbr + if (hasVBRInfo) { + //vbr stuff TODO + bps=avrBitrate; + } + else { + //some guessing + bps=avrBitrate; + } + } + bps=bps/8; + return bps; +} + +ULONG DemuxerAudio::getSecondsFromLen(ULONG len) { + if (! hasHdrInfo) return 0; + if (vbr) { + //first find the index where we are between + //rough starting point: + ULONG idx=100*len/vbr->numBytes; + if (idx >= 100) idx=99; + ULONG idxPos=(vbr->table[idx]) * vbr->numBytes/256; + ULONG pbefore=idxPos; + ULONG pafter=idxPos; + //OK now we know whether we have to go up or down + if (idxPos > len) { + //down + while (idxPos > len && idx > 0) { + pafter=idxPos; + idx--; + idxPos=(vbr->table[idx]) * vbr->numBytes/256; + pbefore=idxPos; + } + //OK we are now before our postion + } + else { + //up + while (idxPos < len && idx < 100 ) { + pbefore=idxPos; + idx++; + idxPos=(vbr->table[idx]) * vbr->numBytes/256; + pafter=idxPos; + } + //after our position + if (idx > 0) idx --; + } + //idx is now the index before our position + //approximate between the 2 points + ULONG idxTime=idx * vbr->fileSeconds/100; + if (pafter == pbefore) return idxTime; + ULONG rt=idxTime+ (len-pbefore)* vbr->fileSeconds/(100 * (pafter-pbefore)) ; + if (rt > vbr -> fileSeconds) return vbr->fileSeconds; + if (rt < idxTime) return idxTime; + return rt; + } + else { + ULONG bps=getBytesPerSecond(); + if (bps == 0) return 0; + return len/bps; + } +} + +ULONG DemuxerAudio::positionFromSeconds(ULONG seconds) { + if (! hasHdrInfo) return 0; + if (vbr) { + ULONG idx=seconds*100/vbr->fileSeconds; + if (idx > 99) idx=99; + ULONG idxPos=vbr->table[idx] * vbr->numBytes/256; + ULONG idx2=idx; + if (idx < 99) idx2++; + ULONG nextPos=vbr->table[idx] * vbr->numBytes/256; + ULONG rt=idxPos+(nextPos-idxPos) * (seconds - idx * vbr->fileSeconds /256); + if (rt < idxPos) return idxPos; + if ( rt > vbr->numBytes) return vbr->numBytes; + return rt; + } + else { + ULONG bps=getBytesPerSecond(); + return bps*seconds; + } +} + +int id3_tag::stringlen(bool withTitle) const { + if (!withTitle) + return strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+ + strlen(track)+strlen(composer)+strlen(comment)+8+3+ + 90; //30 chars for each name... + else + return strlen(title)+strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+ + strlen(track)+strlen(composer)+strlen(comment)+8+3+ + 120; //30 chars for each name... +} +//create a string out of the id3 tag info +//delete this string afterwards if you did not provide the buffer +char * id3_tag::toString(char *b, int len, bool withTitle) const { + char *ta=tr("Artist"); +//char *tg=tr("Genre"); +//char *ty=tr("Year"); + char *tx=tr("Album"); +//char *to=tr("Composer"); +//char *tc=tr("Comment"); + char *tn=tr("Track"); + /* in the moment: + Title: + Artist: + Album: year - name + Track: + */ + if (b == NULL) { + len=stringlen(withTitle); + b=new char[len]; + } + const char * del=" - "; + if (strlen(year) == 0) del=""; + if (withTitle){ + char *tt=tr("Title"); + snprintf(b,len-1,"%s: %s\n%s: %s\n%s: %s%s%s\n%s: %s", + tt,title,ta,artist,tx,year,del,album,tn,track); + } + else { + snprintf(b,len-1,"%s: %s\n%s: %s%s%s\n%s: %s", + ta,artist,tx,year,del,album,tn,track); + } + b[len-1]=0; + return b; +} + +void DemuxerAudio::setSkipFactor(int factor) { + Log::getInstance()->log("DemuxerAudio",Log::DEBUG,"set skipfactor %d\n",factor); + buffer->setSkipFactor(factor); +} + diff --git a/demuxeraudio.h b/demuxeraudio.h new file mode 100644 index 0000000..95dbd11 --- /dev/null +++ b/demuxeraudio.h @@ -0,0 +1,172 @@ +/* + Copyright 2006 Mark Calderbank, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef DEMUXERAUDIO_H +#define DEMUXERAUDIO_H + +#include "demuxer.h" +#include "defines.h" +#include "id3.h" + +class PacketBuffer; +class DemuxerAudio : public Demuxer +{ + public: + typedef struct { + char mpegVersion[8]; //MPEG1/2/2,5 + int mpegLayer; //1,2,3 + int bitRate; + int avrBitrate; + int sampleRate; + char info[20]; //channel mode,... + } mpegInfo; + + typedef struct { + ULONG numFrames; + ULONG numBytes; + ULONG fileSeconds; + UCHAR table[100]; + } vbrInfo; + DemuxerAudio(int p_vID = 0, int p_aID = 0); + virtual ~DemuxerAudio(); + virtual void flush(); + virtual void reset(); + virtual int scan(UCHAR* buf, int len); + virtual int findPTS(UCHAR* buf, int len, ULLONG* dest); + virtual void setVID(int p_vID); + virtual void setAID(int p_aID); + virtual int put(UCHAR* buf, int len); + + //special functions for the audioplayer + bool isSync() ; + UINT getSyncErrors(); + + //set the skip factor + void setSkipFactor(int faktor); + + //how many bytes do we need at the file start + //ID3V2, VBR + some spare to detect a first header + static UINT headerBytes() { + return 4096; + } + //how many bytes do we need at the end + static UINT footerBytes() { + return 128; + } + //return a reference to an ID3 tag + //if not found (from checkStart or checkID3) + //NULL will be returned + const id3_tag * getId3Tag(); + const mpegInfo * getMpegInfo(); + const vbrInfo * getVBRINfo(); + + //check for a valid header at the beginning (and parse all we find) + //will return the offset in buffer where the first valid + //header was found (with potentially skipping ID3 and VBR) + //return -1 if nothing found + int checkStart(UCHAR * buffer, int len); + //check for ID3V1 (at the end) + //return 0 if found + int checkID3(UCHAR * buffer, int len); + + //return length if we can do this + //otherwise return 0 + ULONG getSecondsFromLen(ULONG len); + + //position within a file + //return 0 if we cannot + ULONG positionFromSeconds(ULONG seconds); + + //try to read the iD3 tag value (V2xx) and + //fill it into the ID3 store + bool fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23); + + const static UINT PACKET_SIZE=4096; + private: + /*max size of a packet + see http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm + assumptions: max br 448kb/s, min sample 16k + L2/L3 FrameLengthInBytes = 144 * BitRate / SampleRate + Padding + L2/3 28*144+8 -> 4040 + L1 FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4 + L1 (336+32)*4 = 1472 + */ + Log * log; + + int readHeader(UCHAR* buf, int len, bool writeInfo=false); + + //info flags for the player + bool inSync; //set if we read a valid header + UINT outOfSync; //counts each invalid read headers + bool hasHdrInfo; //if we have been able to successfully + //parse the first header + bool hasVBRInfo; //we have VBR and the necessary info + //infos from header + int hdrFramelen; + int hdrBitrate; + int hdrSamplingRate; + //a little bit guessing for VBR + int avrBitrate; + + int streamtype; + bool isStarting; + ULONG readHeaders; + ULONG globalBytesWritten; + + + //search for the start of a header + //return NULL if none found + UCHAR * findHeader(UCHAR * buf, int len, bool writeInfo=false); + //we need a buffer to be able to read at least a complete header + const static int HDRLEN=4; + UCHAR tmpBuffer[PACKET_SIZE+HDRLEN]; + UINT tmpFill; + + //compute the bytes/second from the BR info + //return 0 if not there; + ULONG getBytesPerSecond(); + + //parse ID3V2 at the beginning + //set info if there + //return length of header, -1 if not found + int parseID3V2(UCHAR *data, int len); + int id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame); + int id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame); + + //parse ID3V1 at the end + //set info if there, return -1 otherwise + int parseID3V1(UCHAR *data, int len) ; + + //parse a VBR header and set VBR info + //input has to point to firts MPEG header in file + //potentially after ID3V2 + int parseVBR(UCHAR *data, int len); + + + class PacketBuffer* buffer; + + id3_tag *id3; + mpegInfo *info; + vbrInfo *vbr; + + +}; + +#endif diff --git a/id3.h b/id3.h new file mode 100644 index 0000000..f1a9661 --- /dev/null +++ b/id3.h @@ -0,0 +1,92 @@ +/* + Copyright 2006 Brian Walton , Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef ID3TAGS_H +#define ID3TAGS_H + +typedef struct id3_flags_t +{ + int unsynchronisation; + int extended_header; + int experimental; + int footer; +} id3_flags; + +typedef struct extended_header_t +{ + int size; + int flag_bytes; + int flags; +} extended_header; + +typedef struct id3_header_t +{ + int major; + int minor; + id3_flags flags; + int size; + struct extended_header_t extended_header; +} id3_header; + +typedef struct id3_frame_flags_t +{ + int tagAlterPreserv; + int filelterPreserv; + int readOnly; + int groupId; + int compression; + int encryption; + int unsync; + int dataLen; +} id3_frame_flags; + +typedef struct id3_frame_t +{ + int size; + id3_frame_flags flags; +} id3_frame; + +typedef struct id3_tag_t +{ + public: + id3_tag_t() { + title[0]=0; + artist[0]=0; + genre[0]=0; + year[0]=0; + album[0]=0; + track[0]=0; + composer[0]=0; + comment[0]=0; + }; + int stringlen(bool withTitle=false) const; + //write to buffer, delete it afterwards (if you did not provide it) + char *toString(char * buf=NULL, int len=0,bool withTitle=false) const; + char title[50]; + char artist[50]; + char genre[50]; + char year[5]; + char album[50]; + char track[7]; + char composer[50]; + char comment[29]; +} id3_tag; + +#endif diff --git a/language-data.h b/language-data.h index d1ddc51..a59dcea 100644 --- a/language-data.h +++ b/language-data.h @@ -1057,7 +1057,288 @@ const I18n::tI18nPhrase I18n::Phrases[] = "", "86", }, - // End marker. + //Media Player + //we have a new main menu with this... + { "5. MediaPlayer", + "5. MedienWiedergabe", + "", + "", + "", + "", + "", + "87", + }, + { "6. Options", + "6. Einstellungen", + "6. Inställningar", + "6. Beállítások", + "6. Asetukset", + "6. Options", + "6. Instellingen", + "88", + }, + { "7. Reboot", + "7. Neustart", + "7. Starta om", + "7. Újraindítás", + "7. Uudelleenkäynnistys", + "7. Redemarrer", + "7. Herstarten", + "89", + }, + { "Loading", + "Laden", + "", + "", + "", + "", + "", + "90", + }, + { "rotate", + "Drehen", + "", + "", + "", + "", + "", + "91", + }, + { "No Media found", + "Keine Medien Datei gefunden", + "", + "", + "", + "", + "", + "92", + }, + { "no VDR connection", + "Keine Verbindung zum VDR", + "", + "", + "", + "", + "", + "93", + }, + { "Directory", + "Verzeichnis", + "", + "", + "", + "", + "", + "94", + }, + { "Format(px)", + "", + "", + "", + "", + "", + "", + "95", + }, + { "Filesize", + "Dateigröße", + "", + "", + "", + "", + "", + "96", + }, + { "Time", + "Zeit", + "", + "", + "", + "", + "", + "97", + }, + { "Rotation", + "Drehung", + "", + "", + "", + "", + "", + "98", + }, + { "Scale", + "Skalierung", + "", + "", + "", + "", + "", + "99", + }, + { "MpegInfo", + "", + "", + "", + "", + "", + "", + "100", + }, + { "FileName", + "Dateiname", + "", + "", + "", + "", + "", + "101", + }, + { "Playlist", + "Playlist", //Wiedergabeliste??? + "", + "", + "", + "", + "", + "102", + }, + { "unable to open audio file", + "kann die Audio Datei nicht öffnen", + "", + "", + "", + "", + "", + "103", + }, + { "Rand", + "Zuf." + "", + "", + "", + "", + "", + "104", + }, + { "unable to get media list", + "kann die Medienliste nicht laden", + "", + "", + "", + "", + "", + "105", + }, + { "JointStereo", + "JointStereo", + "", + "", + "", + "", + "", + "106", + }, + { "Stereo", + "Stereo", + "", + "", + "", + "", + "", + "107", + }, + { "Dual", + "Dual", + "", + "", + "", + "", + "", + "108", + }, + { "Mono", + "Mono", + "", + "", + "", + "", + "", + "109", + }, + { "Artist", + "Künstler", + "", + "", + "", + "", + "", + "110", + }, + { "Year", + "Jahr", + "", + "", + "", + "", + "", + "111", + }, + { "Genre", + "Genre", + "", + "", + "", + "", + "", + "112", + }, + { "Album", + "Album", + "", + "", + "", + "", + "", + "113", + }, + { "Title", + "Titel", + "", + "", + "", + "", + "", + "114", + }, + { "Track", + "Nummer", + "", + "", + "", + "", + "", + "115", + }, + { "Composer", + "Komponist", + "", + "", + "", + "", + "", + "116", + }, + { "Comment", + "Kommentar", + "", + "", + "", + "", + "", + "117", + }, + // End marker. { NULL } }; diff --git a/mark.h b/mark.h index 7dbc0d5..e904f96 100644 --- a/mark.h +++ b/mark.h @@ -21,10 +21,12 @@ #ifndef MARK_H #define MARK_H +#include #include #include "defines.h" +using namespace std; class Mark { @@ -34,4 +36,6 @@ class Mark int pos; }; +typedef vector MarkList; + #endif diff --git a/media.cc b/media.cc new file mode 100644 index 0000000..25b1ac5 --- /dev/null +++ b/media.cc @@ -0,0 +1,183 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "media.h" +#include "vdr.h" +#include + +Media* Media::recInfoFor = NULL; + + +Media::Media() +{ + start = 0; + displayName = NULL; + fileName = NULL; + index = -1; + markList = NULL; + mediaType=MEDIA_TYPE_UNKNOWN; +} + +Media::~Media() +{ + if (displayName) { delete[] displayName; displayName = NULL; } + if (fileName) { delete[] fileName; fileName = NULL; } + index = -1; // just in case + + if (markList && markList->size()) + { + for(UINT i = 0; i < markList->size(); i++) + { + delete (*markList)[i]; + } + markList->clear(); + Log::getInstance()->log("Media", Log::DEBUG, "Media destructor, marks list deleted"); + } + + if (markList) delete markList; +} + +ULONG Media::getTime() const +{ + return start; +} + +const char* Media::getDisplayName() const +{ + if (displayName) return displayName; + return fileName; +} + +const char* Media::getFileName() const +{ + return fileName; +} + +void Media::setTime(ULONG tstartTime) +{ + start = tstartTime; +} + +void Media::setMediaType(int mtype) +{ + mediaType=mtype; +} + +int Media::getMediaType() const +{ + return mediaType; +} + +void Media::setDisplayName(char* tDisplayName) +{ + if (displayName) delete[] displayName; + + displayName = new char[strlen(tDisplayName) + 1]; + if (displayName) strcpy(displayName, tDisplayName); +} + +void Media::setFileName(char* tFileName) +{ + if (fileName) delete[] fileName; + + fileName = new char[strlen(tFileName) + 1]; + if (fileName) strcpy(fileName, tFileName); +} + +char * Media::getTimeString(char * buffer) const { + if (! buffer) buffer=new char[TIMEBUFLEN]; + struct tm ltime; + time_t recStartTime = (time_t)getTime(); + struct tm *btime = localtime_r(&recStartTime,<ime); + if (btime && recStartTime != 0) { +#ifndef _MSC_VER + strftime(buffer,TIMEBUFLEN, "%0g/%0m/%0d %0H:%0M ", btime); +#else + strftime(buffer, TIMEBUFLEN, "%g/%m/%d %H:%M ", btime); +#endif + } + else { + snprintf(buffer,TIMEBUFLEN,"00/00/00 00:00 "); + } + return buffer; +} + +void Media::loadMarks() +{ + markList = VDR::getInstance()->getMarks(fileName); +} + + + +int Media::getPrevMark(int currentFrame) +{ + MarkList::reverse_iterator i; + Mark* loopMark = NULL; + + if (!markList || !markList->size()) return 0; + + for(i = markList->rbegin(); i != markList->rend(); i++) + { + loopMark = *i; + Log::getInstance()->log("Media", Log::NOTICE, "findprev:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame); + + if (loopMark->pos < currentFrame) + { + Log::getInstance()->log("Media", Log::NOTICE, "findprev:setting pos %i to jumpframe_target",loopMark->pos); + return loopMark->pos; + } + } + + // No previous mark + return 0; +} + +int Media::getNextMark(int currentFrame) +{ + MarkList::iterator i; + Mark* loopMark = NULL; + + if (!markList || !markList->size()) return 0; + + for(i = markList->begin(); i != markList->end(); i++) + { + loopMark = *i; + Log::getInstance()->log("Media", Log::NOTICE, "findnext:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame); + + if (loopMark->pos > currentFrame) + { + Log::getInstance()->log("Media", Log::NOTICE, "findnext:setting pos %i to jumpframe_target",loopMark->pos); + return loopMark->pos; + } + } + + // No next mark + return 0; +} + +bool Media::hasMarks() +{ + return (markList && markList->size()); +} + +MarkList* Media::getMarkList() +{ + return markList; +} diff --git a/media.h b/media.h new file mode 100644 index 0000000..994f777 --- /dev/null +++ b/media.h @@ -0,0 +1,90 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef MEDIA_H +#define MEDIA_H + +#include +#include +#include "defines.h" +#include "recinfo.h" +#include "mark.h" +#include "log.h" +#include "demuxer.h" + +/* media types form a bitmask + so you can add them to have > 1*/ +#define MEDIA_TYPE_DIR 1 +#define MEDIA_TYPE_AUDIO 2 +#define MEDIA_TYPE_VIDEO 4 +#define MEDIA_TYPE_PICTURE 8 +#define MEDIA_TYPE_UNKNOWN 0 + +#define MEDIA_TYPE_ALL (1+2+4+8) + + + +class Media +{ + public: + Media(); + ~Media(); + + void setTime(ULONG startTime); + void setDisplayName(char* displayName); + void setFileName(char* fileName); + void setMediaType(int mtype); + + ULONG getTime() const; + const char* getDisplayName() const; + const char* getFileName() const; + //return the time as a string + //if the user provides a buffer, this one is used, if NULL + //is given a new buffer is allocated + //caller must delete the buffer after usage! + char * getTimeString(char *buffer) const; + //length for the time display buffer + const static int TIMEBUFLEN=100; + int index; + int getMediaType() const; + + void loadMarks(); + int getPrevMark(int currentFrame); + int getNextMark(int currentFrame); + bool hasMarks(); + MarkList* getMarkList(); + + + private: + ULONG start; + char* displayName; + char* fileName; + int mediaType; + + // I only want 1 RecInfo loaded at a time + // if (recInfoFor == this) then recInfo is valid + // else delete recInfo and reload for this recording + static Media* recInfoFor; + + MarkList* markList; +}; +typedef vector MediaList; + +#endif diff --git a/objects.mk b/objects.mk index e927708..d9eb4c2 100644 --- a/objects.mk +++ b/objects.mk @@ -15,4 +15,6 @@ OBJECTS1 = command.o log.o tcp.o dsock.o thread.o timers.o i18n.o mutex.o \ widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o \ woptionbox.o wtextbox.o wwss.o \ fonts/helvB24.o fonts/helvB18.o \ - remote.o led.o mtd.o video.o audio.o osd.o surface.o + remote.o led.o mtd.o video.o audio.o osd.o surface.o \ + vmedialist.o media.o vpicture.o vpicturebanner.o \ + vaudioplayer.o audioplayer.o demuxeraudio.o diff --git a/player.cc b/player.cc index b47ef45..c0578af 100644 --- a/player.cc +++ b/player.cc @@ -671,6 +671,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame) startup = true; audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); audio->systemMuteOff(); video->reset(); demuxer->reset(); @@ -769,6 +770,7 @@ void Player::restartAtFrame(ULONG newFrame) video->stop(); video->reset(); audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); demuxer->flush(); demuxer->seek(); currentFrameNumber = newFrame; @@ -913,6 +915,7 @@ void Player::threadMethod() // if execution gets to here, threadFeedScan hit the start, go to play mode state = S_PLAY; audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); demuxer->flush(); demuxer->seek(); demuxer->setFrameNum(currentFrameNumber); diff --git a/playerradio.cc b/playerradio.cc index 16c74c0..ee1a788 100644 --- a/playerradio.cc +++ b/playerradio.cc @@ -367,6 +367,7 @@ void PlayerRadio::switchState(UCHAR toState, ULONG jumpPacket) startup = true; audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); audio->systemMuteOff(); demuxer->reset(); if (isRecording) @@ -438,6 +439,7 @@ void PlayerRadio::restartAtPacket(ULONG newPacket) afeed.stop(); threadStop(); audio->reset(); + audio->setStreamType(Audio::MPEG2_PES); demuxer->flush(); currentPacketNumber = newPacket; demuxer->setPacketNum(newPacket); diff --git a/readme_media.txt b/readme_media.txt new file mode 100644 index 0000000..65af5e5 --- /dev/null +++ b/readme_media.txt @@ -0,0 +1,120 @@ +Media PLayer Functions +Author: Andreas Vogel (andvogel@online.de) +Version: 2007/03/14 + +Basics: +Adding some media player functions to VOMP. +This should enable VOMP to: + - work as a picture viewer (for digicam pictures) + - play audio (at least mp3) + - play (mpeg) video +For me the priorities are like above, i.e. Prio 1 for the (jpeg) pictures, afterwards +audio, later video. +It requires some extensions on the server side (of course). + +First Version (delivered 2007/03/14): + Basic picture viewer functions + You can add a list of directories to vomp.conf that will be used as base dirs to + find media files. + Example: + [Media] + Dir.1=/home/Andreas/pictest + Dir.2=/home/video + Dir.3=/home/bilder + Dir.4=/media + + You can have up to 10 directories there. + The recognition of media types is based on extensions today. In the moment it recognizes: + JPEG,jpeg,JPG,jpg as Jpeg + mp3,MP3 as mp3 (not played yet) + + In the vom main menu a new item is added (5 MediaPlayer). + This will open a menu with the configured base dirs (/ if none configured). + SELECTOR WINDOW + You can traverse through the dirs with the normal keys. + When pointing to a media file (currently only jpeg) you can activate the viewer with + [OK] (normal view) or [PLAY] (slide show). + PICTURE VIEWER + When viewing a picture, this gets loaded from the server (currently still a little bit slow), + scaled (jpeg libs allows for 1/2, 1/4 and 1/8 only - but seems to be OK for many photos), and + displayed. With [OK] you can toggle the status line, With [RED] you can rotate the picture clockwise, + [GREEN] brings up an info box with some picture data. [PLAY] starts the slideshow, |<- and ->| as + well as [UP], [DOWN] travers trough all pictures within the directory. + << and >> will slower/fasten the slide show (time getting displayed in the status line). II will + pause the slide show [STOP] will pause and reset time to 5s. + +Version 2007/03/23 + Added playing audio files. + In the moment playing is (on the server side) limited to mp3 (MP3) files. The client side is + prepared to play all MPEG1 files - although only tested for MP3 CBR/VBR stereo, 44,1kBit. + Player control is similar to the other available players (recordings). + You can use 0...9 to position by x0 percent in the file. |<- and ->| will skip each 10s back/ forward. + In principle >> does also work - but seems not to be very usefull in the moment. + [UP] and [DOWN] will move backward/forward in a play list. + You can start playing all MP3's in a dir from the media view by pressing [PLAY], a single file by pressing OK. + The player window will disappear app. 30s after the last key press. + The players parses ID3V1,V2.2,V2.3 (thanks to Walt for the supported code) and displays selected info. + It also parses VBR headers ("Xing") and uses this info for display and movements. + If the player cannot parse the files, it tries to play them anyway, it will (for the display) assume + 128kBit/s CBR. + Currently the player uses the same media stream like the picture viewer -so no parallel viewing and + listening is possible (will be changed soon). + + The media view has some minor changes: + Added the option to sort also randomly (e.g. for playing audio files). + Directories are marked with [ ] in the display, they are always displayed on top. + + Additionally some bug fixes (thanks to muellerph and Walt for reporting them) for error handling. + + +Development Issues +1. Major Changes +1.1 Surface +To be able to handle deeply nested directories, the number of views has been extended to 120. This made +dynamic surface handling necessary. Therefore I changed the surface member. Box has a virtual +getSurface method, that gets implemented by Widget and View. The View really has a surface member, +Widgets have a parent (Box) member and query this for the surface. +At the View there is a new unmap() method, that releases the surface. The next getSurface will +acquire a new one and will redraw the view (calling it's draw method). This has to be used with +care... +1.2. Jpeg +The WJpeg has been extended to be able to load arbitrary jpeg data via a reader class. It will +scale the jpeg if possible to fit onto the screen and will rotate it on demand. +1.3. VDR and server +VDR has new commands to get a media list, to initialize picture data transfer and to transfer +picture blocks (used a new command there for possible parallel action with audio later on...). +On the server side the media list reading has been added and a simple file reader to retrieve the +Jpeg data. +1.4. extensions +New classes for Media, MediaList, VMediaList, VPicture, VPicturebanner, VPictureinfo. +2007/03/23 +New classes for audioplayer, demuxeraudio, vaudioplayer + +2. Problems +2.1. setting up devenv needed some changes... +2.2. -Wno-format was necessary to allow for %g for the date display +2.3. merging with today's CVS: +I encountered big problems with the new timer handling. Dangerous situations: +- timer retrieved from list, waiting to fire but destination gone (canceled timer), + nextTimer will not be NULL there... - so need a add. check +- cancel called within the timer firing - deadlock today, could delete timer from list.. +OK -I added the patch. +2007/03/23 + +3. Open/Next Steps +For me the next important steps would be: +Audio Playing - basicly done 2007/03/23 + + Performance Improvements +1. have the transfer being handled in a separate thread (would save 50% on large files) +2. make a scaling (+pot. decoding) on the server side, could lower the sizes and fit + better to the screen (but needs some jpeg stuff on the server - I will try to grep from + mediamvp - worked on this already...). +Picture Functions +1. Colour handling - we would need some colour profile for appropriate picture + viewing... +4. sorting - multiple sort options for media lists.. + done 2007/03/23 + +Translations +English and German are there... diff --git a/remote.cc b/remote.cc index e1abbfc..8da388b 100644 --- a/remote.cc +++ b/remote.cc @@ -224,6 +224,7 @@ void Remote::InitHWCListwithDefaults() translist[RECORD] = RECORD; translist[STOP] = STOP; translist[PAUSE] = PAUSE; + translist[PLAY] = PLAY; translist[SKIPBACK] = SKIPBACK; translist[SKIPFORWARD] = SKIPFORWARD; diff --git a/vaudioplayer.cc b/vaudioplayer.cc new file mode 100644 index 0000000..fbb9829 --- /dev/null +++ b/vaudioplayer.cc @@ -0,0 +1,594 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "vaudioplayer.h" +#include "audioplayer.h" +#include + +VAudioplayer::VAudioplayer(VMediaList *p) +{ + parent=p; + Video* video = Video::getInstance(); + create(video->getScreenWidth(),video->getScreenHeight()); + banner=NULL; + fullname=NULL; + filename=NULL; + ftime=0; + playall=false; + currentMedia=NULL; + justPlaying=false; + numentries=p->getNumEntries(MEDIA_TYPE_AUDIO); + bannerTime=0; + + barBlue.set(0, 0, 150, 150); + +} + +AudioPlayer * VAudioplayer::getPlayer(bool createIfNeeded) +{ + AudioPlayer* rt=AudioPlayer::getInstance(this,false); + if (! createIfNeeded && rt == NULL) return NULL; + if (rt == NULL) { + rt=AudioPlayer::getInstance(this); + rt->run(); + } + return rt; +} + +VAudioplayer::~VAudioplayer() +{ + if (banner) ViewMan::getInstance()->removeView(banner); + if (fullname) delete fullname; + if (filename) delete filename; + Timers::getInstance()->cancelTimer(this,1); + Timers::getInstance()->cancelTimer(this,2); + Timers::getInstance()->cancelTimer(this,3); + //TODO leave this to medialist and stop only... + if (getPlayer(false)) { + AudioPlayer::getInstance(NULL,false)->shutdown(); + } +} + + + +void VAudioplayer::draw() +{ + Log::getInstance()->log("VAudioplayer::draw", Log::DEBUG, "p=%p", this); +} + + +int VAudioplayer::handleCommand(int command) +{ + Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "p=%p,cmd=%d", this,command); + Timers::getInstance()->cancelTimer(this,1); + int rt=1; + switch(command) + { + case Remote::DF_UP: + case Remote::UP: + play(VMediaList::MV_PREV); + ViewMan::getInstance()->updateView(this); + rt= 2; + break; + case Remote::FORWARD: + getPlayer()->fastForward(); + rt=2; + break; + case Remote::DF_DOWN: + case Remote::DOWN: + play(VMediaList::MV_NEXT); + ViewMan::getInstance()->updateView(this); + rt= 2; + break; + case Remote::SKIPFORWARD: + getPlayer()->skipForward(10); + rt=2; + break; + case Remote::SKIPBACK: + getPlayer()->skipBackward(10); + rt=2; + break; + case Remote::REVERSE: + rt=2; + break; + case Remote::ZERO: + getPlayer()->jumpToPercent(0); + rt=2; + break; + case Remote::ONE: + getPlayer()->jumpToPercent(10); + rt=2; + break; + case Remote::TWO: + getPlayer()->jumpToPercent(20); + rt=2; + break; + case Remote::THREE: + getPlayer()->jumpToPercent(30); + rt=2; + break; + case Remote::FOUR: + getPlayer()->jumpToPercent(40); + rt=2; + break; + case Remote::FIVE: + getPlayer()->jumpToPercent(50); + rt=2; + break; + case Remote::SIX: + getPlayer()->jumpToPercent(60); + rt=2; + break; + case Remote::SEVEN: + getPlayer()->jumpToPercent(70); + rt=2; + break; + case Remote::EIGHT: + getPlayer()->jumpToPercent(80); + rt=2; + break; + case Remote::NINE: + getPlayer()->jumpToPercent(90); + rt=2; + break; + case Remote::OK: + case Remote::GREEN: + { + if (banner) { + destroyBanner(); + } + else showBanner(true); + if (getPlayer()->getState() == AudioPlayer::S_ERROR) { + if (playall) play(VMediaList::MV_NEXT); + } + rt= 2; + } + break; + case Remote::PLAY: + { + getPlayer()->unpause(); + if (getPlayer()->getState() != AudioPlayer::S_ERROR) justPlaying=true; + else if (playall) play(VMediaList::MV_NEXT); + rt= 2; + } + break; + case Remote::PAUSE: + //playall=false; + getPlayer()->pause(); + justPlaying=false; + rt= 2; + break; + case Remote::STOP: + getPlayer()->stop(); + justPlaying=false; + rt= 2; + break; + case Remote::BACK: + { + rt= 4; + } + break; + } + if (playall) startPlayall(); + if (rt == 2) retriggerBanner(); + // stop command getting to any more views + Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "returns p=%p,cmd=%d, rt=%d", this,command,rt); + return rt; +} + +void VAudioplayer::processMessage(Message* m) +{ + if (m->message == Message::MOUSE_MOVE) + { + ; + } + else if (m->message == Message::MOUSE_LBDOWN) + { + + //check if press is outside this view! then simulate cancel + int x=(m->parameter>>16)-getScreenX(); + int y=(m->parameter&0xFFFF)-getScreenY(); + if (x<0 || y <0 || x>getWidth() || y>getHeight()) + { + ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press + } + } + else if (m->message == Message::PLAYER_EVENT) { + switch (m->parameter) { + case AudioPlayer::STREAM_ERR: + case AudioPlayer::STREAM_END: + if (playall) play(VMediaList::MV_NEXT); + else sendViewMsg(this,true); + break; + case AudioPlayer::STATUS_CHANGE: + updateBanner(false); + break; + case AudioPlayer::SHORT_UPDATE: + if (banner) { + drawClocks(); + ViewMan::getInstance()->updateView(banner,&clocksRegion); + ViewMan::getInstance()->updateView(banner,&barRegion); + Timers::getInstance()->setTimerD(this, 3, 1); + } + break; + case AudioPlayer::NEW_SONG: + updateBanner(true); + break; + case AudioPlayer::CONNECTION_LOST: + destroyBanner(); + if (AudioPlayer *player=getPlayer(false)) { + player->shutdown(); + player=NULL; + } + Command::getInstance()->connectionLost(); + break; + } + } +} + +VAudioplayer * VAudioplayer::createPlayer(VMediaList * mparent, bool bplayall) { + Log::getInstance()->log("VAudioplayer::createPlayer", Log::DEBUG, "p=%p", + mparent); + VAudioplayer *vmn=new VAudioplayer(mparent); + ViewMan::getInstance()->add(vmn); + ViewMan::getInstance()->updateView(vmn); + vmn->play(); + if (bplayall) vmn->startPlayall(); + ViewMan::getInstance()->updateView(vmn); + return vmn; +} + +void VAudioplayer::startPlayall() { + playall=true; +} + +void VAudioplayer::play(ULONG move) { + currentMedia=parent->getMedia(MEDIA_TYPE_AUDIO,move); + mediaError=NULL; + const char *fname=currentMedia->getFileName(); + ftime=currentMedia->getTime(); + int len=strlen(fname)+2+strlen(parent->getDirname()); + if (fullname) { + delete fullname; + fullname=NULL; + } + fullname=new char[len]; + sprintf(fullname,"%s/%s",parent->getDirname(),fname); + if (filename) delete filename; + len=strlen(fname)+1; + filename=new char[len]; + strcpy(filename,fname); + Log::getInstance()->log("VAudioplayer::load", Log::DEBUG, "filename=%s,p=%p", + fullname,this); + VDR* vdr=VDR::getInstance(); + if (vdr->isConnected()) { + Log::getInstance()->log("VAudioplayer", Log::DEBUG, "request file %s",filename); + int wseq=getPlayer()->play(fullname); + if (getPlayer()->waitForSequence(wseq,2)<0) { + mediaError=tr("unable to open audio file"); + } + justPlaying=true; + //leave this to the update message from the player: + //showBanner(); + } + else { + if (AudioPlayer * player=getPlayer(false)) { + player->shutdown(); + } + Command::getInstance()->connectionLost(); + } + if (mediaError) { + showBanner(true); + } + Log::getInstance()->log("VAudioplayer", Log::DEBUG, "player started for %s",filename); +} + +void VAudioplayer::retriggerBanner() { + if (! banner) return; + bannerTime=time(NULL); +} + +void VAudioplayer::showBanner(bool forceNewTime) { + //if the loading flag is set, + //we are in the main thread - so we can (and must) safely hard destroy/create the banner + Timers::getInstance()->cancelTimer(this,1); + Timers::getInstance()->cancelTimer(this,3); + if (! filename || ! currentMedia) { + //hmm... + destroyBanner(); + return; + } + drawBanner(); + time_t curtime=time(NULL); + if (forceNewTime) retriggerBanner(); + time_t remainingTime=BANNER_TIME+2-(curtime-bannerTime); + if (remainingTime < 2) remainingTime=2; + bool playerError=getPlayer()->getState()==AudioPlayer::S_ERROR; + if (remainingTime < BANNER_ERROR_TIME+2 && playerError) remainingTime=BANNER_ERROR_TIME+2; + Timers::getInstance()->setTimerD(this,1,remainingTime); + if (playerError) Timers::getInstance()->setTimerD(this,2,BANNER_ERROR_TIME); + Timers::getInstance()->setTimerD(this,3, 1); + ViewMan::getInstance()->updateView(banner); + } + +void VAudioplayer::destroyBanner() { + if (banner) { + ViewMan::getInstance()->removeView(banner); + banner=NULL; + } + Timers::getInstance()->cancelTimer(this,1); + Timers::getInstance()->cancelTimer(this,3); +} +void VAudioplayer::updateBanner(bool restart) { + time_t currTime=time(NULL); + time_t lastBanner=bannerTime; + if (currTime - lastBanner > BANNER_TIME){ + destroyBanner(); + } + if (banner||restart) { + showBanner(restart); + } +} +void VAudioplayer::timercall(int clientref) { + Log::getInstance()->log("VAudioplayer::timercall", Log::DEBUG, "id=%d",clientref); + switch(clientref) + { + case 1: + case 3: + { + Message* m = new Message(); + //we misuse PLAYER_EVENT here + m->message = Message::PLAYER_EVENT; + m->to = this; + m->from = this; + m->parameter=(clientref==1)?AudioPlayer::STATUS_CHANGE: + AudioPlayer::SHORT_UPDATE; + Command::getInstance()->postMessageFromOuterSpace(m); + break; + } + case 2: + { + bool stillError=false; + if (AudioPlayer * player=getPlayer(false)) { + stillError=player->getState()==AudioPlayer::S_ERROR; + } + if (stillError) { + //send a stream end to trigger nex in playlist + Message* m = new Message(); + //we misuse PLAYER_EVENT here + m->message = Message::PLAYER_EVENT; + m->to = this; + m->from = this; + m->parameter=AudioPlayer::STREAM_END; + Command::getInstance()->postMessageFromOuterSpace(m); + } + break; + } + } + + } + +void VAudioplayer::sendViewMsg(View *v,bool vdestroy) { + Message* m = new Message(); + m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW; + m->to = ViewMan::getInstance(); + m->from = v; + m->parameter=(ULONG)v; + Command::getInstance()->postMessageFromOuterSpace(m); +} + +View * VAudioplayer::createBannerView(int numlines) { + View *rt=new View(); + UINT height=numlines*30+60; + UINT vheight=Video::getInstance()->getScreenHeight(); + UINT vwidth=Video::getInstance()->getScreenWidth(); + if (height > vheight-2*BANNER_BOTTOM_MARGIN) + height=vheight-2*BANNER_BOTTOM_MARGIN; + rt->create(vwidth -2*BANNER_X_MARGIN, height); + if (Video::getInstance()->getFormat() == Video::PAL) + { + rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN); + } + else + { + rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN); + } + + rt->setBackgroundColour(Colour::VIEWBACKGROUND); + rt->setTitleBarOn(0); + //from vvideorec + //set the regions for the closcks and bars on banner + barRegion.x = rt->area.w-BARLEN-20; + barRegion.y = rt->area.h - 30; // FIXME, need to be - 1? and below? + barRegion.w = rt->area.w-BARLEN+10; + barRegion.h = 30; + + clocksRegion.x = 130; + clocksRegion.y = barRegion.y + 3; + clocksRegion.w = 190; + clocksRegion.h = surface->getFontHeight(); + Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"created banner %p",rt); + ViewMan::getInstance()->add(rt); + return rt; +} + + +void VAudioplayer::drawBanner(){ + Log::getInstance()->log("VAudioPlayer",Log::DEBUG, "draw banner for %p",banner); + char *title=NULL; + char *playerTitle=getPlayer()->getTitle(); + if (playerTitle) title=playerTitle; + else title=filename; + char *pl=tr("Playlist"); + int num=parent->getNumEntries(MEDIA_TYPE_AUDIO,currentMedia->index); + bool playerError=getPlayer()->getState() == AudioPlayer::S_ERROR; + //1more line for long dirs + int numlines=playall?5:4; + int len=0; + char *first=NULL; + char *playerInfo=NULL; + if (playerError) { + numlines=3; + first=tr("Unable to play audio file"); + len=strlen(first)+3; + } + else { + playerInfo=getPlayer()->getID3Info(); + len=strlen(filename)+strlen(pl)+30+strlen(parent->getDirname())+Media::TIMEBUFLEN+100; + if (playerInfo) { + for (UINT i=0;igetFileName(), + pl,num,numentries, + tr("Directory"),parent->getDirname(), + tr("Time"),currentMedia->getTimeString(tbuf)); + } + else { + snprintf(buf,len,"%s\n" + "%s:%s\n" + "%s:%s\n" + "%s:%s\n", + first, + tr("FileName"),currentMedia->getFileName(), + tr("Directory"),parent->getDirname(), + tr("Time"),currentMedia->getTimeString(tbuf)); + } + } + Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"info: (%d)%s",strlen(buf),buf); + //now the real drawing functions + banner->draw(); + WSymbol w; + w.setSurface(banner->surface); + //title + banner->rectangle(0, 0, banner->area.w, 30, Colour::TITLEBARBACKGROUND); + banner->drawText(title, 5, 5, Colour::LIGHTTEXT); + banner->drawPara(buf,5,32,Colour::LIGHTTEXT); + int x=10; + int ybottom=banner->area.h; + banner->rectangle(0, ybottom - barRegion.h, banner->area.w, barRegion.h, Colour::TITLEBARBACKGROUND); + bool drawSymbol=true; + switch(getPlayer()->getState()) { + case AudioPlayer::S_PAUSE: + w.nextSymbol = WSymbol::PAUSE; + break; + case AudioPlayer::S_PLAY: + w.nextSymbol = WSymbol::PLAY; + break; + case AudioPlayer::S_DONE: + if (playall) + w.nextSymbol = WSymbol::PLAY; + else + drawSymbol=false; + break; + case AudioPlayer::S_BACK: + w.nextSymbol = WSymbol::FBWD ; + break; + case AudioPlayer::S_FF: + w.nextSymbol = WSymbol::FFWD ; + break; + default: + drawSymbol=false; + break; + } + if (drawSymbol) { + w.setSurfaceOffset(x, ybottom-24); + w.draw(); + } + else { + //stop symbol + banner->rectangle(x, ybottom - 23, 18, 16, Colour::LIGHTTEXT); + } + x+=30; + if (playerInfo) delete playerInfo; + if (playerTitle) delete playerTitle; + drawClocks(); +} + +void VAudioplayer::drawClocks() { + if (! banner) return; + //draw clocks and bar + banner->rectangle(clocksRegion, Colour::TITLEBARBACKGROUND); + + time_t currentSec = (time_t)(getPlayer()->getCurrentTimes()); + time_t lengthSec=(time_t)(getPlayer()->getSonglen()); + struct tm cpos; + struct tm slen; + gmtime_r(¤tSec,&cpos); + gmtime_r(&lengthSec,&slen); + + char buffer[100]; + if (currentSec >= lengthSec) + { + snprintf(buffer,99, "-:--:-- / -:--:-- %03dk",getPlayer()->getCurrentBitrate()/1000); + } + else + { + SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i %03dk", cpos.tm_hour, cpos.tm_min, cpos.tm_sec, slen.tm_hour, slen.tm_min, slen.tm_sec, + getPlayer()->getCurrentBitrate()/1000); + //Log::getInstance()->log("VAudioplayer", Log::DEBUG, buffer); + } + + banner->drawText(buffer, clocksRegion.x, clocksRegion.y, Colour::LIGHTTEXT); + + // Draw progress bar + int progBarXbase = 0; + int barlen=250; + + banner->rectangle(barRegion.x + progBarXbase, barRegion.y + 3, barlen+10, 24, Colour::LIGHTTEXT); + banner->rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 5, barlen+6, 20, barBlue); + + if (currentSec > lengthSec) currentSec=lengthSec; + if (lengthSec == 0) return; + + // Draw yellow portion + int progressWidth = (barlen+2) * currentSec / lengthSec; + banner->rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 7, progressWidth, 16, Colour::SELECTHIGHLIGHT); + +} + + diff --git a/vaudioplayer.h b/vaudioplayer.h new file mode 100644 index 0000000..9514573 --- /dev/null +++ b/vaudioplayer.h @@ -0,0 +1,109 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VAUDIOPLAYER_H +#define VAUDIOPLAYER_H + +#include +#include +#include + +#include "view.h" +#include "remote.h" +#include "wsymbol.h" +#include "viewman.h" +#include "vdr.h" +#include "media.h" +#include "colour.h" +#include "i18n.h" +#include "vmedialist.h" +#include "wjpeg.h" + +/** + * the audio player frontend + * will ineract with the parent List for ff,back, slide show... + * +*/ +class AudioPlayer; + +class VAudioplayer : public View,public TimerReceiver +{ + public: + ~VAudioplayer(); + + void processMessage(Message* m); + int handleCommand(int command); + void draw(); + void timercall(int clientReference); + //factory method + //return NULL on error + static VAudioplayer *createPlayer(VMediaList * parent, bool startPlayall=false); + //show the picture currently selected in the parent + //potentially moving to next/previous one + void play(ULONG move=VMediaList::MV_NONE); + + //start a playall + void startPlayall(); + + + private: + const static int BANNER_TIME=30; + //show time of an error + const static int BANNER_ERROR_TIME=8; + + + //margin on SCREEN on each side + const static int BANNER_X_MARGIN=50; + //margin on bottom of screen + const static int BANNER_BOTTOM_MARGIN=30; + //length of the progress bar + const static int BARLEN=250; + + VAudioplayer(VMediaList * plist); + void retriggerBanner(); + void showBanner(bool forceRestart=false); + void destroyBanner(); + void updateBanner(bool restart=false); + void drawBanner(); + void drawClocks(); + View *createBannerView(int numlines); + int bannerlines; + //add or destroy a view (banner, info) + void sendViewMsg(View *v,bool destroy=false); + //get the player - create it if necessary + AudioPlayer* getPlayer(bool create=true); + VMediaList *parent; + View *banner; + char * fullname; + char * filename; + ULONG ftime; + bool playall; + char * mediaError; + Media * currentMedia; + bool justPlaying; + int numentries; + time_t bannerTime; + Region barRegion; + Region clocksRegion; + Colour barBlue; + +}; + +#endif diff --git a/vdr.cc b/vdr.cc index 307f7c3..c89b746 100644 --- a/vdr.cc +++ b/vdr.cc @@ -604,12 +604,22 @@ int VDR::stopStreaming() return toReturn; } +UCHAR* VDR::getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived) +{ + return getBlock(position, maxAmount, amountReceived, VDR_GETIMAGEBLOCK); +} + UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived) +{ + return getBlock(position, maxAmount, amountReceived, VDR_GETBLOCK); +} + +UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd) { UCHAR buffer[20]; *(unsigned long*)&buffer[0] = htonl(16); - *(unsigned long*)&buffer[4] = htonl(VDR_GETBLOCK); + *(unsigned long*)&buffer[4] = htonl(cmd); *(ULLONG*)&buffer[8] = htonll(position); *(unsigned long*)&buffer[16] = htonl(maxAmount); @@ -1261,3 +1271,164 @@ void VDR::getChannelPids(Channel* channel) return ; } + +/** + * media List Request: + * 4 length + * 4 VDR_GETMEDIALIST + * 4 flags (currently unused) + * n dirname + * n+1 0 + * Media List response: + * 4 length + * 4 VDR_ + * 4 numentries + * per entry: + * 4 media type + * 4 time stamp + * 4 flags + * 4 strlen (incl. 0 Byte) + * string + * 0 +*/ +MediaList* VDR::getMediaList(const char* parent,int mediaType) +{ + Log::getInstance()->log("VDR", Log::DEBUG, "getMediaList %s,type=%d", + (parent?parent:"NULL"), mediaType); + unsigned long totalLength = 12; + if (parent) totalLength+=strlen(parent) + 1; + UCHAR* buffer = new UCHAR[totalLength]; + + *(unsigned long*)&buffer[0] = htonl(totalLength - 4); + *(unsigned long*)&buffer[4] = htonl(VDR_GETMEDIALIST); + //unused flags + for (int i=8;i<12;i++) buffer[i]=0; + //name + if (parent) { + strcpy((char*)&buffer[12], parent); + buffer[totalLength-1]=0; + } + MUTEX_LOCK(&mutex); + if (!connected) { MUTEX_UNLOCK(&mutex); return 0; } + + unsigned int a = tcp->sendPacket(buffer, totalLength); + delete []buffer; + + if (a != totalLength) + { + disconnect(); + MUTEX_UNLOCK(&mutex); + return NULL; + } + + if (!getPacket()) + { + MUTEX_UNLOCK(&mutex); + return NULL; + } + + if (serverError()) + { + MUTEX_UNLOCK(&mutex); + return NULL; + } + if (packetLength < 12) { + Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList packet too short, expected 12, got %d", packetLength); + freePacket(); + MUTEX_UNLOCK(&mutex); + return NULL; + } + MediaList* mediaList = new MediaList(); + ULONG code=0; + code=extractULONG(); + ULONG numEntries=extractULONG(); + Log::getInstance()->log("VDR", Log::DEBUG, "receiveMediaList with %d entries",numEntries); + while (packetPos < packetLength && numEntries >0) + { + Media* m = new Media(); + ULONG mtype = extractULONG(); + ULONG mtime=extractULONG(); + ULONG flags=0; + flags=extractULONG(); + ULONG stsize=extractULONG(); + char * name=extractString(); + if (! name || stsize != (strlen(name)+1)) { + Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList invalid packet entry, read size %d, strlen %d", stsize, strlen(name)+1); + delete m; + delete mediaList; + freePacket(); + MUTEX_UNLOCK(&mutex); + return NULL; + } + //ignore . and .. entries + if (strcmp(name,".") == 0 || strcmp(name,"..")==0) { + delete m; + continue; + } + m->setFileName(name); + m->setTime(mtime); + m->setMediaType(mtype); + mediaList->push_back(m); + Log::getInstance()->log("VDR", Log::DEBUG, "Have added a media to list. %s, type=%d, time=%d", name,mtype,mtime); + numEntries--; + } + + freePacket(); + MUTEX_UNLOCK(&mutex); + + return mediaList; +} +/** + * get image Request: + * 4 length + * 4 VDR_GETIMAGE + * 4 flags (currently unused) + * 4 x size + * 4 y size + * n filename + * n+1 0 + * get image response: + * 4 length + * 4 VDR_GETIMAGE + * 4 len of image +*/ +ULONG VDR::loadImage(const char* fileName, ULONG x, ULONG y) +{ + unsigned long totalLength = 20 + strlen(fileName) + 1; + UCHAR* buffer = new UCHAR[totalLength]; + + *(unsigned long*)&buffer[0] = htonl(totalLength - 4); + *(unsigned long*)&buffer[4] = htonl(VDR_GETIMAGE); + *(unsigned long*)&buffer[8] = htonl(0); + *(unsigned long*)&buffer[8] = htonl(x); + *(unsigned long*)&buffer[8] = htonl(y); + strcpy((char*)&buffer[12], fileName); + + MUTEX_LOCK(&mutex); + if (!connected) { MUTEX_UNLOCK(&mutex); return 0; } + + unsigned int a = tcp->sendPacket(buffer, totalLength); + delete []buffer; + + if (a != totalLength) + { + disconnect(); + MUTEX_UNLOCK(&mutex); + return 0; + } + + if (!getPacket()) + { + MUTEX_UNLOCK(&mutex); + return 0; + } + ULONG cmd=0; + cmd=extractULONG(); + ULONG lengthBytes = extractULONG(); + freePacket(); + MUTEX_UNLOCK(&mutex); + + Log::getInstance()->log("VDR", Log::DEBUG, "getImage %s: %lu", fileName,lengthBytes); + + return lengthBytes; +} diff --git a/vdr.h b/vdr.h index 2cb41e7..ce74fea 100644 --- a/vdr.h +++ b/vdr.h @@ -41,13 +41,13 @@ #include "recinfo.h" #include "mark.h" #include "wol.h" +#include "media.h" using namespace std; typedef vector EventList; typedef vector ChannelList; typedef vector RecTimerList; -typedef vector MarkList; struct VDRServer { @@ -131,6 +131,8 @@ class VDR void getChannelPids(Channel* channel); UCHAR* getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived); + //get image blocks separate - we can do this in parallel + UCHAR* getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived); int stopStreaming(); EventList* getChannelSchedule(ULONG number); EventList* getChannelSchedule(ULONG number, time_t start, ULONG duration); @@ -139,6 +141,16 @@ class VDR ULONG setEventTimer(char* timerString); RecTimerList* getRecTimersList(); + /** + * ge a list of media entries + * if parent==NULL this is the configured base list + */ + MediaList* getMediaList(const char* parent=NULL,int mediaType=MEDIA_TYPE_ALL); + /** + * start loading a JPEG image + * return size, 0 if not found + */ + ULONG loadImage(const char * filename, ULONG xsize=0,ULONG ysize=0); // end @@ -154,6 +166,7 @@ class VDR private: + UCHAR* getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd); static VDR* instance; Log* logger; int initted; @@ -197,7 +210,9 @@ class VDR const static ULONG VDR_GETRECINFO = 20; const static ULONG VDR_GETMARKS = 21; const static ULONG VDR_GETCHANNELPIDS = 22; - + const static ULONG VDR_GETMEDIALIST = 30; + const static ULONG VDR_GETIMAGE = 31; + const static ULONG VDR_GETIMAGEBLOCK = 32; int getPacket(); void freePacket(); int serverError(); diff --git a/vmedialist.cc b/vmedialist.cc new file mode 100644 index 0000000..baaa129 --- /dev/null +++ b/vmedialist.cc @@ -0,0 +1,710 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include +#include "unistd.h" +#include "vmedialist.h" +#include +#include +#include "vpicture.h" +#include "vaudioplayer.h" + + +using namespace std; +class MediaDirectory { + private: + char * dirname; + char * displayname; + char * fullpath; + int selection; + int sortorder; + ULONG mtype; + public: + void setDirname(const char * n) { + if (dirname) delete dirname; + dirname=NULL; + if (!n) return; + dirname=new char[strlen(n)+1]; + strcpy(dirname,n); + } + void setDisplayname(const char * n) { + if (displayname) delete displayname; + displayname=NULL; + if (!n) return; + displayname=new char[strlen(n)+1]; + strcpy(displayname,n); + } + void setFullpath(const char * n) { + if (fullpath) delete fullpath; + fullpath=NULL; + if (!n) return; + fullpath=new char[strlen(n)+1]; + strcpy(fullpath,n); + } + void setSelection(int s) { + selection=s; + } + void setMediaType(ULONG n) {mtype=n;} + void setSortorder(int s) { + sortorder=s; + } + const char * getDirname() { + return dirname; + } + const char * getDisplayname() { + if (displayname) return displayname; + if (fullpath) return fullpath; + if (dirname) return dirname; + return "/"; + } + const char * getFullPath() { + return fullpath; + } + int getSelection() { + return selection; + } + ULONG getMediaType(){return mtype;} + int getSortorder() { + return sortorder; + } + public: + MediaDirectory(const char *d) : dirname(NULL),displayname(NULL),fullpath(NULL),selection(0), + sortorder(0),mtype(MEDIA_TYPE_ALL){ + setDirname(d); + } + MediaDirectory(MediaDirectory &c) { + MediaDirectory(c.getDirname()); + setDisplayname(c.getDisplayname()); + setSelection(c.getSelection()); + setMediaType(c.getMediaType()); + setSortorder(c.getSortorder()); + } + ~MediaDirectory() { + if (dirname) delete dirname; + if (displayname) delete displayname; + if (fullpath) delete fullpath; + } +}; + +typedef vector MDirList; +class DirList { + private: + int current; + MDirList list; + public: + DirList() { + current=0; + list.push_back(new MediaDirectory(NULL)); + } + ~DirList() { + MDirList::iterator it; + for (it=list.begin();itgetFullPath(); + } + int dropTop() { + if (current > 0) { + current--; + delete list.back(); + list.pop_back(); + } + return current; + } + int append(const char *dirname) { + if (! dirname) return current; + MediaDirectory* md=new MediaDirectory(dirname); + const char *cp=getCurrent()->getFullPath(); + int len=strlen(dirname)+2; + if (cp) len+=strlen(cp); + char * fp=new char[len]; + *fp=0; + if (cp) { + strcpy(fp,cp); + strcat(fp,"/"); + } + else if (*dirname != '/' ) strcpy(fp,"/"); + strcat(fp,dirname); + md->setFullpath(fp); + delete fp; + list.push_back(md); + current++; + return current; + } + + int getLevel() { + return current; + } + +}; + + +class MediaSorterName +{ + public: + bool operator() (const Media* a, const Media* b) + { + if (b->getMediaType() == MEDIA_TYPE_DIR && + a->getMediaType() != MEDIA_TYPE_DIR) + return false; + if ( b->getMediaType() != MEDIA_TYPE_DIR && + a->getMediaType() == MEDIA_TYPE_DIR) + return true; + int c = strcmp(b->getFileName(), a->getFileName()); + if (c > 0) return true; + return false; + } +}; + +//a sorter with a randomly initialized order +class MediaSorterRandom +{ + public: + MediaSorterRandom(int start) { + mystart=start; + } + bool operator() (const Media* a, const Media* b) + { + if (b->getMediaType() == MEDIA_TYPE_DIR && + a->getMediaType() != MEDIA_TYPE_DIR) + return false; + if ( b->getMediaType() != MEDIA_TYPE_DIR && + a->getMediaType() == MEDIA_TYPE_DIR) + return true; + unsigned char suma=sum(a->getFileName(),(unsigned char)mystart); + unsigned char sumb=sum(b->getFileName(),(unsigned char)mystart); + if (sumb > suma) return true; + return false; + } + private: + unsigned char sum(const char *name,unsigned char start) { + for (;*name!=0;name++) start=start ^ (unsigned char)*name; + return start; + } + int mystart; +}; + + +struct MediaSorterTime +{ + bool operator() (const Media* a, const Media* b) + { + if (b->getMediaType() == MEDIA_TYPE_DIR && + a->getMediaType() != MEDIA_TYPE_DIR) + return false; + if ( b->getMediaType() != MEDIA_TYPE_DIR && + a->getMediaType() == MEDIA_TYPE_DIR) + return true; + if (b->getTime()>a->getTime()) return true; + return false; + } +}; + + +VMediaList::VMediaList() +{ + dirlist=new DirList(); + Log::getInstance()->log("VMediaList::VMediaList", Log::DEBUG, "dirlist=%p,curren=%p",dirlist,dirlist->getCurrent()); + dirlist->getCurrent()->setSortorder(SORT_NAME); + create(570, 420); + if (Video::getInstance()->getFormat() == Video::PAL) + { + setScreenPos(80, 70); + } + else + { + setScreenPos(70, 35); + } + + setBackgroundColour(Colour::VIEWBACKGROUND); + setTitleBarOn(1); + setTitleBarColour(Colour::TITLEBARBACKGROUND); + + sl.setSurface(surface); + sl.setSurfaceOffset(10, 30 + 5); + sl.setDimensions(area.w - 20, area.h - 30 - 15 - 30); + sl.addColumn(0); + sl.addColumn(60); + mediaList=NULL; + loading=true; + sortOrder=SORT_NONE; + + +} + +VMediaList::~VMediaList() +{ + delete dirlist; + clearMediaList(); +} + +void VMediaList::clearMediaList() { + if (mediaList) + { + for (UINT i = 0; i < mediaList->size(); i++) + { + delete (*mediaList)[i]; + } + + mediaList->clear(); + delete mediaList; + } +} + + + +int VMediaList::getNumEntries(int mediaType,int lowerThen) { + if (mediaType == MEDIA_TYPE_ALL && lowerThen < 0) return mediaList->size(); + if (lowerThen < 0) lowerThen=mediaList->size(); + int rt=0; + for (int i=0;i<(int)(mediaList->size()) && i <= lowerThen;i++) { + if ((*mediaList)[i]->getMediaType() & mediaType) rt++; + } + return rt; +} + +void VMediaList::setList(MediaList* tlist) +{ + if (mediaList != tlist) { + clearMediaList(); + } + sortOrder=SORT_NONE; + mediaList = tlist; + sortList(dirlist->getCurrent()->getSortorder()); + updateSelectList(dirlist->getCurrent()->getSelection()); +} + + +void VMediaList::updateSelectList(){ + updateSelectList(-1); +} +void VMediaList::updateSelectList(int currentNumber){ + char str[5000]; + char tempA[Media::TIMEBUFLEN]; + Log::getInstance()->log("VMediaList::updateSelectList", Log::DEBUG, "media=%p",mediaList); + + ULONG currentSelection=0; + if (sl.getNumOptions() >= 1 && currentNumber < 0) { + currentSelection=sl.getCurrentOptionData(); + } + sl.clear(); + + Media* media; + int first = 1; + if (mediaList) + { + for (UINT i = 0; i < mediaList->size(); i++) + { + media = (*mediaList)[i]; + if (media->getMediaType() == MEDIA_TYPE_DIR) { + sprintf(str, "%04u %s [%s]", i,media->getTimeString(tempA), media->getDisplayName()); + //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str); + media->index = sl.addOption(str, (ULONG)media, first); + } + else { + sprintf(str, "%04u %s %s", i,media->getTimeString(tempA), media->getDisplayName()); + //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str); + media->index = sl.addOption(str, (ULONG)media, first); + } + first = 0; + } + } + if (currentNumber >= 0) sl.hintSetCurrent(currentNumber); + else sl.hintSetCurrent(0); + if (currentSelection != 0) { + bool found=false; + //position to the previous selection + for (int i=0;i 0) + sl.draw(); + doShowingBar(); +} + +void VMediaList::highlightMedia(Media* media) +{ + sl.hintSetCurrent(media->index); + sl.draw(); + doShowingBar(); + ViewMan::getInstance()->updateView(this); +} + +void VMediaList::draw() +{ + Log::getInstance()->log("VMediaList::draw", Log::DEBUG, "namestr=%s",dirlist->getCurrent()->getDisplayname()); + char title[400]; + SNPRINTF(title, 398, tr("Media - %s"), dirlist->getCurrent()->getDisplayname()); + title[399]=0; + setTitleText(title); + View::draw(); + if (loading) + { + drawText(tr("Loading..."), 240, 180, Colour::LIGHTTEXT); + } + else { + if (sl.getNumOptions() > 0) sl.draw(); + + // Put the status stuff at the bottom + + WSymbol w; + w.setSurface(surface); + + w.nextSymbol = WSymbol::UP; + w.setSurfaceOffset(20, 385); + w.draw(); + + w.nextSymbol = WSymbol::DOWN; + w.setSurfaceOffset(50, 385); + w.draw(); + + w.nextSymbol = WSymbol::SKIPBACK; + w.setSurfaceOffset(85, 385); + w.draw(); + + w.nextSymbol = WSymbol::SKIPFORWARD; + w.setSurfaceOffset(115, 385); + w.draw(); + + w.nextSymbol = WSymbol::PLAY; + w.setSurfaceOffset(150, 385); + w.draw(); + + doShowingBar(); + } +} + +void VMediaList::doShowingBar() +{ + int topOption = sl.getTopOption() + 1; + if (sl.getNumOptions() == 0) topOption = 0; + + char showing[250]; + char * strmode=tr("Name"); + switch (sortOrder) { + case SORT_TIME: + strmode=tr("Rand"); + break; + case SORT_NAME: + strmode=tr("Time"); + break; + default: + break; + } + snprintf(showing, 250,tr("%i to %i of %i"), + topOption, sl.getBottomOption(), sl.getNumOptions()); + +// Box b; +// b.setSurfaceOffset(220, 385); +// b.setDimensions(160, 25); +// b.fillColour(Colour::VIEWBACKGROUND); +// b.drawText(showing, 0, 0, Colour::LIGHTTEXT); + + rectangle(200, 384, 18, 16, Colour::VIDEOBLUE); + rectangle(220, 385, 220+160, 385+25, Colour::VIEWBACKGROUND); + drawText(strmode, 220, 385, Colour::LIGHTTEXT); + drawText(showing, 280, 385, Colour::LIGHTTEXT); + if (sl.getCurrentOptionData() != 0) Log::getInstance()->log("VMediaList",Log::DEBUG,"selected %s",((Media *)sl.getCurrentOptionData())->getDisplayName()); +} + +Media * VMediaList::getMedia(int ltype,ULONG move) { + int cur=sl.getCurrentOption(); + Media *m; + bool more=true; + while (more) { + int last=sl.getCurrentOption(); + switch (move) { + case MV_NEXT: + sl.down(); + break; + case MV_PREV: + sl.up(); + break; + default: + more=false; + break; + } + m=(Media*)sl.getCurrentOptionData(); + if (m->getMediaType() & ltype) { + sl.draw(); + return m; + } + //stop if we are done + if (sl.getCurrentOption() == cur || sl.getCurrentOption() == last) break; + } + return NULL; +} + +int VMediaList::handleCommand(int command) +{ + switch(command) + { + case Remote::ONE: + { + sl.hintSetCurrent(0); + sl.draw(); + doShowingBar(); + ViewMan::getInstance()->updateView(this); + return 2; + } + case Remote::DF_UP: + case Remote::UP: + { + sl.up(); + sl.draw(); + + doShowingBar(); + ViewMan::getInstance()->updateView(this); + return 2; + } + case Remote::DF_DOWN: + case Remote::DOWN: + { + sl.down(); + sl.draw(); + + doShowingBar(); + ViewMan::getInstance()->updateView(this); + return 2; + } + case Remote::SKIPBACK: + { + sl.pageUp(); + sl.draw(); + + doShowingBar(); + ViewMan::getInstance()->updateView(this); + return 2; + } + case Remote::SKIPFORWARD: + { + sl.pageDown(); + sl.draw(); + + doShowingBar(); + ViewMan::getInstance()->updateView(this); + return 2; + } + case Remote::BLUE: + { + switch(sortOrder) { + case SORT_NAME: + sortList(SORT_TIME); + ViewMan::getInstance()->updateView(this); + return 2; + case SORT_TIME: + sortList(SORT_RANDOM); + ViewMan::getInstance()->updateView(this); + return 2; + default: + sortList(SORT_NAME); + ViewMan::getInstance()->updateView(this); + return 2; + } + } + case Remote::OK: + case Remote::PLAY: + { + Media* media = NULL; + if (mediaList) media = (Media*)sl.getCurrentOptionData(); + if (media == NULL) return 2; + Log::getInstance()->log("VMediaList", Log::DEBUG, "activated %lu", media->index); + switch(media->getMediaType()) + { + case MEDIA_TYPE_DIR: + { + //create child + Log::getInstance()->log("VMediaList", Log::DEBUG, "create child for %s",media->getFileName()); + if (media->getFileName() == NULL ) return 2; + if (sl.getNumOptions() >=1) { + dirlist->getCurrent()->setSelection(sl.getCurrentOption()); + } + dirlist->getCurrent()->setSortorder(sortOrder); + dirlist->append(media->getFileName()); + //same sort order for next level + dirlist->getCurrent()->setSortorder(sortOrder); + load(); + break; + } + case MEDIA_TYPE_AUDIO: + Log::getInstance()->log("VMediaList", Log::DEBUG, "play audio file %s", + media->getFileName()); + VAudioplayer::createPlayer(this,command==Remote::PLAY); + break; + case MEDIA_TYPE_VIDEO: + Log::getInstance()->log("VMediaList", Log::DEBUG, "play video file %s", + media->getFileName()); + //play video + break; + case MEDIA_TYPE_PICTURE: + Log::getInstance()->log("VMediaList", Log::DEBUG, "show picture file %s", + media->getFileName()); + VPicture::createViewer(this,command==Remote::PLAY); + //play video + break; + default: + Log::getInstance()->log("VMediaList", Log::DEBUG, "unknown media type %d file %s", + media->getMediaType(),media->getFileName()); + + } + /* + VVideoLive* v = new VVideoLive(mediaList, media->type, this); + + v->draw(); + ViewMan::getInstance()->add(v); + ViewMan::getInstance()->updateView(v); + + v->medianelChange(VVideoLive::NUMBER, media->number); + */ + + return 2; + } + case Remote::BACK: + { + if (dirlist->getLevel() < 1) return 4; + dirlist->dropTop(); + load(); + } + } + // stop command getting to any more views + return 1; +} + +void VMediaList::processMessage(Message* m) +{ + if (m->message == Message::MOUSE_MOVE) + { + if (sl.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY())) + { + sl.draw(); + doShowingBar(); + ViewMan::getInstance()->updateView(this); + } + } + else if (m->message == Message::MOUSE_LBDOWN) + { + if (sl.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY())) + { + ViewMan::getInstance()->handleCommand(Remote::OK); //simulate OK press + } + else + { //check if press is outside this view! then simulate cancel + int x=(m->parameter>>16)-getScreenX(); + int y=(m->parameter&0xFFFF)-getScreenY(); + if (x<0 || y <0 || x>getWidth() || y>getHeight()) + { + ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press + } + } + } +} + +int VMediaList::createList() { + Log::getInstance()->log("VMediaList::createList", Log::DEBUG, ""); + VMediaList *vmn=new VMediaList(); + //show the "loading" indicator + ViewMan::getInstance()->add(vmn); + int rt=vmn->load(); + if ( rt != 0) { + ViewMan::getInstance()->removeView(vmn); + } + return rt; +} + +void VMediaList::sortList(int newSort) { + if (sortOrder == newSort) return; + Log::getInstance()->log("VMediaList::sortList", Log::DEBUG, "p=%p,sort=%d, size=%d",this,newSort,mediaList->size()); + if (mediaList->begin() != mediaList->end()) { + switch (newSort) { + case SORT_TIME: + ::sort(mediaList->begin(),mediaList->end(),MediaSorterTime()); + break; + case SORT_NAME: + ::sort(mediaList->begin(),mediaList->end(),MediaSorterName()); + break; + case SORT_RANDOM: + ::sort(mediaList->begin(),mediaList->end(),MediaSorterRandom(time(NULL))); + break; + } + } + sortOrder=newSort; + updateSelectList(); +} + + +int VMediaList::load() { + + loading=true; + draw(); + ViewMan::getInstance()->updateView(this); + VDR* vdr=VDR::getInstance(); + Log::getInstance()->log("VMediaList::load", Log::DEBUG, "load list for %s",dirlist->getPath()); + if (vdr->isConnected()) { + MediaDirectory *md=dirlist->getCurrent(); + MediaList *mn=vdr->getMediaList(md->getFullPath(),md->getMediaType()); + if (mn != NULL) { + setList(mn); + draw(); + ViewMan::getInstance()->updateView(this); + return 0; + } + } + if (! vdr->isConnected()) { + Command::getInstance()->connectionLost(); + } + else { + Log::getInstance()->log("VMediaList", Log::ERR, "unable to get MediaList for %s",dirlist->getPath()); + VInfo* vi = new VInfo(); + vi->create(400, 150); + vi->setExitable(); + vi->setBorderOn(1); + vi->setTitleBarOn(0); + + if (Video::getInstance()->getFormat() == Video::PAL) + vi->setScreenPos(170, 200); + else + vi->setScreenPos(160, 150); + vi->setOneLiner(tr("unable to get media list")); + vi->draw(); + + Message* m = new Message(); + m->message = Message::ADD_VIEW; + m->to = ViewMan::getInstance(); + m->parameter = (ULONG)vi; + Command::getInstance()->postMessageNoLock(m); + } + return 1; +} + +const char * VMediaList::getDirname() const { + return dirlist->getCurrent()->getFullPath(); +} diff --git a/vmedialist.h b/vmedialist.h new file mode 100644 index 0000000..ff2b541 --- /dev/null +++ b/vmedialist.h @@ -0,0 +1,96 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VMEDIALIST_H +#define VMEDIALIST_H + +#include +#include +#include + +#include "view.h" +#include "wselectlist.h" +#include "remote.h" +#include "wsymbol.h" +#include "viewman.h" +#include "vdr.h" +#include "media.h" +#include "vvideolive.h" +#include "colour.h" +#include "video.h" +#include "i18n.h" + +class DirList; +class VMediaList : public View +{ + public: + VMediaList(); + ~VMediaList(); + + void setList(MediaList* chanList); + void highlightMedia(Media* media); + const char *getDirname() const; + void processMessage(Message* m); + int handleCommand(int command); + void draw(); + + //factory method + //return 0 on success + static int createList(); + + //move functions for getMedia + const static ULONG MV_NEXT=1; + const static ULONG MV_PREV=2; + const static ULONG MV_RND=3; + const static ULONG MV_NONE=0; + + //move selection to the next matching media + //with the given move + //return NULL if none found + Media * getMedia(int type,ULONG move=MV_NONE); + + //get the number of media entries of particular types in this list + //if lowerThen is set, only count entries lt this one + int getNumEntries(int mediaType,int lowerThen=-1); + + private: + /** + * fill the medialist basing on the current dirname + */ + int load(); + MediaList *mediaList; + WSelectList sl; + bool loading; + void doShowingBar(); + int sortOrder; + //sort list defined by new order + void sortList(int order); + static const int SORT_NONE=0; + static const int SORT_NAME=1; + static const int SORT_TIME=2; + static const int SORT_RANDOM=3; + void clearMediaList(); + void updateSelectList(); + void updateSelectList(int current); + DirList* dirlist; + +}; + +#endif diff --git a/vpicture.cc b/vpicture.cc new file mode 100644 index 0000000..139d587 --- /dev/null +++ b/vpicture.cc @@ -0,0 +1,420 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "vpicture.h" +#include "vpicturebanner.h" +#include + +Colour VPicture::pictureBack=Colour(140,140,140); +Colour VPicture::infoBack=Colour(110,110,110); +//the jpeg reader + +class VPreader : public JpegReader { + private: + VPicture * parent; + public: + VPreader(VPicture *p){ + parent=p;}; + virtual ULONG readChunk(ULONG offset,ULONG len,char ** buf) { + Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "read chunk o=%d,len=%d,buf=%p", + offset,len,*buf); + UINT numrec=0; + *buf=(char *)VDR::getInstance()->getImageBlock(offset,(UINT)len,&numrec); + Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "got n=%d,buf=%p", + numrec,*buf); + return numrec; + } + virtual ULONG initRead(const char *filename) { + Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s",filename); + Video* video = Video::getInstance(); + ULONG size=VDR::getInstance()->loadImage(filename,video->getScreenWidth(), video->getScreenHeight()); + Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s returned %d",filename,size); + return size; + } +}; + +VPicture::VPicture(VMediaList *p) +{ + parent=p; + reader=new VPreader(this); + needDraw=false; + Video* video = Video::getInstance(); + create(video->getScreenWidth(), video->getScreenHeight()); + jpeg.setSurface(surface); + jpeg.setDimensions(area.w,area.h); + banner=NULL; + fullname=NULL; + filename=NULL; + ftime=0; + slideshow=false; + showtime=INITIAL_SHOWTIME; + mediaError=NULL; + currentMedia=NULL; + shortBanner=false; + rotate=0; + info=NULL; + jpeg.setBackgroundColour(pictureBack); +} + +VPicture::~VPicture() +{ + delete reader; + if (banner) ViewMan::getInstance()->removeView(banner); + if (fullname) delete fullname; + if (filename) delete filename; + Timers::getInstance()->cancelTimer(this,1); + Timers::getInstance()->cancelTimer(this,2); + Timers::getInstance()->cancelTimer(this,3); + destroyInfo(); + +} + +void VPicture::draw() +{ + Log::getInstance()->log("VPicture::draw", Log::DEBUG, "needDraw=%d,p=%p", + needDraw,this); + View::draw(); + if (mediaError) { + drawText(mediaError,20,area.h-10,Colour::LIGHTTEXT); + return; + } + if (needDraw) { + jpeg.draw(); + } +} + + +int VPicture::handleCommand(int command) +{ + Timers::getInstance()->cancelTimer(this,1); + int rt=1; + switch(command) + { + case Remote::DF_UP: + case Remote::UP: + case Remote::SKIPBACK: + showPicture(VMediaList::MV_PREV); + ViewMan::getInstance()->updateView(this); + rt= 2; + break; + case Remote::FORWARD: + if (showtime > 1) showtime--; + updateBanner(true); + break; + case Remote::DF_DOWN: + case Remote::DOWN: + case Remote::SKIPFORWARD: + showPicture(VMediaList::MV_NEXT); + ViewMan::getInstance()->updateView(this); + rt= 2; + break; + case Remote::REVERSE: + if (showtime < 50 ) showtime++; + updateBanner(true); + break; + case Remote::OK: + { + if (banner) { + destroyBanner(); + destroyInfo(); + } + else showBanner(); + rt= 2; + } + break; + case Remote::PLAY: + { + slideshow=true; + showPicture(VMediaList::MV_NEXT); + ViewMan::getInstance()->updateView(this); + rt= 2; + } + break; + case Remote::PAUSE: + slideshow=false; + updateBanner(); + rt= 2; + break; + case Remote::STOP: + slideshow=false; + showtime=INITIAL_SHOWTIME; + updateBanner(); + rt= 2; + break; + case Remote::RED: + switch(rotate) { + case WJpeg::ROT_0: + rotate=WJpeg::ROT_90; + break; + case WJpeg::ROT_90: + rotate=WJpeg::ROT_180; + break; + case WJpeg::ROT_180: + rotate=WJpeg::ROT_270; + break; + case WJpeg::ROT_270: + rotate=WJpeg::ROT_0; + break; + } + showPicture(VMediaList::MV_NONE,rotate); + ViewMan::getInstance()->updateView(this); + rt=2; + break; + case Remote::GREEN: + if (info) destroyInfo(); + else showInfo(); + rt=2; + break; + case Remote::BACK: + { + rt= 4; + } + break; + } + if (slideshow) startSlideshow(); + // stop command getting to any more views + return rt; +} + +void VPicture::processMessage(Message* m) +{ + if (m->message == Message::MOUSE_MOVE) + { + ; + } + else if (m->message == Message::MOUSE_LBDOWN) + { + + //check if press is outside this view! then simulate cancel + int x=(m->parameter>>16)-getScreenX(); + int y=(m->parameter&0xFFFF)-getScreenY(); + if (x<0 || y <0 || x>getWidth() || y>getHeight()) + { + ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press + } + } +} + +VPicture * VPicture::createViewer(VMediaList * mparent, bool bslideshow) { + Log::getInstance()->log("VPicture::createViewer", Log::DEBUG, "p=%p", + mparent); + VPicture *vmn=new VPicture(mparent); + ViewMan::getInstance()->add(vmn); + ViewMan::getInstance()->updateView(vmn); + vmn->showPicture(); + if (bslideshow) vmn->startSlideshow(); + vmn->showBanner(); + ViewMan::getInstance()->updateView(vmn); + return vmn; +} + +void VPicture::startSlideshow() { + slideshow=true; + Timers::getInstance()->setTimerD(this,1,showtime); +} + +void VPicture::showPicture(ULONG move, int rt) { + rotate=rt; + currentMedia=parent->getMedia(MEDIA_TYPE_PICTURE,move); + load(currentMedia); + if (mediaError || jpeg.hasError()) destroyInfo(); + else updateInfo(); + ViewMan::getInstance()->updateView(this); +} + + +int VPicture::load(Media *md) { + jpeg.init(NULL,false,NULL); + mediaError=tr("No media found"); + needDraw=false; + if (! md) return 1; + const char *fname=md->getFileName(); + ftime=md->getTime(); + int len=strlen(fname)+2+strlen(parent->getDirname()); + if (fullname) { + delete fullname; + fullname=NULL; + } + fullname=new char[len]; + sprintf(fullname,"%s/%s",parent->getDirname(),fname); + if (filename) delete filename; + len=strlen(fname)+1; + filename=new char[len]; + strcpy(filename,fname); + Log::getInstance()->log("VPicture::load", Log::DEBUG, "filename=%s,p=%p", + fullname,this); + VDR* vdr=VDR::getInstance(); + //do we have a banner? + bool haveBanner=banner!=NULL && ! shortBanner; + if (vdr->isConnected()) { + showBanner(true); + jpeg.init(fullname,false,reader); + jpeg.setRotate(rotate); + needDraw=true; + mediaError=NULL; + draw(); + //only show the banner if it was there before + if (haveBanner) showBanner(); + else { + if(banner) destroyBanner(); + } + Log::getInstance()->log("VPicture::load", Log::DEBUG, "success: filename=%s,p=%p", + fullname,this); + return 0; + } + else { + mediaError=tr("no VDR connection"); + } + return 1; +} + +void VPicture::showBanner(bool loading,int shortDisplay) { + //if the loading flag is set, + //we are in the main thread - so we can (and must) safely hard destroy/create the banner + Timers::getInstance()->cancelTimer(this,2); + if (! filename || ! currentMedia) { + //hmm... + destroyBanner(!loading); + return; + } + if (banner) destroyBanner(!loading); + banner= new VPictureBanner(this, loading, slideshow); + banner->setBackgroundColour(infoBack); + if (! loading) { + int len=strlen(filename)+Media::TIMEBUFLEN+20; + char buf[len]; + char tbuf[Media::TIMEBUFLEN]; + snprintf(buf,len,"%c%02ds%c %s %s ", + slideshow?' ':'[', + showtime, + slideshow?' ':']', + currentMedia->getTimeString(tbuf), + filename); + banner->setText(buf); + } + else { + banner->setText(filename); + } + banner->draw(); + if (shortDisplay != 0) shortBanner=true; + if (! slideshow && shortDisplay == 0) shortDisplay=8; + //OK we start timer if we don't load and either shortDisplay or no slideshow + if (! loading && shortDisplay != 0) Timers::getInstance()->setTimerD(this,2,shortDisplay); + if (! loading) sendViewMsg(banner,false); + else { + ViewMan::getInstance()->add(banner); + ViewMan::getInstance()->updateView(banner); + } + + } + +void VPicture::destroyBanner(bool fromTimer) { + shortBanner=false; + if (banner) { + if (fromTimer) sendViewMsg(banner,true); + else ViewMan::getInstance()->removeView(banner); + banner=NULL; + if (! fromTimer) Timers::getInstance()->cancelTimer(this,2); + } +} +void VPicture::updateBanner(bool shortDisplay) { + if (banner && ! shortBanner) { + showBanner(false); + } + else if (shortDisplay) { + showBanner(false,2); + } +} +void VPicture::timercall(int clientref) { + switch(clientref) + { + case 1: + if (! slideshow) return; + Log::getInstance()->log("VPicture::timercall", Log::DEBUG, "slideshow"); + sendCommandMsg(Remote::PLAY); + break; + case 2: + destroyBanner(true); + break; + case 3: + destroyInfo(true); + break; + } + + } + +#define INFOBUF 1000 +void VPicture::showInfo(){ + if (info) destroyInfo(); + if (! currentMedia) return; + info=new VInfo(); + info->setTitleText(currentMedia->getFileName()); + info->setDropThrough(); + info->create(500, 300); + info->setBorderOn(1); + info->setTitleBarOn(1); + + if (Video::getInstance()->getFormat() == Video::PAL) + info->setScreenPos(100, 180); + else + info->setScreenPos(100, 150); + char buf[INFOBUF]; + char tbuf[Media::TIMEBUFLEN]; + snprintf(buf,INFOBUF,"%s= %s\n%s= %ld x %ld\n%s= %ld kBytes\n%s= %s\n%s= %ld\n%s= 1/%ld", + tr("Directory"), parent->getDirname(), + tr("Format(px)"),jpeg.getJpegInfo(WJpeg::JPEG_WIDTH),jpeg.getJpegInfo(WJpeg::JPEG_HEIGHT), + tr("Filesize"),jpeg.getJpegInfo(WJpeg::JPEG_SIZE)/1000, + tr("Time"),currentMedia->getTimeString(tbuf), + tr("Rotation"),90*jpeg.getJpegInfo(WJpeg::JPEG_ROTATE), + tr("Scale"),jpeg.getJpegInfo(WJpeg::JPEG_SCALE)); + info->setMainText(buf); + info->draw(); + sendViewMsg(info,false); + Timers::getInstance()->setTimerD(this,3,8); +} +void VPicture::updateInfo(){ + if (info) { + showInfo(); + } +} +void VPicture::destroyInfo(bool fromTimer){ + if (info) { + sendViewMsg(info,true); + info=NULL; + } + if (! fromTimer) Timers::getInstance()->cancelTimer(this,3); +} + +void VPicture::sendViewMsg(View *v,bool vdestroy) { + Message* m = new Message(); + m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW; + m->to = ViewMan::getInstance(); + m->from = v; + m->parameter=(ULONG)v; + Command::getInstance()->postMessageFromOuterSpace(m); +} +void VPicture::sendCommandMsg(int command) { + Message* m = new Message(); + m->message = Message::UDP_BUTTON; + m->to = Command::getInstance(); + m->from = this; + m->parameter=command; + Command::getInstance()->postMessageFromOuterSpace(m); +} diff --git a/vpicture.h b/vpicture.h new file mode 100644 index 0000000..3c8eaa0 --- /dev/null +++ b/vpicture.h @@ -0,0 +1,103 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VPICTURE_H +#define VPICTURE_H + +#include +#include +#include + +#include "view.h" +#include "wselectlist.h" +#include "remote.h" +#include "wsymbol.h" +#include "viewman.h" +#include "vdr.h" +#include "media.h" +#include "vvideolive.h" +#include "colour.h" +#include "video.h" +#include "vinfo.h" +#include "i18n.h" +#include "vmedialist.h" +#include "wjpeg.h" + +/** + * the picture viewer + * will ineract with the parent List for ff,back, slide show... + * +*/ +class VPictureBanner; + +class VPicture : public View,public TimerReceiver +{ + public: + ~VPicture(); + + void processMessage(Message* m); + int handleCommand(int command); + void draw(); + void timercall(int clientReference); + //factory method + //return NULL on error + static VPicture *createViewer(VMediaList * parent, bool startSlideshow=false); + //show the picture currently selected in the parent + //potentially moving to next/previous one + void showPicture(ULONG move=VMediaList::MV_NONE,int rotate=WJpeg::ROT_0); + + //start a slideshow + void startSlideshow(); + + + private: + VPicture(VMediaList * plist); + void showBanner(bool loading=false, int shortDisplayTime=0); + void destroyBanner(bool fromTimer=false); + void updateBanner(bool shortDisplay=false); + void showInfo(); + void updateInfo(); + void destroyInfo(bool fromTimer=false); + //add or destroy a view (banner, info) + void sendViewMsg(View *v,bool destroy=false); + //send command in main thread + void sendCommandMsg(int command); + VMediaList *parent; + int load(Media *m); + JpegReader *reader; + bool needDraw; + WJpeg jpeg; + VPictureBanner *banner; + char * fullname; + char * filename; + ULONG ftime; + bool slideshow; + int showtime; + char * mediaError; + Media * currentMedia; + const static int INITIAL_SHOWTIME=5; + bool shortBanner; + int rotate; + VInfo * info; + static Colour pictureBack; + static Colour infoBack; +}; + +#endif diff --git a/vpicturebanner.cc b/vpicturebanner.cc new file mode 100644 index 0000000..13abc08 --- /dev/null +++ b/vpicturebanner.cc @@ -0,0 +1,136 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "vpicturebanner.h" +#include "vpicture.h" + + +VPictureBanner::VPictureBanner(VPicture *p, bool ld, bool sl) +{ + loading=ld; + slideshow=sl; + parent=p; + Video *v=Video::getInstance(); + create(v->getScreenWidth()-100, 36); + setScreenPos(50, v->getScreenHeight()-50); + setBackgroundColour(Colour::VIEWBACKGROUND); + setTitleBarOn(0); + info=NULL; + Log::getInstance()->log("VPictureBanner",Log::DEBUG,"created %p",this); + //TODO compute sizes from text + rotsize=70; + infsize=50; +} + +VPictureBanner::~VPictureBanner() +{ + Log::getInstance()->log("VPictureBanner",Log::DEBUG,"deleted %p",this); +} + + + +void VPictureBanner::draw() +{ + View::draw(); + if (loading) { + if (info) { + char buf[strlen(info)+100]; + sprintf(buf,"%s %s",tr("Loading"),info); + drawText(buf,5,area.h-25,Colour::LIGHTTEXT); + } + else drawText(tr("Loading"),5,3,Colour::LIGHTTEXT); + } + else { + int x=5; + rectangle(x, area.h - 24, 18, 16, Colour::RED); + x+=18+3; + drawText(tr("rotate"), x, area.h - 25, Colour::LIGHTTEXT); + x+=rotsize+3; + rectangle(x, area.h - 24, 18, 16, Colour::GREEN); + x+=18+3; + drawText(tr("info"), 5+18+3+rotsize+3+18+3, area.h - 25, Colour::LIGHTTEXT); + x+=infsize+3; + WSymbol w; + w.setSurface(surface); + if (slideshow) { + w.nextSymbol = WSymbol::PAUSE; + } + else { + w.nextSymbol = WSymbol::PLAY; + } + w.setSurfaceOffset(x, area.h-24); + w.draw(); + x+=20+3; + if (info) { + drawText(info,x,area.h - 25,Colour::LIGHTTEXT); + } + } +} + +int VPictureBanner::handleCommand(int command) +{ + //don not handle commands - leave this to the picture viewer + return 0; +} + + + +void VPictureBanner::processMessage(Message* m) +{ + if (m->message == Message::MOUSE_MOVE) + { + ; + } + else if (m->message == Message::MOUSE_LBDOWN) + { + //check if press is outside this view! then simulate cancel + int x=(m->parameter>>16)-getScreenX(); + int y=(m->parameter&0xFFFF)-getScreenY(); + if (x<0 || y <0 || x>getWidth() || y>getHeight()) + { + ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press + } + else if (y>=(int)area.h-24 && y<=(int)area.h-6) + { + //y coordinate is right! + if (x>=7 &&x<=25) + { + ViewMan::getInstance()->handleCommand(Remote::RED); //simulate red press + } + else if (x>=110 &&x<=128) + { + ViewMan::getInstance()->handleCommand(Remote::GREEN); //simulate red press + } + } + } +} + +void VPictureBanner::setText(const char * text) { + if (info) delete info; + info=NULL; + if(!text) return; + info=new char[strlen(text)+1]; + strcpy(info,text); +} + + + + + diff --git a/vpicturebanner.h b/vpicturebanner.h new file mode 100644 index 0000000..49f5d1c --- /dev/null +++ b/vpicturebanner.h @@ -0,0 +1,58 @@ +/* + Copyright 2004-2005 Chris Tallon, Andreas Vogel + + This file is part of VOMP. + + VOMP 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. + + VOMP 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 VOMP; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef VPICTUREBANNER_H +#define VPICTUREBANNER_H + +#include +#include +#include +#include + +#include "view.h" +#include "remote.h" +#include "colour.h" +#include "video.h" +#include "vinfo.h" +#include "viewman.h" +#include "i18n.h" + +class VPicture; + +class VPictureBanner : public View +{ + public: + VPictureBanner(VPicture *p,bool loading, bool slideshow); + ~VPictureBanner(); + int handleCommand(int command); + void processMessage(Message* m); + void draw(); + void setText(const char * text); + + private: + VPicture * parent; + char * info; + bool loading; + bool slideshow; + int rotsize; + int infsize; +}; + +#endif diff --git a/vwelcome.cc b/vwelcome.cc index 7bfd9cb..b2bc219 100644 --- a/vwelcome.cc +++ b/vwelcome.cc @@ -29,7 +29,7 @@ VWelcome::VWelcome() clockRegion.w = 60; clockRegion.h = 30; - create(460, 200); + create(460, 220); if (Video::getInstance()->getFormat() == Video::PAL) { setScreenPos(140, 170); @@ -45,15 +45,16 @@ VWelcome::VWelcome() sl.setSurface(surface); sl.setSurfaceOffset(20, 40); - sl.setDimensions(170, 140); + sl.setDimensions(200, 160); setTitleText(tr("Welcome")); sl.addOption(tr("1. Live TV"), 1, 1); sl.addOption(tr("2. Radio"), 2, 0); sl.addOption(tr("3. Recordings"), 3, 0); sl.addOption(tr("4. Timers"), 4, 0); - sl.addOption(tr("5. Options"), 5, 0); - sl.addOption(tr("6. Reboot"), 6, 0); + sl.addOption(tr("5. MediaPlayer"), 5, 0); + sl.addOption(tr("6. Options"), 6, 0); + sl.addOption(tr("7. Reboot"), 7, 0); jpeg.setSurface(surface); jpeg.setSurfaceOffset(240, 60); @@ -147,10 +148,15 @@ int VWelcome::handleCommand(int command) } case Remote::FIVE: { - doOptions(); + doMediaList(); return 2; } case Remote::SIX: + { + doOptions(); + return 2; + } + case Remote::SEVEN: { Command::getInstance()->doReboot(); } @@ -179,10 +185,15 @@ int VWelcome::handleCommand(int command) } else if (option == 5) { - doOptions(); + doMediaList(); return 2; } else if (option == 6) + { + doOptions(); + return 2; + } + else if (option == 7) { Command::getInstance()->doReboot(); return 2; @@ -262,6 +273,10 @@ void VWelcome::doRecordingsList() } } +void VWelcome::doMediaList() +{ + VMediaList::createList(); +} void VWelcome::doTimersList() { VTimerList* vtl = new VTimerList(); diff --git a/vwelcome.h b/vwelcome.h index 320cc0f..56f7b14 100644 --- a/vwelcome.h +++ b/vwelcome.h @@ -41,6 +41,7 @@ #include "i18n.h" #include "timers.h" #include "vscreensaver.h" +#include "vmedialist.h" class VWelcome : public View, public TimerReceiver { @@ -65,6 +66,7 @@ class VWelcome : public View, public TimerReceiver void doTimersList(); void doOptions(); void drawClock(); + void doMediaList(); Region clockRegion; }; diff --git a/wjpeg.cc b/wjpeg.cc index 91805d6..4e750a0 100644 --- a/wjpeg.cc +++ b/wjpeg.cc @@ -19,75 +19,340 @@ */ #include "wjpeg.h" +#include +#include "i18n.h" -int WJpeg::init(char* tfileName) + +extern "C" void +jpeg_memio_src (j_decompress_ptr cinfo, void * userdata); +extern "C" void +jpeg_memio_cleanup (j_decompress_ptr cinfo); + +WJpeg::WJpeg(){ + fileName=NULL; + reader=NULL; + useImageDimensions=true; + jheight=0; + jwidth=0; + jsize=0; + jerror=true; + rotate=0; + jscale=1; +} + +WJpeg::~WJpeg() { + if (fileName) delete fileName; +} + +int WJpeg::init(char* tfileName,bool useImage, JpegReader *rdr) { - fileName = tfileName; + rotate=0; + if (fileName) delete fileName; + fileName=NULL; + if (tfileName) { + fileName = new char[strlen(tfileName)+1]; + strcpy(fileName,tfileName); + } + reader=rdr; + useImageDimensions=useImage; return 1; } -void WJpeg::draw() + +ULONG WJpeg::getJpegInfo(ULONG tag){ + switch(tag) { + case JPEG_HEIGHT: + return jheight; + break; + case JPEG_WIDTH: + return jwidth; + break; + case JPEG_SIZE: + return jsize; + break; + case JPEG_ROTATE: + return rotate; + break; + case JPEG_SCALE: + return jscale; + break; + } + return 0; +} + +int WJpeg::getJpegInfo(ULONG tag, char * buffer) { + switch (tag) { + case JPEG_FILENAME: + strncpy(buffer,fileName,INFO_BUFFER-1); + buffer[INFO_BUFFER-1]=0; + return 0; + break; + } + return -1; +} + +void WJpeg::setRotate(int amount) { + rotate=amount; +} + +extern "C" { + + +struct my_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields */ + FILE *infile; /* to be used in error handler */ + + jmp_buf setjmp_buffer; /* for return to caller */ +}; + +typedef struct my_error_mgr * my_error_ptr; + +/* + * Here's the routine that will replace the standard error_exit method: + */ + +METHODDEF(void) +my_error_exit (j_common_ptr cinfo) { -#ifndef WIN32 - Log* logger = Log::getInstance(); + /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ + my_error_ptr myerr = (my_error_ptr) cinfo->err; - FILE* infile = fopen(fileName, "r"); - if (infile == NULL) - { - logger->log("BJpeg", Log::ERR, "Can't open JPEG"); - return; + /* Always display the message. */ + /* We could postpone this until after returning, if we chose. */ + (*cinfo->err->output_message) (cinfo); + if (myerr->infile) fclose(myerr->infile); + /* Return control to the setjmp point */ + longjmp(myerr->setjmp_buffer, 1); +} + +} + +bool WJpeg::hasError() { + return jerror; +} +void WJpeg::draw() +{ + jerror=false; + if (drawJpeg() != 0) { + jerror=true; + drawTextCentre(tr("Jpeg ERROR"), 240, 180, Colour::LIGHTTEXT); } - logger->log("BJpeg", Log::DEBUG, "File opened"); +} +/* handle picture rotation + 90: xr=h-y + yr=x + 180:xr=w-x + yr=h-y + 270:xr=y + yr=w-x +*/ +void WJpeg::drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c){ + int xb=0; + int yb=0; + switch(rotate) { + case ROT_0: + xb=x; + yb=y; + break; + case ROT_90: + xb=h-y; + yb=x; + break; + case ROT_180: + xb=w-x; + yb=h-y; + break; + case ROT_270: + xb=y; + yb=w-x; + break; + } + xb+=xpos; + yb+=ypos; + if (xb < 0 || yb < 0 ) { + Log::getInstance()->log("WJpeg:drawPixel",Log::ERR,"pixpos < 0 x=%d, y=%d",xb,yb); + return; + } + Box::drawPixel((UINT)xb,(UINT)yb,c); +} + +int WJpeg::drawJpeg() { +#ifndef WIN32 + Log* logger = Log::getInstance(); + unsigned char* buffer =NULL; struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr jerr; - cinfo.err = jpeg_std_error(&jerr); + struct my_error_mgr jerr; + jerr.infile=NULL; + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + /* Establish the setjmp return context for my_error_exit to use. */ + if (setjmp(jerr.setjmp_buffer)) { + /* If we get here, the JPEG code has signaled an error. + * We need to clean up the JPEG object, close the input file, and return. + */ + if (reader) jpeg_memio_cleanup(&cinfo); + jpeg_destroy_decompress(&cinfo); + logger->log("BJpeg", Log::ERR, "JPEG error"); + if (buffer) free(buffer); + return 1; + } + int xpos=0; + int ypos=0; jpeg_create_decompress(&cinfo); - jpeg_stdio_src(&cinfo, infile); + if (fileName && ! reader) { + jsize=0; //TODO: compute size for local files + jerr.infile=fopen(fileName, "r"); + if (jerr.infile == NULL) + { + logger->log("BJpeg", Log::ERR, "Can't open JPEG"); + jpeg_destroy_decompress(&cinfo); + return 1; + } + logger->log("BJpeg", Log::DEBUG, "File opened"); + jpeg_stdio_src(&cinfo, jerr.infile); + } + else if (reader) { + jsize=reader->initRead(fileName); + if (jsize <= 0) { + logger->log("BJpeg", Log::ERR, "Can't init JPEG transfer"); + jpeg_destroy_decompress(&cinfo); + return 1; + } + jpeg_memio_src(&cinfo,(void *)reader); + } + else { + logger->log("BJpeg", Log::ERR, "neither filename nor reader set"); + jpeg_destroy_decompress(&cinfo); + return 1; + } jpeg_read_header(&cinfo, TRUE); - jpeg_start_decompress(&cinfo); + logger->log("BJpeg", Log::DEBUG, "JPEG read header w=%i h=%i, rot=%i", cinfo.image_width, cinfo.image_height, rotate); + int picturew=cinfo.image_width; + int pictureh=cinfo.image_height; + switch (rotate){ + case ROT_90: + case ROT_270: + pictureh=cinfo.image_width; + picturew=cinfo.image_height; + break; + } + if (! useImageDimensions) { + //scale - we can have factors 1,2,4,8 + int scalew=getWidth()*1000/picturew; + int scaleh=getHeight()*1000/pictureh; + int scale=scaleh; + if (scalew < scaleh) scale=scalew; + int fac=8; + //we allow for 10% bigger... + if (scale >= 900) fac=1; + else if (scale >= 450 ) fac=2; + else if (scale >= 225 ) fac=4; + cinfo.scale_denom=fac; + logger->log("BJpeg", Log::DEBUG, "JPEG scaling (1/1000) pw=%i ph=%i w=%i h=%i r=%i f=%i", + getWidth(), + getHeight(), + scalew,scaleh,scale,fac); + jscale=fac; + } - logger->log("BJpeg", Log::DEBUG, "JPEG startup done %i %i", cinfo.output_width, cinfo.output_height); + //remember picture parameters + jheight=pictureh; + jwidth=picturew; - // Init the surface - setDimensions(cinfo.output_width, cinfo.output_height); + jpeg_start_decompress(&cinfo); + //recompute width based on rotation (consider scale) + picturew=cinfo.output_width; + pictureh=cinfo.output_height; + switch (rotate){ + case ROT_90: + case ROT_270: + pictureh=cinfo.output_width; + picturew=cinfo.output_height; + break; + } + //if our image is smaller - center it + if (! useImageDimensions) { + xpos=(getWidth()-picturew)/2; + if (xpos <0) xpos=0; + ypos=(getHeight()-pictureh)/2; + if (ypos <0) ypos=0; + } + //center point for rotation + int w=cinfo.output_width; + int h=cinfo.output_height; + logger->log("BJpeg", Log::DEBUG, "JPEG startup done pw=%i ph=%i, xo=%i,yo=%i, iw=%i, ih=%i", picturew, pictureh,xpos,ypos,w,h); + // Init the surface + if (useImageDimensions) setDimensions(picturew, pictureh); + fillColour(backgroundColour); + //line len in bytes (3 bytes per Pixel) - for buffer (can be bigger then surface) + int linelen=cinfo.output_width*3; +#ifdef USE_BUFFER // MAKE THE 2D ARRAY + buffer = (unsigned char*)malloc(config.output_height * linelen); + logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, cinfo.output_height, linelen); + if (buffer == NULL) { + if (reader) jpeg_memio_cleanup(&cinfo); + jpeg_destroy_decompress(&cinfo); + if (jerr.infile) fclose(jerr.infile); + logger->log("BJpeg", Log::ERR, "JPEG error - no room for buffer"); + return 1; + } +#endif - unsigned char* buffer = (unsigned char*)malloc(area.w * area.h * 3); - logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, area.w, area.h); - - unsigned char* bufferPointers[area.h]; - for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3); - - logger->log("BJpeg", Log::DEBUG, "done array check"); + //unsigned char* bufferPointers[area.h]; + //for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3); + + logger->log("BJpeg", Log::DEBUG, "header w=%d,h=%d",cinfo.output_width,cinfo.output_height); +#ifndef USE_BUFFER + unsigned char lbuf[linelen]; + unsigned char * ptr=lbuf; +#else + unsigned char * ptr=buffer; +#endif int rowsread = 0; + + Colour c; while (cinfo.output_scanline < cinfo.output_height) { // logger->log("BJpeg", Log::DEBUG, "%i", rowsread); - rowsread += jpeg_read_scanlines(&cinfo, &bufferPointers[rowsread], area.h); + rowsread += jpeg_read_scanlines(&cinfo,&ptr,1); +#ifdef USE_BUFFER + ptr+=linelen; +#else + int x=0; + for (unsigned char * lp=ptr;lp < (ptr+linelen);lp+=3,x++) { + c.red=*lp ; + c.green=*(lp+1); + c.blue=*(lp+2); + drawPixel(x, rowsread-1,w,h,xpos,ypos, c); + } + +#endif } logger->log("BJpeg", Log::DEBUG, "Done all jpeg_read"); jpeg_finish_decompress(&cinfo); + if (reader) jpeg_memio_cleanup(&cinfo); jpeg_destroy_decompress(&cinfo); - fclose(infile); + if (jerr.infile) fclose(jerr.infile); logger->log("BJpeg", Log::DEBUG, "jpeg shutdown done, x, y %u %u", area.w, area.h); - +#ifdef USE_BUFFER Colour c; UINT x, y, xoff; unsigned char* p; - for (y = 0; y < area.h; y++) + for (y = 0; y < numlines; y++) { p = bufferPointers[y]; - for (x = 0; x < area.w; x++) + for (x = 0; x < sfclen; x++) { xoff = x * 3; @@ -100,11 +365,12 @@ void WJpeg::draw() c.green = p[xoff + 1]; c.blue = p[xoff + 2]; - drawPixel(x, y, c); + drawPixel(x, y, w,h,xpos,ypos,c); } } free(buffer); +#endif logger->log("BJpeg", Log::DEBUG, "deleted buffer"); #else DWORD width,height; @@ -114,5 +380,229 @@ void WJpeg::draw() setDimensions(width, height); #endif + return 0; +} + + +extern "C" { +ULONG jpeg_call_reader(ULONG offset,ULONG size,char ** buf,void *cb) { + JpegReader *rd=(JpegReader *)cb; + return rd->readChunk(offset,size,buf); +} +} +//the memory buffer reader for the jpeg lib +//taken from jdatasrc.c + +extern "C" { +/* Expanded data source object for stdio input */ +#include "jinclude.h" +#include "jpeglib.h" +#include "jerror.h"/* Expanded data source object for stdio input */ + +typedef struct { + struct jpeg_source_mgr pub; /* public fields */ + + JOCTET * buffer; /* start of buffer */ + boolean start_of_file; /* have we gotten any data yet? */ + void * userdata; /* used for callback */ + ULONG offset; +} my_source_mgr; + +typedef my_source_mgr * my_src_ptr; + +#define INPUT_BUF_SIZE (64*4096) /* choose an efficiently fread'able size */ + + +/* + * Initialize source --- called by jpeg_read_header + * before any data is actually read. + */ + +METHODDEF(void) +linit_source (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* We reset the empty-input-file flag for each image, + * but we don't clear the input buffer. + * This is correct behavior for reading a series of images from one source. + */ + src->start_of_file = TRUE; + src->offset=0; +} + + +/* + * Fill the input buffer --- called whenever buffer is emptied. + * + * In typical applications, this should read fresh data into the buffer + * (ignoring the current state of next_input_byte & bytes_in_buffer), + * reset the pointer & count to the start of the buffer, and return TRUE + * indicating that the buffer has been reloaded. It is not necessary to + * fill the buffer entirely, only to obtain at least one more byte. + * + * There is no such thing as an EOF return. If the end of the file has been + * reached, the routine has a choice of ERREXIT() or inserting fake data into + * the buffer. In most cases, generating a warning message and inserting a + * fake EOI marker is the best course of action --- this will allow the + * decompressor to output however much of the image is there. However, + * the resulting error message is misleading if the real problem is an empty + * input file, so we handle that case specially. + * + * In applications that need to be able to suspend compression due to input + * not being available yet, a FALSE return indicates that no more data can be + * obtained right now, but more may be forthcoming later. In this situation, + * the decompressor will return to its caller (with an indication of the + * number of scanlines it has read, if any). The application should resume + * decompression after it has loaded more data into the input buffer. Note + * that there are substantial restrictions on the use of suspension --- see + * the documentation. + * + * When suspending, the decompressor will back up to a convenient restart point + * (typically the start of the current MCU). next_input_byte & bytes_in_buffer + * indicate where the restart point will be if the current call returns FALSE. + * Data beyond this point must be rescanned after resumption, so move it to + * the front of the buffer rather than discarding it. + */ + +METHODDEF(boolean) +lfill_input_buffer (j_decompress_ptr cinfo) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + size_t nbytes; + if (src->buffer) free(src->buffer); + src->buffer=NULL; + nbytes = jpeg_call_reader(src->offset, INPUT_BUF_SIZE,(char **)&(src->buffer), src->userdata); + + if (nbytes <= 0) { + WARNMS(cinfo, JWRN_JPEG_EOF); + src->buffer = (JOCTET *)malloc(2); + src->buffer[0] = (JOCTET) 0xFF; + src->buffer[1] = (JOCTET) JPEG_EOI; + nbytes = 2; + + } + src->offset+=nbytes; + + src->pub.next_input_byte = src->buffer; + src->pub.bytes_in_buffer = nbytes; + src->start_of_file = FALSE; + + return TRUE; +} + + +/* + * Skip data --- used to skip over a potentially large amount of + * uninteresting data (such as an APPn marker). + * + * Writers of suspendable-input applications must note that skip_input_data + * is not granted the right to give a suspension return. If the skip extends + * beyond the data currently in the buffer, the buffer can be marked empty so + * that the next read will cause a fill_input_buffer call that can suspend. + * Arranging for additional bytes to be discarded before reloading the input + * buffer is the application writer's problem. + */ + +METHODDEF(void) +lskip_input_data (j_decompress_ptr cinfo, long num_bytes) +{ + my_src_ptr src = (my_src_ptr) cinfo->src; + + /* Just a dumb implementation for now. Could use fseek() except + * it doesn't work on pipes. Not clear that being smart is worth + * any trouble anyway --- large skips are infrequent. + */ + if (num_bytes > 0) { + while (num_bytes > (long) src->pub.bytes_in_buffer) { + num_bytes -= (long) src->pub.bytes_in_buffer; + (void) lfill_input_buffer(cinfo); + /* note we assume that fill_input_buffer will never return FALSE, + * so suspension need not be handled. + */ + } + src->pub.next_input_byte += (size_t) num_bytes; + src->pub.bytes_in_buffer -= (size_t) num_bytes; + } +} + + +/* + * An additional method that can be provided by data source modules is the + * resync_to_restart method for error recovery in the presence of RST markers. + * For the moment, this source module just uses the default resync method + * provided by the JPEG library. That method assumes that no backtracking + * is possible. + */ + + +/* + * Terminate source --- called by jpeg_finish_decompress + * after all data has been read. Often a no-op. + * + * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding + * application must deal with any cleanup that should happen even + * for error exit. + */ + +METHODDEF(void) +lterm_source (j_decompress_ptr cinfo) +{ + /* no work necessary here */ +} + + +/* + * Prepare for input from a stdio stream. + * The caller must have already opened the stream, and is responsible + * for closing it after finishing decompression. + */ + +extern "C" void +jpeg_memio_src (j_decompress_ptr cinfo, void * userdata) +{ + my_src_ptr src; + + /* The source object and input buffer are made permanent so that a series + * of JPEG images can be read from the same file by calling jpeg_stdio_src + * only before the first one. (If we discarded the buffer at the end of + * one image, we'd likely lose the start of the next one.) + * This makes it unsafe to use this manager and a different source + * manager serially with the same JPEG object. Caveat programmer. + */ + if (cinfo->src == NULL) { /* first time for this JPEG object? */ + cinfo->src = (struct jpeg_source_mgr *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + SIZEOF(my_source_mgr)); + src = (my_src_ptr) cinfo->src; + src->buffer = NULL; + } + + src = (my_src_ptr) cinfo->src; + src->pub.init_source = linit_source; + src->pub.fill_input_buffer = lfill_input_buffer; + src->pub.skip_input_data = lskip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */ + src->pub.term_source = lterm_source; + src->userdata=userdata; + src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */ + src->pub.next_input_byte = NULL; /* until buffer loaded */ + src->offset=0; + src->userdata=userdata; + if (src->buffer) { + free(src->buffer); + src->buffer=NULL; + } +} +/* cleanup to be called before cleanup of cinfo*/ +extern "C" void +jpeg_memio_cleanup (j_decompress_ptr cinfo) { + my_src_ptr src=(my_src_ptr)cinfo->src; + Log::getInstance()->log("BJpeg", Log::DEBUG, "cleanup src, src=%p, buf=%p",src,(src?src->buffer:0)); + if (src && src->buffer) { + free(src->buffer); + src->buffer=NULL; + } +} } diff --git a/wjpeg.h b/wjpeg.h index ec2d78d..2e2a9c9 100644 --- a/wjpeg.h +++ b/wjpeg.h @@ -36,15 +36,68 @@ extern "C" #include "widget.h" #include "surface.h" +//a reader to be implemented by the caller +class JpegReader { + public: + //init reading the file + //return <=0 if error or size of image + virtual ULONG initRead(const char *filename)=0; + //read the next chunk of jpeg data + //offset - from start of file + //len I buf len (max bytes to read) + //return read len, 0 on EOF, -1 on error, *buf set to buffer + //will be released with free(!!!) after decoding + virtual ULONG readChunk(ULONG offset,ULONG len,char **buf)=0; + virtual ~JpegReader(){}; +}; + class WJpeg : public Widget { public: - - int init(char* fileName); + WJpeg(); + virtual ~WJpeg(); + int init(char* fileName, bool useImage=true, JpegReader *rdr=NULL); + //rotate (with next draw!) by 90/180/270 + void setRotate(int amount); + static const int ROT_0=0; + static const int ROT_90=1; + static const int ROT_180=2; + static const int ROT_270=3; void draw(); + bool hasError(); + /** + * get an integer info from the current picture + */ + ULONG getJpegInfo(ULONG tag); + //in tags + static const ULONG JPEG_WIDTH=1; //picture width + static const ULONG JPEG_HEIGHT=2; //picture height + static const ULONG JPEG_SIZE=3; //picture (compressed) size + static const ULONG JPEG_ROTATE=4; //rotate setting + static const ULONG JPEG_SCALE=5; //scale setting (1/n) + /** + * get a string info from the picture + * the user must provide a buffer of + * INFO_BUFFER size + */ + int getJpegInfo(ULONG tag, char * buffer); + static const int INFO_BUFFER=200; + //string info tags + static const ULONG JPEG_FILENAME=100; //abbreviated filename private: + int drawJpeg(); + //our drawPixel with considers rotation + void drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c); char* fileName; + JpegReader *reader; + bool useImageDimensions; + ULONG jsize; + ULONG jheight; + ULONG jwidth; + ULONG jscale; + bool jerror; + int rotate; }; #endif