Skip to content

Instantly share code, notes, and snippets.

@TerrorBite
Created June 6, 2015 10:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TerrorBite/c0b6f40dafbaee87d89e to your computer and use it in GitHub Desktop.
Save TerrorBite/c0b6f40dafbaee87d89e to your computer and use it in GitHub Desktop.
Seamless audio player
#include <unistd.h>
#include <stdio.h>
#include <ao/ao.h>
#include <fcntl.h>
#include <signal.h>
#include <math.h>
#include <mad.h>
#include <string.h>
#include <sys/types.h>
typedef struct
{
ao_device* device;
int fileno;
char* buffer;
size_t size;
} aloop_data;
void INThandler(int);
void volume16(signed short*, size_t, int);
int get_input(aloop_data*, size_t);
void pcm_play(aloop_data*);
int mad_play(aloop_data*);
static inline unsigned long mad_scale(mad_fixed_t);
#define ERR(...) { fprintf(stderr, __VA_ARGS__); ao_shutdown(); exit(1); }
#define BUF_SIZE 8192
#define TRACE(...) fprintf(stderr, __VA_ARGS__)
int exiting = 0; /* Set to 1 when Ctrl-C is pressed */
char* buf2;
int buf2len;
int main(int argc, char* argv[])
{
int infile; /* Input file descriptor */
int devid; /* Stores device ID of target driver */
ao_sample_format format; /* Struct containing format information */
ao_option* options; /* Pointer to head of linked list of options */
ao_device* device; /* Sound output device */
int volume;
int raw_audio = 0;
char* p = strrchr(argv[1], '.');
if(!strcmp(p, ".mp3"))
raw_audio = 0;
else
raw_audio = 1;
/* Set default format options: 44100Hz stereo 16 bit signed PCM, little-endian */
format.bits = 16;
format.rate = 44100; /* Might need to use 48000 instead of 44100 */
format.channels = 2;
format.byte_format = AO_FMT_LITTLE;
format.matrix = "L,R";
/* Parse arguments */
int option;
while((option = getopt(argc, argv, "rm3")) != -1)
switch(option)
{
case 'r':
raw_audio = 1;
case 'm':
format.channels=1;
case '3':
raw_audio = 0;
}
volume = 100; /* Volume is initially 100 */
signal(SIGHUP, INThandler);
signal(SIGINT, INThandler); /* Listen for SIGINT (i.e. Ctrl-C) */
signal(SIGTERM, INThandler);
/* and just how do you think this is going to work */
signal(SIGKILL, INThandler);
ao_initialize(); /* Initialize libao */
if(argc < 2) ERR("USAGE: %s <filename>\n", argv[0]); /* Must supply a filename */
infile = open(argv[1], O_RDONLY);
if(infile < 0) ERR("Failed to open the specified input file.\n");
/*devid = ao_default_driver_id();*/
devid = ao_driver_id("alsa"); /* ALSA 0.9/1.0 new API */
/*devid = ao_driver_id("default"); /* default */
if(devid == -1)
{
fprintf(stderr, "Error opening \"default\" (ALSA?) driver, falling back to OSS\n");
devid = ao_driver_id("oss"); /* Fall back to OSS */
}
if(devid == -1)
{
/* Bugger. */
close(infile);
ERR("Could not open OSS driver either, exiting\n");
}
options = NULL; /* Set ALSA options (if using OSS, these will be ignored) */
ao_append_option(&options, "use_mmap","1");
ao_append_option(&options, "card", "speakers");
// ao_append_option(&options, "dev", "0");
if(!raw_audio) { format.bits = 32; }
/* Open the sound device */
device = ao_open_live(devid, &format, NULL);
if(!device)
{
close(infile);
switch(errno)
{
case AO_EOPENDEVICE:
/* The device couldn't be opened for some reason
* (e.g. with OSS, another program using /dev/dsp) */
ERR("ERROR: Device or resource busy.\n");
case AO_ENOTLIVE:
ERR("ERROR: Sound device is not live.\n");
case AO_EBADOPTION:
/* We messed up with ao_append_option() */
ERR("ERROR: Bad option given to libao.\n");
case AO_ENODRIVER:
/* The devid was invalid */
ERR("ERROR: There is no driver corresponding to a driver id of %d.\n", devid);
case AO_EFAIL:
/* Generic failure */
ERR(" \"If at first you don't succeed... you fail,\n and the test will be terminated.\"\n--GLaDOS\n");
default:
/* Huh? */
ERR("ERROR: Unspecified nonspecific unknown error.\n");
}
}
/* Allocate buffers */
size_t buf_size;
//if(raw_audio)
buf_size = BUF_SIZE;
/*else
{
buf_size = lseek(infile, 0, SEEK_END);
lseek(infile, 0, SEEK_SET);
}*/
char* inbuf = malloc(buf_size);
if(!inbuf) ERR("malloc() failed for buffer of %d bytes", buf_size);
aloop_data data = {device, infile, inbuf, buf_size};
if(raw_audio)
pcm_play(&data);
else
mad_play(&data); //Do libmad stuffs
/* Free up buffers, close input file, close sound device,
* shutdown libao, and exit.
*/
free(inbuf);
close(infile);
ao_close(device);
ao_shutdown();
fflush(stderr);
return 0;
}
/* This function simply sets exiting to true on a SIGINT, and forks. */
void INThandler(int sig)
{
signal(sig, SIG_IGN);
exiting = 1;
/*if( fork() ) exit(0);*/
}
/* This function operates on a buffer containing 16-bit signed PCM data.
* The number of channels is irrelevant.
* The first parameter is a pointer to a sequence of 16 bit values.
* The second is the length IN BYTES of the buffer (not the number of values).
* The third is the volume to scale to, an int from 0 to 100 inclusive (0% to 100%).
*/
void volume16(signed short* buf, size_t length, int volume)
{
float vol; /* Vlume scaled from 0 to 1 */
int i; /* Loop counter */
vol = (float)volume / 100; /* Scale volume to a decimal */
vol = -cos(vol*M_PI/2)+1; /* Change linear to sinusoidal */
for(i = 0; i < length / 2; i++)
buf[i] = (signed short)((float)buf[i] * vol);
return;
}
/* http://www.mars.org/mailman/public/mad-dev/2003-June/000855.html
* The output from libmad is a 32-bit fixed-point PCM sample format with
* 28 fractional bits, 3 integer bits (headroom for values above full
* scale), and a sign bit. See synth.h for the output PCM structure
* definition, and fixed.h for details of the fixed-point representation.
* void volume32(signed int* buf, size_t length, int volume)
*/
void volume_mad(mad_fixed_t* buf, size_t length, int volume)
{
return;
}
static inline unsigned long mad_scale(mad_fixed_t sample)
{
/* round */
//sample += (1L << (MAD_F_FRACBITS - 16));
/* clip */
if (sample >= MAD_F_ONE)
sample = MAD_F_ONE - 1;
else if (sample < -MAD_F_ONE)
sample = -MAD_F_ONE;
/* quantize */
return sample*8;
}
static enum mad_flow mad_output(void* data, struct mad_header const *header, struct mad_pcm* pcm)
{
unsigned int nchannels, nsamples, i;
mad_fixed_t const* ch_l, *ch_r;
//char* buf, *p;
mad_fixed_t* buf, *p;
aloop_data* d = data;
nchannels = pcm->channels;
i = nsamples = pcm->length;
ch_l = pcm->samples[0];
ch_r = pcm->samples[1];
if(!d->device)
{
ao_sample_format fmt = {32, pcm->samplerate, nchannels, AO_FMT_NATIVE};
ao_open_live(ao_default_driver_id(), &fmt, NULL);
}
// Interleave left and right channels
p = buf = malloc(nsamples*4*nchannels);
while(i--)
{
*p = mad_scale(*ch_l++); p++;
if(nchannels == 2)
{
*p = mad_scale(*ch_r++); p++;
}
}
ao_play(d->device, (char*)buf, nsamples*4*nchannels);
free(buf);
if(exiting) return MAD_FLOW_BREAK;
return MAD_FLOW_CONTINUE;
}
static enum mad_flow mad_input(void* data, struct mad_stream *stream)
{
//static size_t bytes = BUF_SIZE;
aloop_data* d = data;
size_t excess = 0;
static int count = 0;
//fprintf(stderr, "%03d buffer=0x%x, next=0x%x\n", count, d->buffer, stream->next_frame);
if( stream->next_frame )
{
excess = ((size_t)d->buffer + d->size) - (size_t)stream->next_frame;
//fprintf(stderr, "Calculated excess as %d\n", excess);
memmove((void*)d->buffer, (void*)stream->next_frame, excess);
}
get_input(d, excess);
mad_stream_buffer(stream, (unsigned char*)d->buffer, d->size);
count++;
return MAD_FLOW_CONTINUE;
}
static enum mad_flow mad_error(void *data, struct mad_stream *stream, struct mad_frame *frame)
{
aloop_data* d = data;
/*
fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %u\n",
stream->error, mad_stream_errorstr(stream),
stream->this_frame - d->buffer);
*/
fprintf(stderr, "decoding error 0x%04x (%s) at byte offset %ld\n",
stream->error, mad_stream_errorstr(stream),
lseek(d->fileno, 0, SEEK_CUR) - d->size);
/* return MAD_FLOW_BREAK here to stop decoding (and propagate an error) */
return MAD_FLOW_CONTINUE;
}
void pcm_play(aloop_data* data)
{
static int volume = 100;
while(1) /* Read/write cycle */
{
/* Fill input buffer */
int bytes = get_input(data, 0);
//memcpy((void*)outbuf, (void*)inbuf, buf_size);
if(exiting) /* If ctrl-C was pressed and we're on the way out: */
{
volume--; /* Decrement volume, then change the volume of the buffer */
volume16((signed short*)data->buffer, data->size, volume);
if(volume == 0) break; /* If volume is at 0, we exit */
/* Note: Length of the fadeout currently depends on the buffer size and bitrate.
* Generally: fadeout_in_secs = 100 * buf_size / (rate * bytes_per_sample * channels)
* For 16 bit PCM at 44100Hz with a buffer size of 8192 bytes, the fadeout is 4.643sec
*/
}
if(ao_play(data->device, data->buffer, bytes) == 0)
{
/* On write error, close the device and exit */
fprintf(stderr, "Failed to write to sound device\n");
break;
}
}
return;
}
int mad_play(aloop_data* data)
{
TRACE("Entering mad_play()\n");
struct mad_decoder decoder;
int result;
mad_decoder_init(&decoder, data, mad_input, 0, 0, mad_output, mad_error, 0);
//get_input(data, 0);
result = mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
TRACE("Result was %d!\n", result);
mad_decoder_finish(&decoder);
TRACE("Exiting mad_play()\n");
return result;
}
int get_input(aloop_data* data, size_t offset)
{
int bytes;
if( (data->size - offset) <= 0 ) { errno = EINVAL; return -1; }
bytes = read(data->fileno, data->buffer + offset, BUF_SIZE - offset);
if(!bytes) /* EOF */
{
lseek(data->fileno, 0, SEEK_SET);
bytes = read(data->fileno, data->buffer + offset, BUF_SIZE - offset);
}
data->size = bytes + offset;
return bytes + offset;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment