/* audio: audio output interface copyright ?-2023 by the mpg123 project - free software under the terms of the LGPL 2.1 see COPYING and AUTHORS files in distribution or http://mpg123.org initially written by Michael Hipp */ #define _XOPEN_SOURCE 600 #define _POSIX_C_SOURCE 200112L #include "out123_int.h" #ifdef HAVE_SYS_TIME_H #include #endif #ifdef _WIN32 // for Sleep() #include #endif // unistd.h sets those #ifdef HAVE_CLOCK_GETTIME #if _POSIX_TIMERS > 0 #include #ifdef _POSIX_MONSLEEP_CLOCKOTONIC_CLOCK #define SLEEP_CLOCK CLOCK_MONOTONIC #else #define SLEEP_CLOCK CLOCK_REALTIME #endif #endif #endif #include "../version.h" #include "wav.h" #include "hextxt.h" #ifndef NOXFERMEM #include "buffer.h" static int have_buffer(out123_handle *ao) { return (ao->buffer_pid != -1); } #endif #include "stringlists.h" #include "../common/debug.h" const char * attribute_align_arg out123_distversion(unsigned int *major, unsigned int *minor, unsigned int *patch) { if(major) *major = MPG123_MAJOR; if(minor) *minor = MPG123_MINOR; if(patch) *patch = MPG123_PATCH; return MPG123_VERSION; } unsigned int attribute_align_arg out123_libversion(unsigned int *patch) { if(patch) *patch = OUT123_PATCHLEVEL; return OUT123_API_VERSION; } /* An output that is live and does not deal with pausing itself. The device needs to be closed if we stop feeding. */ #define SENSITIVE_OUTPUT(ao) \ ( (ao)->propflags & OUT123_PROP_LIVE \ && !((ao)->propflags & OUT123_PROP_PERSISTENT) ) static const char *default_name = "out123"; static int modverbose(out123_handle *ao, int final) { mdebug( "modverbose: %x %x %x %d %d" , (unsigned)ao->flags, (unsigned)ao->auxflags, (unsigned)OUT123_QUIET , final, ao->verbose ); return final ? (AOQUIET ? 0 : ao->verbose) : -1; } static void check_output_module( out123_handle *ao , const char *name, const char *device, int final ); static void out123_clear_module(out123_handle *ao) { ao->open = NULL; ao->get_formats = NULL; ao->write = NULL; ao->flush = NULL; ao->drain = NULL; ao->close = NULL; ao->deinit = NULL; ao->enumerate = NULL; ao->module = NULL; ao->userptr = NULL; ao->fn = -1; /* The default is live output devices, files are the special case. */ ao->propflags = OUT123_PROP_LIVE; } /* Ensure that real name is not leaked, needs to be freed before any call to ao->open(ao). One might free it on closing already, but it might be sensible to keep it around, might still be the same after re-opening. */ static int aoopen(out123_handle *ao) { if(ao->realname) { free(ao->realname); ao->realname = NULL; } return ao->open(ao); } out123_handle* attribute_align_arg out123_new(void) { out123_handle* ao = malloc( sizeof( out123_handle ) ); if(!ao) return NULL; ao->errcode = 0; #ifndef NOXFERMEM ao->buffer_pid = -1; ao->buffer_fd[0] = -1; ao->buffer_fd[1] = -1; ao->buffermem = NULL; #endif out123_clear_module(ao); ao->name = INT123_compat_strdup(default_name); ao->realname = NULL; ao->driver = NULL; ao->device = NULL; ao->flags = OUT123_KEEP_PLAYING; ao->rate = -1; ao->gain = -1; ao->channels = -1; ao->format = -1; ao->framesize = 0; memset(ao->zerosample, 0, 8); ao->state = play_dead; ao->auxflags = 0; ao->preload = 0.; ao->verbose = 0; ao->device_buffer = 0.; ao->bindir = NULL; return ao; } void attribute_align_arg out123_del(out123_handle *ao) { debug2("[%ld]out123_del(%p)", (long)getpid(), (void*)ao); if(!ao) return; out123_close(ao); /* TODO: That talks to the buffer if present. */ out123_set_buffer(ao, 0); #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_exit(ao); #endif if(ao->name) free(ao->name); if(ao->bindir) free(ao->bindir); free(ao); } void attribute_align_arg out123_free(void *ptr) { free(ptr); } /* Error reporting */ /* Carefully keep that in sync with the error enum! */ /* Sizing according to contents so that we can check! */ static const char *const errstring[] = { "no problem" , "out of memory" , "bad driver name" , "failure loading driver module" , "no driver loaded" , "no active audio device" , "some device playback error" , "failed to open device" , "buffer (communication) error" , "basic module system error" , "bad function argument(s)" , "unknown parameter code" , "attempt to set read-only parameter" , "invalid out123 handle" , "operation not supported" , "device enumeration failed" }; const char* attribute_align_arg out123_strerror(out123_handle *ao) { return out123_plain_strerror(out123_errcode(ao)); } int out123_errcode(out123_handle *ao) { if(!ao) return OUT123_BAD_HANDLE; else return ao->errcode; } const char* attribute_align_arg out123_plain_strerror(int errcode) { if(errcode == OUT123_ERR) return "some generic error"; if(errcode >= OUT123_ERRCOUNT || errcode < 0) return "invalid error code"; /* Let's be paranoid, one _may_ forget to extend errstrings when adding a new entry to the enum. */ if(errcode >= sizeof(errstring)/sizeof(char*)) return "outdated error list (library bug)"; return errstring[errcode]; } static int out123_seterr(out123_handle *ao, enum out123_error errcode) { if(!ao) return OUT123_ERR; ao->errcode = errcode; return errcode == OUT123_OK ? OUT123_OK : OUT123_ERR; } /* pre-playback setup */ int attribute_align_arg out123_set_buffer(out123_handle *ao, size_t buffer_bytes) { debug2("out123_set_buffer(%p, %zu)", (void*)ao, buffer_bytes); if(!ao) return OUT123_ERR; ao->errcode = 0; /* Close any audio output module if present, also kill of buffer if present, then start new buffer process with newly allocated storage if given size is non-zero. */ out123_close(ao); #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_exit(ao); if(buffer_bytes) return INT123_buffer_init(ao, buffer_bytes); #endif return 0; } int attribute_align_arg out123_param2( out123_handle *ao, int code , long value, double fvalue, const char *svalue ) { return out123_param(ao, code, value, fvalue, svalue); } int attribute_align_arg out123_param( out123_handle *ao, enum out123_parms code , long value, double fvalue, const char *svalue ) { int ret = 0; debug4("out123_param(%p, %i, %li, %g)", (void*)ao, (int)code, value, fvalue); if(!ao) return OUT123_ERR; ao->errcode = 0; switch(code) { case OUT123_FLAGS: ao->flags = (int)value; break; case OUT123_ADD_FLAGS: ao->flags |= (int)value; break; case OUT123_REMOVE_FLAGS: ao->flags &= ~((int)value); break; case OUT123_PRELOAD: ao->preload = fvalue; break; case OUT123_GAIN: ao->gain = value; break; case OUT123_VERBOSE: ao->verbose = (int)value; break; case OUT123_DEVICEBUFFER: ao->device_buffer = fvalue; break; case OUT123_PROPFLAGS: ao->errcode = OUT123_SET_RO_PARAM; ret = OUT123_ERR; break; case OUT123_NAME: if(ao->name) free(ao->name); ao->name = INT123_compat_strdup(svalue ? svalue : default_name); break; case OUT123_BINDIR: if(ao->bindir) free(ao->bindir); ao->bindir = INT123_compat_strdup(svalue); break; default: ao->errcode = OUT123_BAD_PARAM; if(!AOQUIET) error1("bad parameter code %i", (int)code); ret = OUT123_ERR; } #ifndef NOXFERMEM /* If there is a buffer, it needs to update its copy of parameters. */ if(have_buffer(ao)) /* No error check; if that fails, buffer is dead and we will notice soon enough. */ INT123_buffer_sync_param(ao); #endif return ret; } int attribute_align_arg out123_getparam2( out123_handle *ao, int code , long *ret_value, double *ret_fvalue, char* *ret_svalue ) { return out123_getparam(ao, code, ret_value, ret_fvalue, ret_svalue); } int attribute_align_arg out123_getparam( out123_handle *ao, enum out123_parms code , long *ret_value, double *ret_fvalue, char* *ret_svalue ) { int ret = 0; long value = 0; double fvalue = 0.; char *svalue = NULL; debug4( "out123_getparam(%p, %i, %p, %p)" , (void*)ao, (int)code, (void*)ret_value, (void*)ret_fvalue ); if(!ao) return OUT123_ERR; ao->errcode = 0; switch(code) { case OUT123_FLAGS: case OUT123_ADD_FLAGS: value = ao->flags; break; case OUT123_PRELOAD: fvalue = ao->preload; break; case OUT123_GAIN: value = ao->gain; break; case OUT123_VERBOSE: value = ao->verbose; break; case OUT123_DEVICEBUFFER: fvalue = ao->device_buffer; break; case OUT123_PROPFLAGS: value = ao->propflags; break; case OUT123_NAME: svalue = ao->realname ? ao->realname : ao->name; break; case OUT123_BINDIR: svalue = ao->bindir; break; default: if(!AOQUIET) error1("bad parameter code %i", (int)code); ao->errcode = OUT123_BAD_PARAM; ret = OUT123_ERR; } if(!ret) { if(ret_value) *ret_value = value; if(ret_fvalue) *ret_fvalue = fvalue; if(ret_svalue) *ret_svalue = svalue; } return ret; } int attribute_align_arg out123_param_from(out123_handle *ao, out123_handle* from_ao) { debug2("out123_param_from(%p, %p)", (void*)ao, (void*)from_ao); if(!ao || !from_ao) return -1; ao->flags = from_ao->flags; ao->preload = from_ao->preload; ao->gain = from_ao->gain; ao->device_buffer = from_ao->device_buffer; ao->verbose = from_ao->verbose; if(ao->name) free(ao->name); ao->name = INT123_compat_strdup(from_ao->name); if(ao->bindir) free(ao->bindir); ao->bindir = INT123_compat_strdup(from_ao->bindir); return 0; } int attribute_align_arg out123_open(out123_handle *ao, const char* driver, const char* device) { debug4( "[%ld]out123_open(%p, %s, %s)", (long)getpid(), (void*)ao , driver ? driver : "", device ? device : "" ); if(!ao) return OUT123_ERR; ao->errcode = 0; out123_close(ao); debug("out123_open() continuing"); /* Ensure that audio format is freshly set for "no format yet" mode. In out123_start*/ ao->rate = -1; ao->channels = -1; ao->format = -1; #ifndef NOXFERMEM if(have_buffer(ao)) { if(INT123_buffer_open(ao, driver, device)) return OUT123_ERR; } else #endif { /* We just quickly check if the device can be accessed at all, same as out123_encodings! */ char *nextname, *modnames; const char *names = driver ? driver : DEFAULT_OUTPUT_MODULE; if(!names) return out123_seterr(ao, OUT123_BAD_DRIVER_NAME); /* It is ridiculous how these error messages are larger than the pieces of memory they are about! */ if(device && !(ao->device = INT123_compat_strdup(device))) { if(!AOQUIET) error("OOM device name copy"); return out123_seterr(ao, OUT123_DOOM); } if(!(modnames = INT123_compat_strdup(names))) { out123_close(ao); /* Frees ao->device, too. */ if(!AOQUIET) error("OOM driver names"); return out123_seterr(ao, OUT123_DOOM); } /* Now loop over the list of possible modules to find one that works. */ nextname = strtok(modnames, ","); while(!ao->open && nextname) { char *curname = nextname; nextname = strtok(NULL, ","); check_output_module(ao, curname, device, !nextname); if(ao->open) { if(AOVERBOSE(2)) fprintf(stderr, "Chosen output module: %s\n", curname); /* A bit redundant, but useful when it's a fake module. */ if(!(ao->driver = INT123_compat_strdup(curname))) { out123_close(ao); if(!AOQUIET) error("OOM driver name"); return out123_seterr(ao, OUT123_DOOM); } } } free(modnames); if(!ao->open) /* At least an open() routine must be present. */ { if(!AOQUIET) error2("Found no driver out of [%s] working with device %s." , names, device ? device : ""); /* Proper more detailed error code could be set already. */ if(ao->errcode == OUT123_OK) ao->errcode = OUT123_BAD_DRIVER; return OUT123_ERR; } } /* Got something. */ ao->state = play_stopped; return OUT123_OK; } /* Be resilient, always do cleanup work regardless of state. */ void attribute_align_arg out123_close(out123_handle *ao) { debug2("[%ld]out123_close(%p)", (long)getpid(), (void*)ao); if(!ao) return; ao->errcode = 0; out123_drain(ao); out123_stop(ao); #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_close(ao); else #endif { if(ao->deinit) ao->deinit(ao); if(ao->module) INT123_close_module(ao->module, modverbose(ao, 0)); /* Null module methods and pointer. */ out123_clear_module(ao); } /* These copies exist in addition to the ones for the buffer. */ if(ao->driver) free(ao->driver); ao->driver = NULL; if(ao->device) free(ao->device); ao->device = NULL; if(ao->realname) free(ao->realname); ao->realname = NULL; ao->state = play_dead; } int attribute_align_arg out123_start(out123_handle *ao, long rate, int channels, int encoding) { debug5( "[%ld]out123_start(%p, %li, %i, %i)", (long)getpid() , (void*)ao, rate, channels, encoding ); if(!ao) return OUT123_ERR; ao->errcode = 0; out123_stop(ao); debug("out123_start() continuing"); if(ao->state != play_stopped) return out123_seterr(ao, OUT123_NO_DRIVER); /* Stored right away as parameters for ao->open() and also for reference. framesize needed for out123_play(). */ ao->rate = rate; ao->channels = channels; ao->format = encoding; int samplesize = out123_encsize(encoding); ao->framesize = samplesize*channels; // The most convoluted way to say nothing at all. for(int i=0; izerosample[samplesize-1-i] = #else ao->zerosample[i] = #endif MPG123_ZEROSAMPLE(ao->format, samplesize, i); #ifndef NOXFERMEM if(have_buffer(ao)) { if(!INT123_buffer_start(ao)) { ao->state = play_live; return OUT123_OK; } else return OUT123_ERR; } else #endif { if(aoopen(ao) < 0) return out123_seterr(ao, OUT123_DEV_OPEN); ao->state = play_live; return OUT123_OK; } } void attribute_align_arg out123_pause(out123_handle *ao) { debug3( "[%ld]out123_pause(%p) %i", (long)getpid() , (void*)ao, ao ? (int)ao->state : -1 ); if(ao && ao->state == play_live) { #ifndef NOXFERMEM if(have_buffer(ao)){ debug("pause with buffer"); INT123_buffer_pause(ao); } else #endif { debug1("pause without buffer, sensitive=%d", SENSITIVE_OUTPUT(ao)); /* Close live devices to avoid underruns. */ if( SENSITIVE_OUTPUT(ao) && ao->close && ao->close(ao) && !AOQUIET ) error("trouble closing device"); } ao->state = play_paused; } } void attribute_align_arg out123_continue(out123_handle *ao) { debug3( "[%ld]out123_continue(%p) %i", (long)getpid() , (void*)ao, ao ? (int)ao->state : -1 ); if(ao && ao->state == play_paused) { #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_continue(ao); else #endif /* Re-open live devices to avoid underruns. */ if(SENSITIVE_OUTPUT(ao) && aoopen(ao) < 0) { /* Will be overwritten by following out123_play() ... */ ao->errcode = OUT123_DEV_OPEN; if(!AOQUIET) error("failed re-opening of device after pause"); return; } ao->state = play_live; } } void attribute_align_arg out123_stop(out123_handle *ao) { debug2("[%ld]out123_stop(%p)", (long)getpid(), (void*)ao); if(!ao) return; ao->errcode = 0; if(!(ao->state == play_paused || ao->state == play_live)) return; #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_stop(ao); else #endif if( ao->state == play_live || (ao->state == play_paused && !SENSITIVE_OUTPUT(ao)) ) { if(ao->close && ao->close(ao) && !AOQUIET) error("trouble closing device"); } ao->state = play_stopped; } // Replace the data in a given block of audio data with zeroes // in the correct encoding. static void mute_block( unsigned char *bytes, int count , unsigned char* zerosample, int samplesize ) { // The count is expected to be a multiple of samplesize, // this is just to ensure that the loop ends properly, should be noop. count -= count % samplesize; if(!count) return; // Initialize with one zero sample, then multiply that // to eventually cover the whole buffer. memcpy(bytes, zerosample, samplesize); int offset = samplesize; count -= samplesize; while(count) { int block = offset > count ? count : offset; memcpy(bytes+offset, bytes, block); offset += block; count -= block; } } size_t attribute_align_arg out123_play(out123_handle *ao, void *bytes, size_t count) { size_t sum = 0; int written; debug5( "[%ld]out123_play(%p, %p, %zu) (%i)", (long)getpid() , (void*)ao, bytes, count, ao ? (int)ao->state : -1 ); if(!ao) return 0; ao->errcode = 0; /* If paused, automatically continue. Other states are an error. */ if(ao->state != play_live) { if(ao->state == play_paused) out123_continue(ao); if(ao->state != play_live) { ao->errcode = OUT123_NOT_LIVE; return 0; } } /* Ensure that we are writing whole PCM frames. */ count -= count % ao->framesize; if(!count) return 0; #ifndef NOXFERMEM if(have_buffer(ao)) return INT123_buffer_write(ao, bytes, count); else #endif { // Write 16K in a piece as maximum, as I've seen random short // writes of big blocks with ALSA. int maxcount = 1<<14; maxcount -= maxcount % ao->framesize; if(maxcount < 1) maxcount = ao->framesize; if(ao->flags & OUT123_MUTE) mute_block( bytes, count, ao->zerosample , MPG123_SAMPLESIZE(ao->format) ); do /* Playback in a loop to be able to continue after interruptions. */ { errno = 0; int block = count > (size_t)maxcount ? maxcount : (int)count; written = ao->write(ao, bytes, block); debug4( "written: %d errno: %i (%s), keep_on=%d" , written, errno, INT123_strerror(errno) , ao->flags & OUT123_KEEP_PLAYING ); if(written > 0) { if(written > block) written = block; // Safeguard against sloppy output modules. bytes = (char*)bytes+written; sum += written; count -= written; } if(written < block && errno != EINTR && errno != EAGAIN #if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN) // Not all platforms define it (or only in more modern POSIX modes). // Standard says it is supposed to be a macro, so simple check here. && errno != EWOULDBLOCK #endif ){ ao->errcode = OUT123_DEV_PLAY; if(!AOQUIET) merror( "Error in writing audio, wrote only %d of %d (%s?)!" , written, block, INT123_strerror(errno) ); /* This is a serious issue ending this playback round. */ break; } } while(count && ao->flags & OUT123_KEEP_PLAYING); } debug3( "out123_play(%p, %p, ...) = %zu" , (void*)ao, bytes, sum ); return sum; } /* Drop means to flush it down. Quickly. */ void attribute_align_arg out123_drop(out123_handle *ao) { debug2("[%ld]out123_drop(%p)", (long)getpid(), (void*)ao); if(!ao) return; ao->errcode = 0; #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_drop(ao); else #endif if(ao->state == play_live) { if(ao->propflags & OUT123_PROP_LIVE && ao->flush) ao->flush(ao); } } void attribute_align_arg out123_drain(out123_handle *ao) { debug2("[%ld]out123_drain(%p) ", (long)getpid(), (void*)ao); if(!ao) return; ao->errcode = 0; /* If paused, automatically continue. */ if(ao->state != play_live) { if(ao->state == play_paused) out123_continue(ao); if(ao->state != play_live) return; } #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_drain(ao); else #endif { if(ao->drain) ao->drain(ao); out123_pause(ao); } } void attribute_align_arg out123_ndrain(out123_handle *ao, size_t bytes) { debug3("[%ld]out123_ndrain(%p, %zu)", (long)getpid(), (void*)ao, bytes); if(!ao) return; ao->errcode = 0; /* If paused, automatically continue. */ if(ao->state != play_live) { if(ao->state == play_paused) out123_continue(ao); if(ao->state != play_live) return; } #ifndef NOXFERMEM if(have_buffer(ao)) INT123_buffer_ndrain(ao, bytes); else #endif { if(ao->drain) ao->drain(ao); out123_pause(ao); } } /* A function that does nothing and returns nothing. */ static void builtin_nothing(out123_handle *ao){} static int test_open(out123_handle *ao) { debug("test_open"); return OUT123_OK; } static int test_get_formats(out123_handle *ao) { debug("test_get_formats"); return MPG123_ENC_ANY; } static int test_write(out123_handle *ao, unsigned char *buf, int len) { debug2("test_write: %i B from %p", len, (void*)buf); return len; } static void test_flush(out123_handle *ao) { debug("test_flush"); } static void test_drain(out123_handle *ao) { debug("test_drain"); } static int test_close(out123_handle *ao) { debug("test_drain"); return 0; } #ifdef SLEEP_CLOCK // Sleep output sleeps for the proper time, but // in a way that still allows overlap with actual // computation, keeping this amount of milliseconds // in a pretend buffer and returning. This has to be less // than 1000(less than a second). static long sleep_buf = 500; // Otherwise, coarse sleep()ing is used. static const long billion = 1000000000; // zero-saturating subtraction of time, paranoid about time_t being signed static void ts_sub_zero(struct timespec *a, const struct timespec *b) { long long nsec = a->tv_nsec - b->tv_nsec; long long sec = a->tv_sec - b->tv_sec; sec += nsec/billion; nsec = nsec%billion; if(nsec < 0) { sec -= 1; nsec = billion+nsec; } if(sec < 0) { sec = 0; nsec = 0; } a->tv_sec = (time_t)sec; a->tv_nsec = (long)nsec; } static void ts_update(struct timespec *mytime) { struct timespec now, passed; if(clock_gettime(SLEEP_CLOCK, &now)) { mytime[1].tv_sec = 0; mytime[1].tv_nsec = 0; return; } passed = now; ts_sub_zero(&passed, mytime); mytime[0] = now; ts_sub_zero(mytime+1, &passed); } #else // Sleep coarslely. Proper operation only with clock. static void sleep_seconds(unsigned long s) { #ifdef _WIN32 Sleep(s*1000); #else sleep(s); #endif } #endif static int sleep_open(out123_handle *ao) { if(!ao) return OUT123_ERR; if(ao->format < 0) { ao->rate = 44100; ao->channels = 2; ao->format = MPG123_ENC_SIGNED_16; return 0; } if(ao->rate < 1) return OUT123_ERR; #ifdef SLEEP_CLOCK // Two time counters: // 0: last time we played something // 1: remaining time in buffer struct timespec *mytime = malloc(2*sizeof(struct timespec)); ao->userptr = mytime; if(mytime) { mytime[0].tv_sec = 0; mytime[0].tv_nsec = 0; mytime[1].tv_sec = 0; mytime[1].tv_nsec = 0; // Check once if clock_gettime() actually works. if(clock_gettime(SLEEP_CLOCK, mytime)) { free(mytime); ao->userptr = NULL; } } #else unsigned long *buffer_ms = malloc(sizeof(unsigned long)); ao->userptr = buffer_ms; if(buffer_ms) *buffer_ms = 0; #endif return (ao->userptr ? OUT123_OK : OUT123_ERR); } static int sleep_close(out123_handle *ao) { if(!ao) return -1; if(ao->userptr) free(ao->userptr); ao->userptr = NULL; return 0; } static int sleep_write(out123_handle *ao, unsigned char *buf, int len) { if(!ao) return -1; double duration = (double)len/((double)ao->framesize*ao->rate); #ifdef SLEEP_CLOCK struct timespec *mytime = ao->userptr; mytime[1].tv_sec += (time_t)duration; mytime[1].tv_nsec += (long)((duration-(time_t)duration)*billion); ts_update(mytime); while(mytime[1].tv_sec > 0 || mytime[1].tv_nsec > sleep_buf*1000000) { useconds_t sleep_ms; if(mytime[1].tv_sec > 0) sleep_ms = 1000 - sleep_buf; else sleep_ms = mytime[1].tv_nsec/1000000 - sleep_buf; usleep(sleep_ms); ts_update(mytime); } #else // Just sleep off the whole seconds; unsigned long *ms = ao->userptr; *ms += (unsigned long)(duration*1000); sleep_seconds(*ms/1000); *ms %= 1000; #endif return len; } static void sleep_drain(out123_handle *ao) { if(!ao || !ao->userptr) return; #ifdef SLEEP_CLOCK struct timespec *mytime = ao->userptr; ts_update(mytime); while(mytime[1].tv_sec || mytime[1].tv_nsec) { usleep(mytime[1].tv_sec ? 1000000 : mytime[1].tv_nsec/1000000); ts_update(mytime); } #else unsigned long *ms = ao->userptr; sleep_seconds(*ms/1000); if(*ms%1000 > 500) sleep_seconds(1); *ms = 0; #endif } /* Open one of our builtin driver modules. */ static int open_fake_module(out123_handle *ao, const char *driver) { if(!strcmp("test", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = test_open; ao->get_formats = test_get_formats; ao->write = test_write; ao->flush = test_flush; ao->drain = test_drain; ao->close = test_close; } else if(!strcmp("sleep", driver)) { ao->propflags |= OUT123_PROP_LIVE|OUT123_PROP_PERSISTENT; ao->open = sleep_open; ao->close = sleep_close; ao->get_formats = test_get_formats; ao->write = sleep_write; ao->flush = builtin_nothing; ao->drain = sleep_drain; ao->close = sleep_close; } else if(!strcmp("raw", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = INT123_raw_open; ao->get_formats = INT123_raw_formats; ao->write = INT123_wav_write; ao->flush = builtin_nothing; ao->drain = INT123_wav_drain; ao->close = INT123_raw_close; } else if(!strcmp("wav", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = INT123_wav_open; ao->get_formats = INT123_wav_formats; ao->write = INT123_wav_write; ao->flush = builtin_nothing; ao->drain = INT123_wav_drain; ao->close = INT123_wav_close; } else if(!strcmp("cdr", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = INT123_cdr_open; ao->get_formats = INT123_cdr_formats; ao->write = INT123_wav_write; ao->flush = builtin_nothing; ao->drain = INT123_wav_drain; ao->close = INT123_raw_close; } else if(!strcmp("au", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = INT123_au_open; ao->get_formats = INT123_au_formats; ao->write = INT123_wav_write; ao->flush = builtin_nothing; ao->drain = INT123_wav_drain; ao->close = INT123_au_close; } else if(!strcmp("hex", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = hex_open; ao->get_formats = hex_formats; ao->write = hex_write; ao->flush = builtin_nothing; ao->drain = hextxt_drain; ao->close = hextxt_close; } else if(!strcmp("txt", driver)) { ao->propflags &= ~OUT123_PROP_LIVE; ao->open = txt_open; ao->get_formats = txt_formats; ao->write = txt_write; ao->flush = builtin_nothing; ao->drain = hextxt_drain; ao->close = hextxt_close; } else return OUT123_ERR; return OUT123_OK; } /* Check if given output module is loadable and has a working device. final flag triggers printing and storing of errors. */ static void check_output_module( out123_handle *ao , const char *name, const char *device, int final ) { int result; debug3("check_output_module %p %p %p", (void*)ao, (void*)device, (void*)ao->device); if(AOVERBOSE(1)) fprintf( stderr, "Trying output module: %s, device: %s\n" , name, ao->device ? ao->device : "" ); /* Use internal code. */ if(open_fake_module(ao, name) == OUT123_OK) return; /* Open the module, initial check for availability+libraries. */ ao->module = INT123_open_module( "output", name, modverbose(ao, final), ao->bindir); if(!ao->module) return; /* Check if module supports output */ if(!ao->module->init_output) { if(final && !AOQUIET) error1("Module '%s' does not support audio output.", name); goto check_output_module_cleanup; } /* Should I do funny stuff with stderr file descriptor instead? */ if(final) { if(AOVERBOSE(2)) fprintf(stderr , "Note: %s is the last output option... showing you any error messages now.\n" , name); } else ao->auxflags |= OUT123_QUIET; /* Probing, so don't spill stderr with errors. */ result = ao->module->init_output(ao); if(result == 0) { /* Try to open the device. I'm only interested in actually working modules. */ ao->format = -1; result = aoopen(ao); debug1("ao->open() = %i", result); if(result >= 0) /* Opening worked, close again. */ ao->close(ao); else { if(!AOQUIET) merror("Module '%s' device open failed.", name); if(ao->deinit) ao->deinit(ao); /* Failed, ensure that cleanup after init_output() occurs. */ } } else if(!AOQUIET) error2("Module '%s' init failed: %i", name, result); ao->auxflags &= ~OUT123_QUIET; if(result >= 0) return; check_output_module_cleanup: /* Only if module did not check out we get to clean up here. */ INT123_close_module(ao->module, modverbose(ao, final)); out123_clear_module(ao); return; } /* static void audio_output_dump(out123_handle *ao) { fprintf(stderr, "ao->fn=%d\n", ao->fn); fprintf(stderr, "ao->userptr=%p\n", ao->userptr); fprintf(stderr, "ao->rate=%ld\n", ao->rate); fprintf(stderr, "ao->gain=%ld\n", ao->gain); fprintf(stderr, "ao->device='%s'\n", ao->device); fprintf(stderr, "ao->channels=%d\n", ao->channels); fprintf(stderr, "ao->format=%d\n", ao->format); } */ int attribute_align_arg out123_drivers(out123_handle *ao, char ***names, char ***descr) { char **tmpnames; char **tmpdescr; int count; if(!ao) return -1; debug3("out123_drivers(%p, %p, %p)", (void*)ao, (void*)names, (void*)descr); /* Wrap the call to isolate the lower levels from the user not being interested in both lists. it's a bit wasteful, but the code looks ugly enough already down there. */ count = INT123_list_modules("output", &tmpnames, &tmpdescr, modverbose(ao, 0), ao->bindir); debug1("INT123_list_modules()=%i", count); if(count < 0) { if(!AOQUIET) error("Dynamic module search failed."); count = 0; } if( INT123_stringlists_add( &tmpnames, &tmpdescr , "raw", "raw headerless stream (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "cdr", "compact disc digital audio stream (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "wav", "RIFF WAVE file (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "au", "Sun AU file (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "test", "output into the void (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "sleep", "output into the void that takes its time (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "hex", "interleaved hex printout (builtin)", &count ) || INT123_stringlists_add( &tmpnames, &tmpdescr , "txt", "plain text printout, a column per channel (builtin)", &count ) ) if(!AOQUIET) error("OOM"); /* Return or free gathered lists of names or descriptions. */ if(names) { *names = tmpnames; tmpnames = NULL; } if(descr) { *descr = tmpdescr; tmpdescr = NULL; } out123_stringlists_free(tmpnames, tmpdescr, count); return count; } struct devlist { int count; char **names; char **descr; }; static int devlist_add(void *dll, const char *name, const char *descr) { struct devlist *dl = (struct devlist*)dll; return dl ? INT123_stringlists_add(&(dl->names), &(dl->descr), name, descr, &(dl->count)) : -1; } int out123_devices( out123_handle *ao, const char *driver, char ***names, char ***descr , char **active_driver ) { int ret = 0; struct devlist dl = {0, NULL, NULL}; char *realdrv = NULL; debug(""); if(!ao) return -1; #ifndef NOXFERMEM if(have_buffer(ao)) return out123_seterr(ao, OUT123_NOT_SUPPORTED); #endif ao->errcode = OUT123_OK; // If the driver is a single word, not a list with commas. // Then don't try to open drivers just to know which we are talking about. if(driver && strchr(driver, ',') == NULL) realdrv = INT123_compat_strdup(driver); else { mdebug("need to find a driver from: %s", driver ? driver : DEFAULT_OUTPUT_MODULE); if(out123_open(ao, driver, NULL) != OUT123_OK) return out123_seterr(ao, OUT123_BAD_DRIVER); mdebug("deduced driver: %s", ao->driver); realdrv = INT123_compat_strdup(ao->driver); } if(realdrv == NULL) return out123_seterr(ao, OUT123_DOOM); out123_close(ao); if(open_fake_module(ao, realdrv) != OUT123_OK) { ao->module = INT123_open_module( "output", realdrv , modverbose(ao, 0), ao->bindir ); /* Open the module, initial check for availability+libraries. */ if( !ao->module || !ao->module->init_output || ao->module->init_output(ao) ) ret = out123_seterr(ao, OUT123_BAD_DRIVER); } if(!ret && ao->enumerate) { if(!ao->enumerate(ao, devlist_add, &dl)) { ret = dl.count; if(names) { *names = dl.names; dl.names = NULL; } if(descr) { *descr = dl.descr; dl.descr = NULL; } if(active_driver) { *active_driver = realdrv; realdrv = NULL; } } else ret = out123_seterr(ao, OUT123_DEV_ENUMERATE); out123_stringlists_free(dl.names, dl.descr, dl.count); if(ao->deinit) ao->deinit(ao); } else if(!ret) ret = out123_seterr(ao, OUT123_NOT_SUPPORTED); free(realdrv); if(ao->module) INT123_close_module(ao->module, modverbose(ao, 0)); out123_clear_module(ao); return ret; } /* We always have ao->driver and ao->device set, also with buffer. The latter can be positively NULL, though. */ int attribute_align_arg out123_driver_info(out123_handle *ao, char **driver, char **device) { debug3( "out123_driver_info(%p, %p, %p)" , (void*)ao, (void*)driver, (void*)device ); if(!ao) return OUT123_ERR; if(!ao->driver) return out123_seterr(ao, OUT123_NO_DRIVER); if(driver) *driver = ao->driver; if(device) *device = ao->device; return OUT123_OK; } int attribute_align_arg out123_encodings(out123_handle *ao, long rate, int channels) { debug4( "[%ld]out123_encodings(%p, %li, %i)", (long)getpid() , (void*)ao, rate, channels ); if(!ao) return OUT123_ERR; ao->errcode = OUT123_OK; out123_stop(ao); /* That brings the buffer into waiting state, too. */ if(ao->state != play_stopped) return out123_seterr(ao, OUT123_NO_DRIVER); ao->channels = channels; ao->rate = rate; #ifndef NOXFERMEM if(have_buffer(ao)) return INT123_buffer_encodings(ao); else #endif { int enc = 0; /* This tells outputs to choose a fitting format so that ao->open() succeeds They possibly set a sample rate and channel count they like best. We should add API to retrieve those defaults, too. */ ao->format = -1; if(aoopen(ao) >= 0) { /* Need to reset those since the choose-your-format open call might have changed them. */ ao->channels = channels; ao->rate = rate; enc = ao->get_formats(ao); ao->close(ao); return enc; } else return out123_seterr(ao, (ao->errcode != OUT123_OK ? ao->errcode : OUT123_DEV_OPEN)); } } int attribute_align_arg out123_encsize(int encoding) { return MPG123_SAMPLESIZE(encoding); } int attribute_align_arg out123_formats( out123_handle *ao, const long *rates, int ratecount , int minchannels, int maxchannels , struct mpg123_fmt **fmtlist ) { debug7( "[%ld]out123_formats(%p, %p, %i, %i, %i, %p)", (long)getpid() , (void*)ao, (void*)rates, ratecount, minchannels, maxchannels , (void*)fmtlist ); if(!ao) return OUT123_ERR; ao->errcode = OUT123_OK; out123_stop(ao); /* That brings the buffer into waiting state, too. */ if(ao->state != play_stopped) return out123_seterr(ao, OUT123_NO_DRIVER); if(ratecount > 0 && !rates) return out123_seterr(ao, OUT123_ARG_ERROR); if(!fmtlist || minchannels > maxchannels) return out123_seterr(ao, OUT123_ARG_ERROR); *fmtlist = NULL; /* Initialize so free(fmtlist) is always allowed. */ #ifndef NOXFERMEM if(have_buffer(ao)) return INT123_buffer_formats( ao, rates, ratecount , minchannels, maxchannels, fmtlist ); else #endif { /* This tells outputs to choose a fitting format so that ao->open() succeeds. */ ao->format = -1; ao->rate = -1; ao->channels = -1; if(aoopen(ao) >= 0) { struct mpg123_fmt *fmts; int ri, ch; int fi = 0; int fmtcount = 1; /* Always the default format. */ if(ratecount > 0) fmtcount += ratecount*(maxchannels-minchannels+1); if(!(fmts = malloc(sizeof(*fmts)*fmtcount))) { ao->close(ao); return out123_seterr(ao, OUT123_DOOM); } /* Store default format if present. */ if(ao->format > 0 && ao->channels > 0 && ao->rate > 0) { fmts[0].rate = ao->rate; fmts[0].channels = ao->channels; fmts[0].encoding = ao->format; } else { /* Ensure consistent -1 in all entries. */ fmts[0].rate = -1; fmts[0].channels = -1; fmts[0].encoding = -1; } /* Test all combinations of rate and channel count. */ for(ri=0; rirate = rates[ri]; ao->channels = ch; fmts[fi].rate = ao->rate; fmts[fi].channels = ao->channels; fmts[fi].encoding = ao->get_formats(ao); } ao->close(ao); *fmtlist = fmts; return fmtcount; } else return out123_seterr(ao, (ao->errcode != OUT123_OK ? ao->errcode : OUT123_DEV_OPEN)); } } size_t attribute_align_arg out123_buffered(out123_handle *ao) { debug2("[%ld]out123_buffered(%p)", (long)getpid(), (void*)ao); if(!ao) return 0; ao->errcode = 0; #ifndef NOXFERMEM if(have_buffer(ao)) { size_t fill = INT123_buffer_fill(ao); debug2("out123_buffered(%p) = %zu", (void*)ao, fill); return fill; } else #endif return 0; } int attribute_align_arg out123_getformat( out123_handle *ao , long *rate, int *channels, int *encoding, int *framesize ) { if(!ao) return OUT123_ERR; if(!(ao->state == play_paused || ao->state == play_live)) return out123_seterr(ao, OUT123_NOT_LIVE); if(rate) *rate = ao->rate; if(channels) *channels = ao->channels; if(encoding) *encoding = ao->format; if(framesize) *framesize = ao->framesize; return OUT123_OK; } struct enc_desc { int code; /* MPG123_ENC_SOMETHING */ const char *longname; /* signed bla bla */ const char *name; /* sXX, short name */ }; static const struct enc_desc encdesc[] = { { MPG123_ENC_SIGNED_16, "signed 16 bit", "s16" } , { MPG123_ENC_UNSIGNED_16, "unsigned 16 bit", "u16" } , { MPG123_ENC_SIGNED_32, "signed 32 bit", "s32" } , { MPG123_ENC_UNSIGNED_32, "unsigned 32 bit", "u32" } , { MPG123_ENC_SIGNED_24, "signed 24 bit", "s24" } , { MPG123_ENC_UNSIGNED_24, "unsigned 24 bit", "u24" } , { MPG123_ENC_FLOAT_32, "float (32 bit)", "f32" } , { MPG123_ENC_FLOAT_64, "float (64 bit)", "f64" } , { MPG123_ENC_SIGNED_8, "signed 8 bit", "s8" } , { MPG123_ENC_UNSIGNED_8, "unsigned 8 bit", "u8" } , { MPG123_ENC_ULAW_8, "mu-law (8 bit)", "ulaw" } , { MPG123_ENC_ALAW_8, "a-law (8 bit)", "alaw" } }; #define KNOWN_ENCS (sizeof(encdesc)/sizeof(struct enc_desc)) int attribute_align_arg out123_enc_list(int **enclist) { int i; if(!enclist) return OUT123_ERR; *enclist = malloc(sizeof(int)*KNOWN_ENCS); if(!(*enclist)) return OUT123_ERR; for(i=0;i