// ------------------------------------------------------------------------ // audioio-db-client.cpp: Client class for double-buffering providing // additional layer of buffering for objects // derived from AUDIO_IO. // Copyright (C) 2000-2005 Kai Vehmanen // // Attributes: // eca-style-version: 3 // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // ------------------------------------------------------------------------ #include /* open(), close() */ #include #include #include "samplebuffer.h" #include "eca-logger.h" #include "audioio-db-client.h" /** * Constructor. The given client object is registered to * the given db server as a client object. * * Ownership of 'aobject' is transfered to this db client * object if 'transfer_ownership' is true. */ AUDIO_IO_DB_CLIENT::AUDIO_IO_DB_CLIENT (AUDIO_IO_DB_SERVER *pserver, AUDIO_IO* aobject, bool transfer_ownership) : pserver_repp(pserver), free_child_rep(transfer_ownership) { set_child(aobject); pbuffer_repp = 0; xruns_rep = 0; finished_rep = false; recursing_rep = false; ECA_LOG_MSG(ECA_LOGGER::user_objects, std::string("DB-client created for ") + child()->label() + "."); // just in case the child object has already been configured fetch_initial_child_data(); } /** * Copy attributes from the proxied (child) object. */ void AUDIO_IO_DB_CLIENT::fetch_initial_child_data(void) { // note! the child object is one that is configured // at this point set_audio_format(child()->audio_format()); set_position_in_samples(child()->position_in_samples()); set_length_in_samples(child()->length_in_samples()); set_buffersize(child()->buffersize()); set_io_mode(child()->io_mode()); set_label(child()->label()); toggle_nonblocking_mode(child()->nonblocking_mode()); } /** * Desctructor. Unregisters the client from the db * server. */ AUDIO_IO_DB_CLIENT::~AUDIO_IO_DB_CLIENT(void) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "destructor " + label() + "."); if (is_open() == true) { close(); } if (pserver_repp != 0) { bool was_running = false; if (pserver_repp->is_running() == true) { was_running = true; pserver_repp->stop(); pserver_repp->wait_for_stop(); DBC_CHECK(pserver_repp->is_running() != true); } pserver_repp->unregister_client(child()); pbuffer_repp = 0; if (was_running == true) { pserver_repp->start(); } } if (free_child_rep != true) { /* to avoid deleting the original registered child */ release_child_no_delete(); } if (xruns_rep > 0) std::cerr << "(audioio-db-client) There were total " << xruns_rep << " xruns." << std::endl; } /** * Whether all data has been processed? If opened in mode 'io_read', * this means that end of stream has been reached. If opened in * 'io_write' or 'io_readwrite' modes, finished status usually * means that an error has occured (no space left, etc). After * finished() has returned 'true', further calls to read_buffer() * and/or write_buffer() won't process any data. */ bool AUDIO_IO_DB_CLIENT::finished(void) const { return finished_rep; } /** * Reads samples to buffer pointed by 'sbuf'. If necessary, the target * buffer will be resized. */ void AUDIO_IO_DB_CLIENT::read_buffer(SAMPLE_BUFFER* sbuf) { DBC_CHECK(pbuffer_repp != 0); if (pbuffer_repp->read_space() > 0) { SAMPLE_BUFFER* source = pbuffer_repp->sbufs_rep[pbuffer_repp->readptr_rep.get()]; sbuf->number_of_channels(source->number_of_channels()); sbuf->copy(*source); pbuffer_repp->advance_read_pointer(); pserver_repp->signal_client_activity(); change_position_in_samples(sbuf->length_in_samples()); } else { if (pbuffer_repp->finished_rep.get() == 1) { finished_rep = true; sbuf->length_in_samples(0); } else { xruns_rep++; sbuf->length_in_samples(0); std::cerr << "(audioio-db-client) WARNING: Underrun in reading from \"" << child()->label() << "\". Trying to recover." << std::endl; } } } /** * Writes all data from sample buffer pointed by 'sbuf'. Notes * concerning read_buffer() also apply to this routine. */ void AUDIO_IO_DB_CLIENT::write_buffer(SAMPLE_BUFFER* sbuf) { DBC_CHECK(pbuffer_repp != 0); if (pbuffer_repp->write_space() > 0) { SAMPLE_BUFFER* target = pbuffer_repp->sbufs_rep[pbuffer_repp->writeptr_rep.get()]; target->number_of_channels(sbuf->number_of_channels()); target->copy(*sbuf); pbuffer_repp->advance_write_pointer(); pserver_repp->signal_client_activity(); change_position_in_samples(sbuf->length_in_samples()); extend_position(); } else { if (pbuffer_repp->finished_rep.get() == 1) finished_rep = true; else { /* NOTE: not always rt-safe, but it's better to break rt-safety than * to lose recorded data */ std::cerr << "(audioio-db-client) WARNING: Overrun in writing to \"" << child()->label() << "\". Trying to recover." << std::endl; xruns_rep++; pserver_repp->wait_for_full(); if (recursing_rep != true && pbuffer_repp->write_space() > 0) { recursing_rep = true; this->write_buffer(sbuf); recursing_rep = false; } else { seek_position(); // hack to force a restart of the db server std::cerr << "(audioio-db-client) Serious trouble with the disk-io subsystem! (output)" << std::endl; } } } } /** * Seeks to the current position. * * Note! Seeking involves stopping the whole db * server, so it's a costly operation. */ void AUDIO_IO_DB_CLIENT::seek_position(void) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "seek " + label() + " to pos " + kvu_numtostr(position_in_seconds_exact()) + "."); bool was_running = false; if (pserver_repp->is_running() == true) { was_running = true; pserver_repp->stop(); pserver_repp->wait_for_stop(); DBC_CHECK(pserver_repp->is_running() != true); } child()->seek_position_in_samples(position_in_samples()); if (pbuffer_repp != 0) { pbuffer_repp->reset(); } finished_rep = false; if (was_running == true) { pserver_repp->start(); pserver_repp->wait_for_full(); DBC_CHECK(pserver_repp->is_running() == true); } } /** * Opens the child audio object (possibly in exclusive mode). * This routine is meant for opening files and devices, * loading libraries, etc. */ void AUDIO_IO_DB_CLIENT::open(void) throw(AUDIO_IO::SETUP_ERROR&) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "open " + label() + "."); if (child()->is_open() != true) { child()->open(); } set_audio_format(child()->audio_format()); set_length_in_samples(child()->length_in_samples()); if (pbuffer_repp == 0) { pserver_repp->register_client(child()); pbuffer_repp = pserver_repp->get_client_buffer(child()); for(unsigned int n = 0; n < pbuffer_repp->sbufs_rep.size(); n++) { pbuffer_repp->sbufs_rep[n]->number_of_channels(channels()); pbuffer_repp->sbufs_rep[n]->length_in_samples(buffersize()); } if (io_mode() == AUDIO_IO::io_read) pbuffer_repp->io_mode_rep = AUDIO_IO::io_read; else pbuffer_repp->io_mode_rep = AUDIO_IO::io_write; } AUDIO_IO::open(); } /** * Closes the child audio object. After calling this routine, * all resources (ie. soundcard) must be freed * (they can be used by other processes). */ void AUDIO_IO_DB_CLIENT::close(void) { ECA_LOG_MSG(ECA_LOGGER::user_objects, "close " + label() + "."); if (child()->is_open() == true) child()->close(); AUDIO_IO::close(); }