From ace408c37bec26438d603832c907fca069a8d491 Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Sun, 15 Jul 2007 16:56:59 +0000 Subject: [PATCH] Media Player From Andreas Vogel --- audio.h | 1 + audioplayer.cc | 734 +++++++++++++++++++++++++++ audioplayer.h | 203 ++++++++ demuxeraudio.cc | 1223 +++++++++++++++++++++++++++++++++++++++++++++ demuxeraudio.h | 172 +++++++ id3.h | 92 ++++ language-data.h | 283 ++++++++++- mark.h | 4 + media.cc | 183 +++++++ media.h | 90 ++++ objects.mk | 4 +- player.cc | 3 + playerradio.cc | 2 + readme_media.txt | 120 +++++ remote.cc | 1 + vaudioplayer.cc | 594 ++++++++++++++++++++++ vaudioplayer.h | 109 ++++ vdr.cc | 173 ++++++- vdr.h | 19 +- vmedialist.cc | 710 ++++++++++++++++++++++++++ vmedialist.h | 96 ++++ vpicture.cc | 420 ++++++++++++++++ vpicture.h | 103 ++++ vpicturebanner.cc | 136 +++++ vpicturebanner.h | 58 +++ vwelcome.cc | 27 +- vwelcome.h | 2 + wjpeg.cc | 552 ++++++++++++++++++-- wjpeg.h | 57 ++- 29 files changed, 6127 insertions(+), 44 deletions(-) create mode 100644 audioplayer.cc create mode 100644 audioplayer.h create mode 100644 demuxeraudio.cc create mode 100644 demuxeraudio.h create mode 100644 id3.h create mode 100644 media.cc create mode 100644 media.h create mode 100644 readme_media.txt create mode 100644 vaudioplayer.cc create mode 100644 vaudioplayer.h create mode 100644 vmedialist.cc create mode 100644 vmedialist.h create mode 100644 vpicture.cc create mode 100644 vpicture.h create mode 100644 vpicturebanner.cc create mode 100644 vpicturebanner.h 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 -- 2.39.5