sintonia/library/ecasound-2.7.2/libecasound/audioio-db-server.cpp

664 lines
18 KiB
C++

// ------------------------------------------------------------------------
// audioio-db-server.cpp: Audio i/o engine serving db clients.
// 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
// ------------------------------------------------------------------------
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <cstdlib>
#include <iostream>
#include <string>
#include <errno.h> /* ETIMEDOUT */
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/time.h> /* gettimeofday() */
#include <kvu_dbc.h>
#include <kvu_utils.h>
#include <kvu_numtostr.h>
#include "sample-specs.h"
#include "samplebuffer.h"
#include "eca-logger.h"
#include "audioio-db-server.h"
#include "audioio-db-server_impl.h"
// --
// Select features
// #define DB_PROFILING
// --
// Macro definitions
#ifdef DB_PROFILING
#define DB_PROFILING_INC(x) ++(x)
#define DB_PROFILING_STATEMENT(x) x
#else
#define DB_PROFILING_INC(x) (void)0
#define DB_PROFILING_STATEMENT(x) (void)0
#endif
// --
// Initialization of static member functions
const int AUDIO_IO_DB_SERVER::buffercount_default = 32;
const long int AUDIO_IO_DB_SERVER::buffersize_default = 1024;
// --
// Initialization of static, global functions
static int timed_wait(pthread_mutex_t* mutex, pthread_cond_t* cond, long int usecs);
static void timed_wait_print_result(int result, const char* tag, bool verbose);
/**
* Helper function for starting the slave thread.
*/
void* start_db_server_io_thread(void *ptr)
{
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, SIGINT);
sigprocmask(SIG_BLOCK, &sigset, 0);
AUDIO_IO_DB_SERVER* pserver =
static_cast<AUDIO_IO_DB_SERVER*>(ptr);
pserver->io_thread();
return 0;
}
/**
* Constructor.
*/
AUDIO_IO_DB_SERVER::AUDIO_IO_DB_SERVER (void)
{
ECA_LOG_MSG(ECA_LOGGER::system_objects, "constructor");
buffercount_rep = buffercount_default;
buffersize_rep = buffersize_default;
impl_repp = new AUDIO_IO_DB_SERVER_impl;
thread_running_rep = false;
pthread_cond_init(&impl_repp->client_cond_rep, NULL);
pthread_mutex_init(&impl_repp->client_mutex_rep, NULL);
pthread_cond_init(&impl_repp->data_cond_rep, NULL);
pthread_mutex_init(&impl_repp->data_mutex_rep, NULL);
pthread_cond_init(&impl_repp->full_cond_rep, NULL);
pthread_mutex_init(&impl_repp->full_mutex_rep, NULL);
pthread_cond_init(&impl_repp->stop_cond_rep, NULL);
pthread_mutex_init(&impl_repp->stop_mutex_rep, NULL);
pthread_cond_init(&impl_repp->flush_cond_rep, NULL);
pthread_mutex_init(&impl_repp->flush_mutex_rep, NULL);
running_rep.set(0);
full_rep.set(0);
stop_request_rep.set(0);
exit_request_rep.set(0);
exit_ok_rep.set(0);
impl_repp->profile_full_rep = 0;
impl_repp->profile_no_processing_rep = 0;
impl_repp->profile_not_full_anymore_rep = 0;
impl_repp->profile_processing_rep = 0;
impl_repp->profile_read_xrun_danger_rep = 0;
impl_repp->profile_write_xrun_danger_rep = 0;
impl_repp->profile_rounds_total_rep = 0;
}
/**
* Destructor. Doesn't delete any client objects.
*/
AUDIO_IO_DB_SERVER::~AUDIO_IO_DB_SERVER(void)
{
ECA_LOG_MSG(ECA_LOGGER::system_objects, "destructor");
stop_request_rep.set(1);
exit_request_rep.set(1);
exit_ok_rep.set(0);
if (thread_running_rep == true) {
pthread_join(impl_repp->io_thread_rep, 0);
}
for(unsigned int p = 0; p < buffers_rep.size(); p++) {
delete buffers_rep[p];
}
delete impl_repp;
DB_PROFILING_STATEMENT(dump_profile_counters());
ECA_LOG_MSG(ECA_LOGGER::system_objects, "destructor-out");
}
/**
* Starts the db server.
*
* @pre is_running() != true
*/
void AUDIO_IO_DB_SERVER::start(void)
{
// --
DBC_REQUIRE(is_running() != true);
// --
ECA_LOG_MSG(ECA_LOGGER::system_objects, "start");
if (thread_running_rep != true) {
int ret = pthread_create(&impl_repp->io_thread_rep,
0,
start_db_server_io_thread,
static_cast<void *>(this));
if (ret != 0) {
ECA_LOG_MSG(ECA_LOGGER::info, "pthread_create failed, exiting");
exit(1);
}
thread_running_rep = true;
}
stop_request_rep.set(0);
running_rep.set(1);
ECA_LOG_MSG(ECA_LOGGER::system_objects, "starting processing");
}
/**
* Stops the db server.
*/
void AUDIO_IO_DB_SERVER::stop(void)
{
ECA_LOG_MSG(ECA_LOGGER::system_objects, "stop");
stop_request_rep.set(1);
}
/**
* Whether the db server has been started?
*/
bool AUDIO_IO_DB_SERVER::is_running(void) const
{
if (running_rep.get() == 0) return false;
return true;
}
/**
* Whether the db server buffers are full?
*/
bool AUDIO_IO_DB_SERVER::is_full(void) const
{
if (full_rep.get() == 0) return false;
return true;
}
/**
* Waits for condition to occur.
*
* @return 0 on success,
* -ETIMEDOUT if timeout occured,
* other nonzero value on other errors
*/
static int timed_wait(pthread_mutex_t* mutex,
pthread_cond_t* cond,
long int msecs)
{
struct timeval now;
gettimeofday(&now, 0);
struct timespec sleepcount;
sleepcount.tv_nsec = now.tv_usec * 1000 + (msecs % 1000) * 1000000;
sleepcount.tv_sec = now.tv_sec + msecs / 1000;
if (sleepcount.tv_nsec > 1000000000) {
sleepcount.tv_sec++;
sleepcount.tv_nsec -= 1000000000;
}
int ret = 0;
pthread_mutex_lock(mutex);
ret = pthread_cond_timedwait(cond,
mutex,
&sleepcount);
pthread_mutex_unlock(mutex);
return ret;
}
/**
* Prints debug information based on the result
* of timed_wait() call.
*/
static void timed_wait_print_result(int result, const char* tag, bool verbose)
{
ECA_LOGGER::Msg_level_t level = ECA_LOGGER::info;
if (verbose != true)
level = ECA_LOGGER::continuous;
if (result != 0) {
if (result == -ETIMEDOUT)
ECA_LOG_MSG(level, std::string(tag) + " failed; timeout");
else
ECA_LOG_MSG(level, std::string(tag) + " failed");
}
}
/**
* Signals the server that one of its client has
* processed data from the db buffers. This
* function helps server to keep its buffers
* full without resorting to polling.
*
* Called by both db clients and the db server.
*/
void AUDIO_IO_DB_SERVER::signal_client_activity(void)
{
pthread_mutex_lock(&impl_repp->client_mutex_rep);
pthread_cond_broadcast(&impl_repp->client_cond_rep);
pthread_mutex_unlock(&impl_repp->client_mutex_rep);
}
/**
* Function that blocks until some client
* activity occurs.
*
* Only called by db server.
*
* @see signal_client_activity()
*
* @pre is_running() != true
*/
void AUDIO_IO_DB_SERVER::wait_for_client_activity(void)
{
// --
DBC_REQUIRE(is_running() == true);
// --
/* note! we only wait for 100msec in case no clients
* clients signal activity but there's still
* room for new data
*/
int res = timed_wait(&impl_repp->client_mutex_rep, &impl_repp->client_cond_rep, 100);
timed_wait_print_result(res, "wait_for_client_activity", false);
}
/**
* Function that blocks until the server signals
* that all its buffers are full.
*/
void AUDIO_IO_DB_SERVER::wait_for_full(void)
{
if (is_running() == true &&
clients_rep.size() > 0) {
/* note! we wait until we get a signal_full() even though
* full_rep could already be set */
int res = timed_wait(&impl_repp->full_mutex_rep, &impl_repp->full_cond_rep, 5000);
timed_wait_print_result(res, "wait_for_full", true);
}
else {
ECA_LOG_MSG(ECA_LOGGER::system_objects, "wait_for_full failed; not running");
}
}
/**
* Function that blocks until the server signals
* that it has stopped.
*/
void AUDIO_IO_DB_SERVER::wait_for_stop(void)
{
if (is_running() == true) {
int res = timed_wait(&impl_repp->stop_mutex_rep, &impl_repp->stop_cond_rep, 5000);
timed_wait_print_result(res, "wait_for_stop", true);
}
}
/**
* Function that blocks until the server signals
* that it has flushed all buffers (after
* exit request).
*/
void AUDIO_IO_DB_SERVER::wait_for_flush(void)
{
if (is_running() == true) {
if (exit_ok_rep.get() == 0) {
signal_client_activity();
int res = timed_wait(&impl_repp->flush_mutex_rep, &impl_repp->flush_cond_rep, 5000);
timed_wait_print_result(res, "wait_for_flush", true);
}
}
else {
ECA_LOG_MSG(ECA_LOGGER::system_objects, "wait_for_flush failed; not running");
}
}
/**
* Sends a signal notifying that server buffers
* are fulls.
*
* Called by db server.
*/
void AUDIO_IO_DB_SERVER::signal_full(void)
{
pthread_mutex_lock(&impl_repp->full_mutex_rep);
pthread_cond_broadcast(&impl_repp->full_cond_rep);
pthread_mutex_unlock(&impl_repp->full_mutex_rep);
}
/**
* Sends a signal notifying that server has
* stopped.
*
* Called by db server.
*/
void AUDIO_IO_DB_SERVER::signal_stop(void)
{
pthread_mutex_lock(&impl_repp->stop_mutex_rep);
pthread_cond_broadcast(&impl_repp->stop_cond_rep);
pthread_mutex_unlock(&impl_repp->stop_mutex_rep);
}
/**
* Sends a signal notifying that server has
* flushed all buffers (after an exit request).
*
* Called by db server.
*/
void AUDIO_IO_DB_SERVER::signal_flush(void)
{
pthread_mutex_lock(&impl_repp->flush_mutex_rep);
pthread_cond_broadcast(&impl_repp->flush_cond_rep);
pthread_mutex_unlock(&impl_repp->flush_mutex_rep);
}
/**
* Sets new default values for sample buffers.
*
* @pre is_running() != true
*/
void AUDIO_IO_DB_SERVER::set_buffer_defaults(int buffers,
long int buffersize)
{
// --
DBC_REQUIRE(is_running() != true);
// --
buffercount_rep = buffers;
buffersize_rep = buffersize;
}
/**
* Registers a new client object.
*
* @pre aobject != 0
* @pre is_running() != true
*/
void AUDIO_IO_DB_SERVER::register_client(AUDIO_IO* aobject)
{
// --
DBC_REQUIRE(aobject != 0);
DBC_REQUIRE(is_running() != true);
// --
clients_rep.push_back(aobject);
ECA_LOG_MSG(ECA_LOGGER::system_objects,
"Registering client " +
kvu_numtostr(clients_rep.size() - 1) +
". Buffer count " +
kvu_numtostr(buffercount_rep) + ".");
buffers_rep.push_back(new AUDIO_IO_DB_BUFFER(buffercount_rep,
buffersize_rep,
aobject->channels()));
client_map_rep[aobject] = clients_rep.size() - 1;
}
/**
* Unregisters the client object given as the argument. No
* resources are freed during this call.
*
*/
void AUDIO_IO_DB_SERVER::unregister_client(AUDIO_IO* aobject)
{
// --
DBC_REQUIRE(is_running() != true);
// --
ECA_LOG_MSG(ECA_LOGGER::system_objects, "unregister_client " + aobject->name() + ".");
if (client_map_rep.find(aobject) != client_map_rep.end()) {
size_t index = client_map_rep[aobject];
if (index >= 0 && index < clients_rep.size()) {
clients_rep[index] = 0;
delete buffers_rep[index];
buffers_rep[index] = 0;
}
else
ECA_LOG_MSG(ECA_LOGGER::system_objects, "unregister_client failed (1)");
}
else
ECA_LOG_MSG(ECA_LOGGER::system_objects, "unregister_client failed (2)");
}
/**
* Gets a pointer to the client buffer belonging to
* the audio object given as parameter. If no
* buffers are found (client not registered, etc),
* null is returned.
*/
AUDIO_IO_DB_BUFFER* AUDIO_IO_DB_SERVER::get_client_buffer(AUDIO_IO* aobject)
{
if (client_map_rep.find(aobject) == client_map_rep.end() ||
clients_rep[client_map_rep[aobject]] == 0)
return 0;
return buffers_rep[client_map_rep[aobject]];
}
/**
* Slave thread.
*/
void AUDIO_IO_DB_SERVER::io_thread(void)
{
ECA_LOG_MSG(ECA_LOGGER::system_objects, "Hey, in the I/O loop!");
int processed = 0;
int passive_rounds = 0;
DB_PROFILING_STATEMENT(bool one_time_full = false);
/* set idle timeout to ~10% of total buffersize (using 44.1Hz as a reference) */
long int sleeplen = buffersize_rep * buffercount_rep * 1000 / 44100 / 10 * 1000000;
ECA_LOG_MSG(ECA_LOGGER::system_objects,
"Using idle timeout of " +
kvu_numtostr(sleeplen) +
" nsecs.");
while(true) {
if (running_rep.get() == 0) {
kvu_sleep(0, sleeplen);
if (exit_request_rep.get() == 1) break;
continue;
}
DB_PROFILING_INC(impl_repp->profile_rounds_total_rep);
processed = 0;
int min_free_space = buffercount_rep;
DB_PROFILING_STATEMENT(impl_repp->looptimer_rep.start());
for(unsigned int p = 0; p < clients_rep.size(); p++) {
if (clients_rep[p] == 0 ||
buffers_rep[p]->finished_rep.get()) {
continue;
}
else if (clients_rep[p]->finished() == true) {
buffers_rep[p]->finished_rep.set(1);
}
int free_space = 0;
if (buffers_rep[p]->io_mode_rep == AUDIO_IO::io_read) {
free_space = buffers_rep[p]->write_space();
if (free_space > 0) {
/* room available, so we can read at least one buffer of data */
if (clients_rep[p]->finished() != true) {
clients_rep[p]->read_buffer(buffers_rep[p]->sbufs_rep[buffers_rep[p]->writeptr_rep.get()]);
if (clients_rep[p]->finished() == true) buffers_rep[p]->finished_rep.set(1);
buffers_rep[p]->advance_write_pointer();
++processed;
}
#ifdef DB_PROFILING
if (buffers_rep[p]->write_space() > 16 && one_time_full == true) {
DB_PROFILING_INC(impl_repp->profile_read_xrun_danger_rep);
}
#endif
}
}
else {
free_space = buffers_rep[p]->read_space();
if (free_space > 0) {
/* room available, so we can write at least one buffer of data */
if (clients_rep[p]->finished() != true) {
clients_rep[p]->write_buffer(buffers_rep[p]->sbufs_rep[buffers_rep[p]->readptr_rep.get()]);
if (clients_rep[p]->finished() == true) buffers_rep[p]->finished_rep.set(1);
buffers_rep[p]->advance_read_pointer();
++processed;
}
#ifdef DB_PROFILING
if (buffers_rep[p]->read_space() < 16 && one_time_full == true) {
DB_PROFILING_INC(impl_repp->profile_write_xrun_danger_rep);
}
#endif
}
}
if (free_space < min_free_space) min_free_space = free_space;
}
DB_PROFILING_STATEMENT(impl_repp->looptimer_rep.stop());
if (stop_request_rep.get() == 1) {
stop_request_rep.set(0);
running_rep.set(0);
full_rep.set(0);
signal_stop();
}
else {
if (processed == 0) passive_rounds++;
else passive_rounds = 0;
if (processed == 0) {
if (passive_rounds > 1) {
/* case 1: nothing processed during the last two rounds ==> signal_full, wait_for_client_activity */
DB_PROFILING_INC(impl_repp->profile_full_rep);
full_rep.set(1);
DB_PROFILING_STATEMENT(if (one_time_full != true) one_time_full = true);
signal_full();
DBC_CHECK(running_rep.get() == 1);
}
else {
/* case 2: nothing processed during the last round ==> wait_for_client_activity */
DB_PROFILING_INC(impl_repp->profile_no_processing_rep);
}
wait_for_client_activity();
}
else {
/* case 3: something processed; business as usual */
DB_PROFILING_INC(impl_repp->profile_processing_rep);
}
/* case X: something processed; room in the buffers ==> data available */
// DB_PROFILING_INC(impl_repp->profile_not_full_anymore_rep);
}
}
flush();
exit_ok_rep.set(1);
// std::cerr << "Exiting db server thread." << std::endl;
}
void AUDIO_IO_DB_SERVER::dump_profile_counters(void)
{
std::cerr << "(audioio-db-server) *** profile begin ***" << std::endl;
std::cerr << "Profile_full_rep: " << impl_repp->profile_full_rep << std::endl;
std::cerr << "Profile_no_processing_rep: " << impl_repp->profile_no_processing_rep << std::endl;
std::cerr << "Profile_not_full_anymore_rep: " << impl_repp->profile_not_full_anymore_rep << std::endl;
std::cerr << "Profile_processing_rep: " << impl_repp->profile_processing_rep << std::endl;
std::cerr << "Profile_read_xrun_danger_rep: " << impl_repp->profile_read_xrun_danger_rep << std::endl;
std::cerr << "Profile_write_xrun_danger_rep: " << impl_repp->profile_write_xrun_danger_rep << std::endl;
std::cerr << "Profile_rounds_total_rep: " << impl_repp->profile_rounds_total_rep << std::endl;
std::cerr << "Fastest/slowest/average loop time: ";
std::cerr << kvu_numtostr(impl_repp->looptimer_rep.min_duration_seconds() * 1000, 1);
std::cerr << "/";
std::cerr << kvu_numtostr(impl_repp->looptimer_rep.max_duration_seconds() * 1000, 1);
std::cerr << "/";
std::cerr << kvu_numtostr(impl_repp->looptimer_rep.average_duration_seconds() * 1000, 1);
std::cerr << " msec." << std::endl;
std::cerr << "(audioio-db-server) *** profile end ***" << std::endl;
}
/**
* Flushes all data in the buffers to disk.
*/
void AUDIO_IO_DB_SERVER::flush(void)
{
int not_finished = 1;
while(not_finished != 0) {
not_finished = 0;
for(unsigned int p = 0; p < clients_rep.size(); p++) {
if (clients_rep[p] == 0 ||
buffers_rep[p]->finished_rep.get()) continue;
if (buffers_rep[p]->io_mode_rep != AUDIO_IO::io_read) {
if (buffers_rep[p]->read_space() > 0) {
++not_finished;
ECA_LOG_MSG(ECA_LOGGER::info,
"Flushing buffer " +
kvu_numtostr(buffers_rep[p]->readptr_rep.get()) +
" of client " +
kvu_numtostr(p) +
" read_space: " +
kvu_numtostr(buffers_rep[p]->read_space()) +
".");
clients_rep[p]->write_buffer(buffers_rep[p]->sbufs_rep[buffers_rep[p]->readptr_rep.get()]);
if (clients_rep[p]->finished() == true) buffers_rep[p]->finished_rep.set(1);
buffers_rep[p]->advance_read_pointer();
}
}
}
}
for(unsigned int p = 0; p < buffers_rep.size(); p++) {
if (buffers_rep[p] != 0) {
buffers_rep[p]->reset();
}
}
signal_flush();
}