summaryrefslogtreecommitdiffstats
path: root/lib/rbcodec/metadata
diff options
context:
space:
mode:
authorSean Bartell <wingedtachikoma@gmail.com>2011-06-24 01:25:21 -0400
committerNils Wallménius <nils@rockbox.org>2012-03-18 12:00:39 +0100
commitb5716df4cb2837bbbc42195cf1aefcf03e21d6a6 (patch)
tree130cd712e2e00893b6df9959a375a8d9523a1aca /lib/rbcodec/metadata
parent24bd9d5393dbe39a5c6194877bc00ede669b1d5d (diff)
downloadrockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.tar.gz
rockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.tar.bz2
rockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.zip
Build librbcodec with DSP and metadata.
All associated files are moved to /lib/rbcodec. Change-Id: I572ddd2b8a996aae1e98c081d06b1ed356dce222
Diffstat (limited to 'lib/rbcodec/metadata')
-rw-r--r--lib/rbcodec/metadata/a52.c103
-rw-r--r--lib/rbcodec/metadata/adx.c124
-rw-r--r--lib/rbcodec/metadata/aiff.c108
-rw-r--r--lib/rbcodec/metadata/ape.c182
-rw-r--r--lib/rbcodec/metadata/asap.c254
-rw-r--r--lib/rbcodec/metadata/asf.c591
-rw-r--r--lib/rbcodec/metadata/au.c105
-rw-r--r--lib/rbcodec/metadata/ay.c148
-rw-r--r--lib/rbcodec/metadata/flac.c127
-rw-r--r--lib/rbcodec/metadata/gbs.c65
-rw-r--r--lib/rbcodec/metadata/hes.c39
-rw-r--r--lib/rbcodec/metadata/id3tags.c1199
-rw-r--r--lib/rbcodec/metadata/kss.c53
-rw-r--r--lib/rbcodec/metadata/metadata.c641
-rw-r--r--lib/rbcodec/metadata/metadata.h353
-rw-r--r--lib/rbcodec/metadata/metadata_common.c374
-rw-r--r--lib/rbcodec/metadata/metadata_common.h69
-rw-r--r--lib/rbcodec/metadata/metadata_parsers.h59
-rw-r--r--lib/rbcodec/metadata/mod.c103
-rw-r--r--lib/rbcodec/metadata/monkeys.c97
-rw-r--r--lib/rbcodec/metadata/mp3.c193
-rw-r--r--lib/rbcodec/metadata/mp3data.c849
-rw-r--r--lib/rbcodec/metadata/mp3data.h89
-rw-r--r--lib/rbcodec/metadata/mp4.c842
-rw-r--r--lib/rbcodec/metadata/mpc.c220
-rw-r--r--lib/rbcodec/metadata/nsf.c278
-rw-r--r--lib/rbcodec/metadata/ogg.c215
-rw-r--r--lib/rbcodec/metadata/oma.c189
-rw-r--r--lib/rbcodec/metadata/replaygain.c222
-rw-r--r--lib/rbcodec/metadata/replaygain.h34
-rw-r--r--lib/rbcodec/metadata/rm.c464
-rw-r--r--lib/rbcodec/metadata/sgc.c67
-rw-r--r--lib/rbcodec/metadata/sid.c89
-rw-r--r--lib/rbcodec/metadata/smaf.c470
-rw-r--r--lib/rbcodec/metadata/spc.c130
-rw-r--r--lib/rbcodec/metadata/tta.c123
-rw-r--r--lib/rbcodec/metadata/vgm.c195
-rw-r--r--lib/rbcodec/metadata/vorbis.c381
-rw-r--r--lib/rbcodec/metadata/vox.c49
-rw-r--r--lib/rbcodec/metadata/wave.c432
-rw-r--r--lib/rbcodec/metadata/wavpack.c160
41 files changed, 10485 insertions, 0 deletions
diff --git a/lib/rbcodec/metadata/a52.c b/lib/rbcodec/metadata/a52.c
new file mode 100644
index 0000000000..a8aad3fa4f
--- /dev/null
+++ b/lib/rbcodec/metadata/a52.c
@@ -0,0 +1,103 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <stdio.h>
+#include "metadata.h"
+#include "logf.h"
+
+#include "metadata_parsers.h"
+
+static const unsigned short a52_bitrates[] =
+{
+ 32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
+ 192, 224, 256, 320, 384, 448, 512, 576, 640
+};
+
+/* Only store frame sizes for 44.1KHz - others are simply multiples
+ of the bitrate */
+static const unsigned short a52_441framesizes[] =
+{
+ 69 * 2, 70 * 2, 87 * 2, 88 * 2, 104 * 2, 105 * 2, 121 * 2,
+ 122 * 2, 139 * 2, 140 * 2, 174 * 2, 175 * 2, 208 * 2, 209 * 2,
+ 243 * 2, 244 * 2, 278 * 2, 279 * 2, 348 * 2, 349 * 2, 417 * 2,
+ 418 * 2, 487 * 2, 488 * 2, 557 * 2, 558 * 2, 696 * 2, 697 * 2,
+ 835 * 2, 836 * 2, 975 * 2, 976 * 2, 1114 * 2, 1115 * 2, 1253 * 2,
+ 1254 * 2, 1393 * 2, 1394 * 2
+};
+
+bool get_a52_metadata(int fd, struct mp3entry *id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned long totalsamples;
+ int i;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5))
+ {
+ return false;
+ }
+
+ if ((buf[0] != 0x0b) || (buf[1] != 0x77))
+ {
+ logf("not an A52/AC3 file\n");
+ return false;
+ }
+
+ i = buf[4] & 0x3e;
+
+ if (i > 36)
+ {
+ logf("A52: Invalid frmsizecod: %d\n",i);
+ return false;
+ }
+
+ id3->bitrate = a52_bitrates[i >> 1];
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ switch (buf[4] & 0xc0)
+ {
+ case 0x00:
+ id3->frequency = 48000;
+ id3->bytesperframe=id3->bitrate * 2 * 2;
+ break;
+
+ case 0x40:
+ id3->frequency = 44100;
+ id3->bytesperframe = a52_441framesizes[i];
+ break;
+
+ case 0x80:
+ id3->frequency = 32000;
+ id3->bytesperframe = id3->bitrate * 3 * 2;
+ break;
+
+ default:
+ logf("A52: Invalid samplerate code: 0x%02x\n", buf[4] & 0xc0);
+ return false;
+ break;
+ }
+
+ /* One A52 frame contains 6 blocks, each containing 256 samples */
+ totalsamples = id3->filesize / id3->bytesperframe * 6 * 256;
+ id3->length = totalsamples / id3->frequency * 1000;
+ return true;
+}
diff --git a/lib/rbcodec/metadata/adx.c b/lib/rbcodec/metadata/adx.c
new file mode 100644
index 0000000000..7c341b4835
--- /dev/null
+++ b/lib/rbcodec/metadata/adx.c
@@ -0,0 +1,124 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "debug.h"
+
+bool get_adx_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char * buf = (unsigned char *)id3->path;
+ int chanstart, channels;
+ int looping = 0, start_adr = 0, end_adr = 0;
+
+ /* try to get the basic header */
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || (read(fd, buf, 0x38) < 0x38))
+ {
+ DEBUGF("lseek or read failed\n");
+ return false;
+ }
+
+ /* ADX starts with 0x80 */
+ if (buf[0] != 0x80) {
+ DEBUGF("get_adx_metadata: wrong first byte %c\n",buf[0]);
+ return false;
+ }
+
+ /* check for a reasonable offset */
+ chanstart = ((buf[2] << 8) | buf[3]) + 4;
+ if (chanstart > 4096) {
+ DEBUGF("get_adx_metadata: bad chanstart %i\n", chanstart);
+ return false;
+ }
+
+ /* check for a workable number of channels */
+ channels = buf[7];
+ if (channels != 1 && channels != 2) {
+ DEBUGF("get_adx_metadata: bad channel count %i\n",channels);
+ return false;
+ }
+
+ id3->frequency = get_long_be(&buf[8]);
+ /* 32 samples per 18 bytes */
+ id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000;
+ id3->length = get_long_be(&buf[12]) / id3->frequency * 1000;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ /* get loop info */
+ if (!memcmp(buf+0x10,"\x01\xF4\x03",3)) {
+ /* Soul Calibur 2 style (type 03) */
+ DEBUGF("get_adx_metadata: type 03 found\n");
+ /* check if header is too small for loop data */
+ if (chanstart-6 < 0x2c) looping=0;
+ else {
+ looping = get_long_be(&buf[0x18]);
+ end_adr = get_long_be(&buf[0x28]);
+ start_adr = get_long_be(&buf[0x1c])/32*channels*18+chanstart;
+ }
+ } else if (!memcmp(buf+0x10,"\x01\xF4\x04",3)) {
+ /* Standard (type 04) */
+ DEBUGF("get_adx_metadata: type 04 found\n");
+ /* check if header is too small for loop data */
+ if (chanstart-6 < 0x38) looping=0;
+ else {
+ looping = get_long_be(&buf[0x24]);
+ end_adr = get_long_be(&buf[0x34]);
+ start_adr = get_long_be(&buf[0x28])/32*channels*18+chanstart;
+ }
+ } else {
+ DEBUGF("get_adx_metadata: error, couldn't determine ADX type\n");
+ return false;
+ }
+
+ /* is file using encryption */
+ if (buf[0x13]==0x08) {
+ DEBUGF("get_adx_metadata: error, encrypted ADX not supported\n");
+ return false;
+ }
+
+ if (looping) {
+ /* 2 loops, 10 second fade */
+ id3->length = (start_adr-chanstart + 2*(end_adr-start_adr))
+ *8 / id3->bitrate + 10000;
+ }
+
+ /* try to get the channel header */
+ if ((lseek(fd, chanstart-6, SEEK_SET) < 0)
+ || (read(fd, buf, 6) < 6))
+ {
+ return false;
+ }
+
+ /* check channel header */
+ if (memcmp(buf, "(c)CRI", 6) != 0) return false;
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/aiff.c b/lib/rbcodec/metadata/aiff.c
new file mode 100644
index 0000000000..654f37cf98
--- /dev/null
+++ b/lib/rbcodec/metadata/aiff.c
@@ -0,0 +1,108 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+
+#include "debug.h"
+
+/* compressionType: AIFC QuickTime IMA ADPCM */
+#define AIFC_FORMAT_QT_IMA_ADPCM "ima4"
+
+bool get_aiff_metadata(int fd, struct mp3entry* id3)
+{
+ unsigned char buf[512];
+ unsigned long numChannels = 0;
+ unsigned long numSampleFrames = 0;
+ unsigned long numbytes = 0;
+ bool is_aifc = false;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, &buf[0], 12) < 12) ||
+ (memcmp(&buf[0], "FORM", 4) != 0) || (memcmp(&buf[8], "AIF", 3) != 0) ||
+ (!(is_aifc = (buf[11] == 'C')) && buf[11] != 'F'))
+ {
+ return false;
+ }
+
+ while (read(fd, &buf[0], 8) == 8)
+ {
+ size_t size = get_long_be(&buf[4]); /* chunkSize */
+
+ if (memcmp(&buf[0], "SSND", 4) == 0)
+ {
+ numbytes = size - 8;
+ break; /* assume COMM was already read */
+ }
+
+ /* odd chunk sizes must be padded */
+ size += size & 1;
+
+ if (size > sizeof(buf))
+ {
+ DEBUGF("AIFF \"%4.4s\" chunk too large (%zd > %zd)",
+ (char*) &buf[0], size, sizeof(buf));
+ }
+
+ if (memcmp(&buf[0], "COMM", 4) == 0)
+ {
+ if (size > sizeof(buf) || read(fd, &buf[0], size) != (ssize_t)size)
+ return false;
+
+ numChannels = ((buf[0]<<8)|buf[1]);
+
+ numSampleFrames = get_long_be(&buf[2]);
+
+ /* sampleRate */
+ id3->frequency = get_long_be(&buf[10]);
+ id3->frequency >>= (16+14-buf[9]);
+
+ /* save format infos */
+ id3->bitrate = ((buf[6]<<8)|buf[7]) * numChannels * id3->frequency;
+ id3->bitrate /= 1000;
+
+ if (!is_aifc || memcmp(&buf[18], AIFC_FORMAT_QT_IMA_ADPCM, 4) != 0)
+ id3->length = ((int64_t) numSampleFrames * 1000) / id3->frequency;
+ else
+ {
+ /* QuickTime IMA ADPCM is 1block = 64 data for each channel */
+ id3->length = ((int64_t) numSampleFrames * 64000LL) / id3->frequency;
+ }
+
+ id3->vbr = false; /* AIFF files are CBR */
+ id3->filesize = filesize(fd);
+ }
+ else
+ {
+ /* skip chunk */
+ if (lseek(fd, size, SEEK_CUR) < 0)
+ return false;
+ }
+ }
+
+ return numbytes && numChannels;
+}
diff --git a/lib/rbcodec/metadata/ape.c b/lib/rbcodec/metadata/ape.c
new file mode 100644
index 0000000000..0bd2477431
--- /dev/null
+++ b/lib/rbcodec/metadata/ape.c
@@ -0,0 +1,182 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "structec.h"
+
+#define APETAG_HEADER_LENGTH 32
+#define APETAG_HEADER_FORMAT "8llll8"
+#define APETAG_ITEM_HEADER_FORMAT "ll"
+#define APETAG_ITEM_TYPE_MASK 3
+
+#ifdef HAVE_ALBUMART
+/* The AA header consists of the pseudo filename "Album Cover (Front).ext"
+ * whereas ".ext" is the file extension. For now ".jpg" and ".png" are
+ * supported by this APE metadata parser. Therefore the length is 22. */
+#define APETAG_AA_HEADER_LENGTH 22
+#endif
+
+struct apetag_header
+{
+ char id[8];
+ long version;
+ long length;
+ long item_count;
+ long flags;
+ char reserved[8];
+};
+
+struct apetag_item_header
+{
+ long length;
+ long flags;
+};
+
+/* Read the items in an APEV2 tag. Only looks for a tag at the end of a
+ * file. Returns true if a tag was found and fully read, false otherwise.
+ */
+bool read_ape_tags(int fd, struct mp3entry* id3)
+{
+ struct apetag_header header;
+
+ if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0)
+ || (ecread(fd, &header, 1, APETAG_HEADER_FORMAT, IS_BIG_ENDIAN)
+ != APETAG_HEADER_LENGTH)
+ || (memcmp(header.id, "APETAGEX", sizeof(header.id))))
+ {
+ return false;
+ }
+
+ if ((header.version == 2000) && (header.item_count > 0)
+ && (header.length > APETAG_HEADER_LENGTH))
+ {
+ char *buf = id3->id3v2buf;
+ unsigned int buf_remaining = sizeof(id3->id3v2buf)
+ + sizeof(id3->id3v1buf);
+ unsigned int tag_remaining = header.length - APETAG_HEADER_LENGTH;
+ int i;
+
+ if (lseek(fd, -header.length, SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ for (i = 0; i < header.item_count; i++)
+ {
+ struct apetag_item_header item;
+ char name[TAG_NAME_LENGTH];
+ char value[TAG_VALUE_LENGTH];
+ long r;
+
+ if (tag_remaining < sizeof(item))
+ {
+ break;
+ }
+
+ if (ecread(fd, &item, 1, APETAG_ITEM_HEADER_FORMAT, IS_BIG_ENDIAN)
+ < (long) sizeof(item))
+ {
+ return false;
+ }
+
+ tag_remaining -= sizeof(item);
+ r = read_string(fd, name, sizeof(name), 0, tag_remaining);
+
+ if (r == -1)
+ {
+ return false;
+ }
+
+ tag_remaining -= r + item.length;
+
+ if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0)
+ {
+ long len;
+
+ if (read_string(fd, value, sizeof(value), -1, item.length)
+ != item.length)
+ {
+ return false;
+ }
+
+ len = parse_tag(name, value, id3, buf, buf_remaining,
+ TAGTYPE_APE);
+ buf += len;
+ buf_remaining -= len;
+ }
+ else
+ {
+#ifdef HAVE_ALBUMART
+ if (strcasecmp(name, "cover art (front)") == 0)
+ {
+ /* Allow to read at least APETAG_AA_HEADER_LENGTH bytes. */
+ r = read_string(fd, name, sizeof(name), 0, APETAG_AA_HEADER_LENGTH);
+ if (r == -1)
+ {
+ return false;
+ }
+
+ /* Gather the album art format from the pseudo file name's ending. */
+ strcpy(name, name + strlen(name) - 4);
+ id3->albumart.type = AA_TYPE_UNKNOWN;
+ if (strcasecmp(name, ".jpg") == 0)
+ {
+ id3->albumart.type = AA_TYPE_JPG;
+ }
+ else if (strcasecmp(name, ".png") == 0)
+ {
+ id3->albumart.type = AA_TYPE_PNG;
+ }
+
+ /* Set the album art size and position. */
+ if (id3->albumart.type != AA_TYPE_UNKNOWN)
+ {
+ id3->albumart.pos = lseek(fd, 0, SEEK_CUR);
+ id3->albumart.size = item.length - r;
+ id3->has_embedded_albumart = true;
+ }
+
+ /* Seek back to this APE items begin. */
+ if (lseek(fd, -r, SEEK_CUR) < 0)
+ {
+ return false;
+ }
+ }
+#endif
+ /* Seek to the next APE item. */
+ if (lseek(fd, item.length, SEEK_CUR) < 0)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/asap.c b/lib/rbcodec/metadata/asap.c
new file mode 100644
index 0000000000..9e7f227031
--- /dev/null
+++ b/lib/rbcodec/metadata/asap.c
@@ -0,0 +1,254 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2008 Dominik Wenger
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+#include "debug.h"
+
+#define MAX_SONGS 32
+
+static bool parse_dec(int *retval, const char *p, int minval, int maxval)
+{
+ int r = 0;
+ do {
+ char c = *p;
+ if (c >= '0' && c <= '9')
+ r = 10 * r + c - '0';
+ else
+ return false;
+ if (r > maxval)
+ return false;
+ } while (*++p != '\0');
+ if (r < minval)
+ return false;
+ *retval = r;
+ return true;
+}
+
+static bool parse_text(char *retval, const char *p)
+{
+ int i;
+ if (*p != '"')
+ return false;
+ p++;
+ if (p[0] == '<' && p[1] == '?' && p[2] == '>' && p[3] == '"')
+ return true;
+ i = 0;
+ while (*p != '"') {
+ if (i >= 127)
+ return false;
+ if (*p == '\0')
+ return false;
+ retval[i++] = *p++;
+ }
+ retval[i] = '\0';
+ return true;
+}
+
+static int ASAP_ParseDuration(const char *s)
+{
+ int r;
+ if (*s < '0' || *s > '9')
+ return -1;
+ r = *s++ - '0';
+ if (*s >= '0' && *s <= '9')
+ r = 10 * r + *s++ - '0';
+ if (*s == ':') {
+ s++;
+ if (*s < '0' || *s > '5')
+ return -1;
+ r = 60 * r + (*s++ - '0') * 10;
+ if (*s < '0' || *s > '9')
+ return -1;
+ r += *s++ - '0';
+ }
+ r *= 1000;
+ if (*s != '.')
+ return r;
+ s++;
+ if (*s < '0' || *s > '9')
+ return r;
+ r += 100 * (*s++ - '0');
+ if (*s < '0' || *s > '9')
+ return r;
+ r += 10 * (*s++ - '0');
+ if (*s < '0' || *s > '9')
+ return r;
+ r += *s - '0';
+ return r;
+}
+
+static bool read_asap_string(char* source, char** buf, char** buffer_end, char** dest)
+{
+ if(parse_text(*buf,source) == false)
+ return false;
+
+ /* set dest pointer */
+ *dest = *buf;
+
+ /* move buf ptr */
+ *buf += strlen(*buf)+1;
+
+ /* check size */
+ if(*buf >= *buffer_end)
+ {
+ DEBUGF("Buffer full\n");
+ return false;
+ }
+ return true;
+}
+
+static bool parse_sap_header(int fd, struct mp3entry* id3, int file_len)
+{
+ int module_index = 0;
+ int sap_signature = -1;
+ int duration_index = 0;
+ unsigned char cur_char = 0;
+ int i;
+
+ /* set defaults */
+ int numSongs = 1;
+ int defSong = 0;
+ int durations[MAX_SONGS];
+ for (i = 0; i < MAX_SONGS; i++)
+ durations[i] = -1;
+
+ /* use id3v2 buffer for our strings */
+ char* buffer = id3->id3v2buf;
+ char* buffer_end = id3->id3v2buf + ID3V2_BUF_SIZE;
+
+ /* parse file */
+ while (1)
+ {
+ char line[256];
+ char *p;
+
+ if (module_index + 8 >= file_len)
+ return false;
+ /* read a char */
+ read(fd,&cur_char,1);
+ /* end of header */
+ if (cur_char == 0xff)
+ break;
+
+ i = 0;
+ while (cur_char != 0x0d)
+ {
+ line[i++] = cur_char;
+ module_index++;
+ if (module_index >= file_len || (unsigned)i >= sizeof(line) - 1)
+ return false;
+ /* read a char */
+ read(fd,&cur_char,1);
+ }
+ if (++module_index >= file_len )
+ return false;
+ /* read a char */
+ read(fd,&cur_char,1);
+ if ( cur_char != 0x0a)
+ return false;
+
+ line[i] = '\0';
+ for (p = line; *p != '\0'; p++) {
+ if (*p == ' ') {
+ *p++ = '\0';
+ break;
+ }
+ }
+
+ /* parse tags */
+ if(strcmp(line, "SAP") == 0)
+ sap_signature = 1;
+ if (sap_signature == -1)
+ return false;
+ if (strcmp(line, "AUTHOR") == 0)
+ {
+ if(read_asap_string(p, &buffer, &buffer_end, &id3->artist) == false)
+ return false;
+ }
+ else if(strcmp(line, "NAME") == 0)
+ {
+ if(read_asap_string(p, &buffer, &buffer_end, &id3->title) == false)
+ return false;
+ }
+ else if(strcmp(line, "DATE") == 0)
+ {
+ if(read_asap_string(p, &buffer, &buffer_end, &id3->year_string) == false)
+ return false;
+ }
+ else if (strcmp(line, "SONGS") == 0)
+ {
+ if (parse_dec(&numSongs, p, 1, MAX_SONGS) == false )
+ return false;
+ }
+ else if (strcmp(line, "DEFSONG") == 0)
+ {
+ if (parse_dec(&defSong, p, 0, MAX_SONGS) == false)
+ return false;
+ }
+ else if (strcmp(line, "TIME") == 0)
+ {
+ int durationTemp = ASAP_ParseDuration(p);
+ if (durationTemp < 0 || duration_index >= MAX_SONGS)
+ return false;
+ durations[duration_index++] = durationTemp;
+ }
+ }
+
+ /* set length: */
+ int length = durations[defSong];
+ if (length < 0)
+ length = 180 * 1000;
+ id3->length = length;
+
+ lseek(fd, 0, SEEK_SET);
+ return true;
+}
+
+
+bool get_asap_metadata(int fd, struct mp3entry* id3)
+{
+
+ int filelength = filesize(fd);
+
+ if(parse_sap_header(fd, id3, filelength) == false)
+ {
+ DEBUGF("parse sap header failed.\n");
+ return false;
+ }
+
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ id3->vbr = false;
+ id3->filesize = filelength;
+ id3->genre_string = id3_get_num_genre(36);
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/asf.c b/lib/rbcodec/metadata/asf.c
new file mode 100644
index 0000000000..b815c09769
--- /dev/null
+++ b/lib/rbcodec/metadata/asf.c
@@ -0,0 +1,591 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ *
+ * $Id$
+ *
+ * Copyright (C) 2007 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "metadata.h"
+#include "replaygain.h"
+#include "debug.h"
+#include "rbunicode.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "system.h"
+#include <codecs/libasf/asf.h>
+
+/* TODO: Just read the GUIDs into a 16-byte array, and use memcmp to compare */
+struct guid_s {
+ uint32_t v1;
+ uint16_t v2;
+ uint16_t v3;
+ uint8_t v4[8];
+};
+typedef struct guid_s guid_t;
+
+struct asf_object_s {
+ guid_t guid;
+ uint64_t size;
+ uint64_t datalen;
+};
+typedef struct asf_object_s asf_object_t;
+
+static const guid_t asf_guid_null =
+{0x00000000, 0x0000, 0x0000, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}};
+
+/* top level object guids */
+
+static const guid_t asf_guid_header =
+{0x75B22630, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
+
+static const guid_t asf_guid_data =
+{0x75B22636, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
+
+static const guid_t asf_guid_index =
+{0x33000890, 0xE5B1, 0x11CF, {0x89, 0xF4, 0x00, 0xA0, 0xC9, 0x03, 0x49, 0xCB}};
+
+/* header level object guids */
+
+static const guid_t asf_guid_file_properties =
+{0x8cabdca1, 0xa947, 0x11cf, {0x8E, 0xe4, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}};
+
+static const guid_t asf_guid_stream_properties =
+{0xB7DC0791, 0xA9B7, 0x11CF, {0x8E, 0xE6, 0x00, 0xC0, 0x0C, 0x20, 0x53, 0x65}};
+
+static const guid_t asf_guid_content_description =
+{0x75B22633, 0x668E, 0x11CF, {0xA6, 0xD9, 0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C}};
+
+static const guid_t asf_guid_extended_content_description =
+{0xD2D0A440, 0xE307, 0x11D2, {0x97, 0xF0, 0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50}};
+
+static const guid_t asf_guid_content_encryption =
+{0x2211b3fb, 0xbd23, 0x11d2, {0xb4, 0xb7, 0x00, 0xa0, 0xc9, 0x55, 0xfc, 0x6e}};
+
+static const guid_t asf_guid_extended_content_encryption =
+{0x298ae614, 0x2622, 0x4c17, {0xb9, 0x35, 0xda, 0xe0, 0x7e, 0xe9, 0x28, 0x9c}};
+
+/* stream type guids */
+
+static const guid_t asf_guid_stream_type_audio =
+{0xF8699E40, 0x5B4D, 0x11CF, {0xA8, 0xFD, 0x00, 0x80, 0x5F, 0x5C, 0x44, 0x2B}};
+
+static int asf_guid_match(const guid_t *guid1, const guid_t *guid2)
+{
+ if((guid1->v1 != guid2->v1) ||
+ (guid1->v2 != guid2->v2) ||
+ (guid1->v3 != guid2->v3) ||
+ (memcmp(guid1->v4, guid2->v4, 8))) {
+ return 0;
+ }
+
+ return 1;
+}
+
+/* Read the 16 byte GUID from a file */
+static void asf_readGUID(int fd, guid_t* guid)
+{
+ read_uint32le(fd, &guid->v1);
+ read_uint16le(fd, &guid->v2);
+ read_uint16le(fd, &guid->v3);
+ read(fd, guid->v4, 8);
+}
+
+static void asf_read_object_header(asf_object_t *obj, int fd)
+{
+ asf_readGUID(fd, &obj->guid);
+ read_uint64le(fd, &obj->size);
+ obj->datalen = 0;
+}
+
+/* Parse an integer from the extended content object - we always
+ convert to an int, regardless of native format.
+*/
+static int asf_intdecode(int fd, int type, int length)
+{
+ uint16_t tmp16;
+ uint32_t tmp32;
+ uint64_t tmp64;
+
+ if (type == 3) {
+ read_uint32le(fd, &tmp32);
+ lseek(fd,length - 4,SEEK_CUR);
+ return (int)tmp32;
+ } else if (type == 4) {
+ read_uint64le(fd, &tmp64);
+ lseek(fd,length - 8,SEEK_CUR);
+ return (int)tmp64;
+ } else if (type == 5) {
+ read_uint16le(fd, &tmp16);
+ lseek(fd,length - 2,SEEK_CUR);
+ return (int)tmp16;
+ }
+
+ return 0;
+}
+
+/* Decode a LE utf16 string from a disk buffer into a fixed-sized
+ utf8 buffer.
+*/
+
+static void asf_utf16LEdecode(int fd,
+ uint16_t utf16bytes,
+ unsigned char **utf8,
+ int* utf8bytes
+ )
+{
+ unsigned long ucs;
+ int n;
+ unsigned char utf16buf[256];
+ unsigned char* utf16 = utf16buf;
+ unsigned char* newutf8;
+
+ n = read(fd, utf16buf, MIN(sizeof(utf16buf), utf16bytes));
+ utf16bytes -= n;
+
+ while (n > 0) {
+ /* Check for a surrogate pair */
+ if (utf16[1] >= 0xD8 && utf16[1] < 0xE0) {
+ if (n < 4) {
+ /* Run out of utf16 bytes, read some more */
+ utf16buf[0] = utf16[0];
+ utf16buf[1] = utf16[1];
+
+ n = read(fd, utf16buf + 2, MIN(sizeof(utf16buf)-2, utf16bytes));
+ utf16 = utf16buf;
+ utf16bytes -= n;
+ n += 2;
+ }
+
+ if (n < 4) {
+ /* Truncated utf16 string, abort */
+ break;
+ }
+ ucs = 0x10000 + ((utf16[0] << 10) | ((utf16[1] - 0xD8) << 18)
+ | utf16[2] | ((utf16[3] - 0xDC) << 8));
+ utf16 += 4;
+ n -= 4;
+ } else {
+ ucs = (utf16[0] | (utf16[1] << 8));
+ utf16 += 2;
+ n -= 2;
+ }
+
+ if (*utf8bytes > 6) {
+ newutf8 = utf8encode(ucs, *utf8);
+ *utf8bytes -= (newutf8 - *utf8);
+ *utf8 += (newutf8 - *utf8);
+ }
+
+ /* We have run out of utf16 bytes, read more if available */
+ if ((n == 0) && (utf16bytes > 0)) {
+ n = read(fd, utf16buf, MIN(sizeof(utf16buf), utf16bytes));
+ utf16 = utf16buf;
+ utf16bytes -= n;
+ }
+ }
+
+ *utf8[0] = 0;
+ --*utf8bytes;
+
+ if (utf16bytes > 0) {
+ /* Skip any remaining bytes */
+ lseek(fd, utf16bytes, SEEK_CUR);
+ }
+ return;
+}
+
+static int asf_parse_header(int fd, struct mp3entry* id3,
+ asf_waveformatex_t* wfx)
+{
+ asf_object_t current;
+ asf_object_t header;
+ uint64_t datalen;
+ int i;
+ int fileprop = 0;
+ uint64_t play_duration;
+ uint16_t flags;
+ uint32_t subobjects;
+ uint8_t utf8buf[512];
+ int id3buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ unsigned char* id3buf = (unsigned char*)id3->id3v2buf;
+
+ asf_read_object_header((asf_object_t *) &header, fd);
+
+ //DEBUGF("header.size=%d\n",(int)header.size);
+ if (header.size < 30) {
+ /* invalid size for header object */
+ return ASF_ERROR_OBJECT_SIZE;
+ }
+
+ read_uint32le(fd, &subobjects);
+
+ /* Two reserved bytes - do we need to read them? */
+ lseek(fd, 2, SEEK_CUR);
+
+ //DEBUGF("Read header - size=%d, subobjects=%d\n",(int)header.size, (int)subobjects);
+
+ if (subobjects > 0) {
+ header.datalen = header.size - 30;
+
+ /* TODO: Check that we have datalen bytes left in the file */
+ datalen = header.datalen;
+
+ for (i=0; i<(int)subobjects; i++) {
+ //DEBUGF("Parsing header object %d - datalen=%d\n",i,(int)datalen);
+ if (datalen < 24) {
+ //DEBUGF("not enough data for reading object\n");
+ break;
+ }
+
+ asf_read_object_header(&current, fd);
+
+ if (current.size > datalen || current.size < 24) {
+ //DEBUGF("invalid object size - current.size=%d, datalen=%d\n",(int)current.size,(int)datalen);
+ break;
+ }
+
+ if (asf_guid_match(&current.guid, &asf_guid_file_properties)) {
+ if (current.size < 104)
+ return ASF_ERROR_OBJECT_SIZE;
+
+ if (fileprop) {
+ /* multiple file properties objects not allowed */
+ return ASF_ERROR_INVALID_OBJECT;
+ }
+
+ fileprop = 1;
+
+ /* Get the number of logical packets - uint16_t at offset 31
+ * (Big endian byte order) */
+ lseek(fd, 31, SEEK_CUR);
+ read_uint16be(fd, &wfx->numpackets);
+
+ /* Now get the play duration - uint64_t at offset 40 */
+ lseek(fd, 7, SEEK_CUR);
+ read_uint64le(fd, &play_duration);
+ id3->length = play_duration / 10000;
+
+ //DEBUGF("****** length = %lums\n", id3->length);
+
+ /* Read the packet size - uint32_t at offset 68 */
+ lseek(fd, 20, SEEK_CUR);
+ read_uint32le(fd, &wfx->packet_size);
+
+ /* Skip bytes remaining in object */
+ lseek(fd, current.size - 24 - 72, SEEK_CUR);
+ } else if (asf_guid_match(&current.guid, &asf_guid_stream_properties)) {
+ guid_t guid;
+ uint32_t propdatalen;
+
+ if (current.size < 78)
+ return ASF_ERROR_OBJECT_SIZE;
+
+#if 0
+ asf_byteio_getGUID(&guid, current->data);
+ datalen = asf_byteio_getDWLE(current->data + 40);
+ flags = asf_byteio_getWLE(current->data + 48);
+#endif
+
+ asf_readGUID(fd, &guid);
+
+ lseek(fd, 24, SEEK_CUR);
+ read_uint32le(fd, &propdatalen);
+ lseek(fd, 4, SEEK_CUR);
+ read_uint16le(fd, &flags);
+
+ if (!asf_guid_match(&guid, &asf_guid_stream_type_audio)) {
+ //DEBUGF("Found stream properties for non audio stream, skipping\n");
+ lseek(fd,current.size - 24 - 50,SEEK_CUR);
+ } else if (wfx->audiostream == -1) {
+ lseek(fd, 4, SEEK_CUR);
+ //DEBUGF("Found stream properties for audio stream %d\n",flags&0x7f);
+
+ if (propdatalen < 18) {
+ return ASF_ERROR_INVALID_LENGTH;
+ }
+
+#if 0
+ if (asf_byteio_getWLE(data + 16) > datalen - 16) {
+ return ASF_ERROR_INVALID_LENGTH;
+ }
+#endif
+ read_uint16le(fd, &wfx->codec_id);
+ read_uint16le(fd, &wfx->channels);
+ read_uint32le(fd, &wfx->rate);
+ read_uint32le(fd, &wfx->bitrate);
+ wfx->bitrate *= 8;
+ read_uint16le(fd, &wfx->blockalign);
+ read_uint16le(fd, &wfx->bitspersample);
+ read_uint16le(fd, &wfx->datalen);
+
+ /* Round bitrate to the nearest kbit */
+ id3->bitrate = (wfx->bitrate + 500) / 1000;
+ id3->frequency = wfx->rate;
+
+ if (wfx->codec_id == ASF_CODEC_ID_WMAV1) {
+ read(fd, wfx->data, 4);
+ lseek(fd,current.size - 24 - 72 - 4,SEEK_CUR);
+ wfx->audiostream = flags&0x7f;
+ } else if (wfx->codec_id == ASF_CODEC_ID_WMAV2) {
+ read(fd, wfx->data, 6);
+ lseek(fd,current.size - 24 - 72 - 6,SEEK_CUR);
+ wfx->audiostream = flags&0x7f;
+ } else if (wfx->codec_id == ASF_CODEC_ID_WMAPRO) {
+ /* wma pro decoder needs the extra-data */
+ read(fd, wfx->data, wfx->datalen);
+ lseek(fd,current.size - 24 - 72 - wfx->datalen,SEEK_CUR);
+ wfx->audiostream = flags&0x7f;
+ /* Correct codectype to redirect playback to the proper .codec */
+ id3->codectype = AFMT_WMAPRO;
+ } else if (wfx->codec_id == ASF_CODEC_ID_WMAVOICE) {
+ read(fd, wfx->data, wfx->datalen);
+ lseek(fd,current.size - 24 - 72 - wfx->datalen,SEEK_CUR);
+ wfx->audiostream = flags&0x7f;
+ id3->codectype = AFMT_WMAVOICE;
+ } else {
+ DEBUGF("Unsupported WMA codec (Lossless, Voice, etc)\n");
+ lseek(fd,current.size - 24 - 72,SEEK_CUR);
+ }
+
+ }
+ } else if (asf_guid_match(&current.guid, &asf_guid_content_description)) {
+ /* Object contains five 16-bit string lengths, followed by the five strings:
+ title, artist, copyright, description, rating
+ */
+ uint16_t strlength[5];
+ int i;
+
+ //DEBUGF("Found GUID_CONTENT_DESCRIPTION - size=%d\n",(int)(current.size - 24));
+
+ /* Read the 5 string lengths - number of bytes included trailing zero */
+ for (i=0; i<5; i++) {
+ read_uint16le(fd, &strlength[i]);
+ //DEBUGF("strlength = %u\n",strlength[i]);
+ }
+
+ if (strlength[0] > 0) { /* 0 - Title */
+ id3->title = id3buf;
+ asf_utf16LEdecode(fd, strlength[0], &id3buf, &id3buf_remaining);
+ }
+
+ if (strlength[1] > 0) { /* 1 - Artist */
+ id3->artist = id3buf;
+ asf_utf16LEdecode(fd, strlength[1], &id3buf, &id3buf_remaining);
+ }
+
+ lseek(fd, strlength[2], SEEK_CUR); /* 2 - copyright */
+
+ if (strlength[3] > 0) { /* 3 - description */
+ id3->comment = id3buf;
+ asf_utf16LEdecode(fd, strlength[3], &id3buf, &id3buf_remaining);
+ }
+
+ lseek(fd, strlength[4], SEEK_CUR); /* 4 - rating */
+ } else if (asf_guid_match(&current.guid, &asf_guid_extended_content_description)) {
+ uint16_t count;
+ int i;
+ int bytesleft = current.size - 24;
+ //DEBUGF("Found GUID_EXTENDED_CONTENT_DESCRIPTION\n");
+
+ read_uint16le(fd, &count);
+ bytesleft -= 2;
+ //DEBUGF("extended metadata count = %u\n",count);
+
+ for (i=0; i < count; i++) {
+ uint16_t length, type;
+ unsigned char* utf8 = utf8buf;
+ int utf8length = 512;
+
+ read_uint16le(fd, &length);
+ asf_utf16LEdecode(fd, length, &utf8, &utf8length);
+ bytesleft -= 2 + length;
+
+ read_uint16le(fd, &type);
+ read_uint16le(fd, &length);
+
+ if (!strcmp("WM/TrackNumber",utf8buf)) {
+ if (type == 0) {
+ id3->track_string = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ id3->tracknum = atoi(id3->track_string);
+ } else if ((type >=2) && (type <= 5)) {
+ id3->tracknum = asf_intdecode(fd, type, length);
+ } else {
+ lseek(fd, length, SEEK_CUR);
+ }
+ } else if ((!strcmp("WM/Genre", utf8buf)) && (type == 0)) {
+ id3->genre_string = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ } else if ((!strcmp("WM/AlbumTitle", utf8buf)) && (type == 0)) {
+ id3->album = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ } else if ((!strcmp("WM/AlbumArtist", utf8buf)) && (type == 0)) {
+ id3->albumartist = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ } else if ((!strcmp("WM/Composer", utf8buf)) && (type == 0)) {
+ id3->composer = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ } else if (!strcmp("WM/Year", utf8buf)) {
+ if (type == 0) {
+ id3->year_string = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ id3->year = atoi(id3->year_string);
+ } else if ((type >=2) && (type <= 5)) {
+ id3->year = asf_intdecode(fd, type, length);
+ } else {
+ lseek(fd, length, SEEK_CUR);
+ }
+ } else if (!strncmp("replaygain_", utf8buf, 11)) {
+ char *value = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+ parse_replaygain(utf8buf, value, id3);
+ } else if (!strcmp("MusicBrainz/Track Id", utf8buf)) {
+ id3->mb_track_id = id3buf;
+ asf_utf16LEdecode(fd, length, &id3buf, &id3buf_remaining);
+#ifdef HAVE_ALBUMART
+ } else if (!strcmp("WM/Picture", utf8buf)) {
+ uint32_t datalength, strlength;
+ /* Expected is either "01 00 xx xx 03 yy yy yy yy" or
+ * "03 yy yy yy yy". xx is the size of the WM/Picture
+ * container in bytes. yy equals the raw data length of
+ * the embedded image. */
+ lseek(fd, -4, SEEK_CUR);
+ read(fd, &type, 1);
+ if (type == 1) {
+ lseek(fd, 3, SEEK_CUR);
+ read(fd, &type, 1);
+ /* In case the parsing will fail in the next step we
+ * might at least be able to skip the whole section. */
+ datalength = length - 1;
+ }
+ if (type == 3) {
+ /* Read the raw data length of the embedded image. */
+ read_uint32le(fd, &datalength);
+
+ /* Reset utf8 buffer */
+ utf8 = utf8buf;
+ utf8length = 512;
+
+ /* Gather the album art format, this string has a
+ * double zero-termination. */
+ asf_utf16LEdecode(fd, 32, &utf8, &utf8length);
+ strlength = (strlen(utf8buf) + 2) * 2;
+ lseek(fd, strlength-32, SEEK_CUR);
+ if (!strcmp("image/jpeg", utf8buf)) {
+ id3->albumart.type = AA_TYPE_JPG;
+ } else if (!strcmp("image/png", utf8buf)) {
+ id3->albumart.type = AA_TYPE_PNG;
+ } else {
+ id3->albumart.type = AA_TYPE_UNKNOWN;
+ }
+
+ /* Set the album art size and position. */
+ if (id3->albumart.type != AA_TYPE_UNKNOWN) {
+ id3->albumart.pos = lseek(fd, 0, SEEK_CUR);
+ id3->albumart.size = datalength;
+ id3->has_embedded_albumart = true;
+ }
+ }
+
+ lseek(fd, datalength, SEEK_CUR);
+#endif
+ } else {
+ lseek(fd, length, SEEK_CUR);
+ }
+ bytesleft -= 4 + length;
+ }
+
+ lseek(fd, bytesleft, SEEK_CUR);
+ } else if (asf_guid_match(&current.guid, &asf_guid_content_encryption)
+ || asf_guid_match(&current.guid, &asf_guid_extended_content_encryption)) {
+ //DEBUGF("File is encrypted\n");
+ return ASF_ERROR_ENCRYPTED;
+ } else {
+ //DEBUGF("Skipping %d bytes of object\n",(int)(current.size - 24));
+ lseek(fd,current.size - 24,SEEK_CUR);
+ }
+
+ //DEBUGF("Parsed object - size = %d\n",(int)current.size);
+ datalen -= current.size;
+ }
+
+ if (i != (int)subobjects || datalen != 0) {
+ //DEBUGF("header data doesn't match given subobject count\n");
+ return ASF_ERROR_INVALID_VALUE;
+ }
+
+ //DEBUGF("%d subobjects read successfully\n", i);
+ }
+
+#if 0
+ tmp = asf_parse_header_validate(file, &header);
+ if (tmp < 0) {
+ /* header read ok but doesn't validate correctly */
+ return tmp;
+ }
+#endif
+
+ //DEBUGF("header validated correctly\n");
+
+ return 0;
+}
+
+bool get_asf_metadata(int fd, struct mp3entry* id3)
+{
+ int res;
+ asf_object_t obj;
+ asf_waveformatex_t wfx;
+
+ wfx.audiostream = -1;
+
+ res = asf_parse_header(fd, id3, &wfx);
+
+ if (res < 0) {
+ DEBUGF("ASF: parsing error - %d\n",res);
+ return false;
+ }
+
+ if (wfx.audiostream == -1) {
+ DEBUGF("ASF: No WMA streams found\n");
+ return false;
+ }
+
+ asf_read_object_header(&obj, fd);
+
+ if (!asf_guid_match(&obj.guid, &asf_guid_data)) {
+ DEBUGF("ASF: No data object found\n");
+ return false;
+ }
+
+ /* Store the current file position - no need to parse the header
+ again in the codec. The +26 skips the rest of the data object
+ header.
+ */
+ id3->first_frame_offset = lseek(fd, 0, SEEK_CUR) + 26;
+ id3->filesize = filesize(fd);
+ /* We copy the wfx struct to the MP3 TOC field in the id3 struct so
+ the codec doesn't need to parse the header object again */
+ memcpy(id3->toc, &wfx, sizeof(wfx));
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/au.c b/lib/rbcodec/metadata/au.c
new file mode 100644
index 0000000000..94e7453644
--- /dev/null
+++ b/lib/rbcodec/metadata/au.c
@@ -0,0 +1,105 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 Yoshihisa Uchida
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+static const unsigned char bitspersamples[9] = {
+ 0, /* encoding */
+ 8, /* 1: G.711 MULAW */
+ 8, /* 2: Linear PCM 8bit */
+ 16, /* 3: Linear PCM 16bit */
+ 24, /* 4: Linear PCM 24bit */
+ 32, /* 5: Linear PCM 32bit */
+ 32, /* 6: IEEE float 32bit */
+ 64, /* 7: IEEE float 64bit */
+ /* encoding 8 - 26 unsupported. */
+ 8, /* 27: G.711 ALAW */
+};
+
+static inline unsigned char get_au_bitspersample(unsigned int encoding)
+{
+ if (encoding < 8)
+ return bitspersamples[encoding];
+ else if (encoding == 27)
+ return bitspersamples[8];
+
+ return 0;
+}
+
+bool get_au_metadata(int fd, struct mp3entry* id3)
+{
+ /* temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned long numbytes = 0;
+ int offset;
+
+ id3->vbr = false; /* All Sun audio files are CBR */
+ id3->filesize = filesize(fd);
+ id3->length = 0;
+
+ lseek(fd, 0, SEEK_SET);
+ if ((read(fd, buf, 24) < 24) || (memcmp(buf, ".snd", 4) != 0))
+ {
+ /*
+ * no header
+ *
+ * frequency: 8000 Hz
+ * bits per sample: 8 bit
+ * channel: mono
+ */
+ numbytes = id3->filesize;
+ id3->frequency = 8000;
+ id3->bitrate = 8;
+ }
+ else
+ {
+ /* parse header */
+
+ /* data offset */
+ offset = get_long_be(buf + 4);
+ if (offset < 24)
+ {
+ DEBUGF("CODEC_ERROR: sun audio offset size is small: %d\n", offset);
+ return false;
+ }
+ /* data size */
+ numbytes = get_long_be(buf + 8);
+ if (numbytes == (uint32_t)0xffffffff)
+ numbytes = id3->filesize - offset;
+
+ id3->frequency = get_long_be(buf + 16);
+ id3->bitrate = get_au_bitspersample(get_long_be(buf + 12)) * get_long_be(buf + 20)
+ * id3->frequency / 1000;
+ }
+
+ /* Calculate track length [ms] */
+ if (id3->bitrate)
+ id3->length = (numbytes << 3) / id3->bitrate;
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/ay.c b/lib/rbcodec/metadata/ay.c
new file mode 100644
index 0000000000..5d00264b3d
--- /dev/null
+++ b/lib/rbcodec/metadata/ay.c
@@ -0,0 +1,148 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+/* Taken from blargg's Game_Music_Emu library */
+
+typedef unsigned char byte;
+
+/* AY file header */
+enum { header_size = 0x14 };
+struct header_t
+{
+ byte tag[8];
+ byte vers;
+ byte player;
+ byte unused[2];
+ byte author[2];
+ byte comment[2];
+ byte max_track;
+ byte first_track;
+ byte track_info[2];
+};
+
+struct file_t {
+ struct header_t const* header;
+ byte const* tracks;
+ byte const* end; /* end of file data */
+};
+
+static int get_be16( const void *a )
+{
+ return get_short_be( (void*) a );
+}
+
+/* Given pointer to 2-byte offset of data, returns pointer to data, or NULL if
+ * offset is 0 or there is less than min_size bytes of data available. */
+static byte const* get_data( struct file_t const* file, byte const ptr [], int min_size )
+{
+ int offset = (int16_t) get_be16( ptr );
+ int pos = ptr - (byte const*) file->header;
+ int size = file->end - (byte const*) file->header;
+ int limit = size - min_size;
+ if ( limit < 0 || !offset || (unsigned) (pos + offset) > (unsigned) limit )
+ return NULL;
+ return ptr + offset;
+}
+
+static const char *parse_header( byte const in [], int size, struct file_t* out )
+{
+ if ( size < header_size )
+ return "wrong file type";
+
+ out->header = (struct header_t const*) in;
+ out->end = in + size;
+ struct header_t const* h = (struct header_t const*) in;
+ if ( memcmp( h->tag, "ZXAYEMUL", 8 ) )
+ return "wrong file type";
+
+ out->tracks = get_data( out, h->track_info, (h->max_track + 1) * 4 );
+ if ( !out->tracks )
+ return "missing track data";
+
+ return 0;
+}
+
+static void copy_ay_fields( struct file_t const* file, struct mp3entry* id3, int track )
+{
+ int track_count = file->header->max_track + 1;
+
+ /* calculate track length based on number of subtracks */
+ if (track_count > 1) {
+ id3->length = file->header->max_track * 1000;
+ } else {
+ byte const* track_info = get_data( file, file->tracks + track * 4 + 2, 6 );
+ if (track_info)
+ id3->length = get_be16( track_info + 4 ) * (1000 / 50); /* frames to msec */
+ else id3->length = 120 * 1000;
+ }
+
+ if ( id3->length <= 0 )
+ id3->length = 120 * 1000; /* 2 minutes */
+
+ /* If meta info was found in the m3u skip next step */
+ if (id3->title && id3->title[0]) return;
+
+ /* If file has more than one track will
+ use file name as title */
+ char * tmp;
+ if (track_count <= 1) {
+ tmp = (char *) get_data( file, file->tracks + track * 4, 1 );
+ if ( tmp ) id3->title = tmp;
+ }
+
+ /* Author */
+ tmp = (char *) get_data( file, file->header->author, 1 );
+ if (tmp) id3->artist = tmp;
+
+ /* Comment */
+ tmp = (char *) get_data( file, file->header->comment, 1 );
+ if (tmp) id3->comment = tmp;
+}
+
+static bool parse_ay_header(int fd, struct mp3entry *id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->id3v2buf;
+ struct file_t file;
+ int read_bytes;
+
+ lseek(fd, 0, SEEK_SET);
+ if ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) < header_size)
+ return false;
+
+ buf [ID3V2_BUF_SIZE] = '\0';
+ if ( parse_header( buf, read_bytes, &file ) )
+ return false;
+
+ copy_ay_fields( &file, id3, 0 );
+ return true;
+}
+
+bool get_ay_metadata(int fd, struct mp3entry* id3)
+{
+ char ay_type[8];
+ if ((lseek(fd, 0, SEEK_SET) < 0) ||
+ read(fd, ay_type, 8) < 8)
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* Make sure this is a ZX Ay file */
+ if (memcmp( ay_type, "ZXAYEMUL", 8 ) != 0)
+ return false;
+
+ return parse_ay_header(fd, id3);
+}
diff --git a/lib/rbcodec/metadata/flac.c b/lib/rbcodec/metadata/flac.c
new file mode 100644
index 0000000000..29937173fd
--- /dev/null
+++ b/lib/rbcodec/metadata/flac.c
@@ -0,0 +1,127 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+bool get_flac_metadata(int fd, struct mp3entry* id3)
+{
+ /* A simple parser to read vital metadata from a FLAC file - length,
+ * frequency, bitrate etc. This code should either be moved to a
+ * seperate file, or discarded in favour of the libFLAC code.
+ * The FLAC stream specification can be found at
+ * http://flac.sourceforge.net/format.html#stream
+ */
+
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ bool last_metadata = false;
+ bool rc = false;
+
+ if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4))
+ {
+ return rc;
+ }
+
+ if (memcmp(buf, "fLaC", 4) != 0)
+ {
+ return rc;
+ }
+
+ while (!last_metadata)
+ {
+ unsigned long i;
+ int type;
+
+ if (read(fd, buf, 4) < 0)
+ {
+ return rc;
+ }
+
+ last_metadata = buf[0] & 0x80;
+ type = buf[0] & 0x7f;
+ /* The length of the block */
+ i = (buf[1] << 16) | (buf[2] << 8) | buf[3];
+
+ if (type == 0) /* 0 is the STREAMINFO block */
+ {
+ unsigned long totalsamples;
+
+ if (i >= sizeof(id3->path) || read(fd, buf, i) < 0)
+ {
+ return rc;
+ }
+
+ id3->vbr = true; /* All FLAC files are VBR */
+ id3->filesize = filesize(fd);
+ id3->frequency = (buf[10] << 12) | (buf[11] << 4)
+ | ((buf[12] & 0xf0) >> 4);
+ rc = true; /* Got vital metadata */
+
+ /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */
+ totalsamples = get_long_be(&buf[14]);
+
+ if(totalsamples > 0)
+ {
+ /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+ id3->bitrate = (id3->filesize * 8) / id3->length;
+ }
+ else if (totalsamples == 0)
+ {
+ id3->length = 0;
+ id3->bitrate = 0;
+ }
+ else
+ {
+ logf("flac length invalid!");
+ return false;
+ }
+
+ }
+ else if (type == 4) /* 4 is the VORBIS_COMMENT block */
+ {
+ /* The next i bytes of the file contain the VORBIS COMMENTS. */
+ if (read_vorbis_tags(fd, id3, i) == 0)
+ {
+ return rc;
+ }
+ }
+ else if (!last_metadata)
+ {
+ /* Skip to next metadata block */
+ if (lseek(fd, i, SEEK_CUR) < 0)
+ {
+ return rc;
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/gbs.c b/lib/rbcodec/metadata/gbs.c
new file mode 100644
index 0000000000..68f2b2a393
--- /dev/null
+++ b/lib/rbcodec/metadata/gbs.c
@@ -0,0 +1,65 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+static bool parse_gbs_header(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, buf, 112) < 112)
+ return false;
+
+ /* Calculate track length with number of subtracks */
+ id3->length = buf[4] * 1000;
+
+ /* If meta info was found in the m3u skip next step */
+ if (id3->title && id3->title[0]) return true;
+
+ char *p = id3->id3v2buf;
+
+ /* Some metadata entries have 32 bytes length */
+ /* Game */
+ memcpy(p, &buf[16], 32); *(p + 33) = '\0';
+ id3->title = p;
+ p += strlen(p)+1;
+
+ /* Artist */
+ memcpy(p, &buf[48], 32); *(p + 33) = '\0';
+ id3->artist = p;
+ p += strlen(p)+1;
+
+ /* Copyright */
+ memcpy(p, &buf[80], 32); *(p + 33) = '\0';
+ id3->album = p;
+
+ return true;
+}
+
+bool get_gbs_metadata(int fd, struct mp3entry* id3)
+{
+ char gbs_type[3];
+ if ((lseek(fd, 0, SEEK_SET) < 0) ||
+ (read(fd, gbs_type, 3) < 3))
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ /* we only render 16 bits, 44.1KHz, Stereo */
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* Check for GBS magic */
+ if (memcmp( gbs_type, "GBS", 3 ) != 0)
+ return false;
+
+ return parse_gbs_header(fd, id3);
+}
diff --git a/lib/rbcodec/metadata/hes.c b/lib/rbcodec/metadata/hes.c
new file mode 100644
index 0000000000..6d99d523cb
--- /dev/null
+++ b/lib/rbcodec/metadata/hes.c
@@ -0,0 +1,39 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+#include "plugin.h"
+
+bool get_hes_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the id3v2 buffer part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->id3v2buf;
+ int read_bytes;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 4)) < 4))
+ return false;
+
+ /* Verify this is a HES file */
+ if (memcmp(buf,"HESM",4) != 0)
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ /* we only render 16 bits, 44.1KHz, Stereo */
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* Set default track count (length)*/
+ id3->length = 255 * 1000;
+
+ return true;
+}
+
diff --git a/lib/rbcodec/metadata/id3tags.c b/lib/rbcodec/metadata/id3tags.c
new file mode 100644
index 0000000000..2dd1c662ed
--- /dev/null
+++ b/lib/rbcodec/metadata/id3tags.c
@@ -0,0 +1,1199 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Daniel Stenberg
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+/*
+ * Parts of this code has been stolen from the Ample project and was written
+ * by David H�deman. It has since been extended and enhanced pretty much by
+ * all sorts of friendly Rockbox people.
+ *
+ */
+
+ /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <ctype.h>
+#include "string-extra.h"
+#include "config.h"
+#include "file.h"
+#include "logf.h"
+#include "system.h"
+#include "replaygain.h"
+#include "rbunicode.h"
+
+#include "metadata.h"
+#include "mp3data.h"
+#if CONFIG_CODEC == SWCODEC
+#include "metadata_common.h"
+#endif
+#include "metadata_parsers.h"
+#include "misc.h"
+
+static unsigned long unsync(unsigned long b0,
+ unsigned long b1,
+ unsigned long b2,
+ unsigned long b3)
+{
+ return (((long)(b0 & 0x7F) << (3*7)) |
+ ((long)(b1 & 0x7F) << (2*7)) |
+ ((long)(b2 & 0x7F) << (1*7)) |
+ ((long)(b3 & 0x7F) << (0*7)));
+}
+
+static const char* const genres[] = {
+ "Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk", "Grunge",
+ "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B",
+ "Rap", "Reggae", "Rock", "Techno", "Industrial", "Alternative", "Ska",
+ "Death Metal", "Pranks", "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
+ "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical", "Instrumental",
+ "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock",
+ "Bass", "Soul", "Punk", "Space", "Meditative", "Instrumental Pop",
+ "Instrumental Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Industrial",
+ "Electronic", "Pop-Folk", "Eurodance", "Dream", "Southern Rock", "Comedy",
+ "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle",
+ "Native American", "Cabaret", "New Wave", "Psychadelic", "Rave",
+ "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk", "Acid Jazz",
+ "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock",
+
+ /* winamp extensions */
+ "Folk", "Folk-Rock", "National Folk", "Swing", "Fast Fusion", "Bebob",
+ "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
+ "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
+ "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech",
+ "Chanson", "Opera", "Chamber Music", "Sonata", "Symphony", "Booty Bass",
+ "Primus", "Porn Groove", "Satire", "Slow Jam", "Club", "Tango", "Samba",
+ "Folklore", "Ballad", "Power Ballad", "Rhythmic Soul", "Freestyle",
+ "Duet", "Punk Rock", "Drum Solo", "A capella", "Euro-House", "Dance Hall",
+ "Goa", "Drum & Bass", "Club-House", "Hardcore", "Terror", "Indie",
+ "BritPop", "Negerpunk", "Polsk Punk", "Beat", "Christian Gangsta Rap",
+ "Heavy Metal", "Black Metal", "Crossover", "Contemporary Christian",
+ "Christian Rock", "Merengue", "Salsa", "Thrash Metal", "Anime", "Jpop",
+ "Synthpop"
+};
+
+#if CONFIG_CODEC != SWCODEC
+static
+#endif
+char* id3_get_num_genre(unsigned int genre_num)
+{
+ if (genre_num < ARRAYLEN(genres))
+ return (char*)genres[genre_num];
+ return NULL;
+}
+
+/*
+ HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS
+ Code and comments by Thomas Paul Diffenbach
+
+ To add another ID3v2 Tag, do the following:
+ 1. add a char* named for the tag to struct mp3entry in id3.h,
+ (I (tpd) prefer to use char* rather than ints, even for what seems like
+ numerical values, for cases where a number won't do, e.g.,
+ YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969"
+ TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name"
+ Text is more flexible, and as the main use of id3 data is to
+ display it, converting it to an int just means reconverting to
+ display it, at a runtime cost.)
+
+ 2. If any special processing beyond copying the tag value from the Id3
+ block to the struct mp3entry is rrequired (such as converting to an
+ int), write a function to perform this special processing.
+
+ This function's prototype must match that of
+ typedef tagPostProcessFunc, that is it must be:
+ int func( struct mp3entry*, char* tag, int bufferpos )
+ the first argument is a pointer to the current mp3entry structure the
+ second argument is a pointer to the null terminated string value of the
+ tag found the third argument is the offset of the next free byte in the
+ mp3entry's buffer your function should return the corrected offset; if
+ you don't lengthen or shorten the tag string, you can return the third
+ argument unchanged.
+
+ Unless you have a good reason no to, make the function static.
+ TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED.
+
+ 3. add one or more entries to the tagList array, using the format:
+ char* ID3 Tag symbolic name -- see the ID3 specification for these,
+ sizeof() that name minus 1,
+ offsetof( struct mp3entry, variable_name_in_struct_mp3entry ),
+ pointer to your special processing function or NULL
+ if you need no special processing
+ flag indicating if this tag is binary or textual
+ Many ID3 symbolic names come in more than one form. You can add both
+ forms, each referencing the same variable in struct mp3entry.
+ If both forms are present, the last found will be used.
+ Note that the offset can be zero, in which case no entry will be set
+ in the mp3entry struct; the frame is still read into the buffer and
+ the special processing function is called (several times, if there
+ are several frames with the same name).
+
+ 4. Alternately, use the TAG_LIST_ENTRY macro with
+ ID3 tag symbolic name,
+ variable in struct mp3entry,
+ special processing function address
+
+ 5. Add code to wps-display.c function get_tag to assign a printf-like
+ format specifier for the tag */
+
+/* Structure for ID3 Tag extraction information */
+struct tag_resolver {
+ const char* tag;
+ int tag_length;
+ size_t offset;
+ int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos);
+ bool binary;
+};
+
+static bool global_ff_found;
+
+static int unsynchronize(char* tag, int len, bool *ff_found)
+{
+ int i;
+ unsigned char c;
+ unsigned char *rp, *wp;
+
+ wp = rp = (unsigned char *)tag;
+
+ rp = (unsigned char *)tag;
+ for(i = 0;i < len;i++) {
+ /* Read the next byte and write it back, but don't increment the
+ write pointer */
+ c = *rp++;
+ *wp = c;
+ if(*ff_found) {
+ /* Increment the write pointer if it isn't an unsynch pattern */
+ if(c != 0)
+ wp++;
+ *ff_found = false;
+ } else {
+ if(c == 0xff)
+ *ff_found = true;
+ wp++;
+ }
+ }
+ return (long)wp - (long)tag;
+}
+
+static int unsynchronize_frame(char* tag, int len)
+{
+ bool ff_found = false;
+
+ return unsynchronize(tag, len, &ff_found);
+}
+
+static int read_unsynched(int fd, void *buf, int len)
+{
+ int i;
+ int rc;
+ int remaining = len;
+ char *wp;
+ char *rp;
+
+ wp = buf;
+
+ while(remaining) {
+ rp = wp;
+ rc = read(fd, rp, remaining);
+ if(rc <= 0)
+ return rc;
+
+ i = unsynchronize(wp, remaining, &global_ff_found);
+ remaining -= i;
+ wp += i;
+ }
+
+ return len;
+}
+
+static int skip_unsynched(int fd, int len)
+{
+ int rc;
+ int remaining = len;
+ int rlen;
+ char buf[32];
+
+ while(remaining) {
+ rlen = MIN(sizeof(buf), (unsigned int)remaining);
+ rc = read(fd, buf, rlen);
+ if(rc <= 0)
+ return rc;
+
+ remaining -= unsynchronize(buf, rlen, &global_ff_found);
+ }
+
+ return len;
+}
+
+/* parse numeric value from string */
+static int parsetracknum( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ entry->tracknum = atoi( tag );
+ return bufferpos;
+}
+
+/* parse numeric value from string */
+static int parsediscnum( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ entry->discnum = atoi( tag );
+ return bufferpos;
+}
+
+/* parse numeric value from string */
+static int parseyearnum( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ entry->year = atoi( tag );
+ return bufferpos;
+}
+
+/* parse numeric genre from string, version 2.2 and 2.3 */
+static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ /* Use bufferpos to hold current position in entry->id3v2buf. */
+ bufferpos = tag - entry->id3v2buf;
+
+ if(entry->id3version >= ID3_VER_2_4) {
+ /* In version 2.4 and up, there are no parentheses, and the genre frame
+ is a list of strings, either numbers or text. */
+
+ /* Is it a number? */
+ if(isdigit(tag[0])) {
+ entry->genre_string = id3_get_num_genre(atoi( tag ));
+ return bufferpos;
+ } else {
+ entry->genre_string = tag;
+ return bufferpos + strlen(tag) + 1;
+ }
+ } else {
+ if( tag[0] == '(' && tag[1] != '(' ) {
+ entry->genre_string = id3_get_num_genre(atoi( tag + 1 ));
+ return bufferpos;
+ }
+ else {
+ entry->genre_string = tag;
+ return bufferpos + strlen(tag) + 1;
+ }
+ }
+}
+
+#ifdef HAVE_ALBUMART
+/* parse embed albumart */
+static int parsealbumart( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ entry->has_embedded_albumart = false;
+
+ /* we currently don't support unsynchronizing albumart */
+ if (entry->albumart.type == AA_TYPE_UNSYNC)
+ return bufferpos;
+
+ entry->albumart.type = AA_TYPE_UNKNOWN;
+
+ char *start = tag;
+ /* skip text encoding */
+ tag += 1;
+
+ if (memcmp(tag, "image/", 6) == 0)
+ {
+ /* ID3 v2.3+ */
+ tag += 6;
+ if (strcmp(tag, "jpeg") == 0)
+ {
+ entry->albumart.type = AA_TYPE_JPG;
+ tag += 5;
+ }
+ else if (strcmp(tag, "png") == 0)
+ {
+ entry->albumart.type = AA_TYPE_PNG;
+ tag += 4;
+ }
+ }
+ else
+ {
+ /* ID3 v2.2 */
+ if (memcmp(tag, "JPG", 3) == 0)
+ entry->albumart.type = AA_TYPE_JPG;
+ else if (memcmp(tag, "PNG", 3) == 0)
+ entry->albumart.type = AA_TYPE_PNG;
+ tag += 3;
+ }
+
+ if (entry->albumart.type != AA_TYPE_UNKNOWN)
+ {
+ /* skip picture type */
+ tag += 1;
+ /* skip description */
+ tag = strchr(tag, '\0') + 1;
+ /* fixup offset&size for image data */
+ entry->albumart.pos += tag - start;
+ entry->albumart.size -= tag - start;
+ entry->has_embedded_albumart = true;
+ }
+ /* return bufferpos as we didn't store anything in id3v2buf */
+ return bufferpos;
+}
+#endif
+
+/* parse user defined text, looking for album artist and replaygain
+ * information.
+ */
+static int parseuser( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ char* value = NULL;
+ int desc_len = strlen(tag);
+ int length = 0;
+
+ if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos) {
+ /* At least part of the value was read, so we can safely try to
+ * parse it */
+ value = tag + desc_len + 1;
+
+ if (!strcasecmp(tag, "ALBUM ARTIST")) {
+ length = strlen(value) + 1;
+ strlcpy(tag, value, length);
+ entry->albumartist = tag;
+#if CONFIG_CODEC == SWCODEC
+ } else {
+ /* Call parse_replaygain(). */
+ parse_replaygain(tag, value, entry);
+#endif
+ }
+ }
+
+ return tag - entry->id3v2buf + length;
+}
+
+#if CONFIG_CODEC == SWCODEC
+/* parse RVA2 binary data and convert to replaygain information. */
+static int parserva2( struct mp3entry* entry, char* tag, int bufferpos)
+{
+ int desc_len = strlen(tag);
+ int start_pos = tag - entry->id3v2buf;
+ int end_pos = start_pos + desc_len + 5;
+ unsigned char* value = tag + desc_len + 1;
+
+ /* Only parse RVA2 replaygain tags if tag version == 2.4 and channel
+ * type is master volume.
+ */
+ if (entry->id3version == ID3_VER_2_4 && end_pos < bufferpos
+ && *value++ == 1) {
+ long gain = 0;
+ long peak = 0;
+ long peakbits;
+ long peakbytes;
+ bool album = false;
+
+ /* The RVA2 specification is unclear on some things (id string and
+ * peak volume), but this matches how Quod Libet use them.
+ */
+
+ gain = (int16_t) ((value[0] << 8) | value[1]);
+ value += 2;
+ peakbits = *value++;
+ peakbytes = (peakbits + 7) / 8;
+
+ /* Only use the topmost 24 bits for peak volume */
+ if (peakbytes > 3) {
+ peakbytes = 3;
+ }
+
+ /* Make sure the peak bits were read */
+ if (end_pos + peakbytes < bufferpos) {
+ long shift = ((8 - (peakbits & 7)) & 7) + (3 - peakbytes) * 8;
+
+ for ( ; peakbytes; peakbytes--) {
+ peak <<= 8;
+ peak += *value++;
+ }
+
+ peak <<= shift;
+
+ if (peakbits > 24) {
+ peak += *value >> (8 - shift);
+ }
+ }
+
+ if (strcasecmp(tag, "album") == 0) {
+ album = true;
+ } else if (strcasecmp(tag, "track") != 0) {
+ /* Only accept non-track values if we don't have any previous
+ * value.
+ */
+ if (entry->track_gain != 0) {
+ return start_pos;
+ }
+ }
+
+ parse_replaygain_int(album, gain, peak * 2, entry);
+ }
+
+ return start_pos;
+}
+#endif
+
+static int parsembtid( struct mp3entry* entry, char* tag, int bufferpos )
+{
+ char* value = NULL;
+ int desc_len = strlen(tag);
+ /*DEBUGF("MBID len: %d\n", desc_len);*/
+ /* Musicbrainz track IDs are always 36 chars long */
+ const size_t mbtid_len = 36;
+
+ if ((tag - entry->id3v2buf + desc_len + 2) < bufferpos)
+ {
+ value = tag + desc_len + 1;
+
+ if (strcasecmp(tag, "http://musicbrainz.org") == 0)
+ {
+ if (mbtid_len == strlen(value))
+ {
+ entry->mb_track_id = value;
+ return bufferpos + mbtid_len + 1;
+ }
+ }
+ }
+
+ return bufferpos;
+}
+
+static const struct tag_resolver taglist[] = {
+ { "TPE1", 4, offsetof(struct mp3entry, artist), NULL, false },
+ { "TP1", 3, offsetof(struct mp3entry, artist), NULL, false },
+ { "TIT2", 4, offsetof(struct mp3entry, title), NULL, false },
+ { "TT2", 3, offsetof(struct mp3entry, title), NULL, false },
+ { "TALB", 4, offsetof(struct mp3entry, album), NULL, false },
+ { "TAL", 3, offsetof(struct mp3entry, album), NULL, false },
+ { "TRK", 3, offsetof(struct mp3entry, track_string), &parsetracknum, false },
+ { "TPOS", 4, offsetof(struct mp3entry, disc_string), &parsediscnum, false },
+ { "TPA", 3, offsetof(struct mp3entry, disc_string), &parsediscnum, false },
+ { "TRCK", 4, offsetof(struct mp3entry, track_string), &parsetracknum, false },
+ { "TDRC", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false },
+ { "TYER", 4, offsetof(struct mp3entry, year_string), &parseyearnum, false },
+ { "TYE", 3, offsetof(struct mp3entry, year_string), &parseyearnum, false },
+ { "TCOM", 4, offsetof(struct mp3entry, composer), NULL, false },
+ { "TCM", 3, offsetof(struct mp3entry, composer), NULL, false },
+ { "TPE2", 4, offsetof(struct mp3entry, albumartist), NULL, false },
+ { "TP2", 3, offsetof(struct mp3entry, albumartist), NULL, false },
+ { "TIT1", 4, offsetof(struct mp3entry, grouping), NULL, false },
+ { "TT1", 3, offsetof(struct mp3entry, grouping), NULL, false },
+ { "COMM", 4, offsetof(struct mp3entry, comment), NULL, false },
+ { "COM", 3, offsetof(struct mp3entry, comment), NULL, false },
+ { "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre, false },
+ { "TCO", 3, offsetof(struct mp3entry, genre_string), &parsegenre, false },
+#ifdef HAVE_ALBUMART
+ { "APIC", 4, 0, &parsealbumart, true },
+ { "PIC", 3, 0, &parsealbumart, true },
+#endif
+ { "TXXX", 4, 0, &parseuser, false },
+#if CONFIG_CODEC == SWCODEC
+ { "RVA2", 4, 0, &parserva2, true },
+#endif
+ { "UFID", 4, 0, &parsembtid, false },
+};
+
+#define TAGLIST_SIZE ((int)ARRAYLEN(taglist))
+
+/* Get the length of an ID3 string in the given encoding. Returns the length
+ * in bytes, including end nil, or -1 if the encoding is unknown.
+ */
+static int unicode_len(char encoding, const void* string)
+{
+ int len = 0;
+
+ if (encoding == 0x01 || encoding == 0x02) {
+ char first;
+ const char *s = string;
+ /* string might be unaligned, so using short* can crash on ARM and SH1 */
+ do {
+ first = *s++;
+ } while ((first | *s++) != 0);
+
+ len = s - (const char*) string;
+ } else {
+ len = strlen((char*) string) + 1;
+ }
+
+ return len;
+}
+
+/* Checks to see if the passed in string is a 16-bit wide Unicode v2
+ string. If it is, we convert it to a UTF-8 string. If it's not unicode,
+ we convert from the default codepage */
+static int unicode_munge(char* string, char* utf8buf, int *len) {
+ long tmp;
+ bool le = false;
+ int i = 0;
+ unsigned char *str = (unsigned char *)string;
+ int templen = 0;
+ unsigned char* utf8 = (unsigned char *)utf8buf;
+
+ switch (str[0]) {
+ case 0x00: /* Type 0x00 is ordinary ISO 8859-1 */
+ str++;
+ (*len)--;
+ utf8 = iso_decode(str, utf8, -1, *len);
+ *utf8 = 0;
+ *len = (unsigned long)utf8 - (unsigned long)utf8buf;
+ break;
+
+ case 0x01: /* Unicode with or without BOM */
+ case 0x02:
+ (*len)--;
+ str++;
+
+ /* Handle frames with more than one string
+ (needed for TXXX frames).*/
+ do {
+ tmp = bytes2int(0, 0, str[0], str[1]);
+
+ /* Now check if there is a BOM
+ (zero-width non-breaking space, 0xfeff)
+ and if it is in little or big endian format */
+ if(tmp == 0xfffe) { /* Little endian? */
+ le = true;
+ str += 2;
+ (*len)-=2;
+ } else if(tmp == 0xfeff) { /* Big endian? */
+ str += 2;
+ (*len)-=2;
+ } else
+ /* If there is no BOM (which is a specification violation),
+ let's try to guess it. If one of the bytes is 0x00, it is
+ probably the most significant one. */
+ if(str[1] == 0)
+ le = true;
+
+ while ((i < *len) && (str[0] || str[1])) {
+ if(le)
+ utf8 = utf16LEdecode(str, utf8, 1);
+ else
+ utf8 = utf16BEdecode(str, utf8, 1);
+
+ str+=2;
+ i += 2;
+ }
+
+ *utf8++ = 0; /* Terminate the string */
+ templen += (strlen(&utf8buf[templen]) + 1);
+ str += 2;
+ i+=2;
+ } while(i < *len);
+ *len = templen - 1;
+ break;
+
+ case 0x03: /* UTF-8 encoded string */
+ for(i=0; i < *len; i++)
+ utf8[i] = str[i+1];
+ (*len)--;
+ break;
+
+ default: /* Plain old string */
+ utf8 = iso_decode(str, utf8, -1, *len);
+ *utf8 = 0;
+ *len = (unsigned long)utf8 - (unsigned long)utf8buf;
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Sets the title of an MP3 entry based on its ID3v1 tag.
+ *
+ * Arguments: file - the MP3 file to scen for a ID3v1 tag
+ * entry - the entry to set the title in
+ *
+ * Returns: true if a title was found and created, else false
+ */
+bool setid3v1title(int fd, struct mp3entry *entry)
+{
+ unsigned char buffer[128];
+ static const char offsets[] = {3, 33, 63, 97, 93, 125, 127};
+ int i, j;
+ unsigned char* utf8;
+
+ if (-1 == lseek(fd, -128, SEEK_END))
+ return false;
+
+ if (read(fd, buffer, sizeof buffer) != sizeof buffer)
+ return false;
+
+ if (strncmp((char *)buffer, "TAG", 3))
+ return false;
+
+ entry->id3v1len = 128;
+ entry->id3version = ID3_VER_1_0;
+
+ for (i=0; i < (int)sizeof offsets; i++) {
+ unsigned char* ptr = (unsigned char *)buffer + offsets[i];
+
+ switch(i) {
+ case 0:
+ case 1:
+ case 2:
+ /* kill trailing space in strings */
+ for (j=29; j && (ptr[j]==0 || ptr[j]==' '); j--)
+ ptr[j] = 0;
+ /* convert string to utf8 */
+ utf8 = (unsigned char *)entry->id3v1buf[i];
+ utf8 = iso_decode(ptr, utf8, -1, 30);
+ /* make sure string is terminated */
+ *utf8 = 0;
+ break;
+
+ case 3:
+ /* kill trailing space in strings */
+ for (j=27; j && (ptr[j]==0 || ptr[j]==' '); j--)
+ ptr[j] = 0;
+ /* convert string to utf8 */
+ utf8 = (unsigned char *)entry->id3v1buf[3];
+ utf8 = iso_decode(ptr, utf8, -1, 28);
+ /* make sure string is terminated */
+ *utf8 = 0;
+ break;
+
+ case 4:
+ ptr[4] = 0;
+ entry->year = atoi((char *)ptr);
+ break;
+
+ case 5:
+ /* id3v1.1 uses last two bytes of comment field for track
+ number: first must be 0 and second is track num */
+ if (!ptr[0] && ptr[1]) {
+ entry->tracknum = ptr[1];
+ entry->id3version = ID3_VER_1_1;
+ }
+ break;
+
+ case 6:
+ /* genre */
+ entry->genre_string = id3_get_num_genre(ptr[0]);
+ break;
+ }
+ }
+
+ entry->title = entry->id3v1buf[0];
+ entry->artist = entry->id3v1buf[1];
+ entry->album = entry->id3v1buf[2];
+ entry->comment = entry->id3v1buf[3];
+
+ return true;
+}
+
+
+/*
+ * Sets the title of an MP3 entry based on its ID3v2 tag.
+ *
+ * Arguments: file - the MP3 file to scan for a ID3v2 tag
+ * entry - the entry to set the title in
+ *
+ * Returns: true if a title was found and created, else false
+ */
+void setid3v2title(int fd, struct mp3entry *entry)
+{
+ int minframesize;
+ int size;
+ long bufferpos = 0, totframelen, framelen;
+ char header[10];
+ char tmp[4];
+ unsigned char version;
+ char *buffer = entry->id3v2buf;
+ int bytesread = 0;
+ int buffersize = sizeof(entry->id3v2buf);
+ unsigned char global_flags;
+ int flags;
+ bool global_unsynch = false;
+ bool unsynch = false;
+ int i, j;
+ int rc;
+#if CONFIG_CODEC == SWCODEC
+ bool itunes_gapless = false;
+#endif
+
+ global_ff_found = false;
+
+ /* Bail out if the tag is shorter than 10 bytes */
+ if(entry->id3v2len < 10)
+ return;
+
+ /* Read the ID3 tag version from the header */
+ lseek(fd, 0, SEEK_SET);
+ if(10 != read(fd, header, 10))
+ return;
+
+ /* Get the total ID3 tag size */
+ size = entry->id3v2len - 10;
+
+ version = header[3];
+ switch ( version ) {
+ case 2:
+ version = ID3_VER_2_2;
+ minframesize = 8;
+ break;
+
+ case 3:
+ version = ID3_VER_2_3;
+ minframesize = 12;
+ break;
+
+ case 4:
+ version = ID3_VER_2_4;
+ minframesize = 12;
+ break;
+
+ default:
+ /* unsupported id3 version */
+ return;
+ }
+ entry->id3version = version;
+ entry->tracknum = entry->year = entry->discnum = 0;
+ entry->title = entry->artist = entry->album = NULL; /* FIXME incomplete */
+
+ global_flags = header[5];
+
+ /* Skip the extended header if it is present */
+ if(global_flags & 0x40) {
+ if(version == ID3_VER_2_3) {
+ if(10 != read(fd, header, 10))
+ return;
+ /* The 2.3 extended header size doesn't include the header size
+ field itself. Also, it is not unsynched. */
+ framelen =
+ bytes2int(header[0], header[1], header[2], header[3]) + 4;
+
+ /* Skip the rest of the header */
+ lseek(fd, framelen - 10, SEEK_CUR);
+ }
+
+ if(version >= ID3_VER_2_4) {
+ if(4 != read(fd, header, 4))
+ return;
+
+ /* The 2.4 extended header size does include the entire header,
+ so here we can just skip it. This header is unsynched. */
+ framelen = unsync(header[0], header[1],
+ header[2], header[3]);
+
+ lseek(fd, framelen - 4, SEEK_CUR);
+ }
+ }
+
+ /* Is unsynchronization applied? */
+ if(global_flags & 0x80) {
+ global_unsynch = true;
+ }
+
+ /*
+ * We must have at least minframesize bytes left for the
+ * remaining frames to be interesting
+ */
+ while (size >= minframesize && bufferpos < buffersize - 1) {
+ flags = 0;
+
+ /* Read frame header and check length */
+ if(version >= ID3_VER_2_3) {
+ if(global_unsynch && version <= ID3_VER_2_3)
+ rc = read_unsynched(fd, header, 10);
+ else
+ rc = read(fd, header, 10);
+ if(rc != 10)
+ return;
+ /* Adjust for the 10 bytes we read */
+ size -= 10;
+
+ flags = bytes2int(0, 0, header[8], header[9]);
+
+ if (version >= ID3_VER_2_4) {
+ framelen = unsync(header[4], header[5],
+ header[6], header[7]);
+ } else {
+ /* version .3 files don't use synchsafe ints for
+ * size */
+ framelen = bytes2int(header[4], header[5],
+ header[6], header[7]);
+ }
+ } else {
+ if(6 != read(fd, header, 6))
+ return;
+ /* Adjust for the 6 bytes we read */
+ size -= 6;
+
+ framelen = bytes2int(0, header[3], header[4], header[5]);
+ }
+
+ logf("framelen = %ld, flags = 0x%04x", framelen, flags);
+ if(framelen == 0){
+ if (header[0] == 0 && header[1] == 0 && header[2] == 0)
+ return;
+ else
+ continue;
+ }
+
+ unsynch = false;
+
+ if(flags)
+ {
+ if (version >= ID3_VER_2_4) {
+ if(flags & 0x0040) { /* Grouping identity */
+ lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
+ framelen--;
+ }
+ } else {
+ if(flags & 0x0020) { /* Grouping identity */
+ lseek(fd, 1, SEEK_CUR); /* Skip 1 byte */
+ framelen--;
+ }
+ }
+
+ if(flags & 0x000c) /* Compression or encryption */
+ {
+ /* Skip it */
+ size -= framelen;
+ lseek(fd, framelen, SEEK_CUR);
+ continue;
+ }
+
+ if(flags & 0x0002) /* Unsynchronization */
+ unsynch = true;
+
+ if (version >= ID3_VER_2_4) {
+ if(flags & 0x0001) { /* Data length indicator */
+ if(4 != read(fd, tmp, 4))
+ return;
+
+ /* We don't need the data length */
+ framelen -= 4;
+ }
+ }
+ }
+
+ if (framelen == 0)
+ continue;
+
+ if (framelen < 0)
+ return;
+
+ /* Keep track of the remaining frame size */
+ totframelen = framelen;
+
+ /* If the frame is larger than the remaining buffer space we try
+ to read as much as would fit in the buffer */
+ if(framelen >= buffersize - bufferpos)
+ framelen = buffersize - bufferpos - 1;
+
+ /* Limit the maximum length of an id3 data item to ID3V2_MAX_ITEM_SIZE
+ bytes. This reduces the chance that the available buffer is filled
+ by single metadata items like large comments. */
+ if (ID3V2_MAX_ITEM_SIZE < framelen)
+ framelen = ID3V2_MAX_ITEM_SIZE;
+
+ logf("id3v2 frame: %.4s", header);
+
+ /* Check for certain frame headers
+
+ 'size' is the amount of frame bytes remaining. We decrement it by
+ the amount of bytes we read. If we fail to read as many bytes as
+ we expect, we assume that we can't read from this file, and bail
+ out.
+
+ For each frame. we will iterate over the list of supported tags,
+ and read the tag into entry's buffer. All tags will be kept as
+ strings, for cases where a number won't do, e.g., YEAR: "circa
+ 1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK:
+ "1/12", "1 of 12", GENRE: "Freeform genre name" Text is more
+ flexible, and as the main use of id3 data is to display it,
+ converting it to an int just means reconverting to display it, at a
+ runtime cost.
+
+ For tags that the current code does convert to ints, a post
+ processing function will be called via a pointer to function. */
+
+ for (i=0; i<TAGLIST_SIZE; i++) {
+ const struct tag_resolver* tr = &taglist[i];
+ char** ptag = tr->offset ? (char**) (((char*)entry) + tr->offset)
+ : NULL;
+ char* tag;
+
+ /* Only ID3_VER_2_2 uses frames with three-character names. */
+ if (((version == ID3_VER_2_2) && (tr->tag_length != 3))
+ || ((version > ID3_VER_2_2) && (tr->tag_length != 4))) {
+ continue;
+ }
+
+ if( !memcmp( header, tr->tag, tr->tag_length ) ) {
+
+ /* found a tag matching one in tagList, and not yet filled */
+ tag = buffer + bufferpos;
+
+ if(global_unsynch && version <= ID3_VER_2_3)
+ bytesread = read_unsynched(fd, tag, framelen);
+ else
+ bytesread = read(fd, tag, framelen);
+
+ if( bytesread != framelen )
+ return;
+
+ size -= bytesread;
+
+ if(unsynch || (global_unsynch && version >= ID3_VER_2_4))
+ bytesread = unsynchronize_frame(tag, bytesread);
+
+ /* the COMM frame has a 3 char field to hold an ISO-639-1
+ * language string and an optional short description;
+ * remove them so unicode_munge can work correctly
+ */
+
+ if((tr->tag_length == 4 && !memcmp( header, "COMM", 4)) ||
+ (tr->tag_length == 3 && !memcmp( header, "COM", 3))) {
+ int offset;
+ if(bytesread >= 8 && !strncmp(tag+4, "iTun", 4)) {
+#if CONFIG_CODEC == SWCODEC
+ /* check for iTunes gapless information */
+ if(bytesread >= 12 && !strncmp(tag+4, "iTunSMPB", 8))
+ itunes_gapless = true;
+ else
+#endif
+ /* ignore other with iTunes tags */
+ break;
+ }
+
+ offset = 3 + unicode_len(*tag, tag + 4);
+ if(bytesread > offset) {
+ bytesread -= offset;
+ memmove(tag + 1, tag + 1 + offset, bytesread - 1);
+ }
+ }
+
+ /* Attempt to parse Unicode string only if the tag contents
+ aren't binary */
+ if(!tr->binary) {
+ /* UTF-8 could potentially be 3 times larger */
+ /* so we need to create a new buffer */
+ char utf8buf[(3 * bytesread) + 1];
+
+ unicode_munge( tag, utf8buf, &bytesread );
+
+ if(bytesread >= buffersize - bufferpos)
+ bytesread = buffersize - bufferpos - 1;
+
+ if ( /* Is it an embedded cuesheet? */
+ (tr->tag_length == 4 && !memcmp(header, "TXXX", 4)) &&
+ (bytesread >= 14 && !strncmp(utf8buf, "CUESHEET", 8))
+ ) {
+ unsigned char char_enc = 0;
+ /* [enc type]+"CUESHEET\0" = 10 */
+ unsigned char cuesheet_offset = 10;
+ switch (tag[0]) {
+ case 0x00:
+ char_enc = CHAR_ENC_ISO_8859_1;
+ break;
+ case 0x01:
+ tag++;
+ if (!memcmp(tag,
+ BOM_UTF_16_BE, BOM_UTF_16_SIZE)) {
+ char_enc = CHAR_ENC_UTF_16_BE;
+ } else if (!memcmp(tag,
+ BOM_UTF_16_LE, BOM_UTF_16_SIZE)) {
+ char_enc = CHAR_ENC_UTF_16_LE;
+ }
+ /* \1 + BOM(2) + C0U0E0S0H0E0E0T000 = 21 */
+ cuesheet_offset = 21;
+ break;
+ case 0x02:
+ char_enc = CHAR_ENC_UTF_16_BE;
+ /* \2 + 0C0U0E0S0H0E0E0T00 = 19 */
+ cuesheet_offset = 19;
+ break;
+ case 0x03:
+ char_enc = CHAR_ENC_UTF_8;
+ break;
+ }
+ if (char_enc > 0) {
+ entry->has_embedded_cuesheet = true;
+ entry->embedded_cuesheet.pos = lseek(fd, 0, SEEK_CUR)
+ - framelen + cuesheet_offset;
+ entry->embedded_cuesheet.size = totframelen
+ - cuesheet_offset;
+ entry->embedded_cuesheet.encoding = char_enc;
+ }
+ break;
+ }
+
+ for (j = 0; j < bytesread; j++)
+ tag[j] = utf8buf[j];
+
+ /* remove trailing spaces */
+ while ( bytesread > 0 && isspace(tag[bytesread-1]))
+ bytesread--;
+ }
+
+ if(bytesread == 0)
+ /* Skip empty frames */
+ break;
+
+ tag[bytesread] = 0;
+ bufferpos += bytesread + 1;
+
+#if CONFIG_CODEC == SWCODEC
+ /* parse the tag if it contains iTunes gapless info */
+ if (itunes_gapless)
+ {
+ itunes_gapless = false;
+ entry->lead_trim = get_itunes_int32(tag, 1);
+ entry->tail_trim = get_itunes_int32(tag, 2);
+ }
+#endif
+
+ /* Note that parser functions sometimes set *ptag to NULL, so
+ * the "!*ptag" check here doesn't always have the desired
+ * effect. Should the parser functions (parsegenre in
+ * particular) be updated to handle the case of being called
+ * multiple times, or should the "*ptag" check be removed?
+ */
+ if (ptag && !*ptag)
+ *ptag = tag;
+
+#ifdef HAVE_ALBUMART
+ /* albumart */
+ if ((!entry->has_embedded_albumart) &&
+ ((tr->tag_length == 4 && !memcmp( header, "APIC", 4)) ||
+ (tr->tag_length == 3 && !memcmp( header, "PIC" , 3))))
+ {
+ if (unsynch || (global_unsynch && version <= ID3_VER_2_3))
+ entry->albumart.type = AA_TYPE_UNSYNC;
+ else
+ {
+ entry->albumart.pos = lseek(fd, 0, SEEK_CUR) - framelen;
+ entry->albumart.size = totframelen;
+ entry->albumart.type = AA_TYPE_UNKNOWN;
+ }
+ }
+#endif
+ if( tr->ppFunc )
+ bufferpos = tr->ppFunc(entry, tag, bufferpos);
+ break;
+ }
+ }
+
+ if( i == TAGLIST_SIZE ) {
+ /* no tag in tagList was found, or it was a repeat.
+ skip it using the total size */
+
+ if(global_unsynch && version <= ID3_VER_2_3) {
+ size -= skip_unsynched(fd, totframelen);
+ } else {
+ size -= totframelen;
+ if( lseek(fd, totframelen, SEEK_CUR) == -1 )
+ return;
+ }
+ } else {
+ /* Seek to the next frame */
+ if(framelen < totframelen)
+ lseek(fd, totframelen - framelen, SEEK_CUR);
+ }
+ }
+}
+
+/*
+ * Calculates the size of the ID3v2 tag.
+ *
+ * Arguments: file - the file to search for a tag.
+ *
+ * Returns: the size of the tag or 0 if none was found
+ */
+int getid3v2len(int fd)
+{
+ char buf[6];
+ int offset;
+
+ /* Make sure file has a ID3 tag */
+ if((-1 == lseek(fd, 0, SEEK_SET)) ||
+ (read(fd, buf, 6) != 6) ||
+ (strncmp(buf, "ID3", strlen("ID3")) != 0))
+ offset = 0;
+
+ /* Now check what the ID3v2 size field says */
+ else
+ if(read(fd, buf, 4) != 4)
+ offset = 0;
+ else
+ offset = unsync(buf[0], buf[1], buf[2], buf[3]) + 10;
+
+ logf("ID3V2 Length: 0x%x", offset);
+ return offset;
+}
+
+#ifdef DEBUG_STANDALONE
+
+char *secs2str(int ms)
+{
+ static char buffer[32];
+ int secs = ms/1000;
+ ms %= 1000;
+ snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100);
+ return buffer;
+}
+
+int main(int argc, char **argv)
+{
+ int i;
+ for(i=1; i<argc; i++) {
+ struct mp3entry mp3;
+ mp3.album = "Bogus";
+ if(mp3info(&mp3, argv[i], false)) {
+ printf("Failed to get %s\n", argv[i]);
+ return 0;
+ }
+
+ printf("****** File: %s\n"
+ " Title: %s\n"
+ " Artist: %s\n"
+ " Album: %s\n"
+ " Genre: %s (%d) \n"
+ " Composer: %s\n"
+ " Year: %s (%d)\n"
+ " Track: %s (%d)\n"
+ " Length: %s / %d s\n"
+ " Bitrate: %d\n"
+ " Frequency: %d\n",
+ argv[i],
+ mp3.title?mp3.title:"<blank>",
+ mp3.artist?mp3.artist:"<blank>",
+ mp3.album?mp3.album:"<blank>",
+ mp3.genre_string?mp3.genre_string:"<blank>",
+ mp3.genre,
+ mp3.composer?mp3.composer:"<blank>",
+ mp3.year_string?mp3.year_string:"<blank>",
+ mp3.year,
+ mp3.track_string?mp3.track_string:"<blank>",
+ mp3.tracknum,
+ secs2str(mp3.length),
+ mp3.length/1000,
+ mp3.bitrate,
+ mp3.frequency);
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/lib/rbcodec/metadata/kss.c b/lib/rbcodec/metadata/kss.c
new file mode 100644
index 0000000000..2ae0cf50b0
--- /dev/null
+++ b/lib/rbcodec/metadata/kss.c
@@ -0,0 +1,53 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+static bool parse_kss_header(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, buf, 0x20) < 0x20)
+ return false;
+
+ /* calculate track length with number of tracks */
+ id3->length = 0;
+ if (buf[14] == 0x10) {
+ id3->length = (get_short_le((void *)(buf + 26)) + 1) * 1000;
+ }
+
+ if (id3->length <= 0)
+ id3->length = 255 * 1000; /* 255 tracks */
+
+ return true;
+}
+
+
+bool get_kss_metadata(int fd, struct mp3entry* id3)
+{
+ uint32_t kss_type;
+ if ((lseek(fd, 0, SEEK_SET) < 0) ||
+ read_uint32be(fd, &kss_type) != (int)sizeof(kss_type))
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ /* we only render 16 bits, 44.1KHz, Stereo */
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* Make sure this is an SGC file */
+ if (kss_type != FOURCC('K','S','C','C') && kss_type != FOURCC('K','S','S','X'))
+ return false;
+
+ return parse_kss_header(fd, id3);
+}
diff --git a/lib/rbcodec/metadata/metadata.c b/lib/rbcodec/metadata/metadata.c
new file mode 100644
index 0000000000..b91e00cc4e
--- /dev/null
+++ b/lib/rbcodec/metadata/metadata.c
@@ -0,0 +1,641 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "string-extra.h"
+
+#include "debug.h"
+#include "logf.h"
+#include "settings.h"
+#include "cuesheet.h"
+#include "metadata.h"
+
+#include "metadata_parsers.h"
+
+#if CONFIG_CODEC == SWCODEC
+
+/* For trailing tag stripping and base audio data types */
+#include "buffering.h"
+
+#include "metadata/metadata_common.h"
+
+static bool get_shn_metadata(int fd, struct mp3entry *id3)
+{
+ /* TODO: read the id3v2 header if it exists */
+ id3->vbr = true;
+ id3->filesize = filesize(fd);
+ return skip_id3v2(fd, id3);
+}
+
+static bool get_other_asap_metadata(int fd, struct mp3entry *id3)
+{
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ id3->genre_string = id3_get_num_genre(36);
+ return true;
+}
+#endif /* CONFIG_CODEC == SWCODEC */
+bool write_metadata_log = false;
+
+const struct afmt_entry audio_formats[AFMT_NUM_CODECS] =
+{
+ /* Unknown file format */
+ [0 ... AFMT_NUM_CODECS-1] =
+ AFMT_ENTRY("???", NULL, NULL, NULL, "\0" ),
+
+ /* MPEG Audio layer 2 */
+ [AFMT_MPA_L2] =
+ AFMT_ENTRY("MP2", "mpa", NULL, get_mp3_metadata, "mpa\0mp2\0"),
+
+#if CONFIG_CODEC != SWCODEC
+ /* MPEG Audio layer 3 on HWCODEC: .talk clips, no encoder */
+ [AFMT_MPA_L3] =
+ AFMT_ENTRY("MP3", "mpa", NULL, get_mp3_metadata, "mp3\0talk\0"),
+
+#else /* CONFIG_CODEC == SWCODEC */
+ /* MPEG Audio layer 3 on SWCODEC */
+ [AFMT_MPA_L3] =
+ AFMT_ENTRY("MP3", "mpa", "mp3_enc", get_mp3_metadata, "mp3\0"),
+
+ /* MPEG Audio layer 1 */
+ [AFMT_MPA_L1] =
+ AFMT_ENTRY("MP1", "mpa", NULL, get_mp3_metadata, "mp1\0"),
+ /* Audio Interchange File Format */
+ [AFMT_AIFF] =
+ AFMT_ENTRY("AIFF", "aiff", "aiff_enc", get_aiff_metadata, "aiff\0aif\0"),
+ /* Uncompressed PCM in a WAV file OR ATRAC3 stream in WAV file (.at3) */
+ [AFMT_PCM_WAV] =
+ AFMT_ENTRY("WAV", "wav", "wav_enc", get_wave_metadata, "wav\0at3\0"),
+ /* Ogg Vorbis */
+ [AFMT_OGG_VORBIS] =
+ AFMT_ENTRY("Ogg", "vorbis", NULL, get_ogg_metadata, "ogg\0oga\0"),
+ /* FLAC */
+ [AFMT_FLAC] =
+ AFMT_ENTRY("FLAC", "flac", NULL, get_flac_metadata, "flac\0"),
+ /* Musepack SV7 */
+ [AFMT_MPC_SV7] =
+ AFMT_ENTRY("MPCv7", "mpc", NULL, get_musepack_metadata,"mpc\0"),
+ /* A/52 (aka AC3) audio */
+ [AFMT_A52] =
+ AFMT_ENTRY("AC3", "a52", NULL, get_a52_metadata, "a52\0ac3\0"),
+ /* WavPack */
+ [AFMT_WAVPACK] =
+ AFMT_ENTRY("WV","wavpack","wavpack_enc",get_wavpack_metadata,"wv\0"),
+ /* Apple Lossless Audio Codec */
+ [AFMT_MP4_ALAC] =
+ AFMT_ENTRY("ALAC", "alac", NULL, get_mp4_metadata, "m4a\0m4b\0"),
+ /* Advanced Audio Coding in M4A container */
+ [AFMT_MP4_AAC] =
+ AFMT_ENTRY("AAC", "aac", NULL, get_mp4_metadata, "mp4\0"),
+ /* Shorten */
+ [AFMT_SHN] =
+ AFMT_ENTRY("SHN","shorten", NULL, get_shn_metadata, "shn\0"),
+ /* SID File Format */
+ [AFMT_SID] =
+ AFMT_ENTRY("SID", "sid", NULL, get_sid_metadata, "sid\0"),
+ /* ADX File Format */
+ [AFMT_ADX] =
+ AFMT_ENTRY("ADX", "adx", NULL, get_adx_metadata, "adx\0"),
+ /* NESM (NES Sound Format) */
+ [AFMT_NSF] =
+ AFMT_ENTRY("NSF", "nsf", NULL, get_nsf_metadata, "nsf\0nsfe\0"),
+ /* Speex File Format */
+ [AFMT_SPEEX] =
+ AFMT_ENTRY("Speex", "speex",NULL, get_ogg_metadata, "spx\0"),
+ /* SPC700 Save State */
+ [AFMT_SPC] =
+ AFMT_ENTRY("SPC", "spc", NULL, get_spc_metadata, "spc\0"),
+ /* APE (Monkey's Audio) */
+ [AFMT_APE] =
+ AFMT_ENTRY("APE", "ape", NULL, get_monkeys_metadata,"ape\0mac\0"),
+ /* WMA (WMAV1/V2 in ASF) */
+ [AFMT_WMA] =
+ AFMT_ENTRY("WMA", "wma", NULL, get_asf_metadata,"wma\0wmv\0asf\0"),
+ /* WMA Professional in ASF */
+ [AFMT_WMAPRO] =
+ AFMT_ENTRY("WMAPro","wmapro",NULL, NULL, "wma\0wmv\0asf\0"),
+ /* Amiga MOD File */
+ [AFMT_MOD] =
+ AFMT_ENTRY("MOD", "mod", NULL, get_mod_metadata, "mod\0"),
+ /* Atari SAP File */
+ [AFMT_SAP] =
+ AFMT_ENTRY("SAP", "asap", NULL, get_asap_metadata, "sap\0"),
+ /* Cook in RM/RA */
+ [AFMT_RM_COOK] =
+ AFMT_ENTRY("Cook", "cook", NULL, get_rm_metadata,"rm\0ra\0rmvb\0"),
+ /* AAC in RM/RA */
+ [AFMT_RM_AAC] =
+ AFMT_ENTRY("RAAC", "raac", NULL, NULL, "rm\0ra\0rmvb\0"),
+ /* AC3 in RM/RA */
+ [AFMT_RM_AC3] =
+ AFMT_ENTRY("AC3", "a52_rm", NULL, NULL, "rm\0ra\0rmvb\0"),
+ /* ATRAC3 in RM/RA */
+ [AFMT_RM_ATRAC3] =
+ AFMT_ENTRY("ATRAC3","atrac3_rm",NULL, NULL, "rm\0ra\0rmvb\0"),
+ /* Atari CMC File */
+ [AFMT_CMC] =
+ AFMT_ENTRY("CMC", "asap", NULL, get_other_asap_metadata,"cmc\0"),
+ /* Atari CM3 File */
+ [AFMT_CM3] =
+ AFMT_ENTRY("CM3", "asap", NULL, get_other_asap_metadata,"cm3\0"),
+ /* Atari CMR File */
+ [AFMT_CMR] =
+ AFMT_ENTRY("CMR", "asap", NULL, get_other_asap_metadata,"cmr\0"),
+ /* Atari CMS File */
+ [AFMT_CMS] =
+ AFMT_ENTRY("CMS", "asap", NULL, get_other_asap_metadata,"cms\0"),
+ /* Atari DMC File */
+ [AFMT_DMC] =
+ AFMT_ENTRY("DMC", "asap", NULL, get_other_asap_metadata,"dmc\0"),
+ /* Atari DLT File */
+ [AFMT_DLT] =
+ AFMT_ENTRY("DLT", "asap", NULL, get_other_asap_metadata,"dlt\0"),
+ /* Atari MPT File */
+ [AFMT_MPT] =
+ AFMT_ENTRY("MPT", "asap", NULL, get_other_asap_metadata,"mpt\0"),
+ /* Atari MPD File */
+ [AFMT_MPD] =
+ AFMT_ENTRY("MPD", "asap", NULL, get_other_asap_metadata,"mpd\0"),
+ /* Atari RMT File */
+ [AFMT_RMT] =
+ AFMT_ENTRY("RMT", "asap", NULL, get_other_asap_metadata,"rmt\0"),
+ /* Atari TMC File */
+ [AFMT_TMC] =
+ AFMT_ENTRY("TMC", "asap", NULL, get_other_asap_metadata,"tmc\0"),
+ /* Atari TM8 File */
+ [AFMT_TM8] =
+ AFMT_ENTRY("TM8", "asap", NULL, get_other_asap_metadata,"tm8\0"),
+ /* Atari TM2 File */
+ [AFMT_TM2] =
+ AFMT_ENTRY("TM2", "asap", NULL, get_other_asap_metadata,"tm2\0"),
+ /* Atrac3 in Sony OMA Container */
+ [AFMT_OMA_ATRAC3] =
+ AFMT_ENTRY("ATRAC3","atrac3_oma",NULL, get_oma_metadata, "oma\0aa3\0"),
+ /* SMAF (Synthetic music Mobile Application Format) */
+ [AFMT_SMAF] =
+ AFMT_ENTRY("SMAF", "smaf", NULL, get_smaf_metadata, "mmf\0"),
+ /* Sun Audio file */
+ [AFMT_AU] =
+ AFMT_ENTRY("AU", "au", NULL, get_au_metadata, "au\0snd\0"),
+ /* VOX (Dialogic telephony file formats) */
+ [AFMT_VOX] =
+ AFMT_ENTRY("VOX", "vox", NULL, get_vox_metadata, "vox\0"),
+ /* Wave64 */
+ [AFMT_WAVE64] =
+ AFMT_ENTRY("WAVE64","wav64",NULL, get_wave64_metadata,"w64\0"),
+ /* True Audio */
+ [AFMT_TTA] =
+ AFMT_ENTRY("TTA", "tta", NULL, get_tta_metadata, "tta\0"),
+ /* WMA Voice in ASF */
+ [AFMT_WMAVOICE] =
+ AFMT_ENTRY("WMAVoice","wmavoice",NULL, NULL, "wma\0wmv\0"),
+ /* Musepack SV8 */
+ [AFMT_MPC_SV8] =
+ AFMT_ENTRY("MPCv8", "mpc", NULL, get_musepack_metadata,"mpc\0"),
+ /* Advanced Audio Coding High Efficiency in M4A container */
+ [AFMT_MP4_AAC_HE] =
+ AFMT_ENTRY("AAC-HE","aac", NULL, get_mp4_metadata, "mp4\0"),
+ /* AY (ZX Spectrum, Amstrad CPC Sound Format) */
+ [AFMT_AY] =
+ AFMT_ENTRY("AY", "ay", NULL, get_ay_metadata, "ay\0"),
+ /* GBS (Game Boy Sound Format) */
+ [AFMT_GBS] =
+ AFMT_ENTRY("GBS", "gbs", NULL, get_gbs_metadata, "gbs\0"),
+ /* HES (Hudson Entertainment System Sound Format) */
+ [AFMT_HES] =
+ AFMT_ENTRY("HES", "hes", NULL, get_hes_metadata, "hes\0"),
+ /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */
+ [AFMT_SGC] =
+ AFMT_ENTRY("SGC", "sgc", NULL, get_sgc_metadata, "sgc\0"),
+ /* VGM (Video Game Music Format) */
+ [AFMT_VGM] =
+ AFMT_ENTRY("VGM", "vgm", NULL, get_vgm_metadata, "vgm\0vgz\0"),
+ /* KSS (MSX computer KSS Music File) */
+ [AFMT_KSS] =
+ AFMT_ENTRY("KSS", "kss", NULL, get_kss_metadata, "kss\0"),
+#endif
+};
+
+#if CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING)
+/* get REC_FORMAT_* corresponding AFMT_* */
+const int rec_format_afmt[REC_NUM_FORMATS] =
+{
+ /* give AFMT_UNKNOWN by default */
+ [0 ... REC_NUM_FORMATS-1] = AFMT_UNKNOWN,
+ /* add new entries below this line */
+ [REC_FORMAT_AIFF] = AFMT_AIFF,
+ [REC_FORMAT_MPA_L3] = AFMT_MPA_L3,
+ [REC_FORMAT_WAVPACK] = AFMT_WAVPACK,
+ [REC_FORMAT_PCM_WAV] = AFMT_PCM_WAV,
+};
+
+#if 0 /* Currently unused, left for reference and future use */
+/* get AFMT_* corresponding REC_FORMAT_* */
+const int afmt_rec_format[AFMT_NUM_CODECS] =
+{
+ /* give -1 by default */
+ [0 ... AFMT_NUM_CODECS-1] = -1,
+ /* add new entries below this line */
+ [AFMT_AIFF] = REC_FORMAT_AIFF,
+ [AFMT_MPA_L3] = REC_FORMAT_MPA_L3,
+ [AFMT_WAVPACK] = REC_FORMAT_WAVPACK,
+ [AFMT_PCM_WAV] = REC_FORMAT_PCM_WAV,
+};
+#endif
+#endif /* CONFIG_CODEC == SWCODEC && defined (HAVE_RECORDING) */
+
+#if CONFIG_CODEC == SWCODEC
+/* Get the canonical AFMT type */
+int get_audio_base_codec_type(int type)
+{
+ int base_type = type;
+ switch (type) {
+ case AFMT_MPA_L1:
+ case AFMT_MPA_L2:
+ case AFMT_MPA_L3:
+ base_type = AFMT_MPA_L3;
+ break;
+ case AFMT_MPC_SV7:
+ case AFMT_MPC_SV8:
+ base_type = AFMT_MPC_SV7;
+ break;
+ case AFMT_MP4_AAC:
+ case AFMT_MP4_AAC_HE:
+ base_type = AFMT_MP4_AAC;
+ break;
+ case AFMT_SAP:
+ case AFMT_CMC:
+ case AFMT_CM3:
+ case AFMT_CMR:
+ case AFMT_CMS:
+ case AFMT_DMC:
+ case AFMT_DLT:
+ case AFMT_MPT:
+ case AFMT_MPD:
+ case AFMT_RMT:
+ case AFMT_TMC:
+ case AFMT_TM8:
+ case AFMT_TM2:
+ base_type = AFMT_SAP;
+ break;
+ default:
+ break;
+ }
+
+ return base_type;
+}
+
+/* Get the basic audio type */
+enum data_type get_audio_base_data_type(int afmt)
+{
+ if ((unsigned)afmt >= AFMT_NUM_CODECS)
+ return TYPE_UNKNOWN;
+
+ switch (get_audio_base_codec_type(afmt))
+ {
+ case AFMT_NSF:
+ case AFMT_SPC:
+ case AFMT_SID:
+ case AFMT_MOD:
+ case AFMT_SAP:
+ case AFMT_AY:
+ case AFMT_GBS:
+ case AFMT_HES:
+ case AFMT_SGC:
+ case AFMT_VGM:
+ case AFMT_KSS:
+ /* Type must be allocated and loaded in its entirety onto
+ the buffer */
+ return TYPE_ATOMIC_AUDIO;
+
+ default:
+ /* Assume type may be loaded and discarded incrementally */
+ return TYPE_PACKET_AUDIO;
+
+ case AFMT_UNKNOWN:
+ /* Have no idea at all */
+ return TYPE_UNKNOWN;
+ }
+}
+
+/* Is the format allowed to buffer starting at some offset other than 0
+ or first frame only for resume purposes? */
+bool format_buffers_with_offset(int afmt)
+{
+ switch (afmt)
+ {
+ case AFMT_MPA_L1:
+ case AFMT_MPA_L2:
+ case AFMT_MPA_L3:
+ case AFMT_WAVPACK:
+ /* Format may be loaded at the first needed frame */
+ return true;
+ default:
+ /* Format must be loaded from the beginning of the file
+ (does not imply 'atomic', while 'atomic' implies 'no offset') */
+ return false;
+ }
+}
+#endif /* CONFIG_CODEC == SWCODEC */
+
+
+/* Simple file type probing by looking at the filename extension. */
+unsigned int probe_file_format(const char *filename)
+{
+ char *suffix;
+ unsigned int i;
+
+ suffix = strrchr(filename, '.');
+
+ if (suffix == NULL)
+ {
+ return AFMT_UNKNOWN;
+ }
+
+ /* skip '.' */
+ suffix++;
+
+ for (i = 1; i < AFMT_NUM_CODECS; i++)
+ {
+ /* search extension list for type */
+ const char *ext = audio_formats[i].ext_list;
+
+ do
+ {
+ if (strcasecmp(suffix, ext) == 0)
+ {
+ return i;
+ }
+
+ ext += strlen(ext) + 1;
+ }
+ while (*ext != '\0');
+ }
+
+ return AFMT_UNKNOWN;
+}
+
+/* Note, that this returns false for successful, true for error! */
+bool mp3info(struct mp3entry *entry, const char *filename)
+{
+ int fd;
+ bool result;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return true;
+
+ result = !get_metadata(entry, fd, filename);
+
+ close(fd);
+
+ return result;
+}
+
+/* Get metadata for track - return false if parsing showed problems with the
+ * file that would prevent playback.
+ */
+bool get_metadata(struct mp3entry* id3, int fd, const char* trackname)
+{
+ const struct afmt_entry *entry;
+ int logfd = 0;
+ DEBUGF("Read metadata for %s\n", trackname);
+ if (write_metadata_log)
+ {
+ logfd = open("/metadata.log", O_WRONLY | O_APPEND | O_CREAT, 0666);
+ if (logfd >= 0)
+ {
+ write(logfd, trackname, strlen(trackname));
+ write(logfd, "\n", 1);
+ close(logfd);
+ }
+ }
+
+ /* Clear the mp3entry to avoid having bogus pointers appear */
+ wipe_mp3entry(id3);
+
+ /* Take our best guess at the codec type based on file extension */
+ id3->codectype = probe_file_format(trackname);
+
+ /* default values for embedded cuesheets */
+ id3->has_embedded_cuesheet = false;
+ id3->embedded_cuesheet.pos = 0;
+
+ entry = &audio_formats[id3->codectype];
+
+ /* Load codec specific track tag information and confirm the codec type. */
+ if (!entry->parse_func)
+ {
+ DEBUGF("nothing to parse for %s (format %s)", trackname, entry->label);
+ return false;
+ }
+
+ if (!entry->parse_func(fd, id3))
+ {
+ DEBUGF("parsing %s failed (format: %s)", trackname, entry->label);
+ return false;
+ }
+
+ lseek(fd, 0, SEEK_SET);
+ strlcpy(id3->path, trackname, sizeof(id3->path));
+ /* We have successfully read the metadata from the file */
+ return true;
+}
+
+#ifndef __PCTOOL__
+#if CONFIG_CODEC == SWCODEC
+void strip_tags(int handle_id)
+{
+ static const unsigned char tag[] = "TAG";
+ static const unsigned char apetag[] = "APETAGEX";
+ size_t len, version;
+ void *tail;
+
+ if (bufgettail(handle_id, 128, &tail) != 128)
+ return;
+
+ if (memcmp(tail, tag, 3) == 0)
+ {
+ /* Skip id3v1 tag */
+ logf("Cutting off ID3v1 tag");
+ bufcuttail(handle_id, 128);
+ }
+
+ /* Get a new tail, as the old one may have been cut */
+ if (bufgettail(handle_id, 32, &tail) != 32)
+ return;
+
+ /* Check for APE tag (look for the APE tag footer) */
+ if (memcmp(tail, apetag, 8) != 0)
+ return;
+
+ /* Read the version and length from the footer */
+ version = get_long_le(&((unsigned char *)tail)[8]);
+ len = get_long_le(&((unsigned char *)tail)[12]);
+ if (version == 2000)
+ len += 32; /* APEv2 has a 32 byte header */
+
+ /* Skip APE tag */
+ logf("Cutting off APE tag (%ldB)", len);
+ bufcuttail(handle_id, len);
+}
+#endif /* CONFIG_CODEC == SWCODEC */
+#endif /* ! __PCTOOL__ */
+
+#define MOVE_ENTRY(x) if (x) x += offset;
+
+void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig)
+{
+ long offset;
+ if (orig > dest)
+ offset = -((size_t)orig - (size_t)dest);
+ else
+ offset = ((size_t)dest - (size_t)orig);
+
+ MOVE_ENTRY(entry->title)
+ MOVE_ENTRY(entry->artist)
+ MOVE_ENTRY(entry->album)
+
+ if (entry->genre_string > (char*)orig &&
+ entry->genre_string < (char*)orig + sizeof(struct mp3entry))
+ /* Don't adjust that if it points to an entry of the "genres" array */
+ entry->genre_string += offset;
+
+ MOVE_ENTRY(entry->track_string)
+ MOVE_ENTRY(entry->disc_string)
+ MOVE_ENTRY(entry->year_string)
+ MOVE_ENTRY(entry->composer)
+ MOVE_ENTRY(entry->comment)
+ MOVE_ENTRY(entry->albumartist)
+ MOVE_ENTRY(entry->grouping)
+ MOVE_ENTRY(entry->mb_track_id)
+}
+
+void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig)
+{
+ memcpy(dest, orig, sizeof(struct mp3entry));
+ adjust_mp3entry(dest, dest, orig);
+}
+
+/* A shortcut to simplify the common task of clearing the struct */
+void wipe_mp3entry(struct mp3entry *id3)
+{
+ memset(id3, 0, sizeof (struct mp3entry));
+}
+
+#if CONFIG_CODEC == SWCODEC
+/* Glean what is possible from the filename alone - does not parse metadata */
+void fill_metadata_from_path(struct mp3entry *id3, const char *trackname)
+{
+ char *p;
+
+ /* Clear the mp3entry to avoid having bogus pointers appear */
+ wipe_mp3entry(id3);
+
+ /* Find the filename portion of the path */
+ p = strrchr(trackname, '/');
+ strlcpy(id3->id3v2buf, p ? ++p : id3->path, ID3V2_BUF_SIZE);
+
+ /* Get the format from the extension and trim it off */
+ p = strrchr(id3->id3v2buf, '.');
+ if (p)
+ {
+ /* Might be wrong for container formats - should we bother? */
+ id3->codectype = probe_file_format(p);
+
+ if (id3->codectype != AFMT_UNKNOWN)
+ *p = '\0';
+ }
+
+ /* Set the filename as the title */
+ id3->title = id3->id3v2buf;
+
+ /* Copy the path info */
+ strlcpy(id3->path, trackname, sizeof (id3->path));
+}
+#endif /* CONFIG_CODEC == SWCODEC */
+
+#ifndef __PCTOOL__
+#ifdef HAVE_TAGCACHE
+#if CONFIG_CODEC == SWCODEC
+
+enum { AUTORESUMABLE_UNKNOWN = 0, AUTORESUMABLE_TRUE, AUTORESUMABLE_FALSE };
+
+bool autoresumable(struct mp3entry *id3)
+{
+ char *endp, *path;
+ size_t len;
+ bool is_resumable;
+
+ if (id3->autoresumable) /* result cached? */
+ return id3->autoresumable == AUTORESUMABLE_TRUE;
+
+ is_resumable = false;
+
+ if (id3->path)
+ {
+ for (path = global_settings.autoresume_paths;
+ *path; /* search terms left? */
+ path++)
+ {
+ if (*path == ':') /* Skip empty search patterns */
+ continue;
+
+ /* FIXME: As soon as strcspn or strchrnul are made available in
+ the core, the following can be made more efficient. */
+ endp = strchr(path, ':');
+ if (endp)
+ len = endp - path;
+ else
+ len = strlen(path);
+
+ /* Note: At this point, len is always > 0 */
+
+ if (strncasecmp(id3->path, path, len) == 0)
+ {
+ /* Full directory-name matches only. Trailing '/' in
+ search path OK. */
+ if (id3->path[len] == '/' || id3->path[len - 1] == '/')
+ {
+ is_resumable = true;
+ break;
+ }
+ }
+ path += len - 1;
+ }
+ }
+
+ /* cache result */
+ id3->autoresumable =
+ is_resumable ? AUTORESUMABLE_TRUE : AUTORESUMABLE_FALSE;
+
+ logf("autoresumable: %s is%s resumable",
+ id3->path, is_resumable ? "" : " not");
+
+ return is_resumable;
+}
+
+#endif /* SWCODEC */
+#endif /* HAVE_TAGCACHE */
+#endif /* __PCTOOL__ */
diff --git a/lib/rbcodec/metadata/metadata.h b/lib/rbcodec/metadata/metadata.h
new file mode 100644
index 0000000000..55e4d76f25
--- /dev/null
+++ b/lib/rbcodec/metadata/metadata.h
@@ -0,0 +1,353 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _METADATA_H
+#define _METADATA_H
+
+#include <stdbool.h>
+#include "config.h"
+#include "file.h"
+
+
+/* Audio file types. */
+/* NOTE: The values of the AFMT_* items are used for the %fc tag in the WPS
+ - so new entries MUST be added to the end to maintain compatibility.
+ */
+enum
+{
+ AFMT_UNKNOWN = 0, /* Unknown file format */
+
+ /* start formats */
+ AFMT_MPA_L1, /* MPEG Audio layer 1 */
+ AFMT_MPA_L2, /* MPEG Audio layer 2 */
+ AFMT_MPA_L3, /* MPEG Audio layer 3 */
+
+#if CONFIG_CODEC == SWCODEC
+ AFMT_AIFF, /* Audio Interchange File Format */
+ AFMT_PCM_WAV, /* Uncompressed PCM in a WAV file */
+ AFMT_OGG_VORBIS, /* Ogg Vorbis */
+ AFMT_FLAC, /* FLAC */
+ AFMT_MPC_SV7, /* Musepack SV7 */
+ AFMT_A52, /* A/52 (aka AC3) audio */
+ AFMT_WAVPACK, /* WavPack */
+ AFMT_MP4_ALAC, /* Apple Lossless Audio Codec */
+ AFMT_MP4_AAC, /* Advanced Audio Coding (AAC) in M4A container */
+ AFMT_SHN, /* Shorten */
+ AFMT_SID, /* SID File Format */
+ AFMT_ADX, /* ADX File Format */
+ AFMT_NSF, /* NESM (NES Sound Format) */
+ AFMT_SPEEX, /* Ogg Speex speech */
+ AFMT_SPC, /* SPC700 save state */
+ AFMT_APE, /* Monkey's Audio (APE) */
+ AFMT_WMA, /* WMAV1/V2 in ASF */
+ AFMT_WMAPRO, /* WMA Professional in ASF */
+ AFMT_MOD, /* Amiga MOD File Format */
+ AFMT_SAP, /* Atari 8Bit SAP Format */
+ AFMT_RM_COOK, /* Cook in RM/RA */
+ AFMT_RM_AAC, /* AAC in RM/RA */
+ AFMT_RM_AC3, /* AC3 in RM/RA */
+ AFMT_RM_ATRAC3, /* ATRAC3 in RM/RA */
+ AFMT_CMC, /* Atari 8bit cmc format */
+ AFMT_CM3, /* Atari 8bit cm3 format */
+ AFMT_CMR, /* Atari 8bit cmr format */
+ AFMT_CMS, /* Atari 8bit cms format */
+ AFMT_DMC, /* Atari 8bit dmc format */
+ AFMT_DLT, /* Atari 8bit dlt format */
+ AFMT_MPT, /* Atari 8bit mpt format */
+ AFMT_MPD, /* Atari 8bit mpd format */
+ AFMT_RMT, /* Atari 8bit rmt format */
+ AFMT_TMC, /* Atari 8bit tmc format */
+ AFMT_TM8, /* Atari 8bit tm8 format */
+ AFMT_TM2, /* Atari 8bit tm2 format */
+ AFMT_OMA_ATRAC3, /* Atrac3 in Sony OMA container */
+ AFMT_SMAF, /* SMAF */
+ AFMT_AU, /* Sun Audio file */
+ AFMT_VOX, /* VOX */
+ AFMT_WAVE64, /* Wave64 */
+ AFMT_TTA, /* True Audio */
+ AFMT_WMAVOICE, /* WMA Voice in ASF */
+ AFMT_MPC_SV8, /* Musepack SV8 */
+ AFMT_MP4_AAC_HE, /* Advanced Audio Coding (AAC-HE) in M4A container */
+ AFMT_AY, /* AY (ZX Spectrum, Amstrad CPC Sound Format) */
+ AFMT_GBS, /* GBS (Game Boy Sound Format) */
+ AFMT_HES, /* HES (Hudson Entertainment System Sound Format) */
+ AFMT_SGC, /* SGC (Sega Master System, Game Gear, Coleco Vision Sound Format) */
+ AFMT_VGM, /* VGM (Video Game Music Format) */
+ AFMT_KSS, /* KSS (MSX computer KSS Music File) */
+#endif
+
+ /* add new formats at any index above this line to have a sensible order -
+ specified array index inits are used */
+ /* format arrays defined in id3.c */
+
+ AFMT_NUM_CODECS,
+
+#if CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING)
+ /* masks to decompose parts */
+ CODEC_AFMT_MASK = 0x0fff,
+ CODEC_TYPE_MASK = 0x7000,
+
+ /* switch for specifying codec type when requesting a filename */
+ CODEC_TYPE_DECODER = (0 << 12), /* default */
+ CODEC_TYPE_ENCODER = (1 << 12),
+#endif /* CONFIG_CODEC == SWCODEC && defined(HAVE_RECORDING) */
+};
+
+#if CONFIG_CODEC == SWCODEC
+#if (CONFIG_PLATFORM & PLATFORM_ANDROID)
+#define CODEC_EXTENSION "so"
+#define CODEC_PREFIX "lib"
+#else
+#define CODEC_EXTENSION "codec"
+#define CODEC_PREFIX ""
+#endif
+
+#ifdef HAVE_RECORDING
+enum rec_format_indexes
+{
+ __REC_FORMAT_START_INDEX = -1,
+
+ /* start formats */
+
+ REC_FORMAT_PCM_WAV,
+ REC_FORMAT_AIFF,
+ REC_FORMAT_WAVPACK,
+ REC_FORMAT_MPA_L3,
+
+ /* add new formats at any index above this line to have a sensible order -
+ specified array index inits are used
+ REC_FORMAT_CFG_NUM_BITS should allocate enough bits to hold the range
+ REC_FORMAT_CFG_VALUE_LIST should be in same order as indexes
+ */
+
+ REC_NUM_FORMATS,
+
+ REC_FORMAT_DEFAULT = REC_FORMAT_PCM_WAV,
+ REC_FORMAT_CFG_NUM_BITS = 2
+};
+
+#define REC_FORMAT_CFG_VAL_LIST "wave,aiff,wvpk,mpa3"
+
+/* get REC_FORMAT_* corresponding AFMT_* */
+extern const int rec_format_afmt[REC_NUM_FORMATS];
+/* get AFMT_* corresponding REC_FORMAT_* */
+/* unused: extern const int afmt_rec_format[AFMT_NUM_CODECS]; */
+
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \
+ { label, root_fname, enc_root_fname, func, ext_list }
+#else /* !HAVE_RECORDING */
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \
+ { label, root_fname, func, ext_list }
+#endif /* HAVE_RECORDING */
+
+#else /* !SWCODEC */
+
+#define AFMT_ENTRY(label, root_fname, enc_root_fname, func, ext_list) \
+ { label, func, ext_list }
+#endif /* CONFIG_CODEC == SWCODEC */
+
+/** Database of audio formats **/
+/* record describing the audio format */
+struct mp3entry;
+struct afmt_entry
+{
+ const char *label; /* format label */
+#if CONFIG_CODEC == SWCODEC
+ const char *codec_root_fn; /* root codec filename (sans _enc and .codec) */
+#ifdef HAVE_RECORDING
+ const char *codec_enc_root_fn; /* filename of encoder codec */
+#endif
+#endif
+ bool (*parse_func)(int fd, struct mp3entry *id3); /* return true on success */
+ const char *ext_list; /* NULL terminated extension
+ list for type with the first as
+ the default for recording */
+};
+
+/* database of labels and codecs. add formats per above enum */
+extern const struct afmt_entry audio_formats[AFMT_NUM_CODECS];
+
+#if MEMORYSIZE > 2
+#define ID3V2_BUF_SIZE 900
+#define ID3V2_MAX_ITEM_SIZE 240
+#else
+#define ID3V2_BUF_SIZE 300
+#define ID3V2_MAX_ITEM_SIZE 90
+#endif
+
+enum {
+ ID3_VER_1_0 = 1,
+ ID3_VER_1_1,
+ ID3_VER_2_2,
+ ID3_VER_2_3,
+ ID3_VER_2_4
+};
+
+#ifdef HAVE_ALBUMART
+enum mp3_aa_type {
+ AA_TYPE_UNSYNC = -1,
+ AA_TYPE_UNKNOWN,
+ AA_TYPE_BMP,
+ AA_TYPE_PNG,
+ AA_TYPE_JPG,
+};
+
+struct mp3_albumart {
+ enum mp3_aa_type type;
+ int size;
+ off_t pos;
+};
+#endif
+
+enum character_encoding {
+ CHAR_ENC_ISO_8859_1 = 1,
+ CHAR_ENC_UTF_8,
+ CHAR_ENC_UTF_16_LE,
+ CHAR_ENC_UTF_16_BE,
+};
+
+/* cache embedded cuesheet details */
+struct embedded_cuesheet {
+ int size;
+ off_t pos;
+ enum character_encoding encoding;
+};
+
+struct mp3entry {
+ char path[MAX_PATH];
+ char* title;
+ char* artist;
+ char* album;
+ char* genre_string;
+ char* disc_string;
+ char* track_string;
+ char* year_string;
+ char* composer;
+ char* comment;
+ char* albumartist;
+ char* grouping;
+ int discnum;
+ int tracknum;
+ int layer;
+ int year;
+ unsigned char id3version;
+ unsigned int codectype;
+ unsigned int bitrate;
+ unsigned long frequency;
+ unsigned long id3v2len;
+ unsigned long id3v1len;
+ unsigned long first_frame_offset; /* Byte offset to first real MP3 frame.
+ Used for skipping leading garbage to
+ avoid gaps between tracks. */
+ unsigned long filesize; /* without headers; in bytes */
+ unsigned long length; /* song length in ms */
+ unsigned long elapsed; /* ms played */
+
+ int lead_trim; /* Number of samples to skip at the beginning */
+ int tail_trim; /* Number of samples to remove from the end */
+
+ /* Added for Vorbis, used by mp4 parser as well. */
+ unsigned long samples; /* number of samples in track */
+
+ /* MP3 stream specific info */
+ unsigned long frame_count; /* number of frames in the file (if VBR) */
+
+ /* Used for A52/AC3 */
+ unsigned long bytesperframe; /* number of bytes per frame (if CBR) */
+
+ /* Xing VBR fields */
+ bool vbr;
+ bool has_toc; /* True if there is a VBR header in the file */
+ unsigned char toc[100]; /* table of contents */
+
+ /* Added for ATRAC3 */
+ unsigned int channels; /* Number of channels in the stream */
+ unsigned int extradata_size; /* Size (in bytes) of the codec's extradata from the container */
+
+ /* Added for AAC HE SBR */
+ bool needs_upsampling_correction; /* flag used by aac codec */
+
+ /* these following two fields are used for local buffering */
+ char id3v2buf[ID3V2_BUF_SIZE];
+ char id3v1buf[4][92];
+
+ /* resume related */
+ unsigned long offset; /* bytes played */
+ int index; /* playlist index */
+
+#ifdef HAVE_TAGCACHE
+ unsigned char autoresumable; /* caches result of autoresumable() */
+
+ /* runtime database fields */
+ long tagcache_idx; /* 0=invalid, otherwise idx+1 */
+ int rating;
+ int score;
+ long playcount;
+ long lastplayed;
+ long playtime;
+#endif
+
+ /* replaygain support */
+#if CONFIG_CODEC == SWCODEC
+ long track_level; /* holds the level in dB * (1<<FP_BITS) */
+ long album_level;
+ long track_gain; /* s19.12 signed fixed point. 0 for no gain. */
+ long album_gain;
+ long track_peak; /* s19.12 signed fixed point. 0 for no peak. */
+ long album_peak;
+#endif
+
+#ifdef HAVE_ALBUMART
+ bool has_embedded_albumart;
+ struct mp3_albumart albumart;
+#endif
+
+ /* Cuesheet support */
+ bool has_embedded_cuesheet;
+ struct embedded_cuesheet embedded_cuesheet;
+ struct cuesheet *cuesheet;
+
+ /* Musicbrainz Track ID */
+ char* mb_track_id;
+};
+
+unsigned int probe_file_format(const char *filename);
+bool get_metadata(struct mp3entry* id3, int fd, const char* trackname);
+bool mp3info(struct mp3entry *entry, const char *filename);
+void adjust_mp3entry(struct mp3entry *entry, void *dest, const void *orig);
+void copy_mp3entry(struct mp3entry *dest, const struct mp3entry *orig);
+void wipe_mp3entry(struct mp3entry *id3);
+
+#if CONFIG_CODEC == SWCODEC
+void fill_metadata_from_path(struct mp3entry *id3, const char *trackname);
+int get_audio_base_codec_type(int type);
+void strip_tags(int handle_id);
+enum data_type get_audio_base_data_type(int afmt);
+bool format_buffers_with_offset(int afmt);
+#endif
+
+#ifdef HAVE_TAGCACHE
+bool autoresumable(struct mp3entry *id3);
+#endif
+
+#endif
+
+
diff --git a/lib/rbcodec/metadata/metadata_common.c b/lib/rbcodec/metadata/metadata_common.c
new file mode 100644
index 0000000000..e861644025
--- /dev/null
+++ b/lib/rbcodec/metadata/metadata_common.c
@@ -0,0 +1,374 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include "string-extra.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "replaygain.h"
+
+/* Read a string from the file. Read up to size bytes, or, if eos != -1,
+ * until the eos character is found (eos is not stored in buf, unless it is
+ * nil). Writes up to buf_size chars to buf, always terminating with a nil.
+ * Returns number of chars read or -1 on read error.
+ */
+long read_string(int fd, char* buf, long buf_size, int eos, long size)
+{
+ long read_bytes = 0;
+ char c;
+
+ while (size != 0)
+ {
+ if (read(fd, &c, 1) != 1)
+ {
+ read_bytes = -1;
+ break;
+ }
+
+ read_bytes++;
+ size--;
+
+ if ((eos != -1) && (eos == (unsigned char) c))
+ {
+ break;
+ }
+
+ if (buf_size > 1)
+ {
+ *buf++ = c;
+ buf_size--;
+ }
+ }
+
+ *buf = 0;
+ return read_bytes;
+}
+/* Read an unsigned 8-bit integer from a file. */
+int read_uint8(int fd, uint8_t* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 1);
+ return n;
+}
+
+#ifdef ROCKBOX_LITTLE_ENDIAN
+/* Read an unsigned 16-bit integer from a big-endian file. */
+int read_uint16be(int fd, uint16_t* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 2);
+ *buf = betoh16(*buf);
+ return n;
+}
+/* Read an unsigned 32-bit integer from a big-endian file. */
+int read_uint32be(int fd, uint32_t* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 4);
+ *buf = betoh32(*buf);
+ return n;
+}
+/* Read an unsigned 64-bit integer from a big-endian file. */
+int read_uint64be(int fd, uint64_t* buf)
+{
+ size_t n;
+ uint8_t data[8];
+ int i;
+
+ n = read(fd, data, 8);
+
+ for (i=0, *buf=0; i<=7; i++) {
+ *buf <<= 8;
+ *buf |= data[i];
+ }
+ return n;
+}
+#else
+/* Read unsigned integers from a little-endian file. */
+int read_uint16le(int fd, uint16_t* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 2);
+ *buf = letoh16(*buf);
+ return n;
+}
+int read_uint32le(int fd, uint32_t* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 4);
+ *buf = letoh32(*buf);
+ return n;
+}
+int read_uint64le(int fd, uint64_t* buf)
+{
+ size_t n;
+ uint8_t data[8];
+ int i;
+
+ n = read(fd, data, 8);
+
+ for (i=7, *buf=0; i>=0; i--) {
+ *buf <<= 8;
+ *buf |= data[i];
+ }
+
+ return n;
+}
+#endif
+
+/* Read an unaligned 64-bit little endian unsigned integer from buffer. */
+uint64_t get_uint64_le(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24) | ((uint64_t)p[4] << 32) |
+ ((uint64_t)p[5] << 40) | ((uint64_t)p[6] << 48) | ((uint64_t)p[7] << 56);
+}
+
+/* Read an unaligned 32-bit little endian long from buffer. */
+uint32_t get_long_le(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+/* Read an unaligned 16-bit little endian short from buffer. */
+uint16_t get_short_le(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8);
+}
+
+/* Read an unaligned 32-bit big endian long from buffer. */
+uint32_t get_long_be(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+}
+
+/* Read an unaligned 16-bit little endian short from buffer. */
+uint16_t get_short_be(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return (p[0] << 8) | p[1];
+}
+
+/* Read an unaligned 32-bit little endian long from buffer. */
+int32_t get_slong(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+uint32_t get_itunes_int32(char* value, int count)
+{
+ static const char hexdigits[] = "0123456789ABCDEF";
+ const char* c;
+ int r = 0;
+
+ while (count-- > 0)
+ {
+ while (isspace(*value))
+ {
+ value++;
+ }
+
+ while (*value && !isspace(*value))
+ {
+ value++;
+ }
+ }
+
+ while (isspace(*value))
+ {
+ value++;
+ }
+
+ while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL))
+ {
+ r = (r << 4) | (c - hexdigits);
+ value++;
+ }
+
+ return r;
+}
+
+/* Skip an ID3v2 tag if it can be found. We assume the tag is located at the
+ * start of the file, which should be true in all cases where we need to skip it.
+ * Returns true if successfully skipped or not skipped, and false if
+ * something went wrong while skipping.
+ */
+bool skip_id3v2(int fd, struct mp3entry *id3)
+{
+ char buf[4];
+
+ read(fd, buf, 4);
+ if (memcmp(buf, "ID3", 3) == 0)
+ {
+ /* We have found an ID3v2 tag at the start of the file - find its
+ length and then skip it. */
+ if ((id3->first_frame_offset = getid3v2len(fd)) == 0)
+ return false;
+
+ if ((lseek(fd, id3->first_frame_offset, SEEK_SET) < 0))
+ return false;
+
+ return true;
+ } else {
+ lseek(fd, 0, SEEK_SET);
+ id3->first_frame_offset = 0;
+ return true;
+ }
+}
+
+/* Parse the tag (the name-value pair) and fill id3 and buffer accordingly.
+ * String values to keep are written to buf. Returns number of bytes written
+ * to buf (including end nil).
+ */
+long parse_tag(const char* name, char* value, struct mp3entry* id3,
+ char* buf, long buf_remaining, enum tagtype type)
+{
+ long len = 0;
+ char** p;
+
+ if ((((strcasecmp(name, "track") == 0) && (type == TAGTYPE_APE)))
+ || ((strcasecmp(name, "tracknumber") == 0) && (type == TAGTYPE_VORBIS)))
+ {
+ id3->tracknum = atoi(value);
+ p = &(id3->track_string);
+ }
+ else if (strcasecmp(name, "discnumber") == 0 || strcasecmp(name, "disc") == 0)
+ {
+ id3->discnum = atoi(value);
+ p = &(id3->disc_string);
+ }
+ else if (((strcasecmp(name, "year") == 0) && (type == TAGTYPE_APE))
+ || ((strcasecmp(name, "date") == 0) && (type == TAGTYPE_VORBIS)))
+ {
+ /* Date's can be in any format in Vorbis. However most of them
+ * are in ISO8601 format so if we try and parse the first part
+ * of the tag as a number, we should get the year. If we get crap,
+ * then act like we never parsed it.
+ */
+ id3->year = atoi(value);
+ if (id3->year < 1900)
+ { /* yeah, not likely */
+ id3->year = 0;
+ }
+ p = &(id3->year_string);
+ }
+ else if (strcasecmp(name, "title") == 0)
+ {
+ p = &(id3->title);
+ }
+ else if (strcasecmp(name, "artist") == 0)
+ {
+ p = &(id3->artist);
+ }
+ else if (strcasecmp(name, "album") == 0)
+ {
+ p = &(id3->album);
+ }
+ else if (strcasecmp(name, "genre") == 0)
+ {
+ p = &(id3->genre_string);
+ }
+ else if (strcasecmp(name, "composer") == 0)
+ {
+ p = &(id3->composer);
+ }
+ else if (strcasecmp(name, "comment") == 0)
+ {
+ p = &(id3->comment);
+ }
+ else if (strcasecmp(name, "albumartist") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else if (strcasecmp(name, "album artist") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else if (strcasecmp(name, "ensemble") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else if (strcasecmp(name, "grouping") == 0)
+ {
+ p = &(id3->grouping);
+ }
+ else if (strcasecmp(name, "content group") == 0)
+ {
+ p = &(id3->grouping);
+ }
+ else if (strcasecmp(name, "contentgroup") == 0)
+ {
+ p = &(id3->grouping);
+ }
+ else if (strcasecmp(name, "musicbrainz_trackid") == 0
+ || strcasecmp(name, "http://musicbrainz.org") == 0 )
+ {
+ p = &(id3->mb_track_id);
+ }
+ else
+ {
+ parse_replaygain(name, value, id3);
+ p = NULL;
+ }
+
+ /* Do not overwrite already available metadata. Especially when reading
+ * tags with e.g. multiple genres / artists. This way only the first
+ * of multiple entries is used, all following are dropped. */
+ if (p!=NULL && *p==NULL)
+ {
+ len = strlen(value);
+ len = MIN(len, buf_remaining - 1);
+ len = MIN(len, ID3V2_MAX_ITEM_SIZE); /* Limit max. item size. */
+
+ if (len > 0)
+ {
+ len++;
+ strlcpy(buf, value, len);
+ *p = buf;
+ }
+ else
+ {
+ len = 0;
+ }
+ }
+
+ return len;
+}
diff --git a/lib/rbcodec/metadata/metadata_common.h b/lib/rbcodec/metadata/metadata_common.h
new file mode 100644
index 0000000000..db91729de4
--- /dev/null
+++ b/lib/rbcodec/metadata/metadata_common.h
@@ -0,0 +1,69 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <inttypes.h>
+#include "metadata.h"
+
+#ifdef ROCKBOX_BIG_ENDIAN
+#define IS_BIG_ENDIAN 1
+#else
+#define IS_BIG_ENDIAN 0
+#endif
+
+#define TAG_NAME_LENGTH 32
+#define TAG_VALUE_LENGTH 128
+
+#define FOURCC(a,b,c,d) (((a)<<24) | ((b) << 16) | ((c) << 8) | (d))
+
+enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
+
+bool read_ape_tags(int fd, struct mp3entry* id3);
+long read_vorbis_tags(int fd, struct mp3entry *id3,
+ long tag_remaining);
+
+bool skip_id3v2(int fd, struct mp3entry *id3);
+long read_string(int fd, char* buf, long buf_size, int eos, long size);
+
+int read_uint8(int fd, uint8_t* buf);
+#ifdef ROCKBOX_BIG_ENDIAN
+#define read_uint16be(fd,buf) read((fd), (buf), 2)
+#define read_uint32be(fd,buf) read((fd), (buf), 4)
+#define read_uint64be(fd,buf) read((fd), (buf), 8)
+int read_uint16le(int fd, uint16_t* buf);
+int read_uint32le(int fd, uint32_t* buf);
+int read_uint64le(int fd, uint64_t* buf);
+#else
+int read_uint16be(int fd, uint16_t* buf);
+int read_uint32be(int fd, uint32_t* buf);
+int read_uint64be(int fd, uint64_t* buf);
+#define read_uint16le(fd,buf) read((fd), (buf), 2)
+#define read_uint32le(fd,buf) read((fd), (buf), 4)
+#define read_uint64le(fd,buf) read((fd), (buf), 8)
+#endif
+
+uint64_t get_uint64_le(void* buf);
+uint32_t get_long_le(void* buf);
+uint16_t get_short_le(void* buf);
+uint32_t get_long_be(void* buf);
+uint16_t get_short_be(void* buf);
+int32_t get_slong(void* buf);
+uint32_t get_itunes_int32(char* value, int count);
+long parse_tag(const char* name, char* value, struct mp3entry* id3,
+ char* buf, long buf_remaining, enum tagtype type);
diff --git a/lib/rbcodec/metadata/metadata_parsers.h b/lib/rbcodec/metadata/metadata_parsers.h
new file mode 100644
index 0000000000..304e393538
--- /dev/null
+++ b/lib/rbcodec/metadata/metadata_parsers.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#if CONFIG_CODEC == SWCODEC
+char* id3_get_num_genre(unsigned int genre_num);
+#endif
+int getid3v2len(int fd);
+bool setid3v1title(int fd, struct mp3entry *entry);
+void setid3v2title(int fd, struct mp3entry *entry);
+bool get_mp3_metadata(int fd, struct mp3entry* id3);
+#if CONFIG_CODEC == SWCODEC
+bool get_adx_metadata(int fd, struct mp3entry* id3);
+bool get_aiff_metadata(int fd, struct mp3entry* id3);
+bool get_flac_metadata(int fd, struct mp3entry* id3);
+bool get_mp4_metadata(int fd, struct mp3entry* id3);
+bool get_monkeys_metadata(int fd, struct mp3entry* id3);
+bool get_musepack_metadata(int fd, struct mp3entry *id3);
+bool get_sid_metadata(int fd, struct mp3entry* id3);
+bool get_mod_metadata(int fd, struct mp3entry* id3);
+bool get_spc_metadata(int fd, struct mp3entry* id3);
+bool get_ogg_metadata(int fd, struct mp3entry* id3);
+bool get_wave_metadata(int fd, struct mp3entry* id3);
+bool get_wavpack_metadata(int fd, struct mp3entry* id3);
+bool get_a52_metadata(int fd, struct mp3entry* id3);
+bool get_asf_metadata(int fd, struct mp3entry* id3);
+bool get_asap_metadata(int fd, struct mp3entry* id3);
+bool get_rm_metadata(int fd, struct mp3entry* id3);
+bool get_nsf_metadata(int fd, struct mp3entry* id3);
+bool get_oma_metadata(int fd, struct mp3entry* id3);
+bool get_smaf_metadata(int fd, struct mp3entry* id3);
+bool get_au_metadata(int fd, struct mp3entry* id3);
+bool get_vox_metadata(int fd, struct mp3entry* id3);
+bool get_wave64_metadata(int fd, struct mp3entry* id3);
+bool get_tta_metadata(int fd, struct mp3entry* id3);
+bool get_ay_metadata(int fd, struct mp3entry* id3);
+bool get_gbs_metadata(int fd, struct mp3entry* id3);
+bool get_hes_metadata(int fd, struct mp3entry* id3);
+bool get_sgc_metadata(int fd, struct mp3entry* id3);
+bool get_vgm_metadata(int fd, struct mp3entry* id3);
+bool get_kss_metadata(int fd, struct mp3entry* id3);
+#endif /* CONFIG_CODEC == SWCODEC */
diff --git a/lib/rbcodec/metadata/mod.c b/lib/rbcodec/metadata/mod.c
new file mode 100644
index 0000000000..de76823e91
--- /dev/null
+++ b/lib/rbcodec/metadata/mod.c
@@ -0,0 +1,103 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include <string-extra.h>
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+#define MODULEHEADERSIZE 0x438
+
+bool get_mod_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char *buf = id3->id3v2buf;
+ unsigned char id[4];
+ bool is_mod_file = false;
+
+ /* Seek to file begin */
+ if (lseek(fd, 0, SEEK_SET) < 0)
+ return false;
+ /* Use id3v2buf as buffer for the track name */
+ if (read(fd, buf, sizeof(id3->id3v2buf)) < (ssize_t)sizeof(id3->id3v2buf))
+ return false;
+ /* Seek to MOD ID position */
+ if (lseek(fd, MODULEHEADERSIZE, SEEK_SET) < 0)
+ return false;
+ /* Read MOD ID */
+ if (read(fd, id, sizeof(id)) < (ssize_t)sizeof(id))
+ return false;
+
+ /* Mod type checking based on MikMod */
+ /* Protracker and variants */
+ if ((!memcmp(id, "M.K.", 4)) || (!memcmp(id, "M!K!", 4))) {
+ is_mod_file = true;
+ }
+
+ /* Star Tracker */
+ if (((!memcmp(id, "FLT", 3)) || (!memcmp(id, "EXO", 3))) &&
+ (isdigit(id[3]))) {
+ char numchn = id[3] - '0';
+ if (numchn == 4 || numchn == 8)
+ is_mod_file = true;
+ }
+
+ /* Oktalyzer (Amiga) */
+ if (!memcmp(id, "OKTA", 4)) {
+ is_mod_file = true;
+ }
+
+ /* Oktalyser (Atari) */
+ if (!memcmp(id, "CD81", 4)) {
+ is_mod_file = true;
+ }
+
+ /* Fasttracker */
+ if ((!memcmp(id + 1, "CHN", 3)) && (isdigit(id[0]))) {
+ is_mod_file = true;
+ }
+ /* Fasttracker or Taketracker */
+ if (((!memcmp(id + 2, "CH", 2)) || (!memcmp(id + 2, "CN", 2)))
+ && (isdigit(id[0])) && (isdigit(id[1]))) {
+ is_mod_file = true;
+ }
+
+ /* Don't try to play if we can't find a known mod type
+ * (there are mod files which have nothing to do with music) */
+ if (!is_mod_file)
+ return false;
+
+ id3->title = id3->id3v2buf; /* Point title to previous read ID3 buffer. */
+ id3->bitrate = filesize(fd)/1024; /* size in kb */
+ id3->frequency = 44100;
+ id3->length = 120*1000;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ return true;
+}
+
diff --git a/lib/rbcodec/metadata/monkeys.c b/lib/rbcodec/metadata/monkeys.c
new file mode 100644
index 0000000000..4aff1412aa
--- /dev/null
+++ b/lib/rbcodec/metadata/monkeys.c
@@ -0,0 +1,97 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+
+bool get_monkeys_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned char* header;
+ bool rc = false;
+ uint32_t descriptorlength;
+ uint32_t totalsamples;
+ uint32_t blocksperframe, finalframeblocks, totalframes;
+ int fileversion;
+
+ lseek(fd, 0, SEEK_SET);
+
+ if (read(fd, buf, 4) < 4)
+ {
+ return rc;
+ }
+
+ if (memcmp(buf, "MAC ", 4) != 0)
+ {
+ return rc;
+ }
+
+ read(fd, buf + 4, MAX_PATH - 4);
+
+ fileversion = get_short_le(buf+4);
+ if (fileversion < 3970)
+ {
+ /* Not supported */
+ return false;
+ }
+
+ if (fileversion >= 3980)
+ {
+ descriptorlength = get_long_le(buf+8);
+
+ header = buf + descriptorlength;
+
+ blocksperframe = get_long_le(header+4);
+ finalframeblocks = get_long_le(header+8);
+ totalframes = get_long_le(header+12);
+ id3->frequency = get_long_le(header+20);
+ }
+ else
+ {
+ /* v3.95 and later files all have a fixed framesize */
+ blocksperframe = 73728 * 4;
+
+ finalframeblocks = get_long_le(buf+28);
+ totalframes = get_long_le(buf+24);
+ id3->frequency = get_long_le(buf+12);
+ }
+
+ id3->vbr = true; /* All APE files are VBR */
+ id3->filesize = filesize(fd);
+
+ totalsamples = finalframeblocks;
+ if (totalframes > 1)
+ totalsamples += blocksperframe * (totalframes-1);
+
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+ id3->bitrate = (id3->filesize * 8) / id3->length;
+
+ read_ape_tags(fd, id3);
+ return true;
+}
diff --git a/lib/rbcodec/metadata/mp3.c b/lib/rbcodec/metadata/mp3.c
new file mode 100644
index 0000000000..feb1a52f77
--- /dev/null
+++ b/lib/rbcodec/metadata/mp3.c
@@ -0,0 +1,193 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Daniel Stenberg
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+/*
+ * Parts of this code has been stolen from the Ample project and was written
+ * by David H�deman. It has since been extended and enhanced pretty much by
+ * all sorts of friendly Rockbox people.
+ *
+ */
+
+ /* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include "string-extra.h"
+#include "config.h"
+#include "file.h"
+#include "logf.h"
+
+#include "system.h"
+#include "metadata.h"
+#include "mp3data.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+
+/*
+ * Calculates the length (in milliseconds) of an MP3 file.
+ *
+ * Modified to only use integers.
+ *
+ * Arguments: file - the file to calculate the length upon
+ * entry - the entry to update with the length
+ *
+ * Returns: the song length in milliseconds,
+ * 0 means that it couldn't be calculated
+ */
+static int getsonglength(int fd, struct mp3entry *entry)
+{
+ unsigned long filetime = 0;
+ struct mp3info info;
+ long bytecount;
+
+ /* Start searching after ID3v2 header */
+ if(-1 == lseek(fd, entry->id3v2len, SEEK_SET))
+ return 0;
+
+ bytecount = get_mp3file_info(fd, &info);
+
+ logf("Space between ID3V2 tag and first audio frame: 0x%lx bytes",
+ bytecount);
+
+ if(bytecount < 0)
+ return -1;
+
+ bytecount += entry->id3v2len;
+
+ /* Validate byte count, in case the file has been edited without
+ * updating the header.
+ */
+ if (info.byte_count)
+ {
+ const unsigned long expected = entry->filesize - entry->id3v1len
+ - entry->id3v2len;
+ const unsigned long diff = MAX(10240, info.byte_count / 20);
+
+ if ((info.byte_count > expected + diff)
+ || (info.byte_count < expected - diff))
+ {
+ logf("Note: info.byte_count differs from expected value by "
+ "%ld bytes", labs((long) (expected - info.byte_count)));
+ info.byte_count = 0;
+ info.frame_count = 0;
+ info.file_time = 0;
+ info.enc_padding = 0;
+
+ /* Even if the bitrate was based on "known bad" values, it
+ * should still be better for VBR files than using the bitrate
+ * of the first audio frame.
+ */
+ }
+ }
+
+ entry->bitrate = info.bitrate;
+ entry->frequency = info.frequency;
+ entry->layer = info.layer;
+ switch(entry->layer) {
+#if CONFIG_CODEC==SWCODEC
+ case 0:
+ entry->codectype=AFMT_MPA_L1;
+ break;
+#endif
+ case 1:
+ entry->codectype=AFMT_MPA_L2;
+ break;
+ case 2:
+ entry->codectype=AFMT_MPA_L3;
+ break;
+ }
+
+ /* If the file time hasn't been established, this may be a fixed
+ rate MP3, so just use the default formula */
+
+ filetime = info.file_time;
+
+ if(filetime == 0)
+ {
+ /* Prevent a division by zero */
+ if (info.bitrate < 8)
+ filetime = 0;
+ else
+ filetime = (entry->filesize - bytecount) / (info.bitrate / 8);
+ /* bitrate is in kbps so this delivers milliseconds. Doing bitrate / 8
+ * instead of filesize * 8 is exact, because mpeg audio bitrates are
+ * always multiples of 8, and it avoids overflows. */
+ }
+
+ entry->frame_count = info.frame_count;
+
+ entry->vbr = info.is_vbr;
+ entry->has_toc = info.has_toc;
+
+#if CONFIG_CODEC==SWCODEC
+ if (!entry->lead_trim)
+ entry->lead_trim = info.enc_delay;
+ if (!entry->tail_trim)
+ entry->tail_trim = info.enc_padding;
+#endif
+
+ memcpy(entry->toc, info.toc, sizeof(info.toc));
+
+ /* Update the seek point for the first playable frame */
+ entry->first_frame_offset = bytecount;
+ logf("First frame is at %lx", entry->first_frame_offset);
+
+ return filetime;
+}
+
+/*
+ * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc)
+ * about an MP3 file and updates it's entry accordingly.
+ *
+ Note, that this returns true for successful, false for error! */
+bool get_mp3_metadata(int fd, struct mp3entry *entry)
+{
+ entry->title = NULL;
+ entry->filesize = filesize(fd);
+ entry->id3v2len = getid3v2len(fd);
+ entry->tracknum = 0;
+ entry->discnum = 0;
+
+ if (entry->id3v2len)
+ setid3v2title(fd, entry);
+ int len = getsonglength(fd, entry);
+ if (len < 0)
+ return false;
+ entry->length = len;
+
+ /* Subtract the meta information from the file size to get
+ the true size of the MP3 stream */
+ entry->filesize -= entry->first_frame_offset;
+
+ /* only seek to end of file if no id3v2 tags were found */
+ if (!entry->id3v2len) {
+ setid3v1title(fd, entry);
+ }
+
+ if(!entry->length || (entry->filesize < 8 ))
+ /* no song length or less than 8 bytes is hereby considered to be an
+ invalid mp3 and won't be played by us! */
+ return false;
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/mp3data.c b/lib/rbcodec/metadata/mp3data.c
new file mode 100644
index 0000000000..13ff0a87a7
--- /dev/null
+++ b/lib/rbcodec/metadata/mp3data.c
@@ -0,0 +1,849 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Daniel Stenberg
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+/*
+ * Parts of this code has been stolen from the Ample project and was written
+ * by David Härdeman. It has since been extended and enhanced pretty much by
+ * all sorts of friendly Rockbox people.
+ *
+ * A nice reference for MPEG header info:
+ * http://rockbox.haxx.se/docs/mpeghdr.html
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <limits.h>
+#include "debug.h"
+#include "logf.h"
+#include "mp3data.h"
+#include "file.h"
+#include "system.h"
+
+//#define DEBUG_VERBOSE
+
+#ifdef DEBUG_VERBOSE
+#define VDEBUGF DEBUGF
+#else
+#define VDEBUGF(...) do { } while(0)
+#endif
+
+#define SYNC_MASK (0x7ffL << 21)
+#define VERSION_MASK (3L << 19)
+#define LAYER_MASK (3L << 17)
+#define PROTECTION_MASK (1L << 16)
+#define BITRATE_MASK (0xfL << 12)
+#define SAMPLERATE_MASK (3L << 10)
+#define PADDING_MASK (1L << 9)
+#define PRIVATE_MASK (1L << 8)
+#define CHANNELMODE_MASK (3L << 6)
+#define MODE_EXT_MASK (3L << 4)
+#define COPYRIGHT_MASK (1L << 3)
+#define ORIGINAL_MASK (1L << 2)
+#define EMPHASIS_MASK (3L)
+
+/* Maximum number of bytes needed by Xing/Info/VBRI parser. */
+#define VBR_HEADER_MAX_SIZE (180)
+
+/* MPEG Version table, sorted by version index */
+static const signed char version_table[4] = {
+ MPEG_VERSION2_5, -1, MPEG_VERSION2, MPEG_VERSION1
+};
+
+/* Bitrate table for mpeg audio, indexed by row index and birate index */
+static const short bitrates[5][16] = {
+ {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, /* V1 L1 */
+ {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,0}, /* V1 L2 */
+ {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,0}, /* V1 L3 */
+ {0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256,0}, /* V2 L1 */
+ {0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160,0} /* V2 L2+L3 */
+};
+
+/* Bitrate pointer table, indexed by version and layer */
+static const short *bitrate_table[3][3] =
+{
+ {bitrates[0], bitrates[1], bitrates[2]},
+ {bitrates[3], bitrates[4], bitrates[4]},
+ {bitrates[3], bitrates[4], bitrates[4]}
+};
+
+/* Sampling frequency table, indexed by version and frequency index */
+static const unsigned short freq_table[3][3] =
+{
+ {44100, 48000, 32000}, /* MPEG Version 1 */
+ {22050, 24000, 16000}, /* MPEG version 2 */
+ {11025, 12000, 8000}, /* MPEG version 2.5 */
+};
+
+unsigned long bytes2int(unsigned long b0, unsigned long b1,
+ unsigned long b2, unsigned long b3)
+{
+ return (b0 & 0xFF) << (3*8) |
+ (b1 & 0xFF) << (2*8) |
+ (b2 & 0xFF) << (1*8) |
+ (b3 & 0xFF) << (0*8);
+}
+
+/* check if 'head' is a valid mp3 frame header */
+static bool is_mp3frameheader(unsigned long head)
+{
+ if ((head & SYNC_MASK) != (unsigned long)SYNC_MASK) /* bad sync? */
+ return false;
+ if ((head & VERSION_MASK) == (1L << 19)) /* bad version? */
+ return false;
+ if (!(head & LAYER_MASK)) /* no layer? */
+ return false;
+#if CONFIG_CODEC != SWCODEC
+ /* The MAS can't decode layer 1, so treat layer 1 data as invalid */
+ if ((head & LAYER_MASK) == LAYER_MASK)
+ return false;
+#endif
+ if ((head & BITRATE_MASK) == BITRATE_MASK) /* bad bitrate? */
+ return false;
+ if (!(head & BITRATE_MASK)) /* no bitrate? */
+ return false;
+ if ((head & SAMPLERATE_MASK) == SAMPLERATE_MASK) /* bad sample rate? */
+ return false;
+
+ return true;
+}
+
+static bool mp3headerinfo(struct mp3info *info, unsigned long header)
+{
+ int bitindex, freqindex;
+
+ /* MPEG Audio Version */
+ if ((header & VERSION_MASK) >> 19 >= sizeof(version_table))
+ return false;
+
+ info->version = version_table[(header & VERSION_MASK) >> 19];
+ if (info->version < 0)
+ return false;
+
+ /* Layer */
+ info->layer = 3 - ((header & LAYER_MASK) >> 17);
+ if (info->layer == 3)
+ return false;
+
+/* Rockbox: not used
+ info->protection = (header & PROTECTION_MASK) ? true : false;
+*/
+
+ /* Bitrate */
+ bitindex = (header & BITRATE_MASK) >> 12;
+ info->bitrate = bitrate_table[info->version][info->layer][bitindex];
+ if(info->bitrate == 0)
+ return false;
+
+ /* Sampling frequency */
+ freqindex = (header & SAMPLERATE_MASK) >> 10;
+ if (freqindex == 3)
+ return false;
+ info->frequency = freq_table[info->version][freqindex];
+
+ info->padding = (header & PADDING_MASK) ? 1 : 0;
+
+ /* Calculate number of bytes, calculation depends on layer */
+ if (info->layer == 0) {
+ info->frame_samples = 384;
+ info->frame_size = (12000 * info->bitrate / info->frequency
+ + info->padding) * 4;
+ }
+ else {
+ if ((info->version > MPEG_VERSION1) && (info->layer == 2))
+ info->frame_samples = 576;
+ else
+ info->frame_samples = 1152;
+ info->frame_size = (1000/8) * info->frame_samples * info->bitrate
+ / info->frequency + info->padding;
+ }
+
+ /* Frametime fraction denominator */
+ if (freqindex != 0) { /* 48/32/24/16/12/8 kHz */
+ info->ft_den = 1; /* integer number of milliseconds */
+ }
+ else { /* 44.1/22.05/11.025 kHz */
+ if (info->layer == 0) /* layer 1 */
+ info->ft_den = 147;
+ else /* layer 2+3 */
+ info->ft_den = 49;
+ }
+ /* Frametime fraction numerator */
+ info->ft_num = 1000 * info->ft_den * info->frame_samples / info->frequency;
+
+ info->channel_mode = (header & CHANNELMODE_MASK) >> 6;
+/* Rockbox: not used
+ info->mode_extension = (header & MODE_EXT_MASK) >> 4;
+ info->emphasis = header & EMPHASIS_MASK;
+*/
+ VDEBUGF( "Header: %08lx, Ver %d, lay %d, bitr %d, freq %ld, "
+ "chmode %d, bytes: %d time: %d/%d\n",
+ header, info->version, info->layer+1, info->bitrate,
+ info->frequency, info->channel_mode,
+ info->frame_size, info->ft_num, info->ft_den);
+ return true;
+}
+
+static bool headers_have_same_type(unsigned long header1,
+ unsigned long header2)
+{
+ /* Compare MPEG version, layer and sampling frequency. If header1 is zero
+ * it is assumed both frame headers are of same type. */
+ unsigned int mask = SYNC_MASK | VERSION_MASK | LAYER_MASK | SAMPLERATE_MASK;
+ header1 &= mask;
+ header2 &= mask;
+ return header1 ? (header1 == header2) : true;
+}
+
+/* Helper function to read 4-byte in big endian format. */
+static void read_uint32be_mp3data(int fd, unsigned long *data)
+{
+#ifdef ROCKBOX_BIG_ENDIAN
+ (void)read(fd, (char*)data, 4);
+#else
+ (void)read(fd, (char*)data, 4);
+ *data = betoh32(*data);
+#endif
+}
+
+static unsigned long __find_next_frame(int fd, long *offset, long max_offset,
+ unsigned long reference_header,
+ int(*getfunc)(int fd, unsigned char *c),
+ bool single_header)
+{
+ unsigned long header=0;
+ unsigned char tmp;
+ long pos = 0;
+
+ /* We will search until we find two consecutive MPEG frame headers with
+ * the same MPEG version, layer and sampling frequency. The first header
+ * of this pair is assumed to be the first valid MPEG frame header of the
+ * whole stream. */
+ do {
+ /* Read 1 new byte. */
+ header <<= 8;
+ if (!getfunc(fd, &tmp))
+ return 0;
+ header |= tmp;
+ pos++;
+
+ /* Abort if max_offset is reached. Stop parsing. */
+ if (max_offset > 0 && pos > max_offset)
+ return 0;
+
+ if (is_mp3frameheader(header)) {
+ if (single_header) {
+ /* We search for one _single_ valid header that has the same
+ * type as the reference_header (if reference_header != 0).
+ * In this case we are finished. */
+ if (headers_have_same_type(reference_header, header))
+ break;
+ } else {
+ /* The current header is valid. Now gather the frame size,
+ * seek to this byte position and check if there is another
+ * valid MPEG frame header of the same type. */
+ struct mp3info info;
+
+ /* Gather frame size from given header and seek to next
+ * frame header. */
+ mp3headerinfo(&info, header);
+ lseek(fd, info.frame_size-4, SEEK_CUR);
+
+ /* Read possible next frame header and seek back to last frame
+ * headers byte position. */
+ reference_header = 0;
+ read_uint32be_mp3data(fd, &reference_header);
+ //
+ lseek(fd, -info.frame_size, SEEK_CUR);
+
+ /* If the current header is of the same type as the previous
+ * header we are finished. */
+ if (headers_have_same_type(header, reference_header))
+ break;
+ }
+ }
+
+ } while (true);
+
+ *offset = pos - 4;
+
+ if(*offset)
+ VDEBUGF("Warning: skipping %ld bytes of garbage\n", *offset);
+
+ return header;
+}
+
+static int fileread(int fd, unsigned char *c)
+{
+ return read(fd, c, 1);
+}
+
+unsigned long find_next_frame(int fd,
+ long *offset,
+ long max_offset,
+ unsigned long reference_header)
+{
+ return __find_next_frame(fd, offset, max_offset, reference_header,
+ fileread, true);
+}
+
+#ifndef __PCTOOL__
+static int fnf_read_index;
+static int fnf_buf_len;
+static unsigned char *fnf_buf;
+
+static int buf_getbyte(int fd, unsigned char *c)
+{
+ if(fnf_read_index < fnf_buf_len)
+ {
+ *c = fnf_buf[fnf_read_index++];
+ return 1;
+ }
+ else
+ {
+ fnf_buf_len = read(fd, fnf_buf, fnf_buf_len);
+ if(fnf_buf_len < 0)
+ return -1;
+
+ fnf_read_index = 0;
+
+ if(fnf_buf_len > 0)
+ {
+ *c = fnf_buf[fnf_read_index++];
+ return 1;
+ }
+ else
+ return 0;
+ }
+ return 0;
+}
+
+static int buf_seek(int fd, int len)
+{
+ fnf_read_index += len;
+ if(fnf_read_index > fnf_buf_len)
+ {
+ len = fnf_read_index - fnf_buf_len;
+
+ fnf_buf_len = read(fd, fnf_buf, fnf_buf_len);
+ if(fnf_buf_len < 0)
+ return -1;
+
+ fnf_read_index = 0;
+ fnf_read_index += len;
+ }
+
+ if(fnf_read_index > fnf_buf_len)
+ {
+ return -1;
+ }
+ else
+ return 0;
+}
+
+static void buf_init(unsigned char* buf, size_t buflen)
+{
+ fnf_buf = buf;
+ fnf_buf_len = buflen;
+ fnf_read_index = 0;
+}
+
+static unsigned long buf_find_next_frame(int fd, long *offset, long max_offset)
+{
+ return __find_next_frame(fd, offset, max_offset, 0, buf_getbyte, true);
+}
+
+static size_t mem_buflen;
+static unsigned char* mem_buf;
+static size_t mem_pos;
+static int mem_cnt;
+static int mem_maxlen;
+
+static int mem_getbyte(int dummy, unsigned char *c)
+{
+ (void)dummy;
+
+ *c = mem_buf[mem_pos++];
+ if(mem_pos >= mem_buflen)
+ mem_pos = 0;
+
+ if(mem_cnt++ >= mem_maxlen)
+ return 0;
+ else
+ return 1;
+}
+
+unsigned long mem_find_next_frame(int startpos,
+ long *offset,
+ long max_offset,
+ unsigned long reference_header,
+ unsigned char* buf, size_t buflen)
+{
+ mem_buf = buf;
+ mem_buflen = buflen;
+ mem_pos = startpos;
+ mem_cnt = 0;
+ mem_maxlen = max_offset;
+
+ return __find_next_frame(0, offset, max_offset, reference_header,
+ mem_getbyte, true);
+}
+#endif
+
+/* Extract information from a 'Xing' or 'Info' header. */
+static void get_xing_info(struct mp3info *info, unsigned char *buf)
+{
+ int i = 8;
+
+ /* Is it a VBR file? */
+ info->is_vbr = !memcmp(buf, "Xing", 4);
+
+ if (buf[7] & VBR_FRAMES_FLAG) /* Is the frame count there? */
+ {
+ info->frame_count = bytes2int(buf[i], buf[i+1], buf[i+2], buf[i+3]);
+ if (info->frame_count <= ULONG_MAX / info->ft_num)
+ info->file_time = info->frame_count * info->ft_num / info->ft_den;
+ else
+ info->file_time = info->frame_count / info->ft_den * info->ft_num;
+ i += 4;
+ }
+
+ if (buf[7] & VBR_BYTES_FLAG) /* Is byte count there? */
+ {
+ info->byte_count = bytes2int(buf[i], buf[i+1], buf[i+2], buf[i+3]);
+ i += 4;
+ }
+
+ if (info->file_time && info->byte_count)
+ {
+ if (info->byte_count <= (ULONG_MAX/8))
+ info->bitrate = info->byte_count * 8 / info->file_time;
+ else
+ info->bitrate = info->byte_count / (info->file_time >> 3);
+ }
+
+ if (buf[7] & VBR_TOC_FLAG) /* Is table-of-contents there? */
+ {
+ info->has_toc = true;
+ memcpy( info->toc, buf+i, 100 );
+ i += 100;
+ }
+ if (buf[7] & VBR_QUALITY_FLAG)
+ {
+ /* We don't care about this, but need to skip it */
+ i += 4;
+ }
+#if CONFIG_CODEC==SWCODEC
+ i += 21;
+ info->enc_delay = ((int)buf[i ] << 4) | (buf[i+1] >> 4);
+ info->enc_padding = ((int)(buf[i+1]&0xF) << 8) | buf[i+2];
+ /* TODO: This sanity checking is rather silly, seeing as how the LAME
+ header contains a CRC field that can be used to verify integrity. */
+ if (!(info->enc_delay >= 0 && info->enc_delay <= 2880 &&
+ info->enc_padding >= 0 && info->enc_padding <= 2*1152))
+ {
+ /* Invalid data */
+ info->enc_delay = -1;
+ info->enc_padding = -1;
+ }
+#endif
+}
+
+/* Extract information from a 'VBRI' header. */
+static void get_vbri_info(struct mp3info *info, unsigned char *buf)
+{
+ /* We don't parse the TOC, since we don't yet know how to (FIXME) */
+ /*
+ int i, num_offsets, offset = 0;
+ */
+
+ info->is_vbr = true; /* Yes, it is a FhG VBR file */
+ info->has_toc = false; /* We don't parse the TOC (yet) */
+
+ info->byte_count = bytes2int(buf[10], buf[11], buf[12], buf[13]);
+ info->frame_count = bytes2int(buf[14], buf[15], buf[16], buf[17]);
+ if (info->frame_count <= ULONG_MAX / info->ft_num)
+ info->file_time = info->frame_count * info->ft_num / info->ft_den;
+ else
+ info->file_time = info->frame_count / info->ft_den * info->ft_num;
+
+ if (info->byte_count <= (ULONG_MAX/8))
+ info->bitrate = info->byte_count * 8 / info->file_time;
+ else
+ info->bitrate = info->byte_count / (info->file_time >> 3);
+
+ VDEBUGF("Frame size (%dkpbs): %d bytes (0x%x)\n",
+ info->bitrate, info->frame_size, info->frame_size);
+ VDEBUGF("Frame count: %lx\n", info->frame_count);
+ VDEBUGF("Byte count: %lx\n", info->byte_count);
+
+ /* We don't parse the TOC, since we don't yet know how to (FIXME) */
+ /*
+ num_offsets = bytes2int(0, 0, buf[18], buf[19]);
+ VDEBUGF("Offsets: %d\n", num_offsets);
+ VDEBUGF("Frames/entry: %ld\n", bytes2int(0, 0, buf[24], buf[25]));
+
+ for(i = 0; i < num_offsets; i++)
+ {
+ offset += bytes2int(0, 0, buf[26+i*2], buf[27+i*2]);;
+ VDEBUGF("%03d: %lx\n", i, offset - bytecount,);
+ }
+ */
+}
+
+/* Seek to next mpeg header and extract relevant information. */
+static int get_next_header_info(int fd, long *bytecount, struct mp3info *info,
+ bool single_header)
+{
+ long tmp;
+ unsigned long header = 0;
+
+ header = __find_next_frame(fd, &tmp, 0x20000, 0, fileread, single_header);
+ if(header == 0)
+ return -1;
+
+ if(!mp3headerinfo(info, header))
+ return -2;
+
+ /* Next frame header is tmp bytes away. */
+ *bytecount += tmp;
+
+ return 0;
+}
+
+int get_mp3file_info(int fd, struct mp3info *info)
+{
+ unsigned char frame[VBR_HEADER_MAX_SIZE], *vbrheader;
+ long bytecount = 0;
+ int result, buf_size;
+
+ /* Initialize info and frame */
+ memset(info, 0, sizeof(struct mp3info));
+ memset(frame, 0, sizeof(frame));
+
+#if CONFIG_CODEC==SWCODEC
+ /* These two are needed for proper LAME gapless MP3 playback */
+ info->enc_delay = -1;
+ info->enc_padding = -1;
+#endif
+
+ /* Get the very first single MPEG frame. */
+ result = get_next_header_info(fd, &bytecount, info, true);
+ if(result)
+ return result;
+
+ /* Read the amount of frame data to the buffer that is required for the
+ * vbr tag parsing. Skip the rest. */
+ buf_size = MIN(info->frame_size-4, (int)sizeof(frame));
+ if(read(fd, frame, buf_size) < 0)
+ return -3;
+ lseek(fd, info->frame_size - 4 - buf_size, SEEK_CUR);
+
+ /* Calculate position of a possible VBR header */
+ if (info->version == MPEG_VERSION1) {
+ if (info->channel_mode == 3) /* mono */
+ vbrheader = frame + 17;
+ else
+ vbrheader = frame + 32;
+ } else {
+ if (info->channel_mode == 3) /* mono */
+ vbrheader = frame + 9;
+ else
+ vbrheader = frame + 17;
+ }
+
+ if (!memcmp(vbrheader, "Xing", 4) || !memcmp(vbrheader, "Info", 4))
+ {
+ VDEBUGF("-- XING header --\n");
+
+ /* We want to skip the Xing frame when playing the stream */
+ bytecount += info->frame_size;
+
+ /* Now get the next frame to read the real info about the mp3 stream */
+ result = get_next_header_info(fd, &bytecount, info, false);
+ if(result)
+ return result;
+
+ get_xing_info(info, vbrheader);
+ }
+ else if (!memcmp(vbrheader, "VBRI", 4))
+ {
+ VDEBUGF("-- VBRI header --\n");
+
+ /* We want to skip the VBRI frame when playing the stream */
+ bytecount += info->frame_size;
+
+ /* Now get the next frame to read the real info about the mp3 stream */
+ result = get_next_header_info(fd, &bytecount, info, false);
+ if(result)
+ return result;
+
+ get_vbri_info(info, vbrheader);
+ }
+ else
+ {
+ VDEBUGF("-- No VBR header --\n");
+
+ /* There was no VBR header found. So, we seek back to beginning and
+ * search for the first MPEG frame header of the mp3 stream. */
+ lseek(fd, -info->frame_size, SEEK_CUR);
+ result = get_next_header_info(fd, &bytecount, info, false);
+ if(result)
+ return result;
+ }
+
+ return bytecount;
+}
+
+#ifndef __PCTOOL__
+static void long2bytes(unsigned char *buf, long val)
+{
+ buf[0] = (val >> 24) & 0xff;
+ buf[1] = (val >> 16) & 0xff;
+ buf[2] = (val >> 8) & 0xff;
+ buf[3] = val & 0xff;
+}
+
+int count_mp3_frames(int fd, int startpos, int filesize,
+ void (*progressfunc)(int),
+ unsigned char* buf, size_t buflen)
+{
+ unsigned long header = 0;
+ struct mp3info info;
+ int num_frames;
+ long bytes;
+ int cnt;
+ long progress_chunk = filesize / 50; /* Max is 50%, in 1% increments */
+ int progress_cnt = 0;
+ bool is_vbr = false;
+ int last_bitrate = 0;
+ int header_template = 0;
+
+ if(lseek(fd, startpos, SEEK_SET) < 0)
+ return -1;
+
+ buf_init(buf, buflen);
+
+ /* Find out the total number of frames */
+ num_frames = 0;
+ cnt = 0;
+
+ while((header = buf_find_next_frame(fd, &bytes, header_template))) {
+ mp3headerinfo(&info, header);
+
+ if(!header_template)
+ header_template = header;
+
+ /* See if this really is a VBR file */
+ if(last_bitrate && info.bitrate != last_bitrate)
+ {
+ is_vbr = true;
+ }
+ last_bitrate = info.bitrate;
+
+ buf_seek(fd, info.frame_size-4);
+ num_frames++;
+ if(progressfunc)
+ {
+ cnt += bytes + info.frame_size;
+ if(cnt > progress_chunk)
+ {
+ progress_cnt++;
+ progressfunc(progress_cnt);
+ cnt = 0;
+ }
+ }
+ }
+ VDEBUGF("Total number of frames: %d\n", num_frames);
+
+ if(is_vbr)
+ return num_frames;
+ else
+ {
+ DEBUGF("Not a VBR file\n");
+ return 0;
+ }
+}
+
+static const char cooltext[] = "Rockbox - rocks your box";
+
+/* buf needs to be the audio buffer with TOC generation enabled,
+ and at least MAX_XING_HEADER_SIZE bytes otherwise */
+int create_xing_header(int fd, long startpos, long filesize,
+ unsigned char *buf, unsigned long num_frames,
+ unsigned long rec_time, unsigned long header_template,
+ void (*progressfunc)(int), bool generate_toc,
+ unsigned char *tempbuf, size_t tempbuflen )
+{
+ struct mp3info info;
+ unsigned char toc[100];
+ unsigned long header = 0;
+ unsigned long xing_header_template = header_template;
+ unsigned long filepos;
+ long pos, last_pos;
+ long j;
+ long bytes;
+ int i;
+ int index;
+
+ DEBUGF("create_xing_header()\n");
+
+ if(generate_toc)
+ {
+ lseek(fd, startpos, SEEK_SET);
+ buf_init(tempbuf, tempbuflen);
+
+ /* Generate filepos table */
+ last_pos = 0;
+ filepos = 0;
+ header = 0;
+ for(i = 0;i < 100;i++) {
+ /* Calculate the absolute frame number for this seek point */
+ pos = i * num_frames / 100;
+
+ /* Advance from the last seek point to this one */
+ for(j = 0;j < pos - last_pos;j++)
+ {
+ header = buf_find_next_frame(fd, &bytes, header_template);
+ filepos += bytes;
+ mp3headerinfo(&info, header);
+ buf_seek(fd, info.frame_size-4);
+ filepos += info.frame_size;
+
+ if(!header_template)
+ header_template = header;
+ }
+
+ /* Save a header for later use if header_template is empty.
+ We only save one header, and we want to save one in the
+ middle of the stream, just in case the first and the last
+ headers are corrupt. */
+ if(!xing_header_template && i == 1)
+ xing_header_template = header;
+
+ if(progressfunc)
+ {
+ progressfunc(50 + i/2);
+ }
+
+ /* Fill in the TOC entry */
+ /* each toc is a single byte indicating how many 256ths of the
+ * way through the file, is that percent of the way through the
+ * song. the easy method, filepos*256/filesize, chokes when
+ * the upper 8 bits of the file position are nonzero
+ * (i.e. files over 16mb in size).
+ */
+ if (filepos > (ULONG_MAX/256))
+ {
+ /* instead of multiplying filepos by 256, we divide
+ * filesize by 256.
+ */
+ toc[i] = filepos / (filesize >> 8);
+ }
+ else
+ {
+ toc[i] = filepos * 256 / filesize;
+ }
+
+ VDEBUGF("Pos %d: %ld relpos: %ld filepos: %lx tocentry: %x\n",
+ i, pos, pos-last_pos, filepos, toc[i]);
+
+ last_pos = pos;
+ }
+ }
+
+ /* Use the template header and create a new one.
+ We ignore the Protection bit even if the rest of the stream is
+ protected. */
+ header = xing_header_template & ~(BITRATE_MASK|PROTECTION_MASK|PADDING_MASK);
+ header |= 8 << 12; /* This gives us plenty of space, 192..576 bytes */
+
+ if (!mp3headerinfo(&info, header))
+ return 0; /* invalid header */
+
+ if (num_frames == 0 && rec_time) {
+ /* estimate the number of frames based on the recording time */
+ if (rec_time <= ULONG_MAX / info.ft_den)
+ num_frames = rec_time * info.ft_den / info.ft_num;
+ else
+ num_frames = rec_time / info.ft_num * info.ft_den;
+ }
+
+ /* Clear the frame */
+ memset(buf, 0, MAX_XING_HEADER_SIZE);
+
+ /* Write the header to the buffer */
+ long2bytes(buf, header);
+
+ /* Calculate position of VBR header */
+ if (info.version == MPEG_VERSION1) {
+ if (info.channel_mode == 3) /* mono */
+ index = 21;
+ else
+ index = 36;
+ }
+ else {
+ if (info.channel_mode == 3) /* mono */
+ index = 13;
+ else
+ index = 21;
+ }
+
+ /* Create the Xing data */
+ memcpy(&buf[index], "Xing", 4);
+ long2bytes(&buf[index+4], (num_frames ? VBR_FRAMES_FLAG : 0)
+ | (filesize ? VBR_BYTES_FLAG : 0)
+ | (generate_toc ? VBR_TOC_FLAG : 0));
+ index += 8;
+ if(num_frames)
+ {
+ long2bytes(&buf[index], num_frames);
+ index += 4;
+ }
+
+ if(filesize)
+ {
+ long2bytes(&buf[index], filesize - startpos);
+ index += 4;
+ }
+
+ /* Copy the TOC */
+ memcpy(buf + index, toc, 100);
+
+ /* And some extra cool info */
+ memcpy(buf + index + 100, cooltext, sizeof(cooltext));
+
+#ifdef DEBUG
+ for(i = 0;i < info.frame_size;i++)
+ {
+ if(i && !(i % 16))
+ DEBUGF("\n");
+
+ DEBUGF("%02x ", buf[i]);
+ }
+#endif
+
+ return info.frame_size;
+}
+
+#endif
diff --git a/lib/rbcodec/metadata/mp3data.h b/lib/rbcodec/metadata/mp3data.h
new file mode 100644
index 0000000000..762c2f4583
--- /dev/null
+++ b/lib/rbcodec/metadata/mp3data.h
@@ -0,0 +1,89 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Linus Nielsen Feltzing
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _MP3DATA_H_
+#define _MP3DATA_H_
+
+#define MPEG_VERSION1 0
+#define MPEG_VERSION2 1
+#define MPEG_VERSION2_5 2
+
+#include <string.h> /* size_t */
+
+struct mp3info {
+ /* Standard MP3 frame header fields */
+ int version;
+ int layer;
+ int bitrate;
+ long frequency;
+ int padding;
+ int channel_mode;
+ int frame_size; /* Frame size in bytes */
+ int frame_samples;/* Samples per frame */
+ int ft_num; /* Numerator of frametime in milliseconds */
+ int ft_den; /* Denominator of frametime in milliseconds */
+
+ bool is_vbr; /* True if the file is VBR */
+ bool has_toc; /* True if there is a VBR header in the file */
+ unsigned char toc[100];
+ unsigned long frame_count; /* Number of frames in the file (if VBR) */
+ unsigned long byte_count; /* File size in bytes */
+ unsigned long file_time; /* Length of the whole file in milliseconds */
+ int enc_delay; /* Encoder delay, fetched from LAME header */
+ int enc_padding; /* Padded samples added to last frame. LAME header */
+};
+
+/* Xing header information */
+#define VBR_FRAMES_FLAG 0x01
+#define VBR_BYTES_FLAG 0x02
+#define VBR_TOC_FLAG 0x04
+#define VBR_QUALITY_FLAG 0x08
+
+#define MAX_XING_HEADER_SIZE 576
+
+unsigned long find_next_frame(int fd,
+ long *offset,
+ long max_offset,
+ unsigned long reference_header);
+unsigned long mem_find_next_frame(int startpos,
+ long *offset,
+ long max_offset,
+ unsigned long reference_header,
+ unsigned char* buf, size_t buflen);
+int get_mp3file_info(int fd,
+ struct mp3info *info);
+
+int count_mp3_frames(int fd, int startpos, int filesize,
+ void (*progressfunc)(int),
+ unsigned char* buf, size_t buflen);
+
+int create_xing_header(int fd, long startpos, long filesize,
+ unsigned char *buf, unsigned long num_frames,
+ unsigned long rec_time, unsigned long header_template,
+ void (*progressfunc)(int), bool generate_toc,
+ unsigned char *tempbuf, size_t tempbuflen );
+
+extern unsigned long bytes2int(unsigned long b0,
+ unsigned long b1,
+ unsigned long b2,
+ unsigned long b3);
+
+#endif
diff --git a/lib/rbcodec/metadata/mp4.c b/lib/rbcodec/metadata/mp4.c
new file mode 100644
index 0000000000..df164436f5
--- /dev/null
+++ b/lib/rbcodec/metadata/mp4.c
@@ -0,0 +1,842 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Magnus Holmgren
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "errno.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+#include "debug.h"
+#include "replaygain.h"
+
+#ifdef DEBUGF
+#undef DEBUGF
+#define DEBUGF(...)
+#endif
+
+#define MP4_3gp6 FOURCC('3', 'g', 'p', '6')
+#define MP4_aART FOURCC('a', 'A', 'R', 'T')
+#define MP4_alac FOURCC('a', 'l', 'a', 'c')
+#define MP4_calb FOURCC(0xa9, 'a', 'l', 'b')
+#define MP4_cART FOURCC(0xa9, 'A', 'R', 'T')
+#define MP4_cgrp FOURCC(0xa9, 'g', 'r', 'p')
+#define MP4_cgen FOURCC(0xa9, 'g', 'e', 'n')
+#define MP4_chpl FOURCC('c', 'h', 'p', 'l')
+#define MP4_cnam FOURCC(0xa9, 'n', 'a', 'm')
+#define MP4_cwrt FOURCC(0xa9, 'w', 'r', 't')
+#define MP4_ccmt FOURCC(0xa9, 'c', 'm', 't')
+#define MP4_cday FOURCC(0xa9, 'd', 'a', 'y')
+#define MP4_covr FOURCC('c', 'o', 'v', 'r')
+#define MP4_disk FOURCC('d', 'i', 's', 'k')
+#define MP4_esds FOURCC('e', 's', 'd', 's')
+#define MP4_ftyp FOURCC('f', 't', 'y', 'p')
+#define MP4_gnre FOURCC('g', 'n', 'r', 'e')
+#define MP4_hdlr FOURCC('h', 'd', 'l', 'r')
+#define MP4_ilst FOURCC('i', 'l', 's', 't')
+#define MP4_isom FOURCC('i', 's', 'o', 'm')
+#define MP4_M4A FOURCC('M', '4', 'A', ' ')
+#define MP4_m4a FOURCC('m', '4', 'a', ' ') /*technically its "M4A "*/
+#define MP4_M4B FOURCC('M', '4', 'B', ' ') /*but files exist with lower case*/
+#define MP4_mdat FOURCC('m', 'd', 'a', 't')
+#define MP4_mdia FOURCC('m', 'd', 'i', 'a')
+#define MP4_mdir FOURCC('m', 'd', 'i', 'r')
+#define MP4_meta FOURCC('m', 'e', 't', 'a')
+#define MP4_minf FOURCC('m', 'i', 'n', 'f')
+#define MP4_moov FOURCC('m', 'o', 'o', 'v')
+#define MP4_mp4a FOURCC('m', 'p', '4', 'a')
+#define MP4_mp42 FOURCC('m', 'p', '4', '2')
+#define MP4_qt FOURCC('q', 't', ' ', ' ')
+#define MP4_soun FOURCC('s', 'o', 'u', 'n')
+#define MP4_stbl FOURCC('s', 't', 'b', 'l')
+#define MP4_stsd FOURCC('s', 't', 's', 'd')
+#define MP4_stts FOURCC('s', 't', 't', 's')
+#define MP4_trak FOURCC('t', 'r', 'a', 'k')
+#define MP4_trkn FOURCC('t', 'r', 'k', 'n')
+#define MP4_udta FOURCC('u', 'd', 't', 'a')
+#define MP4_extra FOURCC('-', '-', '-', '-')
+
+/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
+ * buffer.
+ */
+static unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
+ unsigned int buffer_left)
+{
+ unsigned int bytes_read = 0;
+
+ if (buffer_left == 0)
+ {
+ lseek(fd, size_left, SEEK_CUR); /* Skip everything */
+ }
+ else
+ {
+ /* Skip the data tag header - maybe we should parse it properly? */
+ lseek(fd, 16, SEEK_CUR);
+ size_left -= 16;
+
+ if (size_left > buffer_left)
+ {
+ read(fd, buffer, buffer_left);
+ lseek(fd, size_left - buffer_left, SEEK_CUR);
+ bytes_read = buffer_left;
+ }
+ else
+ {
+ read(fd, buffer, size_left);
+ bytes_read = size_left;
+ }
+ }
+
+ return bytes_read;
+}
+
+/* Read a string tag from an MP4 file */
+static unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer,
+ unsigned int* buffer_left, char** dest)
+{
+ unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
+ *buffer_left > 0 ? *buffer_left - 1 : 0);
+ unsigned int length = 0;
+
+ if (bytes_read)
+ {
+ /* Do not overwrite already available metadata. Especially when reading
+ * tags with e.g. multiple genres / artists. This way only the first
+ * of multiple entries is used, all following are dropped. */
+ if (*dest == NULL)
+ {
+ (*buffer)[bytes_read] = 0; /* zero-terminate for correct strlen().*/
+ length = strlen(*buffer) + 1;
+ length = MIN(length, ID3V2_MAX_ITEM_SIZE); /* Limit item size. */
+
+ *dest = *buffer;
+ (*buffer)[length-1] = 0; /* zero-terminate buffer. */
+ *buffer_left -= length;
+ *buffer += length;
+ }
+ }
+ else
+ {
+ *dest = NULL;
+ }
+
+ return length;
+}
+
+static unsigned int read_mp4_atom(int fd, uint32_t* size,
+ uint32_t* type, uint32_t size_left)
+{
+ read_uint32be(fd, size);
+ read_uint32be(fd, type);
+
+ if (*size == 1)
+ {
+ /* FAT32 doesn't support files this big, so something seems to
+ * be wrong. (64-bit sizes should only be used when required.)
+ */
+ errno = EFBIG;
+ *type = 0;
+ return 0;
+ }
+
+ if (*size > 0)
+ {
+ if (*size > size_left)
+ {
+ size_left = 0;
+ }
+ else
+ {
+ size_left -= *size;
+ }
+
+ *size -= 8;
+ }
+ else
+ {
+ *size = size_left;
+ size_left = 0;
+ }
+
+ return size_left;
+}
+
+static unsigned int read_mp4_length(int fd, uint32_t* size)
+{
+ unsigned int length = 0;
+ int bytes = 0;
+ unsigned char c;
+
+ do
+ {
+ read(fd, &c, 1);
+ bytes++;
+ (*size)--;
+ length = (length << 7) | (c & 0x7F);
+ }
+ while ((c & 0x80) && (bytes < 4) && (*size > 0));
+
+ return length;
+}
+
+static bool read_mp4_esds(int fd, struct mp3entry* id3, uint32_t* size)
+{
+ unsigned char buf[8];
+ bool sbr = false;
+
+ lseek(fd, 4, SEEK_CUR); /* Version and flags. */
+ read(fd, buf, 1); /* Verify ES_DescrTag. */
+ *size -= 5;
+
+ if (*buf == 3)
+ {
+ /* read length */
+ if (read_mp4_length(fd, size) < 20)
+ {
+ return sbr;
+ }
+
+ lseek(fd, 3, SEEK_CUR);
+ *size -= 3;
+ }
+ else
+ {
+ lseek(fd, 2, SEEK_CUR);
+ *size -= 2;
+ }
+
+ read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */
+ *size -= 1;
+
+ if (*buf != 4)
+ {
+ return sbr;
+ }
+
+ if (read_mp4_length(fd, size) < 13)
+ {
+ return sbr;
+ }
+
+ lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */
+ read(fd, buf, 1);
+ *size -= 14;
+
+ if (*buf != 5) /* Verify DecSpecificInfoTag. */
+ {
+ return sbr;
+ }
+
+ {
+ static const int sample_rates[] =
+ {
+ 96000, 88200, 64000, 48000, 44100, 32000,
+ 24000, 22050, 16000, 12000, 11025, 8000
+ };
+ unsigned long bits;
+ unsigned int length;
+ unsigned int index;
+ unsigned int type;
+
+ /* Read the (leading part of the) decoder config. */
+ length = read_mp4_length(fd, size);
+ length = MIN(length, *size);
+ length = MIN(length, sizeof(buf));
+ memset(buf, 0, sizeof(buf));
+ read(fd, buf, length);
+ *size -= length;
+
+ /* Maybe time to write a simple read_bits function... */
+
+ /* Decoder config format:
+ * Object type - 5 bits
+ * Frequency index - 4 bits
+ * Channel configuration - 4 bits
+ */
+ bits = get_long_be(buf);
+ type = bits >> 27; /* Object type - 5 bits */
+ index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */
+
+ if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (type == 5)
+ {
+ unsigned int old_index = index;
+
+ sbr = true;
+ index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */
+
+ if (index == 15)
+ {
+ /* 17 bits read so far... */
+ bits = get_long_be(&buf[2]);
+ id3->frequency = (bits >> 7) & 0x00ffffff;
+ }
+ else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (old_index == index)
+ {
+ /* Downsampled SBR */
+ id3->frequency *= 2;
+ }
+ }
+ /* Skip 13 bits from above, plus 3 bits, then read 11 bits */
+ else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7))
+ {
+ /* We found an extensionAudioObjectType */
+ type = bits & 0x1f; /* Object type - 5 bits*/
+ bits = get_long_be(&buf[4]);
+
+ if (type == 5)
+ {
+ sbr = bits >> 31;
+
+ if (sbr)
+ {
+ unsigned int old_index = index;
+
+ /* 1 bit read so far */
+ index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */
+
+ if (index == 15)
+ {
+ /* 5 bits read so far */
+ id3->frequency = (bits >> 3) & 0x00ffffff;
+ }
+ else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (old_index == index)
+ {
+ /* Downsampled SBR */
+ id3->frequency *= 2;
+ }
+ }
+ }
+ }
+
+ if (!sbr && (id3->frequency <= 24000) && (length <= 2))
+ {
+ /* Double the frequency for low-frequency files without a "long"
+ * DecSpecificConfig header. The file may or may not contain SBR,
+ * but here we guess it does if the header is short. This can
+ * fail on some files, but it's the best we can do, short of
+ * decoding (parts of) the file.
+ */
+ id3->frequency *= 2;
+ sbr = true;
+ }
+ }
+
+ return sbr;
+}
+
+static bool read_mp4_tags(int fd, struct mp3entry* id3,
+ uint32_t size_left)
+{
+ uint32_t size;
+ uint32_t type;
+ unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ char* buffer = id3->id3v2buf;
+ bool cwrt = false;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff,
+ type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */
+
+ switch (type)
+ {
+ case MP4_cnam:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->title);
+ break;
+
+ case MP4_cART:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->artist);
+ break;
+
+ case MP4_aART:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->albumartist);
+ break;
+
+ case MP4_cgrp:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->grouping);
+ break;
+
+ case MP4_calb:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->album);
+ break;
+
+ case MP4_cwrt:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ cwrt = false;
+ break;
+
+ case MP4_ccmt:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->comment);
+ break;
+
+ case MP4_cday:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->year_string);
+
+ /* Try to parse it as a year, for the benefit of the database.
+ */
+ if(id3->year_string)
+ {
+ id3->year = atoi(id3->year_string);
+ if (id3->year < 1900)
+ {
+ id3->year = 0;
+ }
+ }
+ else
+ id3->year = 0;
+
+ break;
+
+ case MP4_gnre:
+ {
+ unsigned short genre;
+
+ read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
+ id3->genre_string = id3_get_num_genre(betoh16(genre) - 1);
+ }
+ break;
+
+ case MP4_cgen:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->genre_string);
+ break;
+
+ case MP4_disk:
+ {
+ unsigned short n[2];
+
+ read_mp4_tag(fd, size, (char*) &n, sizeof(n));
+ id3->discnum = betoh16(n[1]);
+ }
+ break;
+
+ case MP4_trkn:
+ {
+ unsigned short n[2];
+
+ read_mp4_tag(fd, size, (char*) &n, sizeof(n));
+ id3->tracknum = betoh16(n[1]);
+ }
+ break;
+
+#ifdef HAVE_ALBUMART
+ case MP4_covr:
+ {
+ int pos = lseek(fd, 0, SEEK_CUR) + 16;
+
+ read_mp4_tag(fd, size, buffer, 8);
+ id3->albumart.type = AA_TYPE_UNKNOWN;
+ if (memcmp(buffer, "\xff\xd8\xff\xe0", 4) == 0)
+ {
+ id3->albumart.type = AA_TYPE_JPG;
+ }
+ else if (memcmp(buffer, "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a", 8) == 0)
+ {
+ id3->albumart.type = AA_TYPE_PNG;
+ }
+
+ if (id3->albumart.type != AA_TYPE_UNKNOWN)
+ {
+ id3->albumart.pos = pos;
+ id3->albumart.size = size - 16;
+ id3->has_embedded_albumart = true;
+ }
+ }
+ break;
+#endif
+
+ case MP4_extra:
+ {
+ char tag_name[TAG_NAME_LENGTH];
+ uint32_t sub_size;
+
+ /* "mean" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, sub_size - 4, SEEK_CUR);
+ /* "name" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, 8, SEEK_CUR);
+ sub_size -= 12;
+
+ if (sub_size > sizeof(tag_name) - 1)
+ {
+ read(fd, tag_name, sizeof(tag_name) - 1);
+ lseek(fd, sub_size - (sizeof(tag_name) - 1), SEEK_CUR);
+ tag_name[sizeof(tag_name) - 1] = 0;
+ }
+ else
+ {
+ read(fd, tag_name, sub_size);
+ tag_name[sub_size] = 0;
+ }
+
+ if ((strcasecmp(tag_name, "composer") == 0) && !cwrt)
+ {
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ }
+ else if (strcasecmp(tag_name, "iTunSMPB") == 0)
+ {
+ char value[TAG_VALUE_LENGTH];
+ char* value_p = value;
+ char* any;
+ unsigned int length = sizeof(value);
+
+ read_mp4_tag_string(fd, size, &value_p, &length, &any);
+ id3->lead_trim = get_itunes_int32(value, 1);
+ id3->tail_trim = get_itunes_int32(value, 2);
+ DEBUGF("AAC: lead_trim %d, tail_trim %d\n",
+ id3->lead_trim, id3->tail_trim);
+ }
+ else if (strcasecmp(tag_name, "musicbrainz track id") == 0)
+ {
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->mb_track_id);
+ }
+ else if ((strcasecmp(tag_name, "album artist") == 0))
+ {
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->albumartist);
+ }
+ else
+ {
+ char* any = NULL;
+ unsigned int length = read_mp4_tag_string(fd, size,
+ &buffer, &buffer_left, &any);
+
+ if (length > 0)
+ {
+ /* Re-use the read buffer as the dest buffer... */
+ buffer -= length;
+ buffer_left += length;
+
+ parse_replaygain(tag_name, buffer, id3);
+ }
+ }
+ }
+ break;
+
+ default:
+ lseek(fd, size, SEEK_CUR);
+ break;
+ }
+ }
+ while ((size_left > 0) && (errno == 0));
+
+ return true;
+}
+
+static bool read_mp4_container(int fd, struct mp3entry* id3,
+ uint32_t size_left)
+{
+ uint32_t size = 0;
+ uint32_t type = 0;
+ uint32_t handler = 0;
+ bool rc = true;
+ bool done = false;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Atom: '%c%c%c%c' (0x%08lx, %lu bytes left)\n",
+ (int) ((type >> 24) & 0xff), (int) ((type >> 16) & 0xff),
+ (int) ((type >> 8) & 0xff), (int) (type & 0xff),
+ type, size); */
+
+ switch (type)
+ {
+ case MP4_ftyp:
+ {
+ uint32_t id;
+
+ read_uint32be(fd, &id);
+ size -= 4;
+
+ if ((id != MP4_M4A) && (id != MP4_M4B) && (id != MP4_mp42)
+ && (id != MP4_qt) && (id != MP4_3gp6) && (id != MP4_m4a)
+ && (id != MP4_isom))
+ {
+ DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n",
+ (int)(id >> 24 & 0xff), (int)(id >> 16 & 0xff),
+ (int)(id >> 8 & 0xff), (int)(id & 0xff));
+ return false;
+ }
+ }
+ break;
+
+ case MP4_meta:
+ lseek(fd, 4, SEEK_CUR); /* Skip version */
+ size -= 4;
+ /* Fall through */
+
+ case MP4_moov:
+ case MP4_udta:
+ case MP4_mdia:
+ case MP4_stbl:
+ case MP4_trak:
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_ilst:
+ /* We need at least a size of 8 to read the next atom. */
+ if (handler == MP4_mdir && size>8)
+ {
+ rc = read_mp4_tags(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_minf:
+ if (handler == MP4_soun)
+ {
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_stsd:
+ lseek(fd, 8, SEEK_CUR);
+ size -= 8;
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_hdlr:
+ lseek(fd, 8, SEEK_CUR);
+ read_uint32be(fd, &handler);
+ size -= 12;
+ /* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff,
+ handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */
+ break;
+
+ case MP4_stts:
+ {
+ uint32_t entries;
+ unsigned int i;
+
+ /* Reset to false. */
+ id3->needs_upsampling_correction = false;
+
+ lseek(fd, 4, SEEK_CUR);
+ read_uint32be(fd, &entries);
+ id3->samples = 0;
+
+ for (i = 0; i < entries; i++)
+ {
+ uint32_t n;
+ uint32_t l;
+
+ read_uint32be(fd, &n);
+ read_uint32be(fd, &l);
+
+ /* Some AAC file use HE profile. In this case the number
+ * of output samples is doubled to a maximum of 2048
+ * samples per frame. This means that files which already
+ * report a frame size of 2048 in their header will not
+ * need any further special handling. */
+ if (id3->codectype==AFMT_MP4_AAC_HE && l<=1024)
+ {
+ id3->samples += n * l * 2;
+ id3->needs_upsampling_correction = true;
+ }
+ else
+ {
+ id3->samples += n * l;
+ }
+ }
+
+ size = 0;
+ }
+ break;
+
+ case MP4_mp4a:
+ {
+ uint32_t subsize;
+ uint32_t subtype;
+
+ /* Move to the next expected mp4 atom. */
+ lseek(fd, 28, SEEK_CUR);
+ read_mp4_atom(fd, &subsize, &subtype, size);
+ size -= 36;
+
+ if (subtype == MP4_esds)
+ {
+ /* Read esds metadata and return if AAC-HE/SBR is used. */
+ if (read_mp4_esds(fd, id3, &size))
+ id3->codectype = AFMT_MP4_AAC_HE;
+ else
+ id3->codectype = AFMT_MP4_AAC;
+ }
+ }
+ break;
+
+ case MP4_alac:
+ {
+ uint32_t frequency;
+ uint32_t subsize;
+ uint32_t subtype;
+
+ /* Move to the next expected mp4 atom. */
+ lseek(fd, 28, SEEK_CUR);
+ read_mp4_atom(fd, &subsize, &subtype, size);
+ size -= 36;
+#if 0
+ /* We might need to parse for the alac metadata atom. */
+ while (!((subsize==28) && (subtype==MP4_alac)) && (size>0))
+ {
+ lseek(fd, -7, SEEK_CUR);
+ read_mp4_atom(fd, &subsize, &subtype, size);
+ size -= 1;
+ errno = 0; /* will most likely be set while parsing */
+ }
+#endif
+ if (subtype == MP4_alac)
+ {
+ lseek(fd, 24, SEEK_CUR);
+ read_uint32be(fd, &frequency);
+ size -= 28;
+ id3->frequency = frequency;
+ id3->codectype = AFMT_MP4_ALAC;
+ }
+ }
+ break;
+
+ case MP4_mdat:
+ /* Some AAC files appear to contain additional empty mdat chunks.
+ Ignore them. */
+ if(size == 0)
+ break;
+ id3->filesize = size;
+ if(id3->samples > 0) {
+ /* We've already seen the moov chunk. */
+ done = true;
+ }
+ break;
+
+ case MP4_chpl:
+ {
+ /* ADDME: add support for real chapters. Right now it's only
+ * used for Nero's gapless hack */
+ uint8_t chapters;
+ uint64_t timestamp;
+
+ lseek(fd, 8, SEEK_CUR);
+ read_uint8(fd, &chapters);
+ size -= 9;
+
+ /* the first chapter will be used as the lead_trim */
+ if (chapters > 0) {
+ read_uint64be(fd, &timestamp);
+ id3->lead_trim = (timestamp * id3->frequency) / 10000000;
+ size -= 8;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ /* Skip final seek. */
+ if (!done)
+ {
+ lseek(fd, size, SEEK_CUR);
+ }
+ } while (rc && (size_left > 0) && (errno == 0) && !done);
+
+ return rc;
+}
+
+bool get_mp4_metadata(int fd, struct mp3entry* id3)
+{
+ id3->codectype = AFMT_UNKNOWN;
+ id3->filesize = 0;
+ errno = 0;
+
+ if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0)
+ && (id3->samples > 0) && (id3->frequency > 0)
+ && (id3->filesize > 0))
+ {
+ if (id3->codectype == AFMT_UNKNOWN)
+ {
+ logf("Not an ALAC or AAC file");
+ return false;
+ }
+
+ id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
+
+ id3->vbr = true; /* ALAC is native VBR, AAC very unlikely is CBR. */
+
+ if (id3->length <= 0)
+ {
+ logf("mp4 length invalid!");
+ return false;
+ }
+
+ id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
+ DEBUGF("MP4 bitrate %d, frequency %ld Hz, length %ld ms\n",
+ id3->bitrate, id3->frequency, id3->length);
+ }
+ else
+ {
+ logf("MP4 metadata error");
+ DEBUGF("MP4 metadata error. errno %d, samples %ld, frequency %ld, "
+ "filesize %ld\n", errno, id3->samples, id3->frequency,
+ id3->filesize);
+ return false;
+ }
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/mpc.c b/lib/rbcodec/metadata/mpc.c
new file mode 100644
index 0000000000..0b75ed04dd
--- /dev/null
+++ b/lib/rbcodec/metadata/mpc.c
@@ -0,0 +1,220 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Thom Johansen
+ * Copyright (C) 2010 Andree Buschmann
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <string.h>
+#include <stdio.h>
+#include <inttypes.h>
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+#include "replaygain.h"
+#include "fixedpoint.h"
+
+/* Needed for replay gain and clipping prevention of SV8 files. */
+#define SV8_TO_SV7_CONVERT_GAIN (6482) /* 64.82 * 100, MPC_OLD_GAIN_REF */
+#define SV8_TO_SV7_CONVERT_PEAK (23119) /* 256 * 20 * log10(32768) */
+
+static int set_replaygain_sv7(struct mp3entry* id3,
+ bool album,
+ long value,
+ long used)
+{
+ long gain = (int16_t) ((value >> 16) & 0xffff);
+ long peak = (uint16_t) (value & 0xffff);
+
+ /* We use a peak value of 0 to indicate a given gain type isn't used. */
+ if (peak != 0) {
+ /* Save the ReplayGain data to id3-structure for further processing. */
+ parse_replaygain_int(album, gain * 512 / 100, peak << 9, id3);
+ }
+
+ return used;
+}
+
+static int set_replaygain_sv8(struct mp3entry* id3,
+ bool album,
+ long gain,
+ long peak,
+ long used)
+{
+ gain = (long)(SV8_TO_SV7_CONVERT_GAIN - ((gain*100)/256));
+
+ /* Transform SV8's logarithmic peak representation to the desired linear
+ * representation: linear = pow(10, peak/256/20).
+ *
+ * FP_BITS = 24 bits = desired fp representation for dsp routines
+ * FRAC_BITS = 12 bits = resolution used for fp_bits
+ * fp_factor(peak*(1<<FRAC_BITS)/256, FRAC_BITS) << (FP_BITS-FRAC_BITS)
+ **/
+ peak = (fp_factor((peak-SV8_TO_SV7_CONVERT_PEAK)*16, 12) << 12);
+
+ /* We use a peak value of 0 to indicate a given gain type isn't used. */
+ if (peak != 0) {
+ /* Save the ReplayGain data to id3-structure for further processing. */
+ parse_replaygain_int(album, gain * 512 / 100, peak, id3);
+ }
+
+ return used;
+}
+
+static int sv8_get_size(uint8_t *buffer, int index, uint64_t *p_size)
+{
+ unsigned char tmp;
+ uint64_t size = 0;
+
+ do {
+ tmp = buffer[index++];
+ size = (size << 7) | (tmp & 0x7F);
+ } while((tmp & 0x80));
+
+ *p_size = size;
+ return index;
+}
+
+bool get_musepack_metadata(int fd, struct mp3entry *id3)
+{
+ static const int32_t sfreqs[4] = { 44100, 48000, 37800, 32000 };
+ uint32_t header[8];
+ uint64_t samples = 0;
+ int i;
+
+ if (!skip_id3v2(fd, id3))
+ return false;
+ if (read(fd, header, 4*8) != 4*8) return false;
+ /* Musepack files are little endian, might need swapping */
+ for (i = 1; i < 8; i++)
+ header[i] = letoh32(header[i]);
+ if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */
+ unsigned int streamversion;
+ header[0] = letoh32(header[0]);
+ streamversion = (header[0] >> 24) & 15;
+ if (streamversion == 7) {
+ unsigned int gapless = (header[5] >> 31) & 0x0001;
+ unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff;
+ unsigned int bufused = 0;
+
+ id3->frequency = sfreqs[(header[2] >> 16) & 0x0003];
+ samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */
+ if (gapless)
+ samples -= 1152 - last_frame_samples;
+ else
+ samples -= 481; /* Musepack subband synth filter delay */
+
+ bufused = set_replaygain_sv7(id3, false, header[3], bufused);
+ bufused = set_replaygain_sv7(id3, true , header[4], bufused);
+
+ id3->codectype = AFMT_MPC_SV7;
+ } else {
+ return false; /* only SV7 is allowed within a "MP+" signature */
+ }
+ } else if (!memcmp(header, "MPCK", 4)) { /* Compare to sig "MPCK" */
+ uint8_t sv8_header[32];
+ /* 4 bytes 'MPCK' */
+ lseek(fd, 4, SEEK_SET);
+ if (read(fd, sv8_header, 2) != 2) return false; /* read frame ID */
+ if (!memcmp(sv8_header, "SH", 2)) { /* Stream Header ID */
+ int32_t k = 0;
+ uint32_t streamversion;
+ uint64_t size = 0; /* tag size */
+ uint64_t dummy = 0; /* used to dummy read data from header */
+
+ /* 4 bytes 'MPCK' + 2 'SH' */
+ lseek(fd, 6, SEEK_SET);
+ if (read(fd, sv8_header, 32) != 32) return false;
+
+ /* Read the size of 'SH'-tag */
+ k = sv8_get_size(sv8_header, k, &size);
+
+ /* Skip crc32 */
+ k += 4;
+
+ /* Read stream version */
+ streamversion = sv8_header[k++];
+ if (streamversion != 8) return false; /* Only SV8 is allowed. */
+
+ /* Number of samples */
+ k = sv8_get_size(sv8_header, k, &samples);
+
+ /* Number of leading zero-samples */
+ k = sv8_get_size(sv8_header, k, &dummy);
+
+ /* Sampling frequency */
+ id3->frequency = sfreqs[(sv8_header[k++] >> 5) & 0x0003];
+
+ /* Number of channels */
+ id3->channels = (sv8_header[k++] >> 4) + 1;
+
+ /* Skip to next tag: k = size -2 */
+ k = size - 2;
+
+ if (!memcmp(sv8_header+k, "RG", 2)) { /* Replay Gain ID */
+ long peak, gain;
+ int bufused = 0;
+
+ k += 2; /* 2 bytes 'RG' */
+
+ /* sv8_get_size must be called to skip the right amount of
+ * bits within the header data. */
+ k = sv8_get_size(sv8_header, k, &size);
+
+ /* Read and set replay gain */
+ if (sv8_header[k++] == 1) {
+ /* Title's peak and gain */
+ gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2;
+ peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2;
+ bufused += set_replaygain_sv8(id3, false, gain, peak, bufused);
+
+ /* Album's peak and gain */
+ gain = (int16_t) ((sv8_header[k]<<8) + sv8_header[k+1]); k += 2;
+ peak = (uint16_t)((sv8_header[k]<<8) + sv8_header[k+1]); k += 2;
+ bufused += set_replaygain_sv8(id3, true , gain, peak, bufused);
+ }
+ }
+
+ id3->codectype = AFMT_MPC_SV8;
+ } else {
+ /* No sv8 stream header found */
+ return false;
+ }
+ } else {
+ return false; /* SV4-6 is not supported anymore */
+ }
+
+ id3->vbr = true;
+ /* Estimate bitrate, we should probably subtract the various header sizes
+ here for super-accurate results */
+ id3->length = ((int64_t) samples * 1000) / id3->frequency;
+
+ if (id3->length <= 0)
+ {
+ logf("mpc length invalid!");
+ return false;
+ }
+
+ id3->filesize = filesize(fd);
+ id3->bitrate = id3->filesize * 8 / id3->length;
+
+ read_ape_tags(fd, id3);
+ return true;
+}
diff --git a/lib/rbcodec/metadata/nsf.c b/lib/rbcodec/metadata/nsf.c
new file mode 100644
index 0000000000..2fa6f36b12
--- /dev/null
+++ b/lib/rbcodec/metadata/nsf.c
@@ -0,0 +1,278 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+#include "string-extra.h"
+
+/* NOTE: This file was modified to work properly with the new nsf codec based
+ on Game_Music_Emu */
+
+struct NESM_HEADER
+{
+ uint32_t nHeader;
+ uint8_t nHeaderExtra;
+ uint8_t nVersion;
+ uint8_t nTrackCount;
+ uint8_t nInitialTrack;
+ uint16_t nLoadAddress;
+ uint16_t nInitAddress;
+ uint16_t nPlayAddress;
+ uint8_t szGameTitle[32];
+ uint8_t szArtist[32];
+ uint8_t szCopyright[32];
+ uint16_t nSpeedNTSC;
+ uint8_t nBankSwitch[8];
+ uint16_t nSpeedPAL;
+ uint8_t nNTSC_PAL;
+ uint8_t nExtraChip;
+ uint8_t nExpansion[4];
+} __attribute__((packed));
+
+struct NSFE_INFOCHUNK
+{
+ uint16_t nLoadAddress;
+ uint16_t nInitAddress;
+ uint16_t nPlayAddress;
+ uint8_t nIsPal;
+ uint8_t nExt;
+ uint8_t nTrackCount;
+ uint8_t nStartingTrack;
+} __attribute__((packed));
+
+
+#define CHAR4_CONST(a, b, c, d) FOURCC(a, b, c, d)
+#define CHUNK_INFO 0x0001
+#define CHUNK_DATA 0x0002
+#define CHUNK_NEND 0x0004
+#define CHUNK_plst 0x0008
+#define CHUNK_time 0x0010
+#define CHUNK_fade 0x0020
+#define CHUNK_tlbl 0x0040
+#define CHUNK_auth 0x0080
+#define CHUNK_BANK 0x0100
+
+static bool parse_nsfe(int fd, struct mp3entry *id3)
+{
+ unsigned int chunks_found = 0;
+ long track_count = 0;
+ long playlist_count = 0;
+
+ struct NSFE_INFOCHUNK info;
+ memset(&info, 0, sizeof(struct NSFE_INFOCHUNK));
+
+ /* default values */
+ info.nTrackCount = 1;
+ id3->length = 150 * 1000;
+
+ /* begin reading chunks */
+ while (!(chunks_found & CHUNK_NEND))
+ {
+ uint32_t chunk_size, chunk_type;
+
+ if (read_uint32le(fd, &chunk_size) != (int)sizeof(uint32_t))
+ return false;
+
+ if (read_uint32be(fd, &chunk_type) != (int)sizeof(uint32_t))
+ return false;
+
+ switch (chunk_type)
+ {
+ /* first three types are mandatory (but don't worry about NEND
+ anyway) */
+ case CHAR4_CONST('I', 'N', 'F', 'O'):
+ {
+ if (chunks_found & CHUNK_INFO)
+ return false; /* only one info chunk permitted */
+
+ chunks_found |= CHUNK_INFO;
+
+ /* minimum size */
+ if (chunk_size < 8)
+ return false;
+
+ ssize_t size = MIN(sizeof(struct NSFE_INFOCHUNK), chunk_size);
+
+ if (read(fd, &info, size) != size)
+ return false;
+
+ if (size >= 9)
+ track_count = info.nTrackCount;
+
+ chunk_size -= size;
+ break;
+ }
+
+ case CHAR4_CONST('D', 'A', 'T', 'A'):
+ {
+ if (!(chunks_found & CHUNK_INFO))
+ return false;
+
+ if (chunks_found & CHUNK_DATA)
+ return false; /* only one may exist */
+
+ if (chunk_size < 1)
+ return false;
+
+ chunks_found |= CHUNK_DATA;
+ break;
+ }
+
+ case CHAR4_CONST('N', 'E', 'N', 'D'):
+ {
+ /* just end parsing regardless of whether or not this really is the
+ last chunk/data (but it _should_ be) */
+ chunks_found |= CHUNK_NEND;
+ continue;
+ }
+
+ /* remaining types are optional */
+
+ case CHAR4_CONST('a', 'u', 't', 'h'):
+ {
+ if (chunks_found & CHUNK_auth)
+ return false; /* only one may exist */
+
+ chunks_found |= CHUNK_auth;
+
+ /* szGameTitle, szArtist, szCopyright */
+ char ** const ar[] = { &id3->title, &id3->artist, &id3->album };
+
+ char *p = id3->id3v2buf;
+ long buf_rem = sizeof (id3->id3v2buf);
+ unsigned int i;
+
+ for (i = 0; i < ARRAYLEN(ar) && chunk_size && buf_rem; i++)
+ {
+ long len = read_string(fd, p, buf_rem, '\0', chunk_size);
+
+ if (len < 0)
+ return false;
+
+ *ar[i] = p;
+ p += len;
+ buf_rem -= len;
+
+ if (chunk_size >= (uint32_t)len)
+ chunk_size -= len;
+ else
+ chunk_size = 0;
+ }
+
+ break;
+ }
+
+ case CHAR4_CONST('p', 'l', 's', 't'):
+ {
+ if (chunks_found & CHUNK_plst)
+ return false; /* only one may exist */
+
+ chunks_found |= CHUNK_plst;
+
+ /* each byte is the index of one track */
+ playlist_count = chunk_size;
+ break;
+ }
+
+ case CHAR4_CONST('t', 'i', 'm', 'e'):
+ case CHAR4_CONST('f', 'a', 'd', 'e'):
+ case CHAR4_CONST('t', 'l', 'b', 'l'): /* we unfortunately can't use these anyway */
+ {
+ /* don't care how many of these there are even though there should
+ be only one */
+ if (!(chunks_found & CHUNK_INFO))
+ return false;
+
+ case CHAR4_CONST('B', 'A', 'N', 'K'):
+ break;
+ }
+
+ default: /* unknown chunk */
+ {
+ /* check the first byte */
+ chunk_type = (uint8_t)chunk_type;
+
+ /* chunk is vital... don't continue */
+ if(chunk_type >= 'A' && chunk_type <= 'Z')
+ return false;
+
+ /* otherwise, just skip it */
+ break;
+ }
+ } /* end switch */
+
+ lseek(fd, chunk_size, SEEK_CUR);
+ } /* end while */
+
+ if (track_count | playlist_count)
+ id3->length = MAX(track_count, playlist_count)*1000;
+
+ /* Single subtrack files will be treated differently
+ by gme's nsf codec */
+ if (id3->length <= 1000) id3->length = 150 * 1000;
+
+ /*
+ * if we exited the while loop without a 'return', we must have hit an NEND
+ * chunk if this is the case, the file was layed out as it was expected.
+ * now.. make sure we found both an info chunk, AND a data chunk... since
+ * these are minimum requirements for a valid NSFE file
+ */
+ return (chunks_found & (CHUNK_INFO | CHUNK_DATA)) ==
+ (CHUNK_INFO | CHUNK_DATA);
+}
+
+static bool parse_nesm(int fd, struct mp3entry *id3)
+{
+ struct NESM_HEADER hdr;
+ char *p = id3->id3v2buf;
+
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ return false;
+
+ /* Length */
+ id3->length = (hdr.nTrackCount > 1 ? hdr.nTrackCount : 150) * 1000;
+
+ /* Title */
+ id3->title = p;
+ p += strlcpy(p, hdr.szGameTitle, 32) + 1;
+
+ /* Artist */
+ id3->artist = p;
+ p += strlcpy(p, hdr.szArtist, 32) + 1;
+
+ /* Copyright (per codec) */
+ id3->album = p;
+ strlcpy(p, hdr.szCopyright, 32);
+
+ return true;
+}
+
+bool get_nsf_metadata(int fd, struct mp3entry* id3)
+{
+ uint32_t nsf_type;
+ if (lseek(fd, 0, SEEK_SET) < 0 ||
+ read_uint32be(fd, &nsf_type) != (int)sizeof(nsf_type))
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ /* we only render 16 bits, 44.1KHz, Mono */
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ if (nsf_type == CHAR4_CONST('N', 'S', 'F', 'E'))
+ return parse_nsfe(fd, id3);
+ else if (nsf_type == CHAR4_CONST('N', 'E', 'S', 'M'))
+ return parse_nesm(fd, id3);
+
+ /* not a valid format*/
+ return false;
+}
+
diff --git a/lib/rbcodec/metadata/ogg.c b/lib/rbcodec/metadata/ogg.c
new file mode 100644
index 0000000000..3a3cb29998
--- /dev/null
+++ b/lib/rbcodec/metadata/ogg.c
@@ -0,0 +1,215 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+/* A simple parser to read vital metadata from an Ogg Vorbis file.
+ * Can also handle parsing Ogg Speex files for metadata. Returns
+ * false if metadata needed by the codec couldn't be read.
+ */
+bool get_ogg_metadata(int fd, struct mp3entry* id3)
+{
+ /* An Ogg File is split into pages, each starting with the string
+ * "OggS". Each page has a timestamp (in PCM samples) referred to as
+ * the "granule position".
+ *
+ * An Ogg Vorbis has the following structure:
+ * 1) Identification header (containing samplerate, numchannels, etc)
+ * 2) Comment header - containing the Vorbis Comments
+ * 3) Setup header - containing codec setup information
+ * 4) Many audio packets...
+ *
+ * An Ogg Speex has the following structure:
+ * 1) Identification header (containing samplerate, numchannels, etc)
+ * Described in this page: (http://www.speex.org/manual2/node7.html)
+ * 2) Comment header - containing the Vorbis Comments
+ * 3) Many audio packets.
+ */
+
+ /* Use the path name of the id3 structure as a temporary buffer. */
+ unsigned char* buf = (unsigned char *)id3->path;
+ long comment_size;
+ long remaining = 0;
+ long last_serial = 0;
+ long serial, r;
+ int segments, header_size;
+ int i;
+ bool eof = false;
+
+ /* 92 bytes is enough for both Vorbis and Speex headers */
+ if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 92) < 92))
+ {
+ return false;
+ }
+
+ /* All Ogg streams start with OggS */
+ if (memcmp(buf, "OggS", 4) != 0)
+ {
+ return false;
+ }
+
+ /* Check for format magic and then get metadata */
+ if (memcmp(&buf[29], "vorbis", 6) == 0)
+ {
+ id3->codectype = AFMT_OGG_VORBIS;
+ id3->frequency = get_long_le(&buf[40]);
+ id3->vbr = true;
+
+ /* Comments are in second Ogg page (byte 58 onwards for Vorbis) */
+ if (lseek(fd, 58, SEEK_SET) < 0)
+ {
+ return false;
+ }
+ }
+ else if (memcmp(&buf[28], "Speex ", 8) == 0)
+ {
+ id3->codectype = AFMT_SPEEX;
+ id3->frequency = get_slong(&buf[64]);
+ id3->vbr = get_long_le(&buf[88]);
+
+ header_size = get_long_le(&buf[60]);
+
+ /* Comments are in second Ogg page (byte 108 onwards for Speex) */
+ if (lseek(fd, 28 + header_size, SEEK_SET) < 0)
+ {
+ return false;
+ }
+ }
+ else
+ {
+ /* Unsupported format, try to print the marker, catches Ogg/FLAC at least */
+ DEBUGF("Usupported format in Ogg stream: %16s\n", &buf[28]);
+ return false;
+ }
+
+ id3->filesize = filesize(fd);
+
+ /* We need to ensure the serial number from this page is the same as the
+ * one from the last page (since we only support a single bitstream).
+ */
+ serial = get_long_le(&buf[14]);
+ comment_size = read_vorbis_tags(fd, id3, remaining);
+
+ /* We now need to search for the last page in the file - identified by
+ * by ('O','g','g','S',0) and retrieve totalsamples.
+ */
+
+ /* A page is always < 64 kB */
+ if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ remaining = 0;
+
+ while (!eof)
+ {
+ r = read(fd, &buf[remaining], MAX_PATH - remaining);
+
+ if (r <= 0)
+ {
+ eof = true;
+ }
+ else
+ {
+ remaining += r;
+ }
+
+ /* Inefficient (but simple) search */
+ i = 0;
+
+ while (i < (remaining - 3))
+ {
+ if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
+ {
+ if (i < (remaining - 17))
+ {
+ /* Note that this only reads the low 32 bits of a
+ * 64 bit value.
+ */
+ id3->samples = get_long_le(&buf[i + 6]);
+ last_serial = get_long_le(&buf[i + 14]);
+
+ /* If this page is very small the beginning of the next
+ * header could be in buffer. Jump near end of this header
+ * and continue */
+ i += 27;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ if (i < remaining)
+ {
+ /* Move the remaining bytes to start of buffer.
+ * Reuse var 'segments' as it is no longer needed */
+ segments = 0;
+ while (i < remaining)
+ {
+ buf[segments++] = buf[i++];
+ }
+ remaining = segments;
+ }
+ else
+ {
+ /* Discard the rest of the buffer */
+ remaining = 0;
+ }
+ }
+
+ /* This file has mutiple vorbis bitstreams (or is corrupt). */
+ /* FIXME we should display an error here. */
+ if (serial != last_serial)
+ {
+ logf("serialno mismatch");
+ logf("%ld", serial);
+ logf("%ld", last_serial);
+ return false;
+ }
+
+ id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
+ if (id3->length <= 0)
+ {
+ logf("ogg length invalid!");
+ return false;
+ }
+
+ id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
+
+ return true;
+}
+
diff --git a/lib/rbcodec/metadata/oma.c b/lib/rbcodec/metadata/oma.c
new file mode 100644
index 0000000000..b82c0a4f73
--- /dev/null
+++ b/lib/rbcodec/metadata/oma.c
@@ -0,0 +1,189 @@
+/*
+ * Sony OpenMG (OMA) demuxer
+ *
+ * Copyright (c) 2008 Maxim Poliakovski
+ * 2008 Benjamin Larsson
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file oma.c
+ * This is a demuxer for Sony OpenMG Music files
+ *
+ * Known file extensions: ".oma", "aa3"
+ * The format of such files consists of three parts:
+ * - "ea3" header carrying overall info and metadata.
+ * - "EA3" header is a Sony-specific header containing information about
+ * the OpenMG file: codec type (usually ATRAC, can also be MP3 or WMA),
+ * codec specific info (packet size, sample rate, channels and so on)
+ * and DRM related info (file encryption, content id).
+ * - Sound data organized in packets follow the EA3 header
+ * (can be encrypted using the Sony DRM!).
+ *
+ * LIMITATIONS: This version supports only plain (unencrypted) OMA files.
+ * If any DRM-protected (encrypted) file is encountered you will get the
+ * corresponding error message. Try to remove the encryption using any
+ * Sony software (for example SonicStage).
+ * CODEC SUPPORT: Only ATRAC3 codec is currently supported!
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include <string.h>
+#include "metadata.h"
+#include "metadata_parsers.h"
+
+#define EA3_HEADER_SIZE 96
+
+#if 0
+#define DEBUGF printf
+#else
+#define DEBUGF(...)
+#endif
+
+/* Various helper macros taken from ffmpeg for reading *
+ * and writing buffers with a specified endianess. */
+# define AV_RB16(x) \
+ ((((const uint8_t*)(x))[0] << 8) | \
+ ((const uint8_t*)(x))[1])
+# define AV_RB24(x) \
+ ((((const uint8_t*)(x))[0] << 16) | \
+ (((const uint8_t*)(x))[1] << 8) | \
+ ((const uint8_t*)(x))[2])
+# define AV_RB32(x) \
+ ((((const uint8_t*)(x))[0] << 24) | \
+ (((const uint8_t*)(x))[1] << 16) | \
+ (((const uint8_t*)(x))[2] << 8) | \
+ ((const uint8_t*)(x))[3])
+# define AV_WL32(p, d) do { \
+ ((uint8_t*)(p))[0] = (d); \
+ ((uint8_t*)(p))[1] = (d)>>8; \
+ ((uint8_t*)(p))[2] = (d)>>16; \
+ ((uint8_t*)(p))[3] = (d)>>24; \
+ } while(0)
+# define AV_WL16(p, d) do { \
+ ((uint8_t*)(p))[0] = (d); \
+ ((uint8_t*)(p))[1] = (d)>>8; \
+ } while(0)
+
+/* Different codecs that could be present in a Sony OMA *
+ * container file. */
+enum {
+ OMA_CODECID_ATRAC3 = 0,
+ OMA_CODECID_ATRAC3P = 1,
+ OMA_CODECID_MP3 = 3,
+ OMA_CODECID_LPCM = 4,
+ OMA_CODECID_WMA = 5,
+};
+
+/* FIXME: This functions currently read different file *
+ * parameters required for decoding. It still *
+ * does not read the metadata - which should be *
+ * present in the ea3 (first) header. The *
+ * metadata in ea3 is stored as a variation of *
+ * the ID3v2 metadata format. */
+static int oma_read_header(int fd, struct mp3entry* id3)
+{
+ static const uint16_t srate_tab[6] = {320,441,480,882,960,0};
+ int ret, ea3_taglen, EA3_pos, jsflag;
+ uint32_t codec_params;
+ int16_t eid;
+ uint8_t buf[EA3_HEADER_SIZE];
+
+ ret = read(fd, buf, 10);
+ if (ret != 10)
+ return -1;
+
+ ea3_taglen = ((buf[6] & 0x7f) << 21) | ((buf[7] & 0x7f) << 14) | ((buf[8] & 0x7f) << 7) | (buf[9] & 0x7f);
+
+ EA3_pos = ea3_taglen + 10;
+ if (buf[5] & 0x10)
+ EA3_pos += 10;
+
+ lseek(fd, EA3_pos, SEEK_SET);
+ ret = read(fd, buf, EA3_HEADER_SIZE);
+ if (ret != EA3_HEADER_SIZE)
+ return -1;
+
+ if (memcmp(buf, ((const uint8_t[]){'E', 'A', '3'}),3) || buf[4] != 0 || buf[5] != EA3_HEADER_SIZE) {
+ DEBUGF("Couldn't find the EA3 header !\n");
+ return -1;
+ }
+
+ eid = AV_RB16(&buf[6]);
+ if (eid != -1 && eid != -128) {
+ DEBUGF("Encrypted file! Eid: %d\n", eid);
+ return -1;
+ }
+
+ codec_params = AV_RB24(&buf[33]);
+
+ switch (buf[32]) {
+ case OMA_CODECID_ATRAC3:
+ id3->frequency = srate_tab[(codec_params >> 13) & 7]*100;
+ if (id3->frequency != 44100) {
+ DEBUGF("Unsupported sample rate, send sample file to developers: %d\n", id3->frequency);
+ return -1;
+ }
+
+ id3->bytesperframe = (codec_params & 0x3FF) * 8;
+ id3->codectype = AFMT_OMA_ATRAC3;
+ jsflag = (codec_params >> 17) & 1; /* get stereo coding mode, 1 for joint-stereo */
+
+ id3->bitrate = id3->frequency * id3->bytesperframe * 8 / (1024 * 1000);
+
+ /* fake the atrac3 extradata (wav format, makes stream copy to wav work) */
+ /* ATRAC3 expects and extra-data size of 14 bytes for wav format, and *
+ * looks for that in the id3v2buf. */
+ id3->extradata_size = 14;
+ AV_WL16(&id3->id3v2buf[0], 1); // always 1
+ AV_WL32(&id3->id3v2buf[2], id3->frequency); // samples rate
+ AV_WL16(&id3->id3v2buf[6], jsflag); // coding mode
+ AV_WL16(&id3->id3v2buf[8], jsflag); // coding mode
+ AV_WL16(&id3->id3v2buf[10], 1); // always 1
+ AV_WL16(&id3->id3v2buf[12], 0); // always 0
+
+ id3->channels = 2;
+ DEBUGF("sample_rate = %d\n", id3->frequency);
+ DEBUGF("frame_size = %d\n", id3->bytesperframe);
+ DEBUGF("stereo_coding_mode = %d\n", jsflag);
+ break;
+ default:
+ DEBUGF("Unsupported codec %d!\n",buf[32]);
+ return -1;
+ break;
+ }
+
+ /* Store the the offset of the first audio frame, to be able to seek to it *
+ * directly in atrac3_oma.codec. */
+ id3->first_frame_offset = EA3_pos + EA3_HEADER_SIZE;
+ return 0;
+}
+
+bool get_oma_metadata(int fd, struct mp3entry* id3)
+{
+ if(oma_read_header(fd, id3) < 0)
+ return false;
+
+ /* Currently, there's no means of knowing the duration *
+ * directly from the the file so we calculate it. */
+ id3->filesize = filesize(fd);
+ id3->length = ((id3->filesize - id3->first_frame_offset) * 8) / id3->bitrate;
+ return true;
+}
diff --git a/lib/rbcodec/metadata/replaygain.c b/lib/rbcodec/metadata/replaygain.c
new file mode 100644
index 0000000000..a178321385
--- /dev/null
+++ b/lib/rbcodec/metadata/replaygain.c
@@ -0,0 +1,222 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Magnus Holmgren
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#include <ctype.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <inttypes.h>
+#include "strlcpy.h"
+#include "strcasecmp.h"
+#include "system.h"
+#include "metadata.h"
+#include "debug.h"
+#include "replaygain.h"
+#include "fixedpoint.h"
+
+#define FP_BITS (12)
+#define FP_ONE (1 << FP_BITS)
+#define FP_MIN (-48 * FP_ONE)
+#define FP_MAX ( 17 * FP_ONE)
+
+void replaygain_itoa(char* buffer, int length, long int_gain)
+{
+ /* int_gain uses Q19.12 format. */
+ int one = abs(int_gain) >> FP_BITS;
+ int cent = ((abs(int_gain) & 0x0fff) * 100 + (FP_ONE/2)) >> FP_BITS;
+ snprintf(buffer, length, "%s%d.%02d dB", (int_gain<0) ? "-":"", one, cent);
+}
+
+static long fp_atof(const char* s, int precision)
+{
+ long int_part = 0;
+ long int_one = BIT_N(precision);
+ long frac_part = 0;
+ long frac_count = 0;
+ long frac_max = ((precision * 4) + 12) / 13;
+ long frac_max_int = 1;
+ long sign = 1;
+ bool point = false;
+
+ while ((*s != '\0') && isspace(*s))
+ {
+ s++;
+ }
+
+ if (*s == '-')
+ {
+ sign = -1;
+ s++;
+ }
+ else if (*s == '+')
+ {
+ s++;
+ }
+
+ while (*s != '\0')
+ {
+ if (*s == '.')
+ {
+ if (point)
+ {
+ break;
+ }
+
+ point = true;
+ }
+ else if (isdigit(*s))
+ {
+ if (point)
+ {
+ if (frac_count < frac_max)
+ {
+ frac_part = frac_part * 10 + (*s - '0');
+ frac_count++;
+ frac_max_int *= 10;
+ }
+ }
+ else
+ {
+ int_part = int_part * 10 + (*s - '0');
+ }
+ }
+ else
+ {
+ break;
+ }
+
+ s++;
+ }
+
+ while (frac_count < frac_max)
+ {
+ frac_part *= 10;
+ frac_count++;
+ frac_max_int *= 10;
+ }
+
+ return sign * ((int_part * int_one)
+ + (((int64_t) frac_part * int_one) / frac_max_int));
+}
+
+static long convert_gain(long gain)
+{
+ /* Don't allow unreasonably low or high gain changes.
+ * Our math code can't handle it properly anyway. :) */
+ gain = MAX(gain, FP_MIN);
+ gain = MIN(gain, FP_MAX);
+
+ return fp_factor(gain, FP_BITS) << (24 - FP_BITS);
+}
+
+/* Get the sample scale factor in Q19.12 format from a gain value. Returns 0
+ * for no gain.
+ *
+ * str Gain in dB as a string. E.g., "-3.45 dB"; the "dB" part is ignored.
+ */
+static long get_replaygain(const char* str)
+{
+ return fp_atof(str, FP_BITS);
+}
+
+/* Get the peak volume in Q7.24 format.
+ *
+ * str Peak volume. Full scale is specified as "1.0". Returns 0 for no peak.
+ */
+static long get_replaypeak(const char* str)
+{
+ return fp_atof(str, 24);
+}
+
+/* Get a sample scale factor in Q7.24 format from a gain value.
+ *
+ * int_gain Gain in dB, multiplied by 100.
+ */
+long get_replaygain_int(long int_gain)
+{
+ return convert_gain(int_gain * FP_ONE / 100);
+}
+
+/* Parse a ReplayGain tag conforming to the "VorbisGain standard". If a
+ * valid tag is found, update mp3entry struct accordingly. Existing values
+ * are not overwritten.
+ *
+ * key Name of the tag.
+ * value Value of the tag.
+ * entry mp3entry struct to update.
+ */
+void parse_replaygain(const char* key, const char* value,
+ struct mp3entry* entry)
+{
+ if (((strcasecmp(key, "replaygain_track_gain") == 0) ||
+ (strcasecmp(key, "rg_radio") == 0)) &&
+ !entry->track_gain)
+ {
+ entry->track_level = get_replaygain(value);
+ entry->track_gain = convert_gain(entry->track_level);
+ }
+ else if (((strcasecmp(key, "replaygain_album_gain") == 0) ||
+ (strcasecmp(key, "rg_audiophile") == 0)) &&
+ !entry->album_gain)
+ {
+ entry->album_level = get_replaygain(value);
+ entry->album_gain = convert_gain(entry->album_level);
+ }
+ else if (((strcasecmp(key, "replaygain_track_peak") == 0) ||
+ (strcasecmp(key, "rg_peak") == 0)) &&
+ !entry->track_peak)
+ {
+ entry->track_peak = get_replaypeak(value);
+ }
+ else if ((strcasecmp(key, "replaygain_album_peak") == 0) &&
+ !entry->album_peak)
+ {
+ entry->album_peak = get_replaypeak(value);
+ }
+}
+
+/* Set ReplayGain values from integers. Existing values are not overwritten.
+ *
+ * album If true, set album values, otherwise set track values.
+ * gain Gain value in dB, multiplied by 512. 0 for no gain.
+ * peak Peak volume in Q7.24 format, where 1.0 is full scale. 0 for no
+ * peak volume.
+ * entry mp3entry struct to update.
+ */
+void parse_replaygain_int(bool album, long gain, long peak,
+ struct mp3entry* entry)
+{
+ gain = gain * FP_ONE / 512;
+
+ if (album)
+ {
+ entry->album_level = gain;
+ entry->album_gain = convert_gain(gain);
+ entry->album_peak = peak;
+ }
+ else
+ {
+ entry->track_level = gain;
+ entry->track_gain = convert_gain(gain);
+ entry->track_peak = peak;
+ }
+}
diff --git a/lib/rbcodec/metadata/replaygain.h b/lib/rbcodec/metadata/replaygain.h
new file mode 100644
index 0000000000..215464dfdf
--- /dev/null
+++ b/lib/rbcodec/metadata/replaygain.h
@@ -0,0 +1,34 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Magnus Holmgren
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+
+#ifndef _REPLAYGAIN_H
+#define _REPLAYGAIN_H
+
+#include "metadata.h"
+
+long get_replaygain_int(long int_gain);
+void parse_replaygain(const char* key, const char* value,
+ struct mp3entry* entry);
+void parse_replaygain_int(bool album, long gain, long peak,
+ struct mp3entry* entry);
+void replaygain_itoa(char* buffer, int length, long int_gain);
+
+#endif
diff --git a/lib/rbcodec/metadata/rm.c b/lib/rbcodec/metadata/rm.c
new file mode 100644
index 0000000000..27f541cb25
--- /dev/null
+++ b/lib/rbcodec/metadata/rm.c
@@ -0,0 +1,464 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2009 Mohamed Tarek
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include <codecs/librm/rm.h>
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+/* Uncomment the following line for debugging */
+//#define DEBUG_RM
+#ifndef DEBUG_RM
+#undef DEBUGF
+#define DEBUGF(...)
+#endif
+
+#define ID3V1_OFFSET -128
+#define METADATA_FOOTER_OFFSET -140
+
+static inline void print_cook_extradata(RMContext *rmctx) {
+
+ DEBUGF(" cook_version = 0x%08lx\n", rm_get_uint32be(rmctx->codec_extradata));
+ DEBUGF(" samples_per_frame_per_channel = %d\n", rm_get_uint16be(&rmctx->codec_extradata[4]));
+ DEBUGF(" number_of_subbands_in_freq_domain = %d\n", rm_get_uint16be(&rmctx->codec_extradata[6]));
+ if(rmctx->extradata_size == 16) {
+ DEBUGF(" joint_stereo_subband_start = %d\n",rm_get_uint16be(&rmctx->codec_extradata[12]));
+ DEBUGF(" joint_stereo_vlc_bits = %d\n", rm_get_uint16be(&rmctx->codec_extradata[14]));
+ }
+}
+
+
+struct real_object_t
+{
+ uint32_t fourcc;
+ uint32_t size;
+ uint16_t version;
+};
+
+static int real_read_object_header(int fd, struct real_object_t* obj)
+{
+ int n;
+
+ if ((n = read_uint32be(fd, &obj->fourcc)) <= 0)
+ return n;
+ if ((n = read_uint32be(fd, &obj->size)) <= 0)
+ return n;
+ if ((n = read_uint16be(fd, &obj->version)) <= 0)
+ return n;
+
+ return 1;
+}
+
+#if (defined(SIMULATOR) && defined(DEBUG_RM))
+static char* fourcc2str(uint32_t f)
+{
+ static char res[5];
+
+ res[0] = (f & 0xff000000) >> 24;
+ res[1] = (f & 0xff0000) >> 16;
+ res[2] = (f & 0xff00) >> 8;
+ res[3] = (f & 0xff);
+ res[4] = 0;
+
+ return res;
+}
+#endif
+
+static inline int real_read_audio_stream_info(int fd, RMContext *rmctx)
+{
+ int skipped = 0;
+ uint32_t version;
+ struct real_object_t obj;
+#ifdef SIMULATOR
+ uint32_t header_size;
+ uint16_t flavor;
+ uint32_t coded_framesize;
+ uint8_t interleaver_id_length;
+ uint8_t fourcc_length;
+#endif
+ uint32_t interleaver_id;
+ uint32_t fourcc = 0;
+
+ memset(&obj,0,sizeof(obj));
+ read_uint32be(fd, &version);
+ skipped += 4;
+
+ DEBUGF(" version=0x%04lx\n",((version >> 16) & 0xff));
+ if (((version >> 16) & 0xff) == 3) {
+ /* Very old version */
+ } else {
+#ifdef SIMULATOR
+ real_read_object_header(fd, &obj);
+ read_uint32be(fd, &header_size);
+ /* obj.size will be filled with an unknown value, replaced with header_size */
+ DEBUGF(" Object: %s, size: %ld bytes, version: 0x%04x\n",fourcc2str(obj.fourcc),header_size,obj.version);
+
+ read_uint16be(fd, &flavor);
+ read_uint32be(fd, &coded_framesize);
+#else
+ lseek(fd, 20, SEEK_CUR);
+#endif
+ lseek(fd, 12, SEEK_CUR); /* unknown */
+ read_uint16be(fd, &rmctx->sub_packet_h);
+ read_uint16be(fd, &rmctx->block_align);
+ read_uint16be(fd, &rmctx->sub_packet_size);
+ lseek(fd, 2, SEEK_CUR); /* unknown */
+ skipped += 40;
+ if (((version >> 16) & 0xff) == 5)
+ {
+ lseek(fd, 6, SEEK_CUR); /* unknown */
+ skipped += 6;
+ }
+ read_uint16be(fd, &rmctx->sample_rate);
+ lseek(fd, 4, SEEK_CUR); /* unknown */
+ read_uint16be(fd, &rmctx->nb_channels);
+ skipped += 8;
+ if (((version >> 16) & 0xff) == 4)
+ {
+#ifdef SIMULATOR
+ read_uint8(fd, &interleaver_id_length);
+ read_uint32be(fd, &interleaver_id);
+ read_uint8(fd, &fourcc_length);
+#else
+ lseek(fd, 6, SEEK_CUR);
+#endif
+ read_uint32be(fd, &fourcc);
+ skipped += 10;
+ }
+ if (((version >> 16) & 0xff) == 5)
+ {
+ read_uint32be(fd, &interleaver_id);
+ read_uint32be(fd, &fourcc);
+ skipped += 8;
+ }
+ lseek(fd, 3, SEEK_CUR); /* unknown */
+ skipped += 3;
+ if (((version >> 16) & 0xff) == 5)
+ {
+ lseek(fd, 1, SEEK_CUR); /* unknown */
+ skipped += 1;
+ }
+
+ switch(fourcc) {
+ case FOURCC('c','o','o','k'):
+ rmctx->codec_type = CODEC_COOK;
+ read_uint32be(fd, &rmctx->extradata_size);
+ skipped += 4;
+ read(fd, rmctx->codec_extradata, rmctx->extradata_size);
+ skipped += rmctx->extradata_size;
+ break;
+
+ case FOURCC('r','a','a','c'):
+ case FOURCC('r','a','c','p'):
+ rmctx->codec_type = CODEC_AAC;
+ read_uint32be(fd, &rmctx->extradata_size);
+ skipped += 4;
+ read(fd, rmctx->codec_extradata, rmctx->extradata_size);
+ skipped += rmctx->extradata_size;
+ break;
+
+ case FOURCC('d','n','e','t'):
+ rmctx->codec_type = CODEC_AC3;
+ break;
+
+ case FOURCC('a','t','r','c'):
+ rmctx->codec_type = CODEC_ATRAC;
+ read_uint32be(fd, &rmctx->extradata_size);
+ skipped += 4;
+ read(fd, rmctx->codec_extradata, rmctx->extradata_size);
+ skipped += rmctx->extradata_size;
+ break;
+
+ default: /* Not a supported codec */
+ return -1;
+ }
+
+ DEBUGF(" flavor = %d\n",flavor);
+ DEBUGF(" coded_frame_size = %ld\n",coded_framesize);
+ DEBUGF(" sub_packet_h = %d\n",rmctx->sub_packet_h);
+ DEBUGF(" frame_size = %d\n",rmctx->block_align);
+ DEBUGF(" sub_packet_size = %d\n",rmctx->sub_packet_size);
+ DEBUGF(" sample_rate= %d\n",rmctx->sample_rate);
+ DEBUGF(" channels= %d\n",rmctx->nb_channels);
+ DEBUGF(" fourcc = %s\n",fourcc2str(fourcc));
+ DEBUGF(" codec_extra_data_length = %ld\n",rmctx->extradata_size);
+ DEBUGF(" codec_extradata :\n");
+ if(rmctx->codec_type == CODEC_COOK) {
+ DEBUGF(" cook_extradata :\n");
+ print_cook_extradata(rmctx);
+ }
+
+ }
+
+ return skipped;
+}
+
+static int rm_parse_header(int fd, RMContext *rmctx, struct mp3entry *id3)
+{
+ struct real_object_t obj;
+ int res;
+ int skipped;
+ off_t curpos __attribute__((unused));
+ uint8_t len; /* Holds a string_length, which is then passed to read_string() */
+
+#ifdef SIMULATOR
+ uint32_t avg_bitrate = 0;
+ uint32_t max_packet_size;
+ uint32_t avg_packet_size;
+ uint32_t packet_count;
+ uint32_t duration;
+ uint32_t preroll;
+ uint32_t index_offset;
+ uint16_t stream_id;
+ uint32_t start_time;
+ uint32_t codec_data_size;
+#endif
+ uint32_t v;
+ uint32_t max_bitrate;
+ uint16_t num_streams;
+ uint32_t next_data_off;
+ uint8_t header_end;
+
+ memset(&obj,0,sizeof(obj));
+ curpos = lseek(fd, 0, SEEK_SET);
+ res = real_read_object_header(fd, &obj);
+
+ if (obj.fourcc == FOURCC('.','r','a',0xfd))
+ {
+ /* Very old .ra format - not yet supported */
+ return -1;
+ }
+ else if (obj.fourcc != FOURCC('.','R','M','F'))
+ {
+ return -1;
+ }
+
+ lseek(fd, 8, SEEK_CUR); /* unknown */
+
+ DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos);
+
+ res = real_read_object_header(fd, &obj);
+ header_end = 0;
+ while(res)
+ {
+ DEBUGF("Object: %s, size: %d bytes, version: 0x%04x, pos: %d\n",fourcc2str(obj.fourcc),(int)obj.size,obj.version,(int)curpos);
+ skipped = 10;
+ if(obj.fourcc == FOURCC('I','N','D','X'))
+ break;
+ switch (obj.fourcc)
+ {
+ case FOURCC('P','R','O','P'): /* File properties */
+ read_uint32be(fd, &max_bitrate);
+ read_uint32be(fd, &rmctx->bit_rate); /*avg bitrate*/
+#ifdef SIMULATOR
+ read_uint32be(fd, &max_packet_size);
+ read_uint32be(fd, &avg_packet_size);
+ read_uint32be(fd, &packet_count);
+#else
+ lseek(fd, 3*sizeof(uint32_t), SEEK_CUR);
+#endif
+ read_uint32be(fd, &rmctx->duration);
+#ifdef SIMULATOR
+ read_uint32be(fd, &preroll);
+ read_uint32be(fd, &index_offset);
+#else
+ lseek(fd, 2*sizeof(uint32_t), SEEK_CUR);
+#endif
+ read_uint32be(fd, &rmctx->data_offset);
+ read_uint16be(fd, &num_streams);
+ read_uint16be(fd, &rmctx->flags);
+ skipped += 40;
+
+ DEBUGF(" max_bitrate = %ld\n",max_bitrate);
+ DEBUGF(" avg_bitrate = %ld\n",rmctx->bit_rate);
+ DEBUGF(" max_packet_size = %ld\n",max_packet_size);
+ DEBUGF(" avg_packet_size = %ld\n",avg_packet_size);
+ DEBUGF(" packet_count = %ld\n",packet_count);
+ DEBUGF(" duration = %ld\n",rmctx->duration);
+ DEBUGF(" preroll = %ld\n",preroll);
+ DEBUGF(" index_offset = %ld\n",index_offset);
+ DEBUGF(" data_offset = %ld\n",rmctx->data_offset);
+ DEBUGF(" num_streams = %d\n",num_streams);
+ DEBUGF(" flags=0x%04x\n",rmctx->flags);
+ break;
+
+ case FOURCC('C','O','N','T'):
+ /* Four strings - Title, Author, Copyright, Comment */
+ read_uint8(fd,&len);
+ skipped += (int)read_string(fd, id3->id3v1buf[0], sizeof(id3->id3v1buf[0]), '\0', len);
+ read_uint8(fd,&len);
+ skipped += (int)read_string(fd, id3->id3v1buf[1], sizeof(id3->id3v1buf[1]), '\0', len);
+ read_uint8(fd,&len);
+ skipped += (int)read_string(fd, id3->id3v1buf[2], sizeof(id3->id3v1buf[2]), '\0', len);
+ read_uint8(fd,&len);
+ skipped += (int)read_string(fd, id3->id3v1buf[3], sizeof(id3->id3v1buf[3]), '\0', len);
+ skipped += 4;
+
+ DEBUGF(" title=\"%s\"\n",id3->id3v1buf[0]);
+ DEBUGF(" author=\"%s\"\n",id3->id3v1buf[1]);
+ DEBUGF(" copyright=\"%s\"\n",id3->id3v1buf[2]);
+ DEBUGF(" comment=\"%s\"\n",id3->id3v1buf[3]);
+ break;
+
+ case FOURCC('M','D','P','R'): /* Media properties */
+#ifdef SIMULATOR
+ read_uint16be(fd,&stream_id);
+ read_uint32be(fd,&max_bitrate);
+ read_uint32be(fd,&avg_bitrate);
+ read_uint32be(fd,&max_packet_size);
+ read_uint32be(fd,&avg_packet_size);
+ read_uint32be(fd,&start_time);
+ read_uint32be(fd,&preroll);
+ read_uint32be(fd,&duration);
+#else
+ lseek(fd, 30, SEEK_CUR);
+#endif
+ skipped += 30;
+ read_uint8(fd,&len);
+ skipped += 1;
+ lseek(fd, len, SEEK_CUR); /* desc */
+ skipped += len;
+ read_uint8(fd,&len);
+ skipped += 1;
+#ifdef SIMULATOR
+ lseek(fd, len, SEEK_CUR); /* mimetype */
+ read_uint32be(fd,&codec_data_size);
+#else
+ lseek(fd, len + 4, SEEK_CUR);
+#endif
+ skipped += len + 4;
+ read_uint32be(fd,&v);
+ skipped += 4;
+
+ DEBUGF(" stream_id = 0x%04x\n",stream_id);
+ DEBUGF(" max_bitrate = %ld\n",max_bitrate);
+ DEBUGF(" avg_bitrate = %ld\n",avg_bitrate);
+ DEBUGF(" max_packet_size = %ld\n",max_packet_size);
+ DEBUGF(" avg_packet_size = %ld\n",avg_packet_size);
+ DEBUGF(" start_time = %ld\n",start_time);
+ DEBUGF(" preroll = %ld\n",preroll);
+ DEBUGF(" duration = %ld\n",duration);
+ DEBUGF(" codec_data_size = %ld\n",codec_data_size);
+ DEBUGF(" v=\"%s\"\n", fourcc2str(v));
+
+ if (v == FOURCC('.','r','a',0xfd))
+ {
+ int temp;
+ temp= real_read_audio_stream_info(fd, rmctx);
+ if(temp < 0)
+ return -1;
+ else
+ skipped += temp;
+ }
+ else if (v == FOURCC('L','S','D',':'))
+ {
+ DEBUGF("Real audio lossless is not supported.");
+ return -1;
+ }
+ else
+ {
+ /* We shall not abort with -1 here. *.rm file often seem
+ * to have a second media properties header that contains
+ * other metadata. */
+ DEBUGF("Unknown header signature :\"%s\"\n", fourcc2str(v));
+ }
+
+
+ break;
+
+ case FOURCC('D','A','T','A'):
+ read_uint32be(fd,&rmctx->nb_packets);
+ skipped += 4;
+ read_uint32be(fd,&next_data_off);
+ skipped += 4;
+
+ /***
+ * nb_packets correction :
+ * in some samples, number of packets may not exactly form
+ * an integer number of scrambling units. This is corrected
+ * by constructing a partially filled unit out of the few
+ * remaining samples at the end of decoding.
+ ***/
+ if(rmctx->nb_packets % rmctx->sub_packet_h)
+ rmctx->nb_packets += rmctx->sub_packet_h - (rmctx->nb_packets % rmctx->sub_packet_h);
+
+ DEBUGF(" data_nb_packets = %ld\n",rmctx->nb_packets);
+ DEBUGF(" next DATA offset = %ld\n",next_data_off);
+ header_end = 1;
+ break;
+ }
+ if(header_end) break;
+ curpos = lseek(fd, obj.size - skipped, SEEK_CUR);
+ res = real_read_object_header(fd, &obj);
+ }
+
+
+ return 0;
+}
+
+
+bool get_rm_metadata(int fd, struct mp3entry* id3)
+{
+ RMContext *rmctx = (RMContext*) (( (intptr_t)id3->id3v2buf + 3 ) &~ 3);
+ memset(rmctx,0,sizeof(RMContext));
+ if(rm_parse_header(fd, rmctx, id3) < 0)
+ return false;
+
+ if (!setid3v1title(fd, id3)) {
+ /* file has no id3v1 tags, use the tags from CONT chunk */
+ id3->title = id3->id3v1buf[0];
+ id3->artist = id3->id3v1buf[1];
+ id3->comment= id3->id3v1buf[3];
+ }
+
+ switch(rmctx->codec_type)
+ {
+ case CODEC_COOK:
+ /* Already set, do nothing */
+ break;
+ case CODEC_AAC:
+ id3->codectype = AFMT_RM_AAC;
+ break;
+
+ case CODEC_AC3:
+ id3->codectype = AFMT_RM_AC3;
+ break;
+
+ case CODEC_ATRAC:
+ id3->codectype = AFMT_RM_ATRAC3;
+ break;
+ }
+
+ id3->channels = rmctx->nb_channels;
+ id3->extradata_size = rmctx->extradata_size;
+ id3->bitrate = rmctx->bit_rate / 1000;
+ id3->frequency = rmctx->sample_rate;
+ id3->length = rmctx->duration;
+ id3->filesize = filesize(fd);
+ return true;
+}
diff --git a/lib/rbcodec/metadata/sgc.c b/lib/rbcodec/metadata/sgc.c
new file mode 100644
index 0000000000..78cacb9b1b
--- /dev/null
+++ b/lib/rbcodec/metadata/sgc.c
@@ -0,0 +1,67 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+static bool parse_sgc_header(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+
+ lseek(fd, 0, SEEK_SET);
+ if (read(fd, buf, 0xA0) < 0xA0)
+ return false;
+
+ /* calculate track length with number of tracks */
+ id3->length = buf[37] * 1000;
+
+ /* If meta info was found in the m3u skip next step */
+ if (id3->title && id3->title[0]) return true;
+
+ char *p = id3->id3v2buf;
+
+ /* Some metadata entries have 32 bytes length */
+ /* Game */
+ memcpy(p, &buf[64], 32); *(p + 33) = '\0';
+ id3->title = p;
+ p += strlen(p)+1;
+
+ /* Artist */
+ memcpy(p, &buf[96], 32); *(p + 33) = '\0';
+ id3->artist = p;
+ p += strlen(p)+1;
+
+ /* Copyright */
+ memcpy(p, &buf[128], 32); *(p + 33) = '\0';
+ id3->album = p;
+ p += strlen(p)+1;
+ return true;
+}
+
+
+bool get_sgc_metadata(int fd, struct mp3entry* id3)
+{
+ uint32_t sgc_type;
+ if ((lseek(fd, 0, SEEK_SET) < 0) ||
+ read_uint32be(fd, &sgc_type) != (int)sizeof(sgc_type))
+ return false;
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+ /* we only render 16 bits, 44.1KHz, Stereo */
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* Make sure this is an SGC file */
+ if (sgc_type != FOURCC('S','G','C',0x1A))
+ return false;
+
+ return parse_sgc_header(fd, id3);
+}
diff --git a/lib/rbcodec/metadata/sid.c b/lib/rbcodec/metadata/sid.c
new file mode 100644
index 0000000000..50b879b56d
--- /dev/null
+++ b/lib/rbcodec/metadata/sid.c
@@ -0,0 +1,89 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+/* PSID metadata info is available here:
+ http://www.unusedino.de/ec64/technical/formats/sidplay.html */
+bool get_sid_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ char *p;
+
+
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || (read(fd, buf, 0x80) < 0x80))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "PSID", 4) != 0))
+ {
+ return false;
+ }
+
+ p = id3->id3v2buf;
+
+ /* Copy Title (assumed max 0x1f letters + 1 zero byte) */
+ id3->title = p;
+ buf[0x16+0x1f] = 0;
+ p = iso_decode(&buf[0x16], p, 0, strlen(&buf[0x16])+1);
+
+ /* Copy Artist (assumed max 0x1f letters + 1 zero byte) */
+ id3->artist = p;
+ buf[0x36+0x1f] = 0;
+ p = iso_decode(&buf[0x36], p, 0, strlen(&buf[0x36])+1);
+
+ /* Copy Year (assumed max 4 letters + 1 zero byte) */
+ buf[0x56+0x4] = 0;
+ id3->year = atoi(&buf[0x56]);
+
+ /* Copy Album (assumed max 0x1f-0x05 letters + 1 zero byte) */
+ id3->album = p;
+ buf[0x56+0x1f] = 0;
+ iso_decode(&buf[0x5b], p, 0, strlen(&buf[0x5b])+1);
+
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+ /* New idea as posted by Marco Alanen (ravon):
+ * Set the songlength in seconds to the number of subsongs
+ * so every second represents a subsong.
+ * Users can then skip the current subsong by seeking
+ *
+ * Note: the number of songs is a 16bit value at 0xE, so this code only
+ * uses the lower 8 bits of the counter.
+ */
+ id3->length = (buf[0xf]-1)*1000;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/smaf.c b/lib/rbcodec/metadata/smaf.c
new file mode 100644
index 0000000000..1b745d3fa1
--- /dev/null
+++ b/lib/rbcodec/metadata/smaf.c
@@ -0,0 +1,470 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 Yoshihisa Uchida
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <inttypes.h>
+#include <stdio.h>
+
+#include "string-extra.h"
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+#include "logf.h"
+
+static const int basebits[4] = { 4, 8, 12, 16 };
+
+static const int frequency[5] = { 4000, 8000, 11025, 22050, 44100 };
+
+static const int support_codepages[5] = {
+#ifdef HAVE_LCD_BITMAP
+ SJIS, ISO_8859_1, -1, GB_2312, BIG_5,
+#else
+ -1, ISO_8859_1, -1, -1, -1,
+#endif
+};
+
+/* extra codepage */
+#define UCS2 (NUM_CODEPAGES + 1)
+
+/* support id3 tag */
+#define TAG_TITLE (('S'<<8)|'T')
+#define TAG_ARTIST (('A'<<8)|'N')
+#define TAG_COMPOSER (('S'<<8)|'W')
+
+/* convert functions */
+#define CONVERT_SMAF_CHANNELS(c) (((c) >> 7) + 1)
+
+
+static inline int convert_smaf_audio_basebit(unsigned int basebit)
+{
+ if (basebit > 3)
+ return 0;
+ return basebits[basebit];
+}
+
+static inline int convert_smaf_audio_frequency(unsigned int freq)
+{
+ if (freq > 4)
+ return 0;
+ return frequency[freq];
+}
+
+static int convert_smaf_codetype(unsigned int codetype)
+{
+ if (codetype < 5)
+ return support_codepages[codetype];
+ else if (codetype == 0x20 || codetype == 0x24) /* In Rockbox, UCS2 and UTF-16 are same. */
+ return UCS2;
+ else if (codetype == 0x23)
+ return UTF_8;
+ else if (codetype == 0xff)
+ return ISO_8859_1;
+ return -1;
+}
+
+static void set_length(struct mp3entry *id3, unsigned int ch, unsigned int basebit,
+ unsigned int numbytes)
+{
+ int bitspersample = convert_smaf_audio_basebit(basebit);
+
+ if (bitspersample != 0 && id3->frequency != 0)
+ {
+ /* Calculate track length [ms] and bitrate [kbit/s] */
+ id3->length = (uint64_t)numbytes * 8000LL
+ / (bitspersample * CONVERT_SMAF_CHANNELS(ch) * id3->frequency);
+ id3->bitrate = bitspersample * id3->frequency / 1000;
+ }
+
+ /* output contents/wave data/id3 info (for debug) */
+ DEBUGF("contents info ----\n");
+ DEBUGF(" TITLE: %s\n", (id3->title)? id3->title : "(NULL)");
+ DEBUGF(" ARTIST: %s\n", (id3->artist)? id3->artist : "(NULL)");
+ DEBUGF(" COMPOSER: %s\n", (id3->composer)? id3->composer : "(NULL)");
+ DEBUGF("wave data info ----\n");
+ DEBUGF(" channels: %u\n", CONVERT_SMAF_CHANNELS(ch));
+ DEBUGF(" bitspersample: %d\n", bitspersample);
+ DEBUGF(" numbytes; %u\n", numbytes);
+ DEBUGF("id3 info ----\n");
+ DEBUGF(" frquency: %u\n", (unsigned int)id3->frequency);
+ DEBUGF(" bitrate: %d\n", id3->bitrate);
+ DEBUGF(" length: %u\n", (unsigned int)id3->length);
+}
+
+/* contents parse functions */
+
+/* Note:
+ * 1) When the codepage is UTF-8 or UCS2, contents data do not start BOM.
+ * 2) The byte order of contents data is big endian.
+ */
+
+static void decode2utf8(const unsigned char *src, unsigned char **dst,
+ int srcsize, int *dstsize, int codepage)
+{
+ unsigned char tmpbuf[srcsize * 3 + 1];
+ unsigned char *p;
+ int utf8size;
+
+ if (codepage < NUM_CODEPAGES)
+ p = iso_decode(src, tmpbuf, codepage, srcsize);
+ else /* codepage == UCS2 */
+ p = utf16BEdecode(src, tmpbuf, srcsize);
+
+ *p = '\0';
+
+ strlcpy(*dst, tmpbuf, *dstsize);
+ utf8size = (p - tmpbuf) + 1;
+ if (utf8size > *dstsize)
+ {
+ DEBUGF("metadata warning: data length: %d > contents store buffer size: %d\n",
+ utf8size, *dstsize);
+ utf8size = *dstsize;
+ }
+ *dst += utf8size;
+ *dstsize -= utf8size;
+}
+
+static int read_audio_track_contets(int fd, int codepage, unsigned char **dst,
+ int *dstsize)
+{
+ /* value length <= 256 bytes */
+ unsigned char buf[256];
+ unsigned char *p = buf;
+ unsigned char *q = buf;
+ int datasize;
+
+ read(fd, buf, 256);
+
+ while (p - buf < 256 && *p != ',')
+ {
+ /* skip yen mark */
+ if (codepage != UCS2)
+ {
+ if (*p == '\\')
+ p++;
+ }
+ else if (*p == '\0' && *(p+1) == '\\')
+ p += 2;
+
+ if (*p > 0x7f)
+ {
+ if (codepage == UTF_8)
+ {
+ while ((*p & MASK) != COMP)
+ *q++ = *p++;
+ }
+#ifdef HAVE_LCD_BITMAP
+ else if (codepage == SJIS)
+ {
+ if (*p <= 0xa0 || *p >= 0xe0)
+ *q++ = *p++;
+ }
+#endif
+ }
+
+ *q++ = *p++;
+ if (codepage == UCS2)
+ *q++ = *p++;
+ }
+ datasize = p - buf + 1;
+ lseek(fd, datasize - 256, SEEK_CUR);
+
+ if (dst != NULL)
+ decode2utf8(buf, dst, q - buf, dstsize, codepage);
+
+ return datasize;
+}
+
+static void read_score_track_contets(int fd, int codepage, int datasize,
+ unsigned char **dst, int *dstsize)
+{
+ unsigned char buf[datasize];
+
+ read(fd, buf, datasize);
+ decode2utf8(buf, dst, datasize, dstsize, codepage);
+}
+
+/* traverse chunk functions */
+
+static unsigned int search_chunk(int fd, const unsigned char *name, int nlen)
+{
+ unsigned char buf[8];
+ unsigned int chunksize;
+
+ while (read(fd, buf, 8) > 0)
+ {
+ chunksize = get_long_be(buf + 4);
+ if (memcmp(buf, name, nlen) == 0)
+ return chunksize;
+
+ lseek(fd, chunksize, SEEK_CUR);
+ }
+ DEBUGF("metadata error: missing '%s' chunk\n", name);
+ return 0;
+}
+
+static bool parse_smaf_audio_track(int fd, struct mp3entry *id3, unsigned int datasize)
+{
+ /* temporary buffer */
+ unsigned char *tmp = (unsigned char*)id3->path;
+ /* contents stored buffer */
+ unsigned char *buf = id3->id3v2buf;
+ int bufsize = sizeof(id3->id3v2buf);
+
+ unsigned int chunksize = datasize;
+ int valsize;
+
+ int codepage;
+
+ /* parse contents info */
+ read(fd, tmp, 5);
+ codepage = convert_smaf_codetype(tmp[2]);
+ if (codepage < 0)
+ {
+ DEBUGF("metadata error: smaf unsupport codetype: %d\n", tmp[2]);
+ return false;
+ }
+
+ datasize -= 5;
+ while ((id3->title == NULL || id3->artist == NULL || id3->composer == NULL)
+ && (datasize > 0 && bufsize > 0))
+ {
+ if (read(fd, tmp, 3) <= 0)
+ return false;
+
+ if (tmp[2] != ':')
+ {
+ DEBUGF("metadata error: illegal tag: %c%c%c\n", tmp[0], tmp[1], tmp[2]);
+ return false;
+ }
+ switch ((tmp[0]<<8)|tmp[1])
+ {
+ case TAG_TITLE:
+ id3->title = buf;
+ valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize);
+ break;
+ case TAG_ARTIST:
+ id3->artist = buf;
+ valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize);
+ break;
+ case TAG_COMPOSER:
+ id3->composer = buf;
+ valsize = read_audio_track_contets(fd, codepage, &buf, &bufsize);
+ break;
+ default:
+ valsize = read_audio_track_contets(fd, codepage, NULL, &bufsize);
+ break;
+ }
+ datasize -= (valsize + 3);
+ }
+
+ /* search PCM Audio Track Chunk */
+ lseek(fd, 16 + chunksize, SEEK_SET);
+
+ chunksize = search_chunk(fd, "ATR", 3);
+ if (chunksize == 0)
+ {
+ DEBUGF("metadata error: missing PCM Audio Track Chunk\n");
+ return false;
+ }
+
+ /*
+ * get format
+ * tmp
+ * +0: Format Type
+ * +1: Sequence Type
+ * +2: bit 7 0:mono/1:stereo, bit 4-6 format, bit 0-3: frequency
+ * +3: bit 4-7: base bit
+ * +4: TimeBase_D
+ * +5: TimeBase_G
+ *
+ * Note: If PCM Audio Track does not include Sequence Data Chunk,
+ * tmp+6 is the start position of Wave Data Chunk.
+ */
+ read(fd, tmp, 6);
+
+ /* search Wave Data Chunk */
+ chunksize = search_chunk(fd, "Awa", 3);
+ if (chunksize == 0)
+ {
+ DEBUGF("metadata error: missing Wave Data Chunk\n");
+ return false;
+ }
+
+ /* set track length and bitrate */
+ id3->frequency = convert_smaf_audio_frequency(tmp[2] & 0x0f);
+ set_length(id3, tmp[2], tmp[3] >> 4, chunksize);
+ return true;
+}
+
+static bool parse_smaf_score_track(int fd, struct mp3entry *id3)
+{
+ /* temporary buffer */
+ unsigned char *tmp = (unsigned char*)id3->path;
+ unsigned char *p = tmp;
+ /* contents stored buffer */
+ unsigned char *buf = id3->id3v2buf;
+ int bufsize = sizeof(id3->id3v2buf);
+
+ unsigned int chunksize;
+ unsigned int datasize;
+ int valsize;
+
+ int codepage;
+
+ /* parse Optional Data Chunk */
+ read(fd, tmp, 21);
+ if (memcmp(tmp + 5, "OPDA", 4) != 0)
+ {
+ DEBUGF("metadata error: missing Optional Data Chunk\n");
+ return false;
+ }
+
+ /* Optional Data Chunk size */
+ chunksize = get_long_be(tmp + 9);
+
+ /* parse Data Chunk */
+ if (memcmp(tmp + 13, "Dch", 3) != 0)
+ {
+ DEBUGF("metadata error: missing Data Chunk\n");
+ return false;
+ }
+
+ codepage = convert_smaf_codetype(tmp[16]);
+ if (codepage < 0)
+ {
+ DEBUGF("metadata error: smaf unsupport codetype: %d\n", tmp[16]);
+ return false;
+ }
+
+ /* Data Chunk size */
+ datasize = get_long_be(tmp + 17);
+ while ((id3->title == NULL || id3->artist == NULL || id3->composer == NULL)
+ && (datasize > 0 && bufsize > 0))
+ {
+ if (read(fd, tmp, 4) <= 0)
+ return false;
+
+ valsize = (tmp[2] << 8) | tmp[3];
+ datasize -= (valsize + 4);
+ switch ((tmp[0]<<8)|tmp[1])
+ {
+ case TAG_TITLE:
+ id3->title = buf;
+ read_score_track_contets(fd, codepage, valsize, &buf, &bufsize);
+ break;
+ case TAG_ARTIST:
+ id3->artist = buf;
+ read_score_track_contets(fd, codepage, valsize, &buf, &bufsize);
+ break;
+ case TAG_COMPOSER:
+ id3->composer = buf;
+ read_score_track_contets(fd, codepage, valsize, &buf, &bufsize);
+ break;
+ default:
+ lseek(fd, valsize, SEEK_CUR);
+ break;
+ }
+ }
+
+ /* search Score Track Chunk */
+ lseek(fd, 29 + chunksize, SEEK_SET);
+
+ if (search_chunk(fd, "MTR", 3) == 0)
+ {
+ DEBUGF("metadata error: missing Score Track Chunk\n");
+ return false;
+ }
+
+ /*
+ * search next chunk
+ * usually, next chunk ('M***') found within 40 bytes.
+ */
+ chunksize = 40;
+ read(fd, tmp, chunksize);
+
+ tmp[chunksize] = 'M'; /* stopper */
+ while (*p != 'M')
+ p++;
+
+ chunksize -= (p - tmp);
+ if (chunksize == 0)
+ {
+ DEBUGF("metadata error: missing Score Track Stream PCM Data Chunk");
+ return false;
+ }
+
+ /* search Score Track Stream PCM Data Chunk */
+ lseek(fd, -chunksize, SEEK_CUR);
+ if (search_chunk(fd, "Mtsp", 4) == 0)
+ {
+ DEBUGF("metadata error: missing Score Track Stream PCM Data Chunk\n");
+ return false;
+ }
+
+ /*
+ * parse Score Track Stream Wave Data Chunk
+ * tmp
+ * +4-7: chunk size (WaveType(3bytes) + wave data count)
+ * +8: bit 7 0:mono/1:stereo, bit 4-6 format, bit 0-3: base bit
+ * +9: frequency (MSB)
+ * +10: frequency (LSB)
+ */
+ read(fd, tmp, 11);
+ if (memcmp(tmp, "Mwa", 3) != 0)
+ {
+ DEBUGF("metadata error: missing Score Track Stream Wave Data Chunk\n");
+ return false;
+ }
+
+ /* set track length and bitrate */
+ id3->frequency = (tmp[9] << 8) | tmp[10];
+ set_length(id3, tmp[8], tmp[8] & 0x0f, get_long_be(tmp + 4) - 3);
+ return true;
+}
+
+bool get_smaf_metadata(int fd, struct mp3entry* id3)
+{
+ /* temporary buffer */
+ unsigned char *tmp = (unsigned char *)id3->path;
+ unsigned int chunksize;
+
+ id3->title = NULL;
+ id3->artist = NULL;
+ id3->composer = NULL;
+
+ id3->vbr = false; /* All SMAF files are CBR */
+ id3->filesize = filesize(fd);
+
+ /* check File Chunk and Contents Info Chunk */
+ lseek(fd, 0, SEEK_SET);
+ read(fd, tmp, 16);
+ if ((memcmp(tmp, "MMMD", 4) != 0) || (memcmp(tmp + 8, "CNTI", 4) != 0))
+ {
+ DEBUGF("metadata error: does not smaf format\n");
+ return false;
+ }
+
+ chunksize = get_long_be(tmp + 12);
+ if (chunksize > 5)
+ return parse_smaf_audio_track(fd, id3, chunksize);
+
+ return parse_smaf_score_track(fd, id3);
+}
diff --git a/lib/rbcodec/metadata/spc.c b/lib/rbcodec/metadata/spc.c
new file mode 100644
index 0000000000..1c0206205d
--- /dev/null
+++ b/lib/rbcodec/metadata/spc.c
@@ -0,0 +1,130 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "debug.h"
+#include "rbunicode.h"
+
+bool get_spc_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char * buf = (unsigned char *)id3->path;
+ char * p;
+
+ unsigned long length;
+ unsigned long fade;
+ bool isbinary = true;
+ int i;
+
+ /* try to get the ID666 tag */
+ if ((lseek(fd, 0x2e, SEEK_SET) < 0)
+ || (read(fd, buf, 0xD2) < 0xD2))
+ {
+ DEBUGF("lseek or read failed\n");
+ return false;
+ }
+
+ p = id3->id3v2buf;
+
+ id3->title = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 32;
+
+ id3->album = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 48;
+
+ id3->comment = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 32;
+
+ /* Date check */
+ if(buf[2] == '/' && buf[5] == '/')
+ isbinary = false;
+
+ /* Reserved bytes check */
+ if(buf[0xD2 - 0x2E - 112] >= '0' &&
+ buf[0xD2 - 0x2E - 112] <= '9' &&
+ buf[0xD3 - 0x2E - 112] == 0x00)
+ isbinary = false;
+
+ /* is length & fade only digits? */
+ for (i=0;i<8 && (
+ (buf[0xA9 - 0x2E - 112+i]>='0'&&buf[0xA9 - 0x2E - 112+i]<='9') ||
+ buf[0xA9 - 0x2E - 112+i]=='\0');
+ i++);
+ if (i==8) isbinary = false;
+
+ if(isbinary) {
+ id3->year = buf[0] | (buf[1]<<8);
+ buf += 11;
+
+ length = (buf[0] | (buf[1]<<8) | (buf[2]<<16)) * 1000;
+ buf += 3;
+
+ fade = (buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24));
+ buf += 4;
+ } else {
+ char tbuf[6];
+
+ buf += 6;
+ buf[4] = 0;
+ id3->year = atoi(buf);
+ buf += 5;
+
+ memcpy(tbuf, buf, 3);
+ tbuf[3] = 0;
+ length = atoi(tbuf) * 1000;
+ buf += 3;
+
+ memcpy(tbuf, buf, 5);
+ tbuf[5] = 0;
+ fade = atoi(tbuf);
+ buf += 5;
+ }
+
+ id3->artist = p;
+ buf[31] = 0;
+ iso_decode(buf, p, 0, 32);
+
+ if (length==0) {
+ length=3*60*1000; /* 3 minutes */
+ fade=5*1000; /* 5 seconds */
+ }
+
+ id3->length = length+fade;
+
+ id3->filesize = filesize(fd);
+ id3->genre_string = id3_get_num_genre(36);
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/tta.c b/lib/rbcodec/metadata/tta.c
new file mode 100644
index 0000000000..1d3d95f118
--- /dev/null
+++ b/lib/rbcodec/metadata/tta.c
@@ -0,0 +1,123 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 Yoshihisa Uchida
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+#define TTA1_SIGN 0x31415454
+
+#define TTA_HEADER_ID 0
+#define TTA_HEADER_AUDIO_FORMAT (TTA_HEADER_ID + sizeof(unsigned int))
+#define TTA_HEADER_NUM_CHANNELS (TTA_HEADER_AUDIO_FORMAT + sizeof(unsigned short))
+#define TTA_HEADER_BITS_PER_SAMPLE (TTA_HEADER_NUM_CHANNELS + sizeof(unsigned short))
+#define TTA_HEADER_SAMPLE_RATE (TTA_HEADER_BITS_PER_SAMPLE + sizeof(unsigned short))
+#define TTA_HEADER_DATA_LENGTH (TTA_HEADER_SAMPLE_RATE + sizeof(unsigned int))
+#define TTA_HEADER_CRC32 (TTA_HEADER_DATA_LENGTH + sizeof(unsigned int))
+#define TTA_HEADER_SIZE (TTA_HEADER_CRC32 + sizeof(unsigned int))
+
+#define TTA_HEADER_GETTER_ID(x) get_long_le(x)
+#define TTA_HEADER_GETTER_AUDIO_FORMAT(x) get_short_le(x)
+#define TTA_HEADER_GETTER_NUM_CHANNELS(x) get_short_le(x)
+#define TTA_HEADER_GETTER_BITS_PER_SAMPLE(x) get_short_le(x)
+#define TTA_HEADER_GETTER_SAMPLE_RATE(x) get_long_le(x)
+#define TTA_HEADER_GETTER_DATA_LENGTH(x) get_long_le(x)
+#define TTA_HEADER_GETTER_CRC32(x) get_long_le(x)
+
+#define GET_HEADER(x, tag) TTA_HEADER_GETTER_ ## tag((x) + TTA_HEADER_ ## tag)
+
+static void read_id3_tags(int fd, struct mp3entry* id3)
+{
+ id3->title = NULL;
+ id3->filesize = filesize(fd);
+ id3->id3v2len = getid3v2len(fd);
+ id3->tracknum = 0;
+ id3->discnum = 0;
+ id3->vbr = false; /* All TTA files are CBR */
+
+ /* first get id3v2 tags. if no id3v2 tags ware found, get id3v1 tags */
+ if (id3->id3v2len)
+ {
+ setid3v2title(fd, id3);
+ id3->first_frame_offset = id3->id3v2len;
+ return;
+ }
+ setid3v1title(fd, id3);
+}
+
+bool get_tta_metadata(int fd, struct mp3entry* id3)
+{
+ unsigned char ttahdr[TTA_HEADER_SIZE];
+ unsigned int datasize;
+ unsigned int origsize;
+ int bps;
+
+ lseek(fd, 0, SEEK_SET);
+
+ /* read id3 tags */
+ read_id3_tags(fd, id3);
+ lseek(fd, id3->id3v2len, SEEK_SET);
+
+ /* read TTA header */
+ if (read(fd, ttahdr, TTA_HEADER_SIZE) < 0)
+ return false;
+
+ /* check for TTA3 signature */
+ if ((GET_HEADER(ttahdr, ID)) != TTA1_SIGN)
+ return false;
+
+ /* skip check CRC */
+
+ id3->channels = (GET_HEADER(ttahdr, NUM_CHANNELS));
+ id3->frequency = (GET_HEADER(ttahdr, SAMPLE_RATE));
+ id3->length = ((GET_HEADER(ttahdr, DATA_LENGTH)) / id3->frequency) * 1000LL;
+ bps = (GET_HEADER(ttahdr, BITS_PER_SAMPLE));
+
+ datasize = id3->filesize - id3->first_frame_offset;
+ origsize = (GET_HEADER(ttahdr, DATA_LENGTH)) * ((bps + 7) / 8) * id3->channels;
+
+ id3->bitrate = (int) ((uint64_t) datasize * id3->frequency * id3->channels * bps
+ / (origsize * 1000LL));
+
+ /* output header info (for debug) */
+ DEBUGF("TTA header info ----\n");
+ DEBUGF("id: %x\n", (unsigned int)(GET_HEADER(ttahdr, ID)));
+ DEBUGF("channels: %d\n", id3->channels);
+ DEBUGF("frequency: %ld\n", id3->frequency);
+ DEBUGF("length: %ld\n", id3->length);
+ DEBUGF("bitrate: %d\n", id3->bitrate);
+ DEBUGF("bits per sample: %d\n", bps);
+ DEBUGF("compressed size: %d\n", datasize);
+ DEBUGF("original size: %d\n", origsize);
+ DEBUGF("id3----\n");
+ DEBUGF("artist: %s\n", id3->artist);
+ DEBUGF("title: %s\n", id3->title);
+ DEBUGF("genre: %s\n", id3->genre_string);
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/vgm.c b/lib/rbcodec/metadata/vgm.c
new file mode 100644
index 0000000000..9ea95b3939
--- /dev/null
+++ b/lib/rbcodec/metadata/vgm.c
@@ -0,0 +1,195 @@
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+
+/* Ripped off from Game_Music_Emu 0.5.2. http://www.slack.net/~ant/ */
+
+typedef unsigned char byte;
+
+enum { header_size = 0x40 };
+enum { max_field = 64 };
+
+struct header_t
+{
+ char tag [4];
+ byte data_size [4];
+ byte version [4];
+ byte psg_rate [4];
+ byte ym2413_rate [4];
+ byte gd3_offset [4];
+ byte track_duration [4];
+ byte loop_offset [4];
+ byte loop_duration [4];
+ byte frame_rate [4];
+ byte noise_feedback [2];
+ byte noise_width;
+ byte unused1;
+ byte ym2612_rate [4];
+ byte ym2151_rate [4];
+ byte data_offset [4];
+ byte unused2 [8];
+};
+
+static byte const* skip_gd3_str( byte const* in, byte const* end )
+{
+ while ( end - in >= 2 )
+ {
+ in += 2;
+ if ( !(in [-2] | in [-1]) )
+ break;
+ }
+ return in;
+}
+
+static byte const* get_gd3_str( byte const* in, byte const* end, char* field )
+{
+ byte const* mid = skip_gd3_str( in, end );
+ int len = (mid - in) / 2 - 1;
+ if ( field && len > 0 )
+ {
+ len = len < (int) max_field ? len : (int) max_field;
+
+ field [len] = 0;
+ /* Conver to utf8 */
+ utf16LEdecode( in, field, len );
+
+ /* Copy string back to id3v2buf */
+ strcpy( (char*) in, field );
+ }
+ return mid;
+}
+
+static byte const* get_gd3_pair( byte const* in, byte const* end, char* field )
+{
+ return skip_gd3_str( get_gd3_str( in, end, field ), end );
+}
+
+static void parse_gd3( byte const* in, byte const* end, struct mp3entry* id3 )
+{
+ char* p = id3->path;
+ id3->title = (char *) in;
+ in = get_gd3_pair( in, end, p ); /* Song */
+
+ id3->album = (char *) in;
+ in = get_gd3_pair( in, end, p ); /* Game */
+
+ in = get_gd3_pair( in, end, NULL ); /* System */
+
+ id3->artist = (char *) in;
+ in = get_gd3_pair( in, end, p ); /* Author */
+
+#if MEMORYSIZE > 2
+ in = get_gd3_str ( in, end, NULL ); /* Copyright */
+ in = get_gd3_pair( in, end, NULL ); /* Dumper */
+
+ id3->comment = (char *) in;
+ in = get_gd3_str ( in, end, p ); /* Comment */
+#endif
+}
+
+int const gd3_header_size = 12;
+
+static long check_gd3_header( byte* h, long remain )
+{
+ if ( remain < gd3_header_size ) return 0;
+ if ( memcmp( h, "Gd3 ", 4 ) ) return 0;
+ if ( get_long_le( h + 4 ) >= 0x200 ) return 0;
+
+ long gd3_size = get_long_le( h + 8 );
+ if ( gd3_size > remain - gd3_header_size )
+ gd3_size = remain - gd3_header_size;
+
+ return gd3_size;
+}
+
+static void get_vgm_length( struct header_t* h, struct mp3entry* id3 )
+{
+ long length = get_long_le( h->track_duration ) * 10 / 441;
+ if ( length > 0 )
+ {
+ long loop_length = 0, intro_length = 0;
+ long loop = get_long_le( h->loop_duration );
+ if ( loop > 0 && get_long_le( h->loop_offset ) )
+ {
+ loop_length = loop * 10 / 441;
+ intro_length = length - loop_length;
+ }
+ else
+ {
+ intro_length = length; /* make it clear that track is no longer than length */
+ loop_length = 0;
+ }
+
+ id3->length = intro_length + 2 * loop_length; /* intro + 2 loops */
+ return;
+ }
+
+ id3->length = 150 * 1000; /* 2.5 minutes */
+}
+
+bool get_vgm_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the id3v2 part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->id3v2buf;
+ int read_bytes;
+
+ memset(buf, 0, ID3V2_BUF_SIZE);
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, header_size)) < header_size))
+ {
+ return false;
+ }
+
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+
+ /* If file is gzipped, will get metadata later */
+ if (memcmp(buf, "Vgm ", 4))
+ {
+ /* We must set a default song length here because
+ the codec can't do it anymore */
+ id3->length = 150 * 1000; /* 2.5 minutes */
+ return true;
+ }
+
+ /* Get song length from header */
+ struct header_t* header = (struct header_t*) buf;
+ get_vgm_length( header, id3 );
+
+ long gd3_offset = get_long_le( header->gd3_offset ) - 0x2C;
+
+ /* No gd3 tag found */
+ if ( gd3_offset < 0 )
+ return true;
+
+ /* Seek to gd3 offset and read as
+ many bytes posible */
+ gd3_offset = id3->filesize - (header_size + gd3_offset);
+ if ((lseek(fd, -gd3_offset, SEEK_END) < 0)
+ || ((read_bytes = read(fd, buf, ID3V2_BUF_SIZE)) <= 0))
+ return true;
+
+ byte* gd3 = buf;
+ long gd3_size = check_gd3_header( gd3, read_bytes );
+
+ /* GD3 tag is zero */
+ if ( gd3_size == 0 )
+ return true;
+
+ /* Finally, parse gd3 tag */
+ if ( gd3 )
+ parse_gd3( gd3 + gd3_header_size, gd3 + read_bytes, id3 );
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/vorbis.c b/lib/rbcodec/metadata/vorbis.c
new file mode 100644
index 0000000000..58bd781873
--- /dev/null
+++ b/lib/rbcodec/metadata/vorbis.c
@@ -0,0 +1,381 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "structec.h"
+
+/* Define LOGF_ENABLE to enable logf output in this file */
+/*#define LOGF_ENABLE*/
+#include "logf.h"
+
+struct file
+{
+ int fd;
+ bool packet_ended;
+ long packet_remaining;
+};
+
+
+/* Read an Ogg page header. file->packet_remaining is set to the size of the
+ * first packet on the page; file->packet_ended is set to true if the packet
+ * ended on the current page. Returns true if the page header was
+ * successfully read.
+ */
+static bool file_read_page_header(struct file* file)
+{
+ unsigned char buffer[64];
+ ssize_t table_left;
+
+ /* Size of page header without segment table */
+ if (read(file->fd, buffer, 27) != 27)
+ {
+ return false;
+ }
+
+ if (memcmp("OggS", buffer, 4))
+ {
+ return false;
+ }
+
+ /* Skip pattern (4), version (1), flags (1), granule position (8),
+ * serial (4), pageno (4), checksum (4)
+ */
+ table_left = buffer[26];
+ file->packet_remaining = 0;
+
+ /* Read segment table for the first packet */
+ do
+ {
+ ssize_t count = MIN(sizeof(buffer), (size_t) table_left);
+ int i;
+
+ if (read(file->fd, buffer, count) < count)
+ {
+ return false;
+ }
+
+ table_left -= count;
+
+ for (i = 0; i < count; i++)
+ {
+ file->packet_remaining += buffer[i];
+
+ if (buffer[i] < 255)
+ {
+ file->packet_ended = true;
+
+ /* Skip remainder of the table */
+ if (lseek(file->fd, table_left, SEEK_CUR) < 0)
+ {
+ return false;
+ }
+
+ table_left = 0;
+ break;
+ }
+ }
+ }
+ while (table_left > 0);
+
+ return true;
+}
+
+
+/* Read (up to) buffer_size of data from the file. If buffer is NULL, just
+ * skip ahead buffer_size bytes (like lseek). Returns number of bytes read,
+ * 0 if there is no more data to read (in the packet or the file), < 0 if a
+ * read error occurred.
+ */
+static ssize_t file_read(struct file* file, void* buffer, size_t buffer_size)
+{
+ ssize_t done = 0;
+ ssize_t count = -1;
+
+ do
+ {
+ if (file->packet_remaining <= 0)
+ {
+ if (file->packet_ended)
+ {
+ break;
+ }
+
+ if (!file_read_page_header(file))
+ {
+ count = -1;
+ break;
+ }
+ }
+
+ count = MIN(buffer_size, (size_t) file->packet_remaining);
+
+ if (buffer)
+ {
+ count = read(file->fd, buffer, count);
+ }
+ else
+ {
+ if (lseek(file->fd, count, SEEK_CUR) < 0)
+ {
+ count = -1;
+ }
+ }
+
+ if (count <= 0)
+ {
+ break;
+ }
+
+ if (buffer)
+ {
+ buffer += count;
+ }
+
+ buffer_size -= count;
+ done += count;
+ file->packet_remaining -= count;
+ }
+ while (buffer_size > 0);
+
+ return (count < 0 ? count : done);
+}
+
+
+/* Read an int32 from file. Returns false if a read error occurred.
+ */
+static bool file_read_int32(struct file* file, int32_t* value)
+{
+ char buf[sizeof(int32_t)];
+
+ if (file_read(file, buf, sizeof(buf)) < (ssize_t) sizeof(buf))
+ {
+ return false;
+ }
+
+ *value = get_long_le(buf);
+ return true;
+}
+
+
+/* Read a string from the file. Read up to buffer_size bytes, or, if eos
+ * != -1, until the eos character is found (eos is not stored in buf,
+ * unless it is nil). Writes up to buffer_size chars to buf, always
+ * terminating with a nil. Returns number of chars read or < 0 if a read
+ * error occurred.
+ *
+ * Unfortunately this is a slightly modified copy of read_string() in
+ * metadata_common.c...
+ */
+static long file_read_string(struct file* file, char* buffer,
+ long buffer_size, int eos, long size)
+{
+ long read_bytes = 0;
+
+ while (size > 0)
+ {
+ char c;
+
+ if (file_read(file, &c, 1) != 1)
+ {
+ read_bytes = -1;
+ break;
+ }
+
+ read_bytes++;
+ size--;
+
+ if ((eos != -1) && (eos == (unsigned char) c))
+ {
+ break;
+ }
+
+ if (buffer_size > 1)
+ {
+ *buffer++ = c;
+ buffer_size--;
+ }
+ else if (eos == -1)
+ {
+ /* No point in reading any more, skip remaining data */
+ if (file_read(file, NULL, size) < 0)
+ {
+ read_bytes = -1;
+ }
+ else
+ {
+ read_bytes += size;
+ }
+
+ break;
+ }
+ }
+
+ *buffer = 0;
+ return read_bytes;
+}
+
+
+/* Init struct file for reading from fd. type is the AFMT_* codec type of
+ * the file, and determines if Ogg pages are to be read. remaining is the
+ * max amount to read if codec type is FLAC; it is ignored otherwise.
+ * Returns true if the file was successfully initialized.
+ */
+static bool file_init(struct file* file, int fd, int type, int remaining)
+{
+ memset(file, 0, sizeof(*file));
+ file->fd = fd;
+
+ if (type == AFMT_OGG_VORBIS || type == AFMT_SPEEX)
+ {
+ if (!file_read_page_header(file))
+ {
+ return false;
+ }
+ }
+
+ if (type == AFMT_OGG_VORBIS)
+ {
+ char buffer[7];
+
+ /* Read packet header (type and id string) */
+ if (file_read(file, buffer, sizeof(buffer)) < (ssize_t) sizeof(buffer))
+ {
+ return false;
+ }
+
+ /* The first byte of a packet is the packet type; comment packets
+ * are type 3.
+ */
+ if (buffer[0] != 3)
+ {
+ return false;
+ }
+ }
+ else if (type == AFMT_FLAC)
+ {
+ file->packet_remaining = remaining;
+ file->packet_ended = true;
+ }
+
+ return true;
+}
+
+
+/* Read the items in a Vorbis comment packet. For Ogg files, the file must
+ * be located on a page start, for other files, the beginning of the comment
+ * data (i.e., the vendor string length). Returns total size of the
+ * comments, or 0 if there was a read error.
+ */
+long read_vorbis_tags(int fd, struct mp3entry *id3,
+ long tag_remaining)
+{
+ struct file file;
+ char *buf = id3->id3v2buf;
+ int32_t comment_count;
+ int32_t len;
+ long comment_size = 0;
+ int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ int i;
+
+ if (!file_init(&file, fd, id3->codectype, tag_remaining))
+ {
+ return 0;
+ }
+
+ /* Skip vendor string */
+
+ if (!file_read_int32(&file, &len) || (file_read(&file, NULL, len) < 0))
+ {
+ return 0;
+ }
+
+ if (!file_read_int32(&file, &comment_count))
+ {
+ return 0;
+ }
+
+ comment_size += 4 + len + 4;
+
+ for (i = 0; i < comment_count && file.packet_remaining > 0; i++)
+ {
+ char name[TAG_NAME_LENGTH];
+ int32_t read_len;
+
+ if (!file_read_int32(&file, &len))
+ {
+ return 0;
+ }
+
+ comment_size += 4 + len;
+ read_len = file_read_string(&file, name, sizeof(name), '=', len);
+
+ if (read_len < 0)
+ {
+ return 0;
+ }
+
+ len -= read_len;
+ read_len = file_read_string(&file, id3->path, sizeof(id3->path), -1, len);
+
+ if (read_len < 0)
+ {
+ return 0;
+ }
+
+ logf("Vorbis comment %d: %s=%s", i, name, id3->path);
+
+ /* Is it an embedded cuesheet? */
+ if (!strcasecmp(name, "CUESHEET"))
+ {
+ id3->has_embedded_cuesheet = true;
+ id3->embedded_cuesheet.pos = lseek(file.fd, 0, SEEK_CUR) - read_len;
+ id3->embedded_cuesheet.size = len;
+ id3->embedded_cuesheet.encoding = CHAR_ENC_UTF_8;
+ }
+ else
+ {
+ len = parse_tag(name, id3->path, id3, buf, buf_remaining,
+ TAGTYPE_VORBIS);
+ }
+
+ buf += len;
+ buf_remaining -= len;
+ }
+
+ /* Skip to the end of the block (needed by FLAC) */
+ if (file.packet_remaining)
+ {
+ if (file_read(&file, NULL, file.packet_remaining) < 0)
+ {
+ return 0;
+ }
+ }
+
+ return comment_size;
+}
diff --git a/lib/rbcodec/metadata/vox.c b/lib/rbcodec/metadata/vox.c
new file mode 100644
index 0000000000..f6bc849a88
--- /dev/null
+++ b/lib/rbcodec/metadata/vox.c
@@ -0,0 +1,49 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2010 Yoshihisa Uchida
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+bool get_vox_metadata(int fd, struct mp3entry* id3)
+{
+ /*
+ * vox is headerless format
+ *
+ * frequency: 8000 Hz
+ * channels: mono
+ * bitspersample: 4
+ */
+ id3->frequency = 8000;
+ id3->bitrate = 8000 * 4 / 1000;
+ id3->vbr = false; /* All VOX files are CBR */
+ id3->filesize = filesize(fd);
+ id3->length = id3->filesize >> 2;
+
+ return true;
+}
diff --git a/lib/rbcodec/metadata/wave.c b/lib/rbcodec/metadata/wave.c
new file mode 100644
index 0000000000..45acea1fa1
--- /dev/null
+++ b/lib/rbcodec/metadata/wave.c
@@ -0,0 +1,432 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ * Copyright (C) 2010 Yoshihisa Uchida
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "rbunicode.h"
+#include "logf.h"
+
+#ifdef DEBUGF
+#undef DEBUGF
+#define DEBUGF(...)
+#endif
+
+/* Wave(RIFF)/Wave64 format */
+
+
+# define AV_WL32(p, d) do { \
+ ((uint8_t*)(p))[0] = (d); \
+ ((uint8_t*)(p))[1] = (d)>>8; \
+ ((uint8_t*)(p))[2] = (d)>>16; \
+ ((uint8_t*)(p))[3] = (d)>>24; \
+ } while(0)
+# define AV_WL16(p, d) do { \
+ ((uint8_t*)(p))[0] = (d); \
+ ((uint8_t*)(p))[1] = (d)>>8; \
+ } while(0)
+
+enum {
+ RIFF_CHUNK = 0,
+ WAVE_CHUNK,
+ FMT_CHUNK,
+ FACT_CHUNK,
+ DATA_CHUNK,
+ LIST_CHUNK,
+};
+
+/* Wave chunk names */
+#define WAVE_CHUNKNAME_LENGTH 4
+#define WAVE_CHUNKSIZE_LENGTH 4
+
+static const unsigned char * const wave_chunklist
+ = "RIFF"
+ "WAVE"
+ "fmt "
+ "fact"
+ "data"
+ "LIST";
+
+/* Wave64 GUIDs */
+#define WAVE64_CHUNKNAME_LENGTH 16
+#define WAVE64_CHUNKSIZE_LENGTH 8
+
+static const unsigned char * const wave64_chunklist
+ = "riff\x2e\x91\xcf\x11\xa5\xd6\x28\xdb\x04\xc1\x00\x00"
+ "wave\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a"
+ "fmt \xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a"
+ "fact\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a"
+ "data\xf3\xac\xd3\x11\x8c\xd1\x00\xc0\x4f\x8e\xdb\x8a"
+ "\xbc\x94\x5f\x92\x5a\x52\xd2\x11\x86\xdc\x00\xc0\x4f\x8e\xdb\x8a";
+
+/* list/info chunk */
+
+struct info_chunk {
+ const unsigned char* tag;
+ size_t offset;
+};
+
+/* info chunk names are common wave/wave64 */
+static const struct info_chunk info_chunks[] = {
+ { "INAM", offsetof(struct mp3entry, title), }, /* title */
+ { "IART", offsetof(struct mp3entry, artist), }, /* artist */
+ { "ISBJ", offsetof(struct mp3entry, albumartist), }, /* albumartist */
+ { "IPRD", offsetof(struct mp3entry, album), }, /* album */
+ { "IWRI", offsetof(struct mp3entry, composer), }, /* composer */
+ { "ICMT", offsetof(struct mp3entry, comment), }, /* comment */
+ { "ISRF", offsetof(struct mp3entry, grouping), }, /* grouping */
+ { "IGNR", offsetof(struct mp3entry, genre_string), }, /* genre */
+ { "ICRD", offsetof(struct mp3entry, year_string), }, /* date */
+ { "IPRT", offsetof(struct mp3entry, track_string), }, /* track/trackcount */
+ { "IFRM", offsetof(struct mp3entry, disc_string), }, /* disc/disccount */
+};
+
+#define INFO_CHUNK_COUNT ((int)ARRAYLEN(info_chunks))
+
+/* support formats */
+enum
+{
+ WAVE_FORMAT_PCM = 0x0001, /* Microsoft PCM Format */
+ WAVE_FORMAT_ADPCM = 0x0002, /* Microsoft ADPCM Format */
+ WAVE_FORMAT_IEEE_FLOAT = 0x0003, /* IEEE Float */
+ WAVE_FORMAT_ALAW = 0x0006, /* Microsoft ALAW */
+ WAVE_FORMAT_MULAW = 0x0007, /* Microsoft MULAW */
+ WAVE_FORMAT_DVI_ADPCM = 0x0011, /* Intel's DVI ADPCM */
+ WAVE_FORMAT_DIALOGIC_OKI_ADPCM = 0x0017, /* Dialogic OKI ADPCM */
+ WAVE_FORMAT_YAMAHA_ADPCM = 0x0020, /* Yamaha ADPCM */
+ WAVE_FORMAT_XBOX_ADPCM = 0x0069, /* XBOX ADPCM */
+ IBM_FORMAT_MULAW = 0x0101, /* same as WAVE_FORMAT_MULAW */
+ IBM_FORMAT_ALAW = 0x0102, /* same as WAVE_FORMAT_ALAW */
+ WAVE_FORMAT_ATRAC3 = 0x0270, /* Atrac3 stream */
+ WAVE_FORMAT_SWF_ADPCM = 0x5346, /* Adobe SWF ADPCM */
+ WAVE_FORMAT_EXTENSIBLE = 0xFFFE,
+};
+
+struct wave_fmt {
+ unsigned int formattag;
+ unsigned int channels;
+ unsigned int blockalign;
+ unsigned int bitspersample;
+ unsigned int samplesperblock;
+ uint32_t totalsamples;
+ uint64_t numbytes;
+};
+
+static unsigned char *convert_utf8(const unsigned char *src, unsigned char *dst,
+ int size, bool is_64)
+{
+ if (is_64)
+ {
+ /* Note: wave64: metadata codepage is UTF-16 only */
+ return utf16LEdecode(src, dst, size);
+ }
+ return iso_decode(src, dst, -1, size);
+}
+
+static void set_totalsamples(struct wave_fmt *fmt, struct mp3entry* id3)
+{
+ switch (fmt->formattag)
+ {
+ case WAVE_FORMAT_PCM:
+ case WAVE_FORMAT_IEEE_FLOAT:
+ case WAVE_FORMAT_ALAW:
+ case WAVE_FORMAT_MULAW:
+ case IBM_FORMAT_ALAW:
+ case IBM_FORMAT_MULAW:
+ fmt->blockalign = fmt->bitspersample * fmt->channels >> 3;
+ fmt->samplesperblock = 1;
+ break;
+ case WAVE_FORMAT_YAMAHA_ADPCM:
+ if (id3->channels != 0)
+ {
+ fmt->samplesperblock =
+ (fmt->blockalign == ((id3->frequency / 60) + 4) * fmt->channels)?
+ id3->frequency / 30 : (fmt->blockalign << 1) / fmt->channels;
+ }
+ break;
+ case WAVE_FORMAT_DIALOGIC_OKI_ADPCM:
+ fmt->blockalign = 1;
+ fmt->samplesperblock = 2;
+ break;
+ case WAVE_FORMAT_SWF_ADPCM:
+ if (fmt->bitspersample != 0 && id3->channels != 0)
+ {
+ fmt->samplesperblock
+ = (((fmt->blockalign << 3) - 2) / fmt->channels - 22)
+ / fmt->bitspersample + 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (fmt->blockalign != 0)
+ fmt->totalsamples = (fmt->numbytes / fmt->blockalign) * fmt->samplesperblock;
+}
+
+static void parse_riff_format(unsigned char* buf, int fmtsize, struct wave_fmt *fmt,
+ struct mp3entry* id3)
+{
+ /* wFormatTag */
+ fmt->formattag = buf[0] | (buf[1] << 8);
+ /* wChannels */
+ fmt->channels = buf[2] | (buf[3] << 8);
+ /* dwSamplesPerSec */
+ id3->frequency = get_long_le(&buf[4]);
+ /* dwAvgBytesPerSec */
+ id3->bitrate = (get_long_le(&buf[8]) * 8) / 1000;
+ /* wBlockAlign */
+ fmt->blockalign = buf[12] | (buf[13] << 8);
+ /* wBitsPerSample */
+ fmt->bitspersample = buf[14] | (buf[15] << 8);
+
+ if (fmt->formattag != WAVE_FORMAT_EXTENSIBLE)
+ {
+ if (fmtsize > 19)
+ {
+ /* wSamplesPerBlock */
+ fmt->samplesperblock = buf[18] | (buf[19] << 8);
+ }
+ }
+ else if (fmtsize > 25)
+ {
+ /* wValidBitsPerSample */
+ fmt->bitspersample = buf[18] | (buf[19] << 8);
+ /* SubFormat */
+ fmt->formattag = buf[24] | (buf[25] << 8);
+ }
+
+ /* Check for ATRAC3 stream */
+ if (fmt->formattag == WAVE_FORMAT_ATRAC3)
+ {
+ int jsflag = 0;
+ if(id3->bitrate == 66 || id3->bitrate == 94)
+ jsflag = 1;
+
+ id3->extradata_size = 14;
+ id3->channels = 2;
+ id3->codectype = AFMT_OMA_ATRAC3;
+ id3->bytesperframe = fmt->blockalign;
+
+ /* Store the extradata for the codec */
+ AV_WL16(&id3->id3v2buf[0], 1); // always 1
+ AV_WL32(&id3->id3v2buf[2], id3->frequency);// samples rate
+ AV_WL16(&id3->id3v2buf[6], jsflag); // coding mode
+ AV_WL16(&id3->id3v2buf[8], jsflag); // coding mode
+ AV_WL16(&id3->id3v2buf[10], 1); // always 1
+ AV_WL16(&id3->id3v2buf[12], 0); // always 0
+ }
+}
+
+static void parse_list_chunk(int fd, struct mp3entry* id3, int chunksize, bool is_64)
+{
+ unsigned char tmpbuf[ID3V2_BUF_SIZE];
+ unsigned char *bp = tmpbuf;
+ unsigned char *endp;
+ unsigned char *data_pos;
+ unsigned char *tag_pos = id3->id3v2buf;
+ int datasize;
+ int infosize;
+ int remain;
+ int i;
+
+ if (is_64)
+ lseek(fd, 4, SEEK_CUR);
+ else if (read(fd, bp, 4) < 4 || memcmp(bp, "INFO", 4))
+ return;
+
+ /* decrease skip bytes */
+ chunksize -= 4;
+
+ infosize = read(fd, bp, (ID3V2_BUF_SIZE > chunksize)? chunksize : ID3V2_BUF_SIZE);
+ if (infosize <= 8)
+ return;
+
+ endp = bp + infosize;
+ while (bp < endp)
+ {
+ datasize = get_long_le(bp + 4);
+ data_pos = bp + 8;
+ remain = ID3V2_BUF_SIZE - (tag_pos - (unsigned char*)id3->id3v2buf);
+ if (remain < 1)
+ break;
+
+ for (i = 0; i < INFO_CHUNK_COUNT; i++)
+ {
+ if (memcmp(bp, info_chunks[i].tag, 4) == 0)
+ {
+ *((char **)(((char*)id3) + info_chunks[i].offset)) = tag_pos;
+ tag_pos = convert_utf8(data_pos, tag_pos,
+ (datasize + 1 >= remain )? remain - 1 : datasize,
+ is_64);
+ *tag_pos++ = 0;
+ break;
+ }
+ }
+ bp = data_pos + datasize + (datasize & 1);
+ };
+}
+
+static bool read_header(int fd, struct mp3entry* id3, const unsigned char *chunknames,
+ bool is_64)
+{
+ /* Use the temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+
+ struct wave_fmt fmt;
+
+ const unsigned int namelen = (is_64)? WAVE64_CHUNKNAME_LENGTH : WAVE_CHUNKNAME_LENGTH;
+ const unsigned int sizelen = (is_64)? WAVE64_CHUNKSIZE_LENGTH : WAVE_CHUNKSIZE_LENGTH;
+ const unsigned int len = namelen + sizelen;
+ uint64_t chunksize;
+ uint64_t offset = len + namelen;
+ int read_data;
+
+ memset(&fmt, 0, sizeof(struct wave_fmt));
+
+ id3->vbr = false; /* All Wave/Wave64 files are CBR */
+ id3->filesize = filesize(fd);
+
+ /* get RIFF chunk header */
+ lseek(fd, 0, SEEK_SET);
+ read(fd, buf, offset);
+
+ if ((memcmp(buf, chunknames + RIFF_CHUNK * namelen, namelen) != 0) ||
+ (memcmp(buf + len, chunknames + WAVE_CHUNK * namelen, namelen) != 0))
+ {
+ DEBUGF("metadata error: missing riff header.\n");
+ return false;
+ }
+
+ /* iterate over WAVE chunks until 'data' chunk */
+ while (read(fd, buf, len) > 0)
+ {
+ offset += len;
+
+ /* get chunk size (when the header is wave64, chunksize includes GUID and data length) */
+ chunksize = (is_64) ? get_uint64_le(buf + namelen) - len :
+ get_long_le(buf + namelen);
+
+ read_data = 0;
+ if (memcmp(buf, chunknames + FMT_CHUNK * namelen, namelen) == 0)
+ {
+ DEBUGF("find 'fmt ' chunk\n");
+
+ if (chunksize < 16)
+ {
+ DEBUGF("metadata error: 'fmt ' chunk is too small: %d\n", (int)chunksize);
+ return false;
+ }
+
+ /* get and parse format */
+ read_data = (chunksize > 25)? 26 : chunksize;
+
+ read(fd, buf, read_data);
+ parse_riff_format(buf, read_data, &fmt, id3);
+ }
+ else if (memcmp(buf, chunknames + FACT_CHUNK * namelen, namelen) == 0)
+ {
+ DEBUGF("find 'fact' chunk\n");
+
+ /* dwSampleLength */
+ if (chunksize >= sizelen)
+ {
+ /* get totalsamples */
+ read_data = sizelen;
+ read(fd, buf, read_data);
+ fmt.totalsamples = (is_64)? get_uint64_le(buf) : get_long_le(buf);
+ }
+ }
+ else if (memcmp(buf, chunknames + DATA_CHUNK * namelen, namelen) == 0)
+ {
+ DEBUGF("find 'data' chunk\n");
+ fmt.numbytes = chunksize;
+ if (fmt.formattag == WAVE_FORMAT_ATRAC3)
+ id3->first_frame_offset = offset;
+ }
+ else if (memcmp(buf, chunknames + LIST_CHUNK * namelen, namelen) == 0)
+ {
+ DEBUGF("find 'LIST' chunk\n");
+ parse_list_chunk(fd, id3, chunksize, is_64);
+ lseek(fd, offset, SEEK_SET);
+ }
+
+ /* padded to next chunk */
+ chunksize += ((is_64)? ((1 + ~chunksize) & 0x07) : (chunksize & 1));
+
+ offset += chunksize;
+ if (offset >= id3->filesize)
+ break;
+
+ lseek(fd, chunksize - read_data, SEEK_CUR);
+ }
+
+ if (fmt.numbytes == 0)
+ {
+ DEBUGF("metadata error: read error or missing 'data' chunk.\n");
+ return false;
+ }
+
+ if (fmt.totalsamples == 0)
+ set_totalsamples(&fmt, id3);
+
+ if (id3->frequency == 0 || id3->bitrate == 0)
+ {
+ DEBUGF("metadata error: frequency or bitrate is 0\n");
+ return false;
+ }
+
+ /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
+ id3->length = (fmt.formattag != WAVE_FORMAT_ATRAC3)?
+ (uint64_t)fmt.totalsamples * 1000 / id3->frequency :
+ ((id3->filesize - id3->first_frame_offset) * 8) / id3->bitrate;
+
+ /* output header/id3 info (for debug) */
+ DEBUGF("%s header info ----\n", (is_64)? "wave64" : "wave");
+ DEBUGF(" format: %04x\n", (int)fmt.formattag);
+ DEBUGF(" channels: %u\n", fmt.channels);
+ DEBUGF(" blockalign: %u\n", fmt.blockalign);
+ DEBUGF(" bitspersample: %u\n", fmt.bitspersample);
+ DEBUGF(" samplesperblock: %u\n", fmt.samplesperblock);
+ DEBUGF(" totalsamples: %u\n", (unsigned int)fmt.totalsamples);
+ DEBUGF(" numbytes: %u\n", (unsigned int)fmt.numbytes);
+ DEBUGF("id3 info ----\n");
+ DEBUGF(" frequency: %u\n", (unsigned int)id3->frequency);
+ DEBUGF(" bitrate: %d\n", id3->bitrate);
+ DEBUGF(" length: %u\n", (unsigned int)id3->length);
+
+ return true;
+}
+
+bool get_wave_metadata(int fd, struct mp3entry* id3)
+{
+ return read_header(fd, id3, wave_chunklist, false);
+}
+
+bool get_wave64_metadata(int fd, struct mp3entry* id3)
+{
+ return read_header(fd, id3, wave64_chunklist, true);
+}
diff --git a/lib/rbcodec/metadata/wavpack.c b/lib/rbcodec/metadata/wavpack.c
new file mode 100644
index 0000000000..f2811df8f3
--- /dev/null
+++ b/lib/rbcodec/metadata/wavpack.c
@@ -0,0 +1,160 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2007 David Bryant
+ *
+ * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "metadata.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "logf.h"
+
+#define ID_UNIQUE 0x3f
+#define ID_LARGE 0x80
+#define ID_SAMPLE_RATE 0x27
+
+#define MONO_FLAG 4
+#define HYBRID_FLAG 8
+
+static const long wavpack_sample_rates [] =
+{
+ 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
+ 32000, 44100, 48000, 64000, 88200, 96000, 192000
+};
+
+/* A simple parser to read basic information from a WavPack file. This
+ * now works with self-extrating WavPack files and also will scan the
+ * metadata for non-standard sampling rates. This no longer fails on
+ * WavPack files containing floating-point audio data because these are
+ * now converted to standard Rockbox format in the decoder, and also
+ * handles the case where up to 15 non-audio blocks might occur at the
+ * beginning of the file.
+ */
+
+bool get_wavpack_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ uint32_t totalsamples = (uint32_t) -1;
+ int i;
+
+ for (i = 0; i < 256; ++i) {
+
+ /* at every 256 bytes into file, try to read a WavPack header */
+
+ if ((lseek(fd, i * 256, SEEK_SET) < 0) || (read(fd, buf, 32) < 32))
+ return false;
+
+ /* if valid WavPack 4 header version, break */
+
+ if (memcmp (buf, "wvpk", 4) == 0 && buf [9] == 4 &&
+ (buf [8] >= 2 && buf [8] <= 0x10))
+ break;
+ }
+
+ if (i == 256) {
+ logf ("Not a WavPack file");
+ return false;
+ }
+
+ id3->vbr = true; /* All WavPack files are VBR */
+ id3->filesize = filesize (fd);
+
+ /* check up to 16 headers before we give up finding one with audio */
+
+ for (i = 0; i < 16; ++i) {
+ uint32_t meta_bytes = get_long_le(&buf [4]) - 24;
+ uint32_t trial_totalsamples = get_long_le(&buf[12]);
+ uint32_t blockindex = get_long_le(&buf[16]);
+ uint32_t blocksamples = get_long_le(&buf[20]);
+ uint32_t flags = get_long_le(&buf[24]);
+
+ if (totalsamples == (uint32_t) -1 && blockindex == 0)
+ totalsamples = trial_totalsamples;
+
+ if (blocksamples) {
+ int srindx = ((buf [26] >> 7) & 1) + ((buf [27] << 1) & 14);
+
+ if (srindx == 15) {
+ uint32_t meta_size;
+
+ id3->frequency = 44100;
+
+ while (meta_bytes >= 6) {
+ if (read(fd, buf, 2) < 2)
+ break;
+
+ if (buf [0] & ID_LARGE) {
+ if (read(fd, buf + 2, 2) < 2)
+ break;
+
+ meta_size = (buf [1] << 1) + (buf [2] << 9) + (buf [3] << 17);
+ meta_bytes -= meta_size + 4;
+ }
+ else {
+ meta_size = buf [1] << 1;
+ meta_bytes -= meta_size + 2;
+
+ if ((buf [0] & ID_UNIQUE) == ID_SAMPLE_RATE) {
+ if (meta_size == 4 && read(fd, buf + 2, 4) == 4)
+ id3->frequency = buf [2] + (buf [3] << 8) + (buf [4] << 16);
+
+ break;
+ }
+ }
+
+ if (meta_size > 0 && lseek(fd, meta_size, SEEK_CUR) < 0)
+ break;
+ }
+ }
+ else
+ id3->frequency = wavpack_sample_rates[srindx];
+
+ /* if the total number of samples is still unknown, make a guess on the high side (for now) */
+
+ if (totalsamples == (uint32_t) -1) {
+ totalsamples = id3->filesize * 3;
+
+ if (!(flags & HYBRID_FLAG))
+ totalsamples /= 2;
+
+ if (!(flags & MONO_FLAG))
+ totalsamples /= 2;
+ }
+
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+ id3->bitrate = id3->filesize / (id3->length / 8);
+
+ read_ape_tags(fd, id3);
+ return true;
+ }
+ else { /* block did not contain audio, so seek to the end and see if there's another */
+ if ((meta_bytes > 0 && lseek(fd, meta_bytes, SEEK_CUR) < 0) ||
+ read(fd, buf, 32) < 32 || memcmp (buf, "wvpk", 4) != 0)
+ break;
+ }
+ }
+
+ return false;
+}