#ifndef DISABLE_FILE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DMX_LOW_BITRATE #define DMX_LOW_BITRATE 0x4000 #endif #if HAVE_DVB_API_VERSION < 3 #include #define DVR_DEV "/dev/dvb/card0/dvr1" #define DEMUX1_DEV "/dev/dvb/card0/demux1" #else #include #if HAVE_DBOX2_DRIVER #define DVR_DEV "/dev/dvb/adapter0/dvr0" #define DEMUX1_DEV "/dev/dvb/adapter0/demux0" #else #define DVR_DEV "/dev/dvb/adapter0/dvr1" #define DEMUX1_DEV "/dev/dvb/adapter0/demux1" #endif #endif static char *begptr(void) { return (char *) &begptr; } static char *endptr(void); extern ePermanentTimeshift permanentTimeshift; static pthread_mutex_t PMTLock = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; inline void lock_pmt() { pthread_mutex_lock(&PMTLock); } inline void unlock_pmt(void*) { pthread_mutex_unlock(&PMTLock); } static int section_length(const unsigned char *buf) { return ((buf[1] << 8) | buf[2]) & 0x0fff; } static int ts_header(unsigned char *dest, int pusi, int pid, int scrmbl, int adap, unsigned int &cc) { dest[0] = 0x47; dest[1] = (!!pusi << 6) | (pid >> 8); dest[2] = pid; dest[3] = (scrmbl << 6) | (adap << 4) | (cc++ & 0x0f); return 4; } static int section2ts(unsigned char *dest, const unsigned char *src, int pid, unsigned int &ccount ) { unsigned char *orig = dest; int pusi = 1; int len, cplen; orig = dest; for (len = section_length(src) + 3; len > 0; len -= cplen) { dest += ts_header(dest, pusi, pid, 0, 1, ccount); if (pusi) { *dest++ = 0x00; /* pointer_field */ cplen = MIN(len, 183); pusi = 0; } else { cplen = MIN(len, 184); } memcpy(dest, src, cplen); dest += cplen; src += cplen; } if ((cplen = (dest - orig) % 188)) { cplen = 188 - cplen; memset(dest, 0xff, cplen); dest += cplen; } return dest - orig; } static int buf_free(int rpos, int wpos, int b_size) { int free; free=rpos-wpos; if (free<=0) free+=b_size; free--; return(free); } static int buf_write(char *data, int size, char *buf, int *rpos, int *wpos, int b_size) { int free; int wsize1; int wsize2; if (!size) return(1); if (size>=b_size) return(0); free=buf_free(*rpos, *wpos, b_size); if (size>free) return(0); wsize1=b_size-*wpos; if (wsize1>size) wsize1=size; if (wsize1) { memcpy(buf+*wpos,data,wsize1); *wpos+=wsize1; } wsize2=size-wsize1; if (wsize2) { memcpy(buf,data+wsize1,wsize2); *wpos=wsize2; } if (*wpos>=b_size) *wpos=0; return(1); } static int buf_read(char *data, int size, char *buf, int *rpos, int *wpos, int b_size) { int avail; int rsize1; int rsize2; avail=*wpos-*rpos; if (avail>=b_size) avail-=b_size; else if (avail<0) avail+=b_size; if (avail==0) return(0); if (size>avail) size=avail; rsize1=b_size-*rpos; if (rsize1>size) rsize1=size; if (rsize1) { memcpy(data,buf+*rpos,rsize1); *rpos+=rsize1; } rsize2=size-rsize1; if (rsize2) { memcpy(data+rsize1,buf,rsize2); *rpos=rsize2; } if (*rpos>=b_size) *rpos=0; return (rsize1+rsize2); } int eDVBRecorder::flushBuffer() { char wbuf[8192]; int towr; if (rpos==wpos) return(0); eDebug("[eDVBRecorder]: flushBuffer - flushing"); while ((towr=buf_read(wbuf,8192,buf,&rpos,&wpos,b_size))) { write(outfd,wbuf,towr); } return(1); } void SAHandler(int ptr) { if ( eDVB::getInstance() && eDVB::getInstance()->recorder ) eDVB::getInstance()->recorder->setWritePatPmtFlag(); } #ifndef EBUFFEROVERFLOW #define EBUFFEROVERFLOW 769 #endif void eDVBRecorder::report_error(void) { state = stateError; rmessagepump.send(eDVBRecorderMessage(eDVBRecorderMessage::rWriteError)); } static int rt_raisepri(int pri) { struct sched_param scp; ::bzero(&scp, sizeof (scp)); scp.sched_priority = sched_get_priority_max(SCHED_RR) - pri; if (::sched_setscheduler(0, SCHED_RR, &scp) < 0) { eDebug("[eDVBRecorder]: WARNING: Cannot set RR-scheduler"); return (-1); } return (0); } void eDVBRecorder::thread() { char first_local_var; fd_set rfds,wfds; struct timeval tv; int retval; int origrflags; int origwflags; int origprio; int mypid; char readbuf[RECORD_BUFFER_LIMIT]; bool synclost; int maxfd; int syncerrcnt; char *lockadr[16]; size_t locklen[16]; int lockcnt,i; char *mycode; char last_local_var; __label__ __here; __here: mycode=(char *)&&__here; eDebug("eDVBRecorder:: this at %08x",this); signal(SIGALRM, SAHandler); PatPmtWrite(); origrflags=::fcntl(dvrfd,F_GETFL); ::fcntl(dvrfd,F_SETFL,origrflags|O_NONBLOCK); origwflags=::fcntl(outfd,F_GETFL); ::fcntl(outfd,F_SETFL,origwflags|O_NONBLOCK); mypid=::getpid(); origprio=::getpriority(PRIO_PROCESS,mypid); ::setpriority(PRIO_PROCESS,mypid,origprio-10); rt_raisepri(0); /* Lock memory */ bzero(lockadr,sizeof(char *)*16); lockcnt=0; lockadr[lockcnt]=&first_local_var; locklen[lockcnt++]=&last_local_var-&first_local_var; lockadr[lockcnt]=(char *)this; locklen[lockcnt++]=sizeof(eDVBRecorder); lockadr[lockcnt]=begptr(); locklen[lockcnt++]=endptr()-begptr(); lockadr[lockcnt]=mycode-1024;/* This is a dirty hack since I don't know where the procedures are exactly in memory */ locklen[lockcnt++]=65536; for(i=0;idvrfd)?outfd:dvrfd; syncerrcnt=0; while (state == stateRunning) { FD_ZERO(&rfds); FD_SET(dvrfd, &rfds); FD_ZERO(&wfds); if (rpos!=wpos) FD_SET(outfd,&wfds); tv.tv_sec = 60; tv.tv_usec = 0; retval=::select(maxfd+1,&rfds,&wfds,NULL,&tv); if (retval<=0) { if (retval<0) { if (errno!=EAGAIN && errno!=EINTR) { eDebug("recording select error %s", strerror(errno)); report_error(); break; } } else { eDebug("timeout while reading DVB stream"); report_error(); break; } } if (FD_ISSET(dvrfd,&rfds)) { char *xrbuf; int rd; rd=RECORD_BUFFER_LIMIT; if (b_size-wpos>RECORD_BUFFER_LIMIT && rpos<=wpos) // fast { xrbuf=buf+wpos; } else { xrbuf=readbuf; //slow // eDebug("slow read"); rd = buf_free(rpos,wpos,b_size); if (!rd) { eDebug("[eDVBRecorder]: Buffer overflow 1"); report_error(); break; } if ( rd > RECORD_BUFFER_LIMIT ) rd = RECORD_BUFFER_LIMIT; } if (rdWRITE_BUFFER_LIMIT) towr=WRITE_BUFFER_LIMIT; xwbuf=buf+rpos; } else // slow { towr=buf_read(readbuf,RECORD_BUFFER_LIMIT,buf,&rpos,&wpos,b_size); xwbuf=readbuf; // eDebug("slow write"); } wr=write(outfd, xwbuf, towr); if (wr<0) { if (errno!=EINTR && errno!=EAGAIN) { eDebug("[eDVBRecorder]: write error %s",strerror(errno)); report_error(); break; } wr=0; } if (xwbuf==readbuf) { if (wrpid); // PMT addNewPID(pmt->PCR_PID); // PCR for (ePtrList::iterator i(pmt->streams); i != pmt->streams.end(); ++i) { int record=0; switch (i->stream_type) { case 1: // video.. case 2: record=1; break; case 3: // audio.. case 4: record=1; break; case 6: for (ePtrList::iterator it(i->ES_info); it != i->ES_info.end(); ++it) { switch (it->Tag()) { case DESCR_AC3: { record=1; break; } #ifdef RECORD_TELETEXT case DESCR_TELETEXT: { record=2; // low bti break; } #endif #ifdef RECORD_SUBTITLES case DESCR_SUBTITLING: { record=2; break; } #endif } } break; } if (record) addNewPID(i->elementary_PID, record==2?DMX_LOW_BITRATE:0); #ifdef RECORD_ECM for (ePtrList::iterator it(i->ES_info); it != i->ES_info.end(); ++it) if (it->Tag() == 9) addNewPID(((CADescriptor*)*it)->CA_PID); #endif } validatePIDs(); pthread_cleanup_push( unlock_pmt, 0 ); lock_pmt(); if (PmtData) delete [] PmtData; PmtData = pmt->getRAW(); pthread_cleanup_pop(1); pmt->unlock(); } } } void eDVBRecorder::gotBackMessage(const eDVBRecorderMessage &msg) { switch (msg.code) { case eDVBRecorderMessage::rWriteError: /* emit */ recMessage(recWriteError); break; default: break; } } int eDVBRecorder::openFile(int suffix) { eString tfilename=filename; if (suffix) tfilename+=eString().sprintf(".%03d", suffix); size=0; written_since_last_sync=0; if (outfd >= 0) ::close(outfd); struct stat64 s; if ( !stat64(tfilename.c_str(), &s) ) { rename(tfilename.c_str(), (tfilename+".$$$").c_str() ); if (eBackgroundFileEraser::getInstance()) eBackgroundFileEraser::getInstance()->erase((tfilename+".$$$").c_str()); } #ifdef HAVE_DREAMBOX_HARDWARE outfd=::open(tfilename.c_str(), O_CREAT|O_WRONLY|O_TRUNC|O_LARGEFILE, 0555); #else if (access("/var/etc/.no_o_sync", R_OK) == 0) outfd=::open(tfilename.c_str(),O_CREAT|O_WRONLY|O_TRUNC|O_LARGEFILE, 0555); else outfd=::open(tfilename.c_str(),O_SYNC|O_CREAT|O_WRONLY|O_TRUNC|O_LARGEFILE, 0555); #endif if (outfd < 0) { eDebug("failed to open DVR file: %s (%m)", tfilename.c_str()); return -1; } /* turn off kernel caching strategies */ posix_fadvise64(outfd, 0, 0, POSIX_FADV_RANDOM); return 0; } void eDVBRecorder::open(const char *_filename) { eDebug("eDVBRecorder::open(%s)", _filename); pids.clear(); newpids.clear(); filename=_filename; int tmp=1024*1024; // 1G if (eConfig::getInstance()) eConfig::getInstance()->getKey("/extras/record_splitsize", tmp); splitsize=tmp; splitsize*=1024; splitsize/=188; // align to 188 bytes.. splitsize*=188; openFile(splits=0); if ( dvrfd >= 0 ) ::close(dvrfd); dvrfd=::open(DVR_DEV, O_RDONLY); if (dvrfd < 0) { eDebug("failed to open "DVR_DEV" (%m)"); ::close(outfd); outfd=-1; return; } if (outfd < 0) rmessagepump.send(eDVBRecorderMessage(eDVBRecorderMessage::rWriteError)); posix_fadvise64(dvrfd, 0, 0, POSIX_FADV_RANDOM); } std::pair::iterator,bool> eDVBRecorder::addPID(int pid, int flags) { eDebugNoNewLine("eDVBRecorder::addPID(0x%x)...", pid ); pid_t p; p.pid=pid; if ( pids.find(p) != pids.end() ) { eDebug("we already have this pid... skip!"); return std::pair::iterator, bool>(pids.end(),false); } p.fd=::open(DEMUX1_DEV, O_RDWR); if (p.fd < 0) { eDebug("failed to open demux1"); return std::pair::iterator, bool>(pids.end(),false); } #if HAVE_DVB_API_VERSION < 3 dmxPesFilterParams flt; flt.pesType=DMX_PES_OTHER; #else dmx_pes_filter_params flt; flt.pes_type=DMX_PES_OTHER; #endif flt.pid=p.pid; flt.input=DMX_IN_FRONTEND; flt.output=DMX_OUT_TS_TAP; flt.flags=flags; eDebug("flags %08x", flags); if (::ioctl(p.fd, DMX_SET_PES_FILTER, &flt)<0) { eDebug("DMX_SET_PES_FILTER failed (for pid %d)", flt.pid); ::close(p.fd); return std::pair::iterator, bool>(pids.end(),false); } return pids.insert(p); } void eDVBRecorder::addNewPID(int pid, int flags) { pid_t p; p.pid = pid; p.flags = flags; newpids.insert(p); } void eDVBRecorder::validatePIDs() { for (std::set::iterator it(pids.begin()); it != pids.end();) { std::set::iterator i = newpids.find(*it); if ( i == newpids.end() ) // no more existing pid... removePID((it++)->pid); else ++it; } for (std::set::iterator it(newpids.begin()); it != newpids.end(); ++it ) { std::pair::iterator,bool> newpid = addPID(it->pid, it->flags); if ( newpid.second ) { if ( state == stateRunning ) { if (newpid.first->fd >= 0) { if (::ioctl(newpid.first->fd, DMX_START, 0)<0) { eDebug("DMX_START failed (%m)"); removePID(it->pid); } } } } else eDebug("error during add new pid"); } newpids.clear(); } void eDVBRecorder::removePID(int pid) { pid_t p; p.pid=pid; std::set::iterator pi=pids.find(p); if (pi != pids.end()) { if (pi->fd >= 0) ::close(pi->fd); pids.erase(pi); eDebug("eDVBRecorder::removePID(0x%x)", pid); } } void eDVBRecorder::start() { if ( state == stateRunning ) return; eDebug("eDVBRecorder::start()"); state = stateRunning; if ( !thread_running() ) { eDebug("eDVBRecorder: run thread"); // run(prio,policy) run(); } for (std::set::iterator i(pids.begin()); i != pids.end(); ++i) { eDebug("starting pidfilter for pid %d", i->pid ); if (i->fd >= 0) { if (::ioctl(i->fd, DMX_START, 0)<0) { eDebug("DMX_START failed"); ::close(i->fd); pids.erase(i); } } } } void eDVBRecorder::stop() { if ( state == stateStopped ) return; eDebug("eDVBRecorder::stop()"); state = stateStopped; int timeout=20; while ( thread_running() && timeout ) { usleep(100000); // 2 sec time for thread shutdown --timeout; } for (std::set::iterator i(pids.begin()); i != pids.end(); ++i) if (i->fd >= 0) ::ioctl(i->fd, DMX_STOP, 0); flushBuffer(); } void eDVBRecorder::close() { if (state != stateStopped) stop(); eDebug("eDVBRecorder::close()"); for (std::set::iterator i(pids.begin()); i != pids.end(); ++i) if (i->fd >= 0) ::close(i->fd); pids.clear(); if (dvrfd >= 0) { ::close(dvrfd); dvrfd = -1; } if (outfd >= 0) { ::close(outfd); outfd = -1; } if ( thread_running() ) kill(true); } // Initialize eDVBRecorder::eDVBRecorder(PMT *pmt,PAT *pat) :state(stateStopped), rmessagepump(eApp, 1), dvrfd(-1) ,outfd(-1) ,rpos(0),wpos(0), b_size(RECORD_BUFFER_SIZE), PmtData(NULL), PatData(NULL) ,PmtCC(0), PatCC(0), writePatPmt(true) { CONNECT(rmessagepump.recv_msg, eDVBRecorder::gotBackMessage); rmessagepump.start(); if (pmt) { CONNECT( tPMT.tableReady, eDVBRecorder::PMTready ); tPMT.start((PMT*)pmt->createNext(), DEMUX1_DEV ); PmtData=pmt->getRAW(); pmtpid=pmt->pid; if (pat) { PAT p; p.entries.setAutoDelete(false); p.version=pat->version; p.transport_stream_id=pat->transport_stream_id; for (ePtrList::iterator it(pat->entries); it != pat->entries.end(); ++it) { if ( it->program_number == pmt->program_number ) { p.entries.push_back(*it); PatData=p.getRAW(); break; } } } } } eDVBRecorder::~eDVBRecorder() { if (PatData) delete [] PatData; if (PmtData) delete [] PmtData; eDVBServiceController *sapi = NULL; if (eDVB::getInstance()) sapi = eDVB::getInstance()->getServiceAPI(); if (sapi) { // workaround for faked service types.. eServiceReferenceDVB tmp = sapi->service; tmp.data[0] = recRef.getServiceType(); if ( tmp != recRef ) { eServiceReferenceDVB ref=sapi->service; eDVBCaPMTClientHandler::distribute_leaveService(recRef); } } close(); } void eDVBRecorder::writeSection(void *data, int pid, unsigned int &cc) { if ( !data ) return; //eDebug("[eDVBRecorder]: writeSection() - wpos=%d",wpos); __u8 secbuf[4300]; // with ts overhead... int len = section2ts(secbuf, (__u8*)data, pid, cc); //int sync=*secbuf; //eDebug("[eDVBRecorder]: writing %d bytes, sync: %x",len,sync); buf_write((char *)secbuf,len,buf,&rpos,&wpos,b_size); } void eDVBRecorder::PatPmtWrite() { //eDebug("[eDVBRecorder]: PatPmtWrite()"); if ( PatData ) writeSection(PatData, 0, PatCC ); pthread_cleanup_push( unlock_pmt, 0 ); lock_pmt(); if ( PmtData ) writeSection(PmtData, pmtpid, PmtCC ); pthread_cleanup_pop(1); alarm(2); } void eDVBRecorder::SetSlice(int slice) { splitlock.lock(); eString oldfilename=filename; if (splits) oldfilename+=eString().sprintf(".%03d", splits); splits = slice; splitlock.unlock(); eString newfilename=filename; if (slice) newfilename+=eString().sprintf(".%03d", slice); eDebug("rename current recording:%s|%s",oldfilename.c_str(),newfilename.c_str()); ::rename(oldfilename.c_str(),newfilename.c_str()); } static char * endptr(void) { return (char *)&endptr; } #endif //DISABLE_FILE