337 lines
9.5 KiB
C++
337 lines
9.5 KiB
C++
// ------------------------------------------------------------------------
|
|
// audioio-db-client.cpp: Client class for double-buffering providing
|
|
// additional layer of buffering for objects
|
|
// derived from AUDIO_IO.
|
|
// Copyright (C) 2000-2005,2009 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 <unistd.h> /* open(), close() */
|
|
|
|
#include <kvu_dbc.h>
|
|
#include <kvu_numtostr.h>
|
|
|
|
#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->copy_all_content(*source);
|
|
pbuffer_repp->advance_read_pointer();
|
|
pserver_repp->signal_client_activity();
|
|
change_position_in_samples(sbuf->length_in_samples());
|
|
}
|
|
else {
|
|
sbuf->number_of_channels(channels());
|
|
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;
|
|
}
|
|
}
|
|
|
|
// --------
|
|
DBC_ENSURE(sbuf->number_of_channels() == channels());
|
|
// --------
|
|
}
|
|
|
|
/**
|
|
* 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->copy_all_content(*sbuf);
|
|
target->number_of_channels(channels());
|
|
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(position_in_samples()); // hack to force a restart of the db server
|
|
std::cerr << "(audioio-db-client) Serious trouble with the disk-io subsystem! (output)" << std::endl;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stops the DB server in case it's running.
|
|
* Returns true if server was running. The return
|
|
* value should be passed to restore_db_server_state()
|
|
* function.
|
|
*/
|
|
bool AUDIO_IO_DB_CLIENT::pause_db_server_if_running(void)
|
|
{
|
|
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);
|
|
}
|
|
|
|
return was_running;
|
|
}
|
|
|
|
void AUDIO_IO_DB_CLIENT::restore_db_server_state(bool was_running)
|
|
{
|
|
if (was_running == true) {
|
|
pserver_repp->start();
|
|
pserver_repp->wait_for_full();
|
|
DBC_CHECK(pserver_repp->is_running() == true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Seeks to the current position.
|
|
*
|
|
* Note! Seeking involves stopping the whole db
|
|
* server, so it's a costly operation.
|
|
*/
|
|
SAMPLE_SPECS::sample_pos_t AUDIO_IO_DB_CLIENT::seek_position(SAMPLE_SPECS::sample_pos_t pos)
|
|
{
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
|
"seek " + label() +
|
|
" to pos " + kvu_numtostr(pos) + ".");
|
|
SAMPLE_SPECS::sample_pos_t res =
|
|
child()->position_in_samples();
|
|
|
|
if (child()->supports_seeking() == true) {
|
|
bool was_running = pause_db_server_if_running();
|
|
|
|
child()->seek_position_in_samples(pos);
|
|
res = child()->position_in_samples();
|
|
if (pbuffer_repp != 0) {
|
|
pbuffer_repp->reset();
|
|
}
|
|
|
|
finished_rep = false;
|
|
|
|
restore_db_server_state(was_running);
|
|
}
|
|
|
|
return AUDIO_IO_PROXY::seek_position(res);
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
|
|
void AUDIO_IO_DB_CLIENT::start_io(void)
|
|
{
|
|
AUDIO_IO_PROXY::start_io();
|
|
|
|
/* note: child may have changed its position after
|
|
* start_io() is issued (via AUDIO_IO_PROXY::start_io() */
|
|
|
|
if (child()->supports_seeking() != true) {
|
|
bool was_running = pause_db_server_if_running();
|
|
|
|
set_position_in_samples(child()->position_in_samples());
|
|
|
|
/* as position might have changed, flush the buffers */
|
|
if (pbuffer_repp != 0) {
|
|
pbuffer_repp->reset();
|
|
}
|
|
|
|
restore_db_server_state(was_running);
|
|
}
|
|
}
|
|
|
|
void AUDIO_IO_DB_CLIENT::stop_io(void)
|
|
{
|
|
AUDIO_IO_PROXY::stop_io();
|
|
}
|