summaryrefslogtreecommitdiff
path: root/lib/rbcodec/metadata/mp3data.c
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/mp3data.c
parent24bd9d5393dbe39a5c6194877bc00ede669b1d5d (diff)
downloadrockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.zip
rockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.tar.gz
rockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.tar.bz2
rockbox-b5716df4cb2837bbbc42195cf1aefcf03e21d6a6.tar.xz
Build librbcodec with DSP and metadata.
All associated files are moved to /lib/rbcodec. Change-Id: I572ddd2b8a996aae1e98c081d06b1ed356dce222
Diffstat (limited to 'lib/rbcodec/metadata/mp3data.c')
-rw-r--r--lib/rbcodec/metadata/mp3data.c849
1 files changed, 849 insertions, 0 deletions
diff --git a/lib/rbcodec/metadata/mp3data.c b/lib/rbcodec/metadata/mp3data.c
new file mode 100644
index 0000000..13ff0a8
--- /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