ref: 4a9f2b0566345b7d0a1c7192a8583a9d25a294b5
dir: /plugins/xmms/aac-XMMS.c/
/* aac-XMMS.c
*
* XMMS input plugin that decodes AAC files.
*
* Copyright (C) 2001 Albert Fong
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/*
* This plugin depends on XMMS 1.2.5+, faad2-26012002, id3lib-3.8.0pre2
* zlib, and at least glib-1.2.
*
* XMMS: www.xmms.org
* FAAC/FAAD: www.audiocoding.com
* id3lib: id3lib.sourceforge.net
*
* Planned updates:
*
* Configuration panel in XMMS.
*
* File info panel in XMMS.
*
* Allow the buffer size to be customized, or buffer the entire
* file before decoding.
*
* Choose to enable or disable seeking. Currently, the plugin needs
* to build a position table first before seeking is possible. This
* requires an initial pass through the file before decoding. This
* is not desirable if the file is streamed across the network or from
* slow storage media.
*
* Choose to enable or disable play time calculation in the playlist.
* Again, this requires an initial pass to calculate the play time.
*
* VBR calculation.
*
* Handle unicode ID3 tags properly.
*
* A more efficient method of handling streaming files, seeking, and play
* time calculation is desirable.
*/
/* Update: February 7, 2002
*
* Updated to remove id3lib modifications, and general clean up for
* release.
*/
/* Update: January 26, 2002
*
* Updated to work with January 26 source release for FAAC/FAAD, to
* use libfaad2. (www.audiocoding.com, faac.sourceforge.net)
*/
/* Set to 0 if you want to disable seeking. Useful when streaming files
* off of a network drive, so you don't have to download the entire file
* to construct the seek_table before playing
*/
#define USE_SEEKING 1
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include "faad.h"
#include "xmms/plugin.h"
#include "xmms/titlestring.h"
#include "id3.h"
#include <glib.h>
#define BUFFER_SIZE 1024*64
static void aac_init(void);
static int aac_is_our_file(char *filename);
static void aac_play_file(char *filename);
static void aac_stop(void);
static void aac_pause(short paused);
static void aac_seek(int time);
static int aac_get_time(void);
static void aac_cleanup(void);
static void aac_get_song_info(char *filename, char **title, int *length);
static void *decode_loop(void *arg);
static gchar *construct_title(char *filename, ID3Tag *id3tag);
/* Constructs a table of file positions for each second of output */
static void get_AAC_info(FILE *infile, unsigned long *bitrate, unsigned long **seek_table, unsigned long *length);
gboolean playing = FALSE;
gboolean output_opened = FALSE;
gint seek_pos = -1;
pthread_t decode_thread;
InputPlugin aac_ip =
{
NULL, /* handle */
NULL, /* filename */
NULL, /* description */
aac_init,
NULL, /* about box */
NULL, /* configure plugin */
aac_is_our_file,
NULL, /* scan dir */
aac_play_file,
aac_stop,
aac_pause,
#if USE_SEEKING
aac_seek,
#else
NULL,
#endif
NULL, /* set equalizer */
aac_get_time,
NULL, /* get volume */
NULL, /* set volume */
aac_cleanup,
NULL, /* obsolete */
NULL, /* send visualization data */
NULL, /* set player window info */
NULL, /* set song title text */
aac_get_song_info,
NULL, /* file info box */
NULL /* pointer to OutputPlugin */
};
InputPlugin *get_iplugin_info(void)
{
aac_ip.description = g_strdup_printf("MPEG-2 AAC Player");
return &aac_ip;
}
static void aac_init(void)
{
}
static int aac_is_our_file(char *filename)
{
/* extension check (.AAC) */
gchar *ext = NULL;
ext = strrchr(filename, '.');
if (ext && !strcasecmp(ext, ".aac"))
return 1;
return 0;
}
/* Quick hack to avoid patching id3lib - will be removed whenever id3lib
* gets a working C API
*/
int _ID3_IsTagHeader(const unsigned char data[ID3_TAGHEADERSIZE])
{
size_t size = 0;
if (data[0] == 0x49 && data[1] == 0x44 && data[2] == 0x33 &&
data[3] < 0xff && data[4] < 0xff && data[5] < 0x80)
{
size = (data[9] & 0x7f) |
((data[8] & 0x7f) << 7) |
((data[7] & 0x7f) << 14) |
((data[6] & 0x7f) << 21);
return size;
}
else
{
return -1;
}
}
#define DECODE_ERROR() \
do { \
printf("decode error at file: %s line: %d\n", __FILE__, __LINE__); \
if (output_opened) \
{ \
aac_ip.output->flush(0); \
aac_ip.output->close_audio(); \
output_opened = FALSE; \
} \
if (decoder) faacDecClose(decoder); \
if (infile) fclose(infile); \
if (id3tag) ID3Tag_Delete(id3tag); \
g_free(seek_table); \
g_free(id3buffer); \
g_free(buffer); \
g_free(sample_buffer); \
playing = FALSE; \
pthread_exit(NULL); \
} while (0);
static void *aac_decode_loop(void *arg)
{
ID3Tag *id3tag = NULL;
FILE *infile = NULL;
gchar *filename = arg;
int tagsize = 0;
unsigned long buffervalid = 0;
unsigned long bufferconsumed = 0;
unsigned long bytesdecoded;
unsigned long samplesdecoded;
unsigned long samplerate;
unsigned long channels;
unsigned long bitrate = -1;
int result = 0;
unsigned char id3header[ID3_TAGHEADERSIZE];
unsigned char *id3buffer = NULL;
unsigned char *buffer = NULL;
short *sample_buffer = NULL;
unsigned long *seek_table = NULL;
unsigned long seek_table_length = 0;
faacDecHandle decoder = NULL;
faacDecConfigurationPtr config = NULL;
faacDecFrameInfo finfo;
buffer = g_malloc0(BUFFER_SIZE);
infile = fopen(filename, "rb");
if (!infile) DECODE_ERROR();
if (fread(id3header, 1, ID3_TAGHEADERSIZE, infile) != ID3_TAGHEADERSIZE)
DECODE_ERROR();
if ((tagsize = _ID3_IsTagHeader(id3header)) != -1)
{
id3buffer = g_malloc0(tagsize);
if (fread(id3buffer, 1, tagsize, infile) != tagsize)
DECODE_ERROR();
id3tag = ID3Tag_New();
ID3Tag_Parse(id3tag, id3header, id3buffer);
}
else
fseek(infile, 0, SEEK_SET);
#if USE_SEEKING
get_AAC_info(infile, &bitrate, &seek_table, &seek_table_length);
#endif
decoder = faacDecOpen();
if (!decoder) DECODE_ERROR();
config = faacDecGetCurrentConfiguration(decoder);
if (!config) DECODE_ERROR();
buffervalid = fread(buffer, 1, BUFFER_SIZE, infile);
bufferconsumed = faacDecInit(decoder, buffer, &samplerate, &channels);
output_opened = aac_ip.output->open_audio(FMT_S16_LE, samplerate, channels);
if (!output_opened)
DECODE_ERROR();
#if USE_SEEKING
aac_ip.set_info(construct_title(filename, id3tag), (seek_table_length-1)*1000, bitrate, samplerate, channels);
#else
aac_ip.set_info(construct_title(filename, id3tag), -1, bitrate, samplerate, channels);
#endif
while (playing && buffervalid > 0)
{
/* handle seeking */
if (seek_pos != -1)
{
if (seek_pos > seek_table_length) seek_pos = seek_table_length;
if (seek_pos < 0) seek_pos = 0;
if (fseek(infile, seek_table[seek_pos], SEEK_SET) == -1)
DECODE_ERROR();
bufferconsumed = 0;
buffervalid = fread(buffer, 1, BUFFER_SIZE, infile);
aac_ip.output->flush(seek_pos*1000);
seek_pos = -1;
}
if (bufferconsumed > 0)
{
memmove(buffer, &buffer[bufferconsumed], buffervalid-bufferconsumed);
buffervalid -= bufferconsumed;
buffervalid += fread(&buffer[buffervalid], 1, BUFFER_SIZE-buffervalid, infile);
}
sample_buffer = faacDecDecode(decoder, &finfo, buffer);
bytesdecoded = finfo.bytesconsumed;
samplesdecoded = finfo.samples;
result = finfo.error;
if (sample_buffer == NULL)
{
buffervalid = 0;
printf("aac-XMMS: %s\n", faacDecGetErrorMessage(result));
}
bufferconsumed = bytesdecoded;
if (sample_buffer && (samplesdecoded > 0))
{
while (playing && aac_ip.output->buffer_free() < (samplesdecoded << 1))
{
if (seek_pos != -1)
break;
else
xmms_usleep(10000);
}
if (seek_pos == -1)
{
aac_ip.add_vis_pcm(aac_ip.output->written_time(), FMT_S16_LE, channels, samplesdecoded << 1, sample_buffer);
aac_ip.output->write_audio(sample_buffer, samplesdecoded << 1);
}
}
}
if (output_opened)
{
while (playing && aac_ip.output->buffer_playing()) {
xmms_usleep(10000);
}
aac_ip.output->flush(0);
aac_ip.output->close_audio();
output_opened = FALSE;
}
if (decoder) faacDecClose(decoder);
if (infile) fclose(infile);
if (id3tag) ID3Tag_Delete(id3tag);
g_free(seek_table);
g_free(id3buffer);
g_free(buffer);
playing = FALSE;
seek_pos = -1;
pthread_exit(NULL);
}
static void aac_play_file(char *filename)
{
playing = TRUE;
pthread_create(&decode_thread, NULL, aac_decode_loop, g_strdup(filename));
}
static void aac_stop(void)
{
playing = FALSE;
pthread_join(decode_thread, NULL);
}
static void aac_pause(short paused)
{
if (output_opened)
aac_ip.output->pause(paused);
}
static void aac_seek(int time)
{
seek_pos = time;
while (playing && seek_pos != -1) xmms_usleep(10000);
}
static int aac_get_time(void)
{
if (!playing)
return -1;
return aac_ip.output->output_time();
}
/*
* Construct a title string based on the given ID3 tag, or from the filename
* if the tag is unavailable. This does not take into account different
* encodings other than ASCII.
*
*/
static gchar *construct_title(char *filename, ID3Tag *id3tag)
{
TitleInput *input = NULL;
ID3Frame *id3frame = NULL;
ID3Field *id3field = NULL;
gchar *ext = NULL;
gchar *title = NULL;
gchar *tmp = NULL;
XMMS_NEW_TITLEINPUT(input);
ext = strrchr(filename, '.');
if (ext) ext++;
input->file_name = g_basename(filename);
input->file_ext = ext;
input->file_path = filename;
if (id3tag)
{
/* performer */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_LEADARTIST);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->performer = g_malloc0((ID3Field_Size(id3field)+1));
ID3Field_GetASCII(id3field, input->performer, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
/* album name */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_ALBUM);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->album_name = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, input->album_name, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
/* track name */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_TITLE);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->track_name = g_malloc0((ID3Field_Size(id3field)+1));
ID3Field_GetASCII(id3field, input->track_name, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
/* track number */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_TRACKNUM);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
/* The track number MAY be extended with a '/'
* followed by a numeric string, eg. "4/9".
* In this case, take only the first number
*/
tmp = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, tmp, ID3Field_Size(id3field));
input->track_number = atoi(tmp);
g_free(tmp);
id3field = NULL;
}
id3frame = NULL;
}
/* year */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_YEAR);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
tmp = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, tmp, ID3Field_Size(id3field));
input->year = atoi(tmp);
g_free(tmp);
id3field = NULL;
}
id3frame = NULL;
}
/* date */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_DATE);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->date = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, input->date, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
/* genre */
/* XXX Genre handling is known to be broken.
* It does not take into account id3v1 genre listings
*/
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_CONTENTTYPE);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->genre = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, input->genre, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
/* comment */
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_COMMENT);
if (id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
input->comment = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, input->comment, ID3Field_Size(id3field));
id3field = NULL;
}
id3frame = NULL;
}
}
title = xmms_get_titlestring(xmms_get_gentitle_format(), input);
g_free(input->performer);
g_free(input->album_name);
g_free(input->track_name);
g_free(input->date);
g_free(input->genre);
g_free(input->comment);
g_free(input);
return title;
}
static void aac_get_song_info(char *filename, char **title, int *length)
{
ID3Tag *id3tag = NULL;
ID3Frame *id3frame = NULL;
ID3Field *id3field = NULL;
unsigned char header[ID3_TAGHEADERSIZE];
unsigned char *buffer = NULL;
unsigned long *seek_table = NULL;
unsigned long seconds, bitrate;
FILE *infile = NULL;
int buffersize;
(*length) = -1;
infile = fopen(filename, "rb");
if (!infile)
return;
memset(header, 0, ID3_TAGHEADERSIZE);
fread(header, 1, ID3_TAGHEADERSIZE, infile);
/* check for a valid ID3 header */
if ((buffersize = _ID3_IsTagHeader(header)) != -1)
{
buffer = g_malloc0(buffersize);
if (fread(buffer, 1, buffersize, infile) != buffersize)
{
g_free(buffer);
fclose(infile);
return;
}
id3tag = ID3Tag_New();
ID3Tag_Parse(id3tag, header, buffer);
id3frame = ID3Tag_FindFrameWithID(id3tag, ID3FID_SONGLEN);
if (!id3frame)
{
id3field = ID3Frame_GetField(id3frame, ID3FN_TEXT);
if (id3field)
{
buffer = g_malloc0(ID3Field_Size(id3field)+1);
ID3Field_GetASCII(id3field, buffer, ID3Field_Size(id3field));
(*length) = atoi(buffer);
g_free(buffer);
}
}
}
(*title) = construct_title(filename, id3tag);
#if USE_SEEKING
get_AAC_info(infile, &bitrate, &seek_table, &seconds);
(*length) = (seconds-1)*1000;
#endif
if (id3tag) ID3Tag_Delete(id3tag);
if (infile) fclose(infile);
g_free(seek_table);
g_free(buffer);
}
static void aac_cleanup(void)
{
if (aac_ip.description)
g_free(aac_ip.description);
}
#define ADTS_HEADER_SIZE 8
#define SEEK_TABLE_CHUNK 60
static int sample_rates[] = { 96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000 };
/* Modeled after the FAAD winamp plugin */
static void get_AAC_info(FILE *infile, unsigned long *bitrate, unsigned long **seek_table, unsigned long *seek_table_length)
{
unsigned long orig_pos, pos;
unsigned char header[ADTS_HEADER_SIZE];
unsigned int framecount, framelength, t_framelength = 0, framesinsec = 0;
unsigned long *tmp_seek_table = NULL;
unsigned long *tmp_realloc = NULL;
unsigned long tmp_seek_table_length;
unsigned long seconds = 0;
float frames_per_sec;
int id;
int sample_rate_index;
orig_pos = ftell(infile);
(*bitrate) = -1;
(*seek_table) = NULL;
(*seek_table_length) = -1;
tmp_seek_table = g_malloc0(SEEK_TABLE_CHUNK * sizeof(unsigned long));
tmp_seek_table_length = SEEK_TABLE_CHUNK;
for (framecount = 0;; framecount++, framesinsec++)
{
pos = ftell(infile);
if (fread(header, 1, ADTS_HEADER_SIZE, infile) != ADTS_HEADER_SIZE)
break;
/* check syncword */
if (!((header[0] == 0xff) && ((header[1] & 0xf6) == 0xf0)))
break;
if (!framecount)
{
/* read the fixed ADTS header only once */
id = header[1] & 0x08;
sample_rate_index = (header[2] & 0x3c) >> 2;
frames_per_sec = sample_rates[sample_rate_index] / 1024.0f;
}
if (id == 0)
{
/* MPEG-4 */
framelength = ((unsigned int)header[4] << 5) | ((unsigned int)header[5] >> 3);
}
else
{
/* MPEG-2 */
framelength = (((unsigned int)header[3] & 0x3) << 11) | ((unsigned int)header[4] << 3) | (header[5] >> 5);
}
t_framelength += framelength;
if (framesinsec == 43)
{
framesinsec = 0;
}
if (framesinsec == 0)
{
if (seconds == tmp_seek_table_length)
{
tmp_seek_table = realloc(tmp_seek_table, (seconds + SEEK_TABLE_CHUNK)*sizeof(unsigned long));
tmp_seek_table_length = seconds + SEEK_TABLE_CHUNK;
}
tmp_seek_table[seconds] = pos;
seconds++;
}
if (fseek(infile, framelength - ADTS_HEADER_SIZE, SEEK_CUR) == -1)
break;
}
if (seconds)
{
(*seek_table) = g_malloc0(seconds*sizeof(unsigned long));
memcpy((*seek_table), tmp_seek_table, seconds*sizeof(unsigned long));
(*seek_table_length) = seconds;
// Bitrate calculation here is unreliable, and variable, so setting it here
// doesn't help us any
// (*bitrate) = (unsigned long)(((t_framelength / framecount)*(sample_rates[sample_rate_index]/1024.0))+0.5)*8;
}
g_free(tmp_seek_table);
fseek(infile, orig_pos, SEEK_SET);
}