[Rhythmbox-devel] better mp3 metadata



Hey,

So the old code was very broken, and slow. Now, it's fast, and should be
more correct as well :)

- Ported from xine-lib to qahog to rhythmbox
- Pretty much self-container (see mp3bitrate.[ch])
- Detects MPEG 2.5 files
- Doesn't use crappy magic numbers for files with Xing headers
- Get the time right for VBR files

Needs testing on big endian machines (but should work fine).

Let me know what you think, and please, commit that :)

---
Bastien Nocera <hadess@hadess.net> 
Her artistic sense was exquisitely refined, like someone who can tell
butter from "I Can't Believe It's Not Butter." 
Index: mp3-stream-info-impl.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/monkey-media/stream-info-impl/mp3-stream-info-impl.c,v
retrieving revision 1.6
diff -u -r1.6 mp3-stream-info-impl.c
--- mp3-stream-info-impl.c	3 Sep 2003 01:27:33 -0000	1.6
+++ mp3-stream-info-impl.c	14 Nov 2003 21:30:11 -0000
@@ -53,10 +53,21 @@
 static int MP3_stream_info_impl_get_n_values (MonkeyMediaStreamInfo *info,
 				              MonkeyMediaStreamInfoField field);
 
+struct MP3BitrateInfo
+{
+	int bitrate;
+	int samplerate;
+	int time;
+	int channels;
+	int version;
+	int vbr;
+};
+
 struct MP3StreamInfoImplPrivate
 {
 	struct id3_tag *tag;
 	struct id3_vfs_file *file;
+	struct MP3BitrateInfo *info_num;
 };
 
 static GObjectClass *parent_class = NULL;
@@ -125,7 +136,8 @@
 
 	if (impl->priv->file != NULL)
 		id3_vfs_close (impl->priv->file);
-	
+
+	g_free (impl->priv->info_num);
 	g_free (impl->priv);
 
 	G_OBJECT_CLASS (parent_class)->finalize (object);
@@ -157,6 +169,25 @@
 	impl->priv->tag = id3_vfs_tag (impl->priv->file);
 }
 
+static void
+MP3_stream_info_impl_get_bitrate_info (MP3StreamInfoImpl *info)
+{
+	if (info->priv->info_num == NULL)
+	{
+		struct MP3BitrateInfo *info_num;
+
+		info_num = g_new0 (struct MP3BitrateInfo, 1);
+		id3_vfs_bitrate (info->priv->file,
+				&info_num->bitrate,
+				&info_num->samplerate,
+				&info_num->time,
+				&info_num->version,
+				&info_num->vbr,
+				&info_num->channels);
+		info->priv->info_num = info_num;
+	}
+}
+
 static int
 MP3_stream_info_impl_get_n_values (MonkeyMediaStreamInfo *info,
 				   MonkeyMediaStreamInfoField field)
@@ -435,13 +466,14 @@
 		}
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_DURATION:
+		g_value_init (value, G_TYPE_LONG);
+		MP3_stream_info_impl_get_bitrate_info (impl);
+
+		if (impl->priv->info_num->vbr == 0)
 		{
-			int bitrate;
 			GnomeVFSFileSize size;
 			GValue val = { 0, };
 
-			g_value_init (value, G_TYPE_LONG);
-
 			MP3_stream_info_impl_get_value (info,
 							MONKEY_MEDIA_STREAM_INFO_FIELD_FILE_SIZE,
 							0,
@@ -449,12 +481,12 @@
 			size = g_value_get_long (&val);
 			g_value_unset (&val);
 
-			bitrate = id3_vfs_bitrate (impl->priv->file);
-
-			if (bitrate > 0)
-				g_value_set_long (value, ((double) size / 1024.0f) / ((double) bitrate / 8.0f));
+			if (impl->priv->info_num->bitrate > 0)
+				g_value_set_long (value, ((double) size / 1000.0f) / ((double) impl->priv->info_num->bitrate / 8.0f / 1000.0f));
 			else
 				g_value_set_long (value, 0);
+		} else {
+			g_value_set_long (value, impl->priv->info_num->time);
 		}
 		break;
 
@@ -464,22 +496,27 @@
 		g_value_set_boolean (value, TRUE);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_CODEC_INFO:
+		MP3_stream_info_impl_get_bitrate_info (impl);
+		g_value_init (value, G_TYPE_STRING);
+		if (impl->priv->info_num->version != 3)
 		{
-			int version = id3_vfs_version (impl->priv->file);
-			tmp = g_strdup_printf (_("MPEG %d Layer III"), version);
-			g_value_init (value, G_TYPE_STRING);
-			g_value_set_string (value, tmp);
-			g_free (tmp);
+			tmp = g_strdup_printf (_("MPEG %d Layer III"), impl->priv->info_num->version);
+		} else {
+			tmp = g_strdup (_("MPEG 2.5 Layer III"));
 		}
+		g_value_set_string (value, tmp);
+		g_free (tmp);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_BIT_RATE:
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_AVERAGE_BIT_RATE:
+		MP3_stream_info_impl_get_bitrate_info (impl);
 		g_value_init (value, G_TYPE_INT);
-		g_value_set_int (value, id3_vfs_bitrate (impl->priv->file));
+		g_value_set_int (value, impl->priv->info_num->bitrate / 1000);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_QUALITY:
+		MP3_stream_info_impl_get_bitrate_info (impl);
 		g_value_init (value, MONKEY_MEDIA_TYPE_AUDIO_QUALITY);
-		g_value_set_enum (value, monkey_media_audio_quality_from_bit_rate (id3_vfs_bitrate (impl->priv->file)));
+		g_value_set_enum (value, monkey_media_audio_quality_from_bit_rate (impl->priv->info_num->bitrate / 1000));
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_TRM_ID:
 		/* FIXME */
@@ -487,16 +524,19 @@
 		g_value_set_string (value, NULL);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_VARIABLE_BIT_RATE:
+		MP3_stream_info_impl_get_bitrate_info (impl);
 		g_value_init (value, G_TYPE_BOOLEAN);
-		g_value_set_boolean (value, id3_vfs_vbr (impl->priv->file));
+		g_value_set_boolean (value, impl->priv->info_num->vbr);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_SAMPLE_RATE:
+		MP3_stream_info_impl_get_bitrate_info (impl);
 		g_value_init (value, G_TYPE_LONG);
-		g_value_set_long (value, id3_vfs_samplerate (impl->priv->file));
+		g_value_set_long (value, impl->priv->info_num->samplerate);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_CHANNELS:
+		MP3_stream_info_impl_get_bitrate_info (impl);
 		g_value_init (value, G_TYPE_INT);
-		g_value_set_int (value, id3_vfs_channels (impl->priv->file));
+		g_value_set_int (value, impl->priv->info_num->channels);
 		break;
 	case MONKEY_MEDIA_STREAM_INFO_FIELD_AUDIO_SERIAL_NUMBER:
 		g_value_init (value, G_TYPE_LONG);
Index: id3-vfs/Makefile.am
===================================================================
RCS file: /cvs/gnome/rhythmbox/monkey-media/stream-info-impl/id3-vfs/Makefile.am,v
retrieving revision 1.3
diff -u -r1.3 Makefile.am
--- id3-vfs/Makefile.am	1 Sep 2003 04:20:58 -0000	1.3
+++ id3-vfs/Makefile.am	14 Nov 2003 21:30:11 -0000
@@ -3,7 +3,7 @@
 if USE_ID3VFS
 noinst_LTLIBRARIES = libid3-vfs.la
 
-libid3_vfs_la_SOURCES = id3-vfs.c id3-vfs.h field.h file.h global.h tag.h
+libid3_vfs_la_SOURCES = id3-vfs.c id3-vfs.h field.h file.h global.h tag.h mp3bitrate.c mp3bitrate.h
 
 libid3_vfs_la_LIBADD = $(ID3TAG_LIBS)
 
Index: id3-vfs/id3-vfs.c
===================================================================
RCS file: /cvs/gnome/rhythmbox/monkey-media/stream-info-impl/id3-vfs/id3-vfs.c,v
retrieving revision 1.3
diff -u -r1.3 id3-vfs.c
--- id3-vfs/id3-vfs.c	1 Sep 2003 04:20:59 -0000	1.3
+++ id3-vfs/id3-vfs.c	14 Nov 2003 21:30:12 -0000
@@ -43,6 +43,7 @@
 # include "file.h"
 # include "tag.h"
 # include "field.h"
+# include "mp3bitrate.h"
 
 struct vfstag {
   struct id3_tag *tag;
@@ -457,260 +458,64 @@
   return 0;
 }
 
-int bitrates[2][16] = {
-{0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,},          /* MPEG2 */
-{0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,}}; /* MPEG1 */
-
-long samprates[2][3] = {
-{ 22050, 24000, 16000 },  /* MPEG2 */
-{ 44100, 48000, 32000 }}; /* MPEG1 */
-
-static int extractI4(unsigned char *buf)
-{
-	int x;
-	/* big endian extract */
-
-	x = buf[0];
-	x <<= 8;
-	x |= buf[1];
-	x <<= 8;
-	x |= buf[2];
-	x <<= 8;
-	x |= buf[3];
-
-	return(x);
-}
-
-/* check for valid MPEG header */
-static int is_mphead(unsigned char *buf)
-{
-	if (buf[0] != 0xff) return(0);		
-	if ((buf[1] & 0xf0) != 0xf0) return(0);	 /* 12 bits framesync */
-	
-	return(1); 
-}
-
-/* check for valid "Xing" VBR header */
-static int is_xhead(unsigned char *buf)
-{
-	if (buf[0] != 'X') return(0);
-	if (buf[1] != 'i') return(0);
-	if (buf[2] != 'n') return(0);
-	if (buf[3] != 'g') return(0);
-	
-	return(1);
-}
-
-int id3_vfs_bitrate (struct id3_vfs_file *file)
+int
+id3_vfs_bitrate (struct id3_vfs_file *file, int *bitrate, int *samplerate,
+		int *time, int *version, int *vbr, int *channels)
 {
-  GnomeVFSFileSize save_position;
+  GnomeVFSFileSize save_position, length_read;
   GnomeVFSHandle *iofile = file->iofile;
   GnomeVFSResult res;
-	int bitrate = 0;
-	GnomeVFSFileSize length_read;
-	guchar buffer[8192];
-
-  if (gnome_vfs_tell(iofile, &save_position) != GNOME_VFS_OK)
-    return 0;
-
-	gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
+  guchar buffer[8192];
+  int found, i;
 
-  res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
-  if ((res == GNOME_VFS_OK) && (length_read > 512))
-  {
-	  int i = 0;
-		int ver,srindex,brindex,xbytes,xframes;
-		long long total_bytes, magic1, magic2;
-	
-	  while(!is_mphead(buffer+i)) {
-		  i++;
-		  if (i>length_read-4) goto bitdone;  /* no valid header, give up */
-	  }	
-
-	  ver = (buffer[i+1] & 0x08) >> 3;
-	  brindex = (buffer[i+2] & 0xf0) >> 4;
-	  srindex = (buffer[i+2] & 0x0c) >> 2;
-
-	  /* but if there is a Xing header we'll use that instead... */	
-	  i=0;
-	  while(!is_xhead(buffer+i)) {
-		  i++;
-		  if (i>length_read-16)
-			{
-			  bitrate = (bitrates[ver][brindex]);
-		    goto bitdone;
-			}
-	  }
-
-	  xframes = extractI4(buffer+i+8);
-	  xbytes = extractI4(buffer+i+12);
-
-	  if (xframes <= 0) {
-		  bitrate = 0;
-		  goto bitdone;
-	  }
-
-	  total_bytes = (long long) samprates[ver][srindex] * (long long) xbytes;
-	  magic1 = total_bytes / (long long) (576 + ver * 576);
-	  magic2 = magic1 / (long long) xframes;
-	  bitrate = (int) ((long long) magic2 / (long long) 125);
-  }
-
-bitdone:
-  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position)
-      != GNOME_VFS_OK)
-    return 0;
-
-	return bitrate;
-}
-
-long id3_vfs_samplerate (struct id3_vfs_file *file)
-{
-  GnomeVFSFileSize save_position;
-  GnomeVFSHandle *iofile = file->iofile;
-  GnomeVFSResult res;
-	long samprate = 0;
-	GnomeVFSFileSize length_read;
-	guchar buffer[8192];
+  *bitrate = 0;
+  *samplerate = 0;
+  *time = 0;
+  *channels = 0;
+  *version = 0;
+  *vbr = 0;
+  found = 0;
 
   if (gnome_vfs_tell(iofile, &save_position) != GNOME_VFS_OK)
     return 0;
 
-	gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
+  gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
 
   res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
-  if ((res == GNOME_VFS_OK) && (length_read > 512))
-  {
-	  int i = 0;
-		int ver,srindex;
-	
-	  while(!is_mphead(buffer+i)) {
-		  i++;
-		  if (i>length_read-4) goto sampdone;  /* no valid header, give up */
-	  }	
-
-	  ver = (buffer[i+1] & 0x08) >> 3;
-	  srindex = (buffer[i+2] & 0x0c) >> 2;
+  if( res != GNOME_VFS_OK || length_read < 512 )
+    goto bitdone;
 
-		samprate = (long) (samprates[ver][srindex]);
-  }
-
-sampdone:
-  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position)
-      != GNOME_VFS_OK)
-    return 0;
-
-	return samprate;
-}
-
-int id3_vfs_channels (struct id3_vfs_file *file)
-{
-  GnomeVFSFileSize save_position;
-  GnomeVFSHandle *iofile = file->iofile;
-  GnomeVFSResult res;
-	int channels = 0;
-	GnomeVFSFileSize length_read;
-	guchar buffer[8192];
-
-  if (gnome_vfs_tell(iofile, &save_position) != GNOME_VFS_OK)
-    return 0;
-
-	gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
-
-  res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
-  if ((res == GNOME_VFS_OK) && (length_read > 512))
+  for (i = 0; i + 4 < length_read; i++)
   {
-	  int i = 0;
-	
-	  while(!is_mphead(buffer+i)) {
-		  i++;
-		  if (i>length_read-4) goto chandone;  /* no valid header, give up */
-	  }	
-
-		channels = (((buffer[i+3] & 0xc0) >> 6) == 2) ? 1 : 2;
+    if (mp3_bitrate_parse_header (buffer+i, length_read - i, bitrate, samplerate, time, version, vbr, channels))
+    {
+      found = 1;
+      break;
+    }
   }
 
-chandone:
-  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position)
-      != GNOME_VFS_OK)
-    return 0;
-
-	return channels;
-}
-
-gboolean id3_vfs_vbr (struct id3_vfs_file *file)
-{
-  GnomeVFSFileSize save_position;
-  GnomeVFSHandle *iofile = file->iofile;
-  GnomeVFSResult res;
-	gboolean vbr = FALSE;
-	GnomeVFSFileSize length_read;
-	guchar buffer[8192];
-
-  if (gnome_vfs_tell(iofile, &save_position) != GNOME_VFS_OK)
-    return 0;
-
-	gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
-
-  res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
-  if ((res == GNOME_VFS_OK) && (length_read > 512))
+  /* If we haven't found anything, try again with 8 more kB */
+  if (found == 0)
   {
-	  int i = 0;
-	
-	  while(!is_mphead(buffer+i)) {
-		  i++;
-		  if (i>length_read-4) goto vbrdone;  /* no valid header, give up */
-	  }	
-
-	  /* but if there is a Xing header we'll use that instead... */	
-	  i=0;
-	  while(!is_xhead(buffer+i)) {
-		  i++;
-		  if (i>length_read-16) goto vbrdone;
-	  }
-
-		vbr = TRUE;
-  }
+    res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
 
-vbrdone:
-  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position)
-      != GNOME_VFS_OK)
-    return 0;
-
-	return vbr;
-}
-
-int id3_vfs_version (struct id3_vfs_file *file)
-{
-  GnomeVFSFileSize save_position;
-  GnomeVFSHandle *iofile = file->iofile;
-  GnomeVFSResult res;
-	int version = 0;
-	GnomeVFSFileSize length_read;
-	guchar buffer[8192];
-
-  if (gnome_vfs_tell(iofile, &save_position) != GNOME_VFS_OK)
-    return 0;
+    if( res != GNOME_VFS_OK || length_read < 512 )
+      goto bitdone;
 
-	gnome_vfs_seek (iofile, GNOME_VFS_SEEK_START, 0);
-
-  res = gnome_vfs_read (iofile, buffer, sizeof (buffer), &length_read);
-  if ((res == GNOME_VFS_OK) && (length_read > 512))
-  {
-	  int i = 0;
-	
-	  while(!is_mphead(buffer+i)) {
-		  i++;
-		  if (i>length_read-4) goto verdone;  /* no valid header, give up */
-	  }	
-
-		version = (buffer[i+1] & 0x08) >> 3;
+    for (i = 0; i + 4 < length_read; i++)
+    {
+      if (mp3_bitrate_parse_header (buffer+i, length_read - i, bitrate, samplerate, time, version, vbr, channels))
+      {
+	      found = 1;
+	      break;
+      }
+    }
   }
 
-verdone:
-  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position)
-      != GNOME_VFS_OK)
+bitdone:
+  if (gnome_vfs_seek(iofile, GNOME_VFS_SEEK_START, save_position) != GNOME_VFS_OK)
     return 0;
 
-	return version;
+  return 1;
 }
+
Index: id3-vfs/id3-vfs.h
===================================================================
RCS file: /cvs/gnome/rhythmbox/monkey-media/stream-info-impl/id3-vfs/id3-vfs.h,v
retrieving revision 1.2
diff -u -r1.2 id3-vfs.h
--- id3-vfs/id3-vfs.h	1 Sep 2003 04:20:59 -0000	1.2
+++ id3-vfs/id3-vfs.h	14 Nov 2003 21:30:12 -0000
@@ -37,11 +37,8 @@
 struct id3_tag *id3_vfs_tag(struct id3_vfs_file const *);
 int id3_vfs_update(struct id3_vfs_file *);
 
-int id3_vfs_bitrate(struct id3_vfs_file *);
-long id3_vfs_samplerate(struct id3_vfs_file *);
-int id3_vfs_channels(struct id3_vfs_file *);
-gboolean id3_vfs_vbr(struct id3_vfs_file *);
-int id3_vfs_version(struct id3_vfs_file *);
+int id3_vfs_bitrate (struct id3_vfs_file *file, int *bitrate, int *samplerate,
+		int *time, int *version, int *vbr, int *channels);
 
 #endif /* _ID3_VFS_H_ */
 
Index: id3-vfs/mp3bitrate.c
===================================================================
RCS file: id3-vfs/mp3bitrate.c
diff -N id3-vfs/mp3bitrate.c
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ id3-vfs/mp3bitrate.c	14 Nov 2003 21:30:13 -0000
@@ -0,0 +1,259 @@
+
+#include "config.h"
+
+#include <glib.h>
+#include "mp3bitrate.h"
+
+#undef LOG
+#ifdef LOG
+#define lprintf(x...) g_print(x)
+#else
+#define lprintf(x...)
+#endif
+
+/* bitrate table tabsel_123[mpeg version][layer][bitrate index]
+ * values stored in kbps
+ */
+const int tabsel_123[2][3][16] = {
+  { {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,},
+    {0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384,},
+    {0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320,} },
+
+  { {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,},
+    {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,},
+    {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,} }
+};
+                                                                                
+static int frequencies[3][3] = {
+  { 44100, 48000, 32000 },
+  { 22050, 24000, 16000 },
+  { 11025, 12000,  8000 }
+};
+
+typedef struct
+{
+  int mpeg25_bit;
+  int layer;
+  int channel_mode;
+  int lsf_bit;
+} MP3Frame;
+
+#ifndef WORDS_BIGENDIAN
+#define BE_32(x) ((((guint8*)(x))[0] << 24) | \
+		(((guint8*)(x))[1] << 16) | \
+		(((guint8*)(x))[2] << 8) | \
+		((guint8*)(x))[3])
+#else
+#define BE_32(x) (*(guint32 *) (x))
+#endif
+
+#define FOURCC_TAG( ch0, ch1, ch2, ch3 )		\
+	( (long)(unsigned char)(ch3) |			\
+	  ( (long)(unsigned char)(ch2) << 8 ) |		\
+	  ( (long)(unsigned char)(ch1) << 16 ) |	\
+	  ( (long)(unsigned char)(ch0) << 24 ) )
+
+#define XING_TAG             FOURCC_TAG('X', 'i', 'n', 'g')
+#define XING_FRAMES_FLAG     0x0001
+#define XING_BYTES_FLAG      0x0002
+#define XING_TOC_FLAG        0x0004
+#define XING_VBR_SCALE_FLAG  0x0008
+
+/* check for valid "Xing" VBR header */
+static int is_xhead(unsigned char *buf)
+{
+  return (BE_32(buf) == XING_TAG);
+}
+
+static int mpg123_parse_xing_header(MP3Frame *frame,
+		int samplerate,
+		guint8 *buf, int bufsize, int *bitrate,
+		int *time)
+{
+  int i;
+  guint8 *ptr = buf;
+  double frame_duration;
+  int xflags, xframes, xbytes, xvbr_scale;
+  int abr;
+  guint8 xtoc[100];
+
+  xframes = xbytes = 0;
+
+  /* offset of the Xing header */
+  if( frame->mpeg25_bit )
+  {
+    /* mpeg1 */
+    if( frame->channel_mode != 3 )
+      ptr += (32 + 4);
+    else
+      ptr += (17 + 4);
+  } else {
+    /* mpeg2 */
+    if( frame->channel_mode != 3 )
+      ptr += (17 + 4);
+    else
+      ptr += (9 + 4);
+  }
+
+  if (ptr >= (buf + bufsize))
+    return 0;
+
+  if (is_xhead(ptr))
+  {
+    lprintf("Xing header found\n");
+
+    ptr += 4; if (ptr >= (buf + bufsize)) return 0;
+
+    xflags = BE_32(ptr);
+    ptr += 4; if (ptr >= (buf + bufsize)) return 0;
+
+    if (xflags & XING_FRAMES_FLAG)
+    {
+      xframes = BE_32(ptr);
+      lprintf("xframes: %d\n", xframes);
+      ptr += 4; if (ptr >= (buf + bufsize)) return 0;
+    }
+    if (xflags & XING_BYTES_FLAG)
+    {
+      xbytes = BE_32(ptr);
+      lprintf("xbytes: %d\n", xbytes);
+      ptr += 4; if (ptr >= (buf + bufsize)) return 0;
+    }
+    if (xflags & XING_TOC_FLAG)
+    {
+      lprintf("toc found\n");
+      for (i = 0; i < 100; i++)
+      {
+        xtoc[i] = *(ptr + i);
+	lprintf("%d ", xtoc[i]);
+      }
+      lprintf("\n");
+    }
+    ptr += 100; if (ptr >= (buf + bufsize)) return 0;
+    xvbr_scale = -1;
+    if (xflags & XING_VBR_SCALE_FLAG) {
+      xvbr_scale = BE_32(ptr);
+      lprintf("xvbr_scale: %d\n", xvbr_scale);
+    }
+
+    /* 1 kbit = 1000 bits ! (and not 1024 bits) */
+    if (xflags & (XING_FRAMES_FLAG | XING_BYTES_FLAG)) {
+      if (frame->layer == 1) {
+        frame_duration = 384.0 / (double)samplerate;
+      } else {
+        int slots_per_frame;
+	slots_per_frame = (frame->layer == 3 && !frame->lsf_bit) ? 72 : 144;
+	frame_duration = slots_per_frame * 8.0 / (double)samplerate;
+      }
+      abr = ((double)xbytes * 8.0) / ((double)xframes * frame_duration);
+      lprintf("abr: %d bps\n", abr);
+      *bitrate = abr;
+      *time = (double)xframes * frame_duration;
+      lprintf("stream_length: %d s, %d min %d s\n", *time,
+		      *time / 60, *time % 60);
+    } else {
+      /* it's a stupid Xing header */
+      lprintf ("not a Xing VBR file\n");
+    }
+    return 1;
+  } else {
+    lprintf("Xing header not found\n");
+    return 0;
+  }
+}
+
+/*
+ * Returns 1 if the header was parsed successfully, 0 if it failed
+ *
+ * bitrate: self-explanatory
+ * samplerate: ditto
+ * time: only informed if we have a VBR file with Xing headers, needs to be
+ *       deduced from the bitrate and filesize otherwise
+ * version: 1 for MPEG Version 1, 2 for MPEG Version 2, and
+ *          3 for MPEG Version 2.5
+ * vbr: whether it is a variable bitrate stream
+ */
+int
+mp3_bitrate_parse_header (guchar *buffer, guint length_read, int *bitrate, int *samplerate, int *time, int *version, int *vbr, int *channels)
+{
+    guint32 head;
+    int i = 0;
+    MP3Frame frame;
+    int bitrate_idx, version_idx, freq_idx, frame_sync;
+
+    head = BE_32(buffer);
+    lprintf ("buffer2: %08X\n", head);
+
+    frame_sync = head >> 21;
+    if (frame_sync != 0x7ff)
+    {
+      lprintf ("invalid frame sync\n");
+      return 0;
+    }
+    /* Magic to detect MPEG version 2.5 */
+    frame.mpeg25_bit = (head >> 20) & 0x1;
+    frame.lsf_bit = (head >> 19) & 0x1;
+    if (!frame.mpeg25_bit)
+    {
+      if (frame.lsf_bit)
+      {
+        lprintf("reserved mpeg25 lsf combination\n");
+	return 0;
+      } else
+        version_idx = 2; /* MPEG Version 2.5 */
+    } else {
+      if (!frame.lsf_bit)
+        version_idx = 1; /* MPEG Version 2 */
+      else
+        version_idx = 0; /* MPEG Version 1 */
+    }
+    lprintf ("version_idx %d\n", version_idx);
+    *version = version_idx + 1;
+
+    frame.layer = 4 - ((head >> 17) & 0x3);
+    if (frame.layer == 4)
+    {
+      lprintf("reserved layer\n");
+      return 0;
+    }
+    lprintf ("layer %d\n", frame.layer);
+
+    bitrate_idx = (head >> 12) & 0xf;
+    if ((bitrate_idx == 0) || (bitrate_idx == 15))
+    {
+      lprintf("invalid bitrate index\n");
+      return 0;
+    }
+    lprintf ("bitrate_idx %d\n", bitrate_idx);
+
+    freq_idx = (head >> 10) & 0x3;
+    if (freq_idx == 3) {
+      lprintf("invalid frequence index\n");
+      return 0;
+    }
+    lprintf ("freq_idx %d\n", freq_idx);
+
+    /* 0: Stereo
+     * 1: Joint Stereo
+     * 2: Dual Stereo
+     * 3: Mono */
+    frame.channel_mode = (head >>  6) & 0x3;
+
+    *bitrate = tabsel_123[!frame.lsf_bit][frame.layer - 1][bitrate_idx] * 1000;
+    *samplerate = frequencies[version_idx][freq_idx];
+    *channels = (frame.channel_mode == 3 ? 1 : 2);
+    lprintf ("frequencies[%d][%d]", version_idx, freq_idx);
+
+    for( i = 0; i + 4 < length_read; i++)
+    {
+      if( mpg123_parse_xing_header (&frame, *samplerate,
+			      buffer+i, length_read-i, bitrate, time ) )
+      {
+        *vbr = 1;
+        break;
+      }
+    }
+
+    return 1;
+}
+
Index: id3-vfs/mp3bitrate.h
===================================================================
RCS file: id3-vfs/mp3bitrate.h
diff -N id3-vfs/mp3bitrate.h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ id3-vfs/mp3bitrate.h	14 Nov 2003 21:30:13 -0000
@@ -0,0 +1,18 @@
+
+/*
+ * Returns 1 if the header was parsed successfully, 0 if it failed
+ *
+ * bitrate: in bps, not kbps
+ * samplerate: ditto
+ * time: only informed if we have a VBR file with Xing headers, needs to be
+ *       deduced from the bitrate and filesize otherwise, in seconds
+ * version: 1 for MPEG Version 1, 2 for MPEG Version 2, and
+ *          3 for MPEG Version 2.5
+ * vbr: whether it is a variable bitrate stream
+ * channels: number of channels used in the stream
+ */
+
+int mp3_bitrate_parse_header (guchar *buffer, guint length_read, int *bitrate,
+		int *samplerate, int *time, int *version, int *vbr,
+		int *channels);
+


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]