summaryrefslogtreecommitdiffstats
path: root/lib/rbcodec/metadata/wave.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rbcodec/metadata/wave.c')
-rw-r--r--lib/rbcodec/metadata/wave.c432
1 files changed, 432 insertions, 0 deletions
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);
+}