/*
*
* audio_out.c
*
* Original Copyright (C) Aaron Holtzman - May 1999
* Modifications Copyright (C) Stan Seibert - July 2000
*
* This file is part of libao, a cross-platform audio output library. See
* README for a history of this source code.
*
* libao 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, or (at your option)
* any later version.
*
* libao 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 GNU Make; see the file COPYING. If not, write to
* the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#ifndef _MSC_VER
# include <unistd.h>
#endif
#include <dirent.h>
#include "ao/ao.h"
#include "ao_private.h"
/* These should have been set by the Makefile */
#ifndef AO_PLUGIN_PATH
#define AO_PLUGIN_PATH "/usr/local/lib/ao"
#endif
#ifndef SHARED_LIB_EXT
#define SHARED_LIB_EXT ".so"
#endif
/* --- Other constants --- */
#define DEF_SWAP_BUF_SIZE 1024
/* --- Driver Table --- */
typedef struct driver_list {
ao_functions *functions;
void *handle;
struct driver_list *next;
} driver_list;
extern ao_functions ao_null;
extern ao_functions ao_wav;
extern ao_functions ao_raw;
extern ao_functions ao_au;
#ifdef HAVE_SYS_AUDIO_H
extern ao_functions ao_aixs;
#endif
static ao_functions *static_drivers[] = {
&ao_null, /* Must have at least one static driver! */
&ao_wav,
&ao_raw,
&ao_au,
#ifdef HAVE_SYS_AUDIO_H
&ao_aixs,
#endif
NULL /* End of list */
};
static driver_list *driver_head = NULL;
static ao_config config = {
NULL /* default_driver */
};
static ao_info **info_table = NULL;
static int driver_count = 0;
/* ---------- Helper functions ---------- */
/* Clear out all of the library configuration options and set them to
defaults. The defaults should match the initializer above. */
static void _clear_config()
{
free(config.default_driver);
config.default_driver = NULL;
}
/* Load a plugin from disk and put the function table into a driver_list
struct. */
static driver_list *_get_plugin(char *plugin_file)
{
driver_list *dt;
void *handle;
handle = dlopen(plugin_file, DLOPEN_FLAG /* See ao_private.h */);
if (handle) {
dt = (driver_list *)malloc(sizeof(driver_list));
if (!dt) return NULL;
dt->handle = handle;
dt->functions = (ao_functions *)malloc(sizeof(ao_functions));
if (!(dt->functions)) {
free(dt);
return NULL;
}
dt->functions->test = dlsym(dt->handle, "ao_plugin_test");
if (!(dt->functions->test)) goto failed;
dt->functions->driver_info =
dlsym(dt->handle, "ao_plugin_driver_info");
if (!(dt->functions->driver_info)) goto failed;
dt->functions->device_init =
dlsym(dt->handle, "ao_plugin_device_init");
if (!(dt->functions->device_init )) goto failed;
dt->functions->set_option =
dlsym(dt->handle, "ao_plugin_set_option");
if (!(dt->functions->set_option)) goto failed;
dt->functions->open = dlsym(dt->handle, "ao_plugin_open");
if (!(dt->functions->open)) goto failed;
dt->functions->play = dlsym(dt->handle, "ao_plugin_play");
if (!(dt->functions->play)) goto failed;
dt->functions->close = dlsym(dt->handle, "ao_plugin_close");
if (!(dt->functions->close)) goto failed;
dt->functions->device_clear =
dlsym(dt->handle, "ao_plugin_device_clear");
if (!(dt->functions->device_clear)) goto failed;
} else {
return NULL;
}
return dt;
failed:
free(dt->functions);
free(dt);
return NULL;
}
/* If *name is a valid driver name, return its driver number.
Otherwise, test all of available live drivers until one works. */
static int _find_default_driver_id (const char *name)
{
int def_id;
int id;
ao_info *info;
driver_list *driver = driver_head;
if ( name == NULL || (def_id = ao_driver_id(name)) < 0 ) {
/* No default specified. Find one among available drivers. */
def_id = -1;
id = 0;
while (driver != NULL) {
info = driver->functions->driver_info();
if ( info->type == AO_TYPE_LIVE &&
info->priority > 0 && /* Skip static drivers */
driver->functions->test() ) {
def_id = id; /* Found a usable driver */
break;
}
driver = driver->next;
id++;
}
}
return def_id;
}
/* Convert the static drivers table into a linked list of drivers. */
static driver_list* _load_static_drivers(driver_list **end)
{
driver_list *head;
driver_list *driver;
int i;
/* insert first driver */
head = driver = malloc(sizeof(driver_list));
if (driver != NULL) {
driver->functions = static_drivers[0];
driver->handle = NULL;
driver->next = NULL;
i = 1;
while (static_drivers[i] != NULL) {
driver->next = malloc(sizeof(driver_list));
if (driver->next == NULL)
break;
driver->next->functions = static_drivers[i];
driver->next->handle = NULL;
driver->next->next = NULL;
driver = driver->next;
i++;
}
}
if (end != NULL)
*end = driver;
return head;
}
/* Load the dynamic drivers from disk and append them to end of the
driver list. end points the driver_list node to append to. */
static void _append_dynamic_drivers(driver_list *end)
{
struct dirent *plugin_dirent;
char *ext;
struct stat statbuf;
char fullpath[PATH_MAX];
DIR *plugindir;
driver_list *plugin;
driver_list *driver = end;
/* now insert any plugins we find */
plugindir = opendir(AO_PLUGIN_PATH);
if (plugindir != NULL) {
while ((plugin_dirent = readdir(plugindir)) != NULL) {
snprintf(fullpath, PATH_MAX, "%s/%s",
AO_PLUGIN_PATH, plugin_dirent->d_name);
if (!stat(fullpath, &statbuf) &&
S_ISREG(statbuf.st_mode) &&
(ext = strrchr(plugin_dirent->d_name, '.')) != NULL) {
if (strcmp(ext, SHARED_LIB_EXT) == 0) {
plugin = _get_plugin(fullpath);
if (plugin) {
driver->next = plugin;
plugin->next = NULL;
driver = driver->next;
}
}
}
}
closedir(plugindir);
}
}
/* Compare two drivers based on priority
Used as compar function for qsort() in _make_info_table() */
static int _compar_driver_priority (const driver_list **a,
const driver_list **b)
{
return memcmp(&((*b)->functions->driver_info()->priority),
&((*a)->functions->driver_info()->priority),
sizeof(int));
}
/* Make a table of driver info structures for ao_driver_info_list(). */
static ao_info ** _make_info_table (driver_list **head, int *driver_count)
{
driver_list *list;
int i;
ao_info **table;
driver_list **drivers_table;
*driver_count = 0;
/* Count drivers */
list = *head;
i = 0;
while (list != NULL) {
i++;
list = list->next;
}
/* Sort driver_list */
drivers_table = (driver_list **) calloc(i, sizeof(driver_list *));
if (drivers_table == NULL)
return (ao_info **) NULL;
list = *head;
*driver_count = i;
for (i = 0; i < *driver_count; i++, list = list->next)
drivers_table[i] = list;
qsort(drivers_table, i, sizeof(driver_list *),
(int(*)(const void *, const void *))
_compar_driver_priority);
*head = drivers_table[0];
for (i = 1; i < *driver_count; i++)
drivers_table[i-1]->next = drivers_table[i];
drivers_table[i-1]->next = NULL;
/* Alloc table */
table = (ao_info **) calloc(i, sizeof(ao_info *));
if (table != NULL) {
for (i = 0; i < *driver_count; i++)
table[i] = drivers_table[i]->functions->driver_info();
}
free(drivers_table);
return table;
}
/* Return the driver struct corresponding to particular driver id
number. */
static driver_list *_get_driver(int driver_id) {
int i = 0;
driver_list *d