/* * Copyright (C) 2005-2007 by Pieter Palmers * * This file is part of FFADO * FFADO = Free Firewire (pro-)audio drivers for linux * * FFADO is based upon FreeBoB. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "TimeSource.h" #include #include #include "libutil/Time.h" namespace Util { IMPL_DEBUG_MODULE( TimeSource, TimeSource, DEBUG_LEVEL_NORMAL ); TimeSource::TimeSource() : m_Master(NULL), m_last_master_time(0), m_last_time(0), m_slave_rate(0.0), m_slave_offset(0), m_last_err(0.0) { } TimeSource::~TimeSource() { } /** * (re)Initializes the TimeSource * * @return true if successful */ void TimeSource::initSlaveTimeSource() { ffado_microsecs_t my_time; ffado_microsecs_t master_time; ffado_microsecs_t my_time2; ffado_microsecs_t master_time2; if (m_Master) { my_time=getCurrentTime(); master_time=m_Master->getCurrentTime(); struct timespec ts; // sleep for ten milliseconds ts.tv_sec=0; ts.tv_nsec=10000000L; nanosleep(&ts,NULL); my_time2=getCurrentTime(); master_time2=m_Master->getCurrentTime(); float diff_slave=my_time2-my_time; float diff_master=master_time2-master_time; m_slave_rate=diff_slave/diff_master; // average of the two offset estimates m_slave_offset = my_time-wrapTime((ffado_microsecs_t)(master_time*m_slave_rate)); m_slave_offset += my_time2-wrapTime((ffado_microsecs_t)(master_time2*m_slave_rate)); m_slave_offset /= 2; m_last_master_time=master_time2; m_last_time=my_time2; debugOutput(DEBUG_LEVEL_NORMAL,"init slave: master=%lu, master2=%lu, diff=%f\n", master_time, master_time2, diff_master); debugOutput(DEBUG_LEVEL_NORMAL,"init slave: slave =%lu, slave2 =%lu, diff=%f\n", my_time, my_time2, diff_slave); debugOutput(DEBUG_LEVEL_NORMAL,"init slave: slave rate=%f, slave_offset=%lu\n", m_slave_rate, m_slave_offset ); } } /** * Maps a time point of the master to a time point * on it's own timeline * * @return the mapped time point */ ffado_microsecs_t TimeSource::mapMasterTime(ffado_microsecs_t master_time) { if(m_Master) { // calculate the slave based upon the master // and the estimated rate // linear interpolation int delta_master=master_time-m_last_master_time; float offset=m_slave_rate * ((float)delta_master); ffado_microsecs_t mapped=m_last_time+(int)offset; debugOutput(DEBUG_LEVEL_VERY_VERBOSE,"map time: master=%d, offset=%f, slave_base=%lu, pred_ticks=%lu\n", master_time, offset, m_last_time,mapped ); return wrapTime(mapped); } else { debugOutput( DEBUG_LEVEL_VERBOSE, "Requested map for non-slave TimeSource\n"); return master_time; } } /** * Update the internal state of the TimeSource * */ bool TimeSource::updateTimeSource() { // update all slaves for ( TimeSourceVectorIterator it = m_Slaves.begin(); it != m_Slaves.end(); ++it ) { // update the slave with the current // master time if (!(*it)->updateTimeSource()) return false; } // this TimeSource has a master if(m_Master) { ffado_microsecs_t my_time=getCurrentTime(); ffado_microsecs_t master_time=m_Master->getCurrentTime(); // we assume that the master and slave time are // measured at the same time, but that of course is // not really true. The DLL will have to filter this // out. // the difference in master time int64_t delta_master; if (master_time > m_last_master_time) { delta_master=master_time-m_last_master_time; } else { // wraparound delta_master=m_Master->unWrapTime(master_time)-m_last_master_time; } // the difference in slave time int64_t delta_slave; if (my_time > m_last_time) { delta_slave=my_time-m_last_time; } else { // wraparound delta_slave=unWrapTime(my_time)-m_last_time; } // the estimated slave difference int delta_slave_est=(int)(m_slave_rate * ((double)delta_master)); // the measured & estimated rate double rate_meas=((double)delta_slave/(double)delta_master); double rate_est=((double)m_slave_rate); m_last_err=(rate_meas-rate_est); m_slave_rate += 0.01*m_last_err; debugOutput(DEBUG_LEVEL_VERBOSE,"update slave: master=%llu, master2=%llu, diff=%lld\n", master_time, m_last_master_time, delta_master); debugOutput(DEBUG_LEVEL_VERBOSE,"update slave: slave =%llu, slave2 =%llu, diff=%lld, diff_est=%d\n", my_time, m_last_time, delta_slave, delta_slave_est); debugOutput(DEBUG_LEVEL_VERBOSE,"update slave: slave rate meas=%f, slave rate est=%f, err=%f, slave rate=%f\n", rate_meas, rate_est, m_last_err, m_slave_rate ); m_last_master_time=master_time; int64_t tmp = delta_slave_est; tmp += m_last_time; m_last_time = tmp; } return true; } /** * Sets the master TimeSource for this timesource. * This TimeSource will sync to the master TimeSource, * making that it will be able to map a time point of * the master to a time point on it's own timeline * * @param ts master TimeSource * @return true if successful */ bool TimeSource::setMaster(TimeSource *ts) { if (m_Master==NULL) { m_Master=ts; // initialize ourselves. initSlaveTimeSource(); return true; } else return false; } /** * Clears the master TimeSource for this timesource. * * @return true if successful */ void TimeSource::clearMaster() { m_Master=NULL; } /** * Registers a slave timesource to this master. * A slave TimeSource will sync to this TimeSource, * making that it will be able to map a time point of * the master (this) TimeSource to a time point on * it's own timeline * * @param ts slave TimeSource to register * @return true if successful */ bool TimeSource::registerSlave(TimeSource *ts) { // TODO: we should check for circular master-slave relationships. debugOutput( DEBUG_LEVEL_VERBOSE, "Registering slave (%p)\n", ts); assert(ts); // inherit debug level ts->setVerboseLevel(getDebugLevel()); if(ts->setMaster(this)) { m_Slaves.push_back(ts); return true; } else { return false; } } /** * Unregisters a slave TimeSource * * @param ts slave TimeSource to unregister * @return true if successful */ bool TimeSource::unregisterSlave(TimeSource *ts) { debugOutput( DEBUG_LEVEL_VERBOSE, "Unregistering TimeSource (%p)\n", ts); assert(ts); for ( TimeSourceVectorIterator it = m_Slaves.begin(); it != m_Slaves.end(); ++it ) { if ( *it == ts ) { m_Slaves.erase(it); ts->clearMaster(); return true; } } debugOutput( DEBUG_LEVEL_VERBOSE, " TimeSource (%p) not found\n", ts); return false; } /** * Set verbosity level. * All slave timesources get the same verbosity level * * @param l verbosity level */ void TimeSource::setVerboseLevel(int l) { setDebugLevel(l); for ( TimeSourceVectorIterator it = m_Slaves.begin(); it != m_Slaves.end(); ++it ) { (*it)->setVerboseLevel(l); } } void TimeSource::printTimeSourceInfo() { debugOutputShort( DEBUG_LEVEL_NORMAL, "TimeSource (%p) info\n", this); debugOutputShort( DEBUG_LEVEL_NORMAL, " Master : %p\n", m_Master); debugOutputShort( DEBUG_LEVEL_NORMAL, " Slave rate : %f\n", m_slave_rate); debugOutputShort( DEBUG_LEVEL_NORMAL, " Slave offset : %lld\n", m_slave_offset); debugOutputShort( DEBUG_LEVEL_NORMAL, " Last error : %f\n", m_last_err); debugOutputShort( DEBUG_LEVEL_NORMAL, " Last master time : %llu\n",m_last_master_time ); debugOutputShort( DEBUG_LEVEL_NORMAL, " Last slave time : %llu\n",m_last_time ); for ( TimeSourceVectorIterator it = m_Slaves.begin(); it != m_Slaves.end(); ++it ) { (*it)->printTimeSourceInfo(); } } } // end of namespace Util