mpv/libao2/ao_plugin.c

288 lines
7.6 KiB
C

#include "../config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "afmt.h"
#include "audio_out.h"
#include "audio_out_internal.h"
#include "audio_plugin.h"
static ao_info_t info =
{
"Plugin audio output",
"plugin",
"Anders",
""
};
LIBAO_EXTERN(plugin)
#define plugin(i) (ao_plugin_local_data.plugins[i])
#define driver() (ao_plugin_local_data.driver)
// local data
typedef struct ao_plugin_local_data_s
{
void* buf; // Output data buffer
int len; // Amount of data in buffer
int channels;
int format;
int bpm; //bit of format
float bps; // Bytes per second out
ao_functions_t* driver; // Output driver
ao_plugin_functions_t** plugins; // List of used plugins
ao_plugin_functions_t* available_plugins[NPL]; // List of available plugins
} ao_plugin_local_data_t;
static ao_plugin_local_data_t ao_plugin_local_data={NULL,0,0,0,0,0.0,NULL,NULL,AO_PLUGINS};
// global data
volatile ao_plugin_data_t ao_plugin_data; // Data used by the plugins
ao_plugin_cfg_t ao_plugin_cfg=CFG_DEFAULTS; // Set in cfg-mplayer.h
// to set/get/query special features/parameters
static int control(int cmd,void *arg){
switch(cmd){
case AOCONTROL_SET_PLUGIN_DRIVER:
ao_plugin_local_data.driver=arg;
return CONTROL_OK;
case AOCONTROL_GET_VOLUME:
case AOCONTROL_SET_VOLUME:
{
int r=audio_plugin_volume.control(cmd,arg);
if(CONTROL_OK != r)
return driver()->control(cmd,arg);
else
return r;
}
default:
return driver()->control(cmd,arg);
}
return CONTROL_UNKNOWN;
}
// Recursive function for adding plugins
// return 1 for success and 0 for error
int add_plugin(int i,char* cfg){
int cnt=0;
// Find end of plugin name
while((cfg[cnt]!=',')&&(cfg[cnt]!='\0')&&(cnt<100)) cnt++;
if(cnt >= 100)
return 0;
// Is this the last iteration or just another plugin
if(cfg[cnt]=='\0'){
ao_plugin_local_data.plugins=malloc((i+2)*sizeof(ao_plugin_functions_t*));
if(ao_plugin_local_data.plugins){
ao_plugin_local_data.plugins[i+1]=NULL;
// Find the plugin matching the cfg string name
cnt=0;
while(ao_plugin_local_data.available_plugins[cnt] && cnt<20){
if(0==strcmp(ao_plugin_local_data.available_plugins[cnt]->info->short_name,cfg)){
ao_plugin_local_data.plugins[i]=ao_plugin_local_data.available_plugins[cnt];
return 1;
}
cnt++;
}
printf("[plugin]: Invalid plugin: %s \n",cfg);
return 0;
}
else
return 0;
} else {
cfg[cnt]='\0';
if(add_plugin(i+1,&cfg[cnt+1])){
cnt=0;
// Find the plugin matching the cfg string name
while(ao_plugin_local_data.available_plugins[cnt] && cnt < 20){
if(0==strcmp(ao_plugin_local_data.available_plugins[cnt]->info->short_name,cfg)){
ao_plugin_local_data.plugins[i]=ao_plugin_local_data.available_plugins[cnt];
return 1;
}
cnt++;
}
printf("[plugin]: Invalid plugin: %s \n",cfg);
return 0;
}
else
return 0;
}
return 0; // Will never happen...
}
// open & setup audio device and plugins
// return: 1=success 0=fail
static int init(int rate,int channels,int format,int flags){
int use_plugin[NPL];
int x,npl,unused=0;
int ok=1;
char *config;
// Create list of plugins from cfg option
int i=0;
if(ao_plugin_cfg.plugin_list){
config = malloc(strlen(ao_plugin_cfg.plugin_list)+1);
if(!config) return 0;
if(!strcpy(config, ao_plugin_cfg.plugin_list) || !add_plugin(i,config)){
free(config);
return 0;
}
free(config);
}
/* Set input parameters and itterate through plugins each plugin
changes the parameters according to its output */
ao_plugin_local_data.format=format;
ao_plugin_local_data.channels=channels;
ao_plugin_local_data.bpm=audio_out_format_bits(format);
ao_plugin_data.rate=rate;
ao_plugin_data.channels=channels;
ao_plugin_data.format=format;
ao_plugin_data.sz_mult=1;
ao_plugin_data.sz_fix=0;
ao_plugin_data.delay_mult=1;
ao_plugin_data.delay_fix=0;
for(i=0;i<NPL && plugin(i);i++){
use_plugin[i]=plugin(i)->init();
if(!use_plugin[i]) plugin(i)->uninit();
}
npl=i;
for(i=0;i<npl && plugin(i);i++)
if(!use_plugin[i+unused]){
unused++;
for(x=i;x<npl && plugin(x+1);x++) plugin(x)=plugin(x+1);
plugin(x)=NULL;
npl--;
i--;
}
i=npl;
// Calculate bps
ao_plugin_local_data.bps=(float)(ao_plugin_data.rate *
ao_plugin_data.channels);
ao_plugin_local_data.bps*=audio_out_format_bits(ao_plugin_data.format)/8;
// This should never happen but check anyway
if(NULL==ao_plugin_local_data.driver)
return 0;
ok = driver()->init(ao_plugin_data.rate,
ao_plugin_data.channels,
ao_plugin_data.format,
flags);
if(!ok) return 0;
/* Now that the driver is initialized we can calculate and set the
input and output buffers for each plugin */
ao_plugin_data.len=driver()->get_space();
while((i>0) && ok)
ok=plugin(--i)->control(AOCONTROL_PLUGIN_SET_LEN,0);
if(!ok) return 0;
// Allocate output buffer
if(ao_plugin_local_data.buf)
free(ao_plugin_local_data.buf);
ao_plugin_local_data.buf=malloc(MAX_OUTBURST);
if(!ao_plugin_local_data.buf) return 0;
return 1;
}
// close audio device
static void uninit(){
int i=0;
driver()->uninit();
while(plugin(i))
plugin(i++)->uninit();
if(ao_plugin_local_data.plugins)
free(ao_plugin_local_data.plugins);
ao_plugin_local_data.plugins=NULL;
if(ao_plugin_local_data.buf)
free(ao_plugin_local_data.buf);
ao_plugin_local_data.buf=NULL;
}
// stop playing and empty buffers (for seeking/pause)
static void reset(){
int i=0;
driver()->reset();
while(plugin(i))
plugin(i++)->reset();
ao_plugin_local_data.len=0;
}
// stop playing, keep buffers (for pause)
static void audio_pause(){
driver()->pause();
}
// resume playing, after audio_pause()
static void audio_resume(){
driver()->resume();
}
// return: how many bytes can be played without blocking
static int get_space(){
double sz;
int isz;
sz=(double)(driver()->get_space());
if(sz+(double)ao_plugin_local_data.len > (double)MAX_OUTBURST)
sz=(double)MAX_OUTBURST-(double)ao_plugin_local_data.len;
sz*=ao_plugin_data.sz_mult;
sz+=ao_plugin_data.sz_fix;
isz=(int)(sz);
isz-=isz%(ao_plugin_local_data.channels*ao_plugin_local_data.bpm/8);
return isz;
}
// plays 'len' bytes of 'data'
// return: number of bytes played
static int play(void* data,int len,int flags){
int l,i=0;
// Limit length to avoid over flow in plugins
int tmp = get_space();
int ret_len =(tmp<len)?tmp:len;
// keep all channels of each sample together
ret_len -= ret_len % (ao_plugin_local_data.channels*ao_plugin_local_data.bpm/8);
if(ret_len){
// Filter data
ao_plugin_data.len=ret_len;
ao_plugin_data.data=data;
while(plugin(i))
plugin(i++)->play();
// Copy data to output buffer
memcpy(ao_plugin_local_data.buf+ao_plugin_local_data.len,
ao_plugin_data.data,ao_plugin_data.len);
// Send data to output
l=driver()->play(ao_plugin_local_data.buf,
ao_plugin_data.len+ao_plugin_local_data.len,flags);
// Save away unsent data
ao_plugin_local_data.len=ao_plugin_data.len+ao_plugin_local_data.len-l;
memcpy(ao_plugin_local_data.buf,ao_plugin_local_data.buf+l,
ao_plugin_local_data.len);
}
return ret_len;
}
// return: delay in seconds between first and last sample in buffer
static float get_delay(){
float delay=driver()->get_delay();
delay+=(float)ao_plugin_local_data.len/ao_plugin_local_data.bps;
delay*=ao_plugin_data.delay_mult;
delay+=ao_plugin_data.delay_fix;
return delay;
}