Created
June 6, 2015 10:43
-
-
Save TerrorBite/c0b6f40dafbaee87d89e to your computer and use it in GitHub Desktop.
Seamless audio player
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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