50a6ca39ad
This is to a) to cleanup firmware/common and firmware/include a bit, but also b) for Rockbox as an application which should use the host system's c library and headers, separating makes it easy to exclude our files from the build. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@25850 a1c6a512-1295-4272-9138-f99709370657
469 lines
13 KiB
C
469 lines
13 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* 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 "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);
|
|
}
|