/*
 * mpg666.c: A simple mp3 player
 *
 * Compile:
 *   gcc mpg666.c -o mpg666 -lmad -lao
 *
 * Copyright (c) 2004, eazy <eazy@ondaquadra.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * *  Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
 * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <mad.h>
#include <ao/ao.h>

int             init = 0;
ao_device      *device = NULL;

typedef struct {
	const unsigned char *address;
	unsigned long   size;
}               file_info;

int
err_quit()
{
	perror("error");
	exit(-1);
}

void
usage(char *prog)
{
	fprintf(stderr, "Usage: %s filename.mp3\n", prog);
	exit(-1);
}

void
init_aolib(const struct mad_pcm * pcm)
{
	int             driver_id;
	ao_sample_format format;

	format.bits = 16;
	format.rate = pcm->samplerate;
	format.channels = pcm->channels;
	format.byte_format = AO_FMT_LITTLE;

	ao_initialize();

	if ((driver_id = ao_default_driver_id()) == -1) {
		fprintf(stderr, "No default device is available\n");
		exit(-1);
	}
	if ((device = ao_open_live(driver_id, &format, NULL)) == NULL)
		err_quit();

	init = 1;
}

enum mad_flow
input_func(void *info, struct mad_stream * stream)
{
	if (!((file_info *) info)->size)
		return (MAD_FLOW_STOP);

	mad_stream_buffer(stream, ((file_info *) info)->address, ((file_info *) info)->size);
	((file_info *) info)->size = 0;

	return (MAD_FLOW_CONTINUE);
}

/* scale() from minimad.c example */
static inline signed int
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 >> (MAD_F_FRACBITS + 1 - 16);
}

enum mad_flow
output_func(void *info, const struct mad_header * header, struct mad_pcm * pcm)
{
	int             lenght, sample;
	const mad_fixed_t *left, *right;
	unsigned char  *buf, *ptr;

	left = pcm->samples[0];	/* left channel */
	right = pcm->samples[1];/* right channel, if(pcm->nchannels == 2) */

	if ((buf = ptr = malloc(pcm->length * pcm->channels * 2)) == NULL)
		err_quit();

	for (lenght = pcm->length; lenght; lenght--) {
		sample = scale(*left++);
		*ptr++ = sample & 0xff;
		*ptr++ = (sample >> 8) & 0xff;

		if (pcm->channels == 2) {
			sample = scale(*right++);
			*ptr++ = sample & 0xff;
			*ptr++ = (sample >> 8) & 0xff;
		}
	}

	if (!init)
		init_aolib(pcm);

	ao_play(device, buf, pcm->length * pcm->channels * 2);
	free(buf);

	return (MAD_FLOW_CONTINUE);
}

enum mad_flow
error_func(void *info, struct mad_stream * stream, struct mad_frame * frame)
{
	fprintf(stderr, "[Byte %u]: %s\n", stream->this_frame - ((file_info *) info)->address,
		mad_stream_errorstr(stream));

	return (MAD_FLOW_CONTINUE);
}

void
play(unsigned char *map, unsigned long size)
{
	file_info       info;
	struct mad_decoder decoder;

	info.address = map;
	info.size = size;

	mad_decoder_init(&decoder, &info, input_func, NULL, NULL, output_func, error_func, NULL);
	mad_decoder_run(&decoder, MAD_DECODER_MODE_SYNC);
	mad_decoder_finish(&decoder);
}

int
main(int argc, char **argv)
{
	int             fd, num;
	struct stat     stat;
	unsigned char  *map;

	if (argc < 2)
		usage(argv[0]);
	
	for(num = 1; num < argc; num++){

	if ((fd = open(argv[num], O_RDONLY)) == -1)
		err_quit();

	if (fstat(fd, &stat) == -1)
		err_quit();

	if ((map = mmap(0, stat.st_size, PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED)
		err_quit();

	play(map, stat.st_size);

	if (munmap(map, stat.st_size) == -1)
		err_quit();
		

	}
	ao_close(device);
	ao_shutdown();

	return (0);
}
