Blob Blame History Raw
/* -*- c-basic-offset: 8; -*- */
/* mp3.c: libshout MP3 format handler
 * $Id: mp3.c 7259 2004-07-22 18:49:41Z brendan $
 *
 *  Copyright (C) 2002-2003 the Icecast team <team@icecast.org>
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <shout/shout.h>
#include "shout_private.h"

/*
 * MP3 frame handling courtesy of Scott Manley - may he always be Manley.
 */

#define MPEG_MODE_MONO 3

/* -- local datatypes -- */
typedef struct {
	unsigned int frames;
	/* the number of samples for the current frame */
	int frame_samples;
	/* the samplerate of the current frame */
	int frame_samplerate;
	/* how many bytes for the rest of this frame */
	unsigned int frame_left;
	/* is the header bridged?? */
	int header_bridges;
	/* put part of header here if it spans a boundary */
	unsigned char header_bridge[3];
} mp3_data_t;

typedef struct {
	int syncword;
	int layer;
	int version;
	int error_protection;
	int bitrate_index;
	int samplerate_index;
	int padding;
	int extension;
	int mode;
	int mode_ext;
	int copyright;
	int original;
	int emphasis;
	int stereo;
	int bitrate;
	unsigned int samplerate;
	unsigned int samples;
	unsigned int framesize;
} mp3_header_t;

/* -- const data -- */
static const unsigned int bitrate[3][3][16] =
{
	{
		{ 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0 },
		{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0 },
		{ 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0 }
	}, {
		{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 },
		{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 },
		{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }
	}, {
		{ 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0 },
		{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 },
		{ 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0 }
	}
};

static const unsigned int samplerate[3][4] =
{
	{ 44100, 48000, 32000, 0 },
	{ 22050, 24000, 16000, 0 },
	{ 11025, 8000, 8000, 0 }
};

/* -- static prototypes -- */
static int send_mp3(shout_t *self, const unsigned char *data, size_t len);
static void close_mp3(shout_t *self);

static void parse_header(mp3_header_t *mh, uint32_t header);
static int mp3_header(uint32_t head, mp3_header_t *mh);

int shout_open_mp3(shout_t *self)
{
	mp3_data_t *mp3_data;

	if (!(mp3_data = (mp3_data_t *)calloc(1, sizeof(mp3_data_t))))
		return SHOUTERR_MALLOC;
	self->format_data = mp3_data;

	self->send = send_mp3;
	self->close = close_mp3;

	return SHOUTERR_SUCCESS;
}

static int send_mp3(shout_t* self, const unsigned char* buff, size_t len)
{
	mp3_data_t* mp3_data = (mp3_data_t*) self->format_data;
	unsigned long pos;
	uint32_t head;
	int ret, count;
	int start, end, error, i;
	unsigned char *bridge_buff;
	mp3_header_t mh;

	bridge_buff = NULL;
	pos = 0;
	start = 0;
	error = 0;
	end = len - 1;
	memset(&mh, 0, sizeof(mh));

	/* finish the previous frame */
	if (mp3_data->frame_left > 0) {
		/* is the rest of the frame here? */
		if (mp3_data->frame_left <= len) {
			self->senttime += (int64_t)((double)mp3_data->frame_samples / (double)mp3_data->frame_samplerate * 1000000);
			mp3_data->frames++;
			pos += mp3_data->frame_left;
			mp3_data->frame_left = 0;
		} else {
			mp3_data->frame_left -= len;
			pos = len;
		}
	}

	/* header was over the boundary, so build a new build a new buffer */
	if (mp3_data->header_bridges) {
		bridge_buff = (unsigned char *)malloc(len + mp3_data->header_bridges);
		if (bridge_buff == NULL) {
			return self->error = SHOUTERR_MALLOC;
		}

		bridge_buff[0] = mp3_data->header_bridge[0];
		bridge_buff[1] = mp3_data->header_bridge[1];
		bridge_buff[2] = mp3_data->header_bridge[2];

		memcpy(&bridge_buff[mp3_data->header_bridges], buff, len);

		buff = bridge_buff;
		len += mp3_data->header_bridges;
		end = len - 1;

		mp3_data->header_bridges = 0;
	}

	/** this is the main loop
	*** we handle everything but the last 4 bytes...
	**/
	while ((pos + 4) <= len) {
		/* find mp3 header */
		head = (buff[pos] << 24) | 
			(buff[pos + 1] << 16) |
			(buff[pos + 2] << 8) |
			(buff[pos + 3]);

		/* is this a valid header? */
		if (mp3_header(head, &mh)) {
			if (error) {
				start = pos;
				end = len - 1;
				error = 0;
			}

			mp3_data->frame_samples = mh.samples;
			mp3_data->frame_samplerate = mh.samplerate;

			/* do we have a complete frame in this buffer? */
			if (len - pos >= mh.framesize) {
				self->senttime += (int64_t)((double)mp3_data->frame_samples / (double)mp3_data->frame_samplerate * 1000000);
				mp3_data->frames++;
				pos += mh.framesize;
			} else {
				mp3_data->frame_left = mh.framesize - (len - pos);
				pos = len;
			}
		} else {
			/* there was an error
			** so we send all the valid data up to this point 
			*/
			if (!error) {
				error = 1;
				end = pos - 1;
				count = end - start + 1;
				if (count > 0)
					ret = shout_send_raw(self, (char *)&buff[start], count);
				else
					ret = 0;

				if (ret != count) {
					if (bridge_buff != NULL)
						free(bridge_buff);
					return self->error = SHOUTERR_SOCKET;
				}
			}
			pos++;
		}
	}

	/* catch the tail if there is one */
	if ((pos > (len - 4)) && (pos < len)) {
		end = pos - 1;

		i = 0;
		while (pos < len) {
			mp3_data->header_bridge[i] = buff[pos];
			pos++;
			i++;
		} 
		mp3_data->header_bridges = i;
	}

	if (!error) {
		/* if there's no errors, lets send the frames */
		count = end - start + 1;
		if (count > 0)
			ret = shout_send_raw(self, (char *)&buff[start], count);
		else
			ret = 0;

		if (bridge_buff != NULL)
			free(bridge_buff);

		if (ret == count) {
			return self->error = SHOUTERR_SUCCESS;
		} else {
			return self->error = SHOUTERR_SOCKET;
		}
	}

	if (bridge_buff != NULL)
		free(bridge_buff);

	return self->error = SHOUTERR_SUCCESS;
}

static void parse_header(mp3_header_t *mh, uint32_t header)
{
	mh->syncword = (header >> 20) & 0x0fff;
	mh->version = ((header >> 19) & 0x01) ? 0 : 1;
	if ((mh->syncword & 0x01) == 0)
		mh->version = 2;
	mh->layer = 3 - ((header >> 17) & 0x03);
	mh->error_protection = ((header >> 16) & 0x01) ? 0 : 1;
	mh->bitrate_index = (header >> 12) & 0x0F;
	mh->samplerate_index = (header >> 10) & 0x03;
	mh->padding = (header >> 9) & 0x01;
	mh->extension = (header >> 8) & 0x01;
	mh->mode = (header >> 6) & 0x03;
	mh->mode_ext = (header >> 4) & 0x03;
	mh->copyright = (header >> 3) & 0x01;
	mh->original = (header >> 2) & 0x01;
	mh->emphasis = header & 0x03;

	mh->stereo = (mh->mode == MPEG_MODE_MONO) ? 1 : 2;
	mh->bitrate = bitrate[mh->version][mh->layer][mh->bitrate_index];
	mh->samplerate = samplerate[mh->version][mh->samplerate_index];

	if (mh->version == 0)
		mh->samples = 1152;
	else
		mh->samples = 576;

	if(mh->samplerate)
		mh->framesize = (mh->samples * mh->bitrate * 1000 / mh->samplerate) / 8 + mh->padding;
}

/* mp3 frame parsing stuff */
static int mp3_header(uint32_t head, mp3_header_t *mh)
{
	/* fill out the header struct */
	parse_header(mh, head);

	/* check for syncword */
	if ((mh->syncword & 0x0ffe) != 0x0ffe)
		return 0;

	/* check for the right layer */
	if (mh->layer != 2)
		return 0;

	/* make sure bitrate is sane */
	if (mh->bitrate == 0)
		return 0;

	/* make sure samplerate is sane */
	if (mh->samplerate == 0)
		return 0;

	return 1;
}

static void close_mp3(shout_t *self)
{
	mp3_data_t *mp3_data = (mp3_data_t *)self->format_data;

	free(mp3_data);
}