From 3ae135303107cfea949125c3c2e30c0aeb52bed1 Mon Sep 17 00:00:00 2001
From: Miguel Vidal <mvidal@libresoft.es>
Date: Mon, 19 Dec 2011 12:06:03 +0100
Subject: [PATCH] Spotify has now started using mp3 as their audio codec.

This upstream patch adds support for decoding mp3 encoded streams using libmpg123. It will automatically select between ogg vorbis and mp3 based upon the stream data, as it is unknown if everything is now mp3 encoded.
---
 README                     |    7 +-
 src/Makefile               |    5 +-
 src/Makefile.local.mk.dist |    2 +-
 src/lib/despotify.h        |    1 +
 src/lib/sndqueue.c         |  293 +++++++++++++++++++++++++++++++++++++++-----
 5 files changed, 269 insertions(+), 39 deletions(-)

diff --git a/README b/README
index efe265e..78adaf2 100644
--- a/README
+++ b/README
@@ -19,12 +19,13 @@ Packages:
     libogg
     libtool
     libvorbis
+    mpg123
 
 
 Source code
 ~~~~~~~~~~~
-The latest version of the source code can be checked out from our Subversion repository
-  $ svn checkout https://despotify.svn.sourceforge.net/svnroot/despotify despotify
+The latest version of the source code can be checked out from our Git repository:
+  $ git clone https://github.com/eest/despotify-obsd.git 
 
 
 Directories
@@ -42,7 +43,7 @@ If you encounter any problems, ask a friend or ask in the channel
 Build instructions for the simple client and library
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1. Install necessary packages (as root)
-   # pkg_add gmake libao libogg libtool libvorbis
+   # pkg_add gmake libao libogg libtool libvorbis mpg123
 
 2. Go to the directory src/ where the despotify library and client is located
    $ cd src
diff --git a/src/Makefile b/src/Makefile
index 86b4788..1164f99 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -6,7 +6,7 @@
 export
 
 CFLAGS = -Wall -Wextra -ggdb -std=gnu99
-LDFLAGS = -lz -lvorbisfile
+LDFLAGS = -lz -lvorbisfile -lmpg123
 
 LD = $(CC)
 
@@ -36,7 +36,8 @@ endif
 # OpenBSD specifics
 ifeq ($(shell uname -s),OpenBSD)
     CFLAGS += -I/usr/local/include
-    LDFLAGS += -L/usr/local/lib -lvorbisfile -logg -lvorbis -lcrypto -lm -lpthread
+    LDFLAGS += -L/usr/local/lib -lmpg123 -lvorbisfile -logg -lvorbis -lcrypto -lm -lpthread
+    INSTALL_PREFIX ?= ${DESTDIR}/usr/local
 endif
 
 # windows specifics
diff --git a/src/Makefile.local.mk.dist b/src/Makefile.local.mk.dist
index d0f177b..906eda7 100644
--- a/src/Makefile.local.mk.dist
+++ b/src/Makefile.local.mk.dist
@@ -14,7 +14,7 @@ CLIENT_GATEWAY   = 1
 # MAEMO4 = 1
 
 ## Install prefix
-# INSTALL_PREFIX = /usr
+INSTALL_PREFIX = /usr/local
 
 ## Specify ncurses include path explicitly. (should contain curses.h)
 # NCURSES_INCLUDE = /usr/local/include/ncursesw
diff --git a/src/lib/despotify.h b/src/lib/despotify.h
index aec0789..4e69a0a 100644
--- a/src/lib/despotify.h
+++ b/src/lib/despotify.h
@@ -206,6 +206,7 @@ struct despotify_session
 
     /* internal data: */
     void* vf;
+    void* mf;
     struct snd_fifo* fifo;
     int dlstate;
     int errorcount;
diff --git a/src/lib/sndqueue.c b/src/lib/sndqueue.c
index 8f0c9c4..dbd4cc0 100644
--- a/src/lib/sndqueue.c
+++ b/src/lib/sndqueue.c
@@ -1,5 +1,5 @@
 /*
- * $Id$
+ * $Id: sndqueue.c 517 2011-12-11 20:13:34Z dalus $
  *
  */
 
@@ -10,6 +10,8 @@
 #include <pthread.h>
 #include <assert.h>
 
+#include <mpg123.h>
+
 #include "sndqueue.h"
 #include "util.h"
 
@@ -27,6 +29,18 @@ static void shortsleep(void)
     nanosleep(&delay, NULL);
 }
 
+void snd_reset_codec(struct despotify_session* ds) {
+	DSFYDEBUG("Resetting audio codec\n");
+	if ( ds->vf ) {
+		ov_clear(ds->vf);
+		DSFYfree(ds->vf);
+	} else if ( ds->mf ) {
+		mpg123_close(ds->mf);
+		mpg123_delete(ds->mf);
+		ds->mf = NULL;
+	}
+}
+
 
 /* Reset for new song */
 void snd_reset(struct despotify_session* ds)
@@ -34,8 +48,7 @@ void snd_reset(struct despotify_session* ds)
 	DSFYDEBUG("Setting state to DL_DRAINING\n");
 	ds->fifo->totbytes = 0;
 	ds->dlstate = DL_DRAINING;
-        ov_clear(ds->vf);
-        DSFYfree(ds->vf);
+	snd_reset_codec(ds);
 }
 
 /* Initialize sound session, called once */
@@ -206,8 +219,7 @@ int snd_next(struct despotify_session *ds)
                             ds->client_callback_data);
 
     /* tell decoder to start over */
-    ov_clear(ds->vf);
-    DSFYfree(ds->vf);
+    snd_reset_codec(ds);
 
     return 1;
 }
@@ -297,21 +309,13 @@ void snd_ioctl (struct despotify_session* ds, int cmd, void *data, int length)
         ds->fifo->lastcmd = cmd;
 }
 
-/*
- * Ogg-Vorbis read() callback
- * Called by both ov_info() and ov_read()
- * 
- * This functions dequeues buffers from the fifo
- *
- */
-size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
+int snd_consume_data(struct despotify_session* ds, int req_bytes, void* private, int (*consumer)(void* source, int bytes, void* private, int offset))
 {
-    struct despotify_session* ds = session;
+    int totlength = 0;
+    bool loop = true;
 
     pthread_mutex_lock(&ds->fifo->lock);
 
-    int totlength = 0;
-    bool loop = true;
 
     /* process data */
     while (loop) {
@@ -323,8 +327,8 @@ size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
         }
 
         DSFYDEBUG_SNDQUEUE("Processing one buffer at ds->fifo->start."
-                           " %zd items of size %zd requested. Totbytes: %d\n",
-                           size, nmemb, ds->fifo->totbytes );
+                           " %zd bytes requested. Totbytes: %d\n",
+                           req_bytes, ds->fifo->totbytes );
 
         struct snd_buffer* b = ds->fifo->start;
         if (!b)
@@ -358,16 +362,16 @@ size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
             {
                 /* data packet */
                 int remaining = b->length - b->consumed;
-                int ptrsize = size * nmemb;
+                //int ptrsize = size * nmemb;
                 int length;
                 
-                if (totlength + remaining < ptrsize)
+                if (totlength + remaining < req_bytes)
                     length = remaining;	/* The entire buffer will fit */
                 else {
-                    length = ptrsize - totlength; /* Don't overrun ptrsize */
+                    length = req_bytes - totlength; /* Don't overrun ptrsize */
                 }
 
-                memcpy (ptr + totlength, b->ptr + b->consumed, length);
+                consumer(b->ptr+b->consumed, length, private, totlength);
 
                 b->consumed += length;
                 totlength += length;
@@ -386,7 +390,7 @@ size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
                 }
 
                 /* exit if input is empty or output is full */
-                if (!ds->fifo->start || totlength == (int)(size*nmemb))
+                if (!ds->fifo->start || totlength == (int)(req_bytes))
                     loop = false;
                 break;
             }
@@ -437,22 +441,49 @@ size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
     pthread_mutex_unlock(&ds->fifo->lock);
 
     /* Return number of bytes read to ogg-layer */
-    _DSFYDEBUG("Returning %d bytes. %d left.\n",
+    DSFYDEBUG("Returning %d bytes. %d left.\n",
                totlength, ds->fifo->totbytes);
     return totlength;
 }
 
-int snd_get_pcm(struct despotify_session* ds, struct pcm_data* pcm)
+
+static int vorbis_consume(void* source, int bytes, void* private, int offset)
 {
-    if (!ds || !ds->fifo || !ds->fifo->start) {
-        pcm->len = 0;
-        shortsleep();
-        return 0;
-    }
+    memcpy(private+offset,source,bytes);
+    return bytes;
+}
+
+/*
+ * Ogg-Vorbis read() callback
+ * Called by both ov_info() and ov_read()
+ * 
+ * This functions dequeues buffers from the fifo
+ *
+ */
+size_t snd_ov_read_callback(void *ptr, size_t size, size_t nmemb, void* session)
+{
+    struct despotify_session* ds = session;
+
+    /* TODO: Add function ptr */
+    return snd_consume_data(ds,size*nmemb,ptr,vorbis_consume);
+}
+
+static int mpeg_consume(void* source, int bytes, void* private, int offset)
+{
+    mpg123_feed(private,source,bytes);
+    return bytes;
+}
 
-    /* top up fifo */
-    snd_fill_fifo(ds);
 
+int snd_mpeg_feed_more_data(struct despotify_session* ds) 
+{
+    /* TODO: Fix this sizeof hack */
+    struct pcm_data data;
+
+    return snd_consume_data(ds,sizeof(data.buf),ds->mf,mpeg_consume);
+}
+
+int snd_do_vorbis(struct despotify_session* ds, struct pcm_data* pcm ) {
     if (!ds->vf) {
         DSFYDEBUG ("Initializing vorbisfile struct\n");
 
@@ -528,6 +559,202 @@ int snd_get_pcm(struct despotify_session* ds, struct pcm_data* pcm)
 
         break;
     }
-
     return 0;
 }
+
+int snd_do_mpeg(struct despotify_session* ds, struct pcm_data* pcm) {
+	int err = MPG123_OK;
+	size_t bytes = 0;
+	long rate;
+	int channels, enc;
+
+	if ( !ds->mf ) {
+		err = mpg123_init();
+		
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to initialize mpg123\n");
+			return err*10;
+		}
+	
+		ds->mf = mpg123_new(NULL,&err);
+		
+		if ( ds->mf == NULL || err != MPG123_OK ) {
+			DSFYDEBUG("Unable to initialize mpg123 (alloc)\n");
+			return err*10;
+		}
+		
+		err = mpg123_format_none(ds->mf);
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to clear output formats (%d)\n",err);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return err*10;
+		}
+		
+		err = mpg123_format(ds->mf, 44100, MPG123_MONO | MPG123_STEREO, MPG123_ENC_SIGNED_16);
+		
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to set output format (%d)\n",err);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return err*10;
+		}
+		
+		err = mpg123_param(ds->mf, MPG123_RVA, MPG123_RVA_MIX, 0);
+		
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to set RVA (%d)\n",err);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return err*10;
+		}
+		
+#ifdef DEBUG
+		err = mpg123_param(ds->mf, MPG123_VERBOSE, 3, 0);
+		
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to set verbose (%d)\n",err);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return err*10;
+		}
+#endif
+		err = mpg123_param(ds->mf, MPG123_ADD_FLAGS, MPG123_SEEKBUFFER, 0);
+		
+		if ( err != MPG123_OK ) {
+			DSFYDEBUG("Unable to add seekbuffer (%d)\n",err);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return err*10;
+		}
+		
+		mpg123_open_feed(ds->mf);
+		/* Initial feed of data */
+		if ( snd_mpeg_feed_more_data(ds) == 0 ) {
+			DSFYDEBUG("Failed during initial feed of data\n");
+			return -1*10;
+		}
+	}
+	
+	while ( 1 ) {
+		do {
+			err = mpg123_read(ds->mf,pcm->buf,sizeof(pcm->buf),&bytes);
+			
+			if ( err == MPG123_NEED_MORE ) {
+				if ( snd_mpeg_feed_more_data(ds) == 0) {
+					DSFYDEBUG("Track is done\n");
+					err = MPG123_DONE;
+				}
+			}
+		}
+		while ( bytes == 0 && err == MPG123_NEED_MORE);
+		
+		if ( err == MPG123_NEW_FORMAT ) {
+			mpg123_getformat(ds->mf,&rate,&channels,&enc);
+			
+			DSFYDEBUG("New format: %li Hz, %i channels, encoding value %i\n",rate,channels,enc);
+			pcm->channels = channels;
+			pcm->samplerate = rate;
+			
+		} else if ( err == MPG123_DONE ) {
+			
+			mpg123_close(ds->mf);
+			mpg123_delete(ds->mf);
+			ds->mf = NULL;
+			return 0;
+		}
+		
+		pcm->len = bytes;
+		if (ds->client_callback) {
+			off_t frame_cur;
+			off_t frame_left;
+			double seconds_cur;
+			double seconds_left;
+			mpg123_position(ds->mf,0,bytes,&frame_cur,&frame_left,&seconds_cur,&seconds_left);
+			
+			double point = (double) seconds_cur;
+			ds->client_callback(ds, DESPOTIFY_TIME_TELL, &point,
+					ds->client_callback_data);
+		}
+		
+		snd_fill_fifo(ds);
+		break;
+	}
+	
+	return 0;
+}
+
+int snd_stream_is_vorbis(struct despotify_session* ds) {
+    int res = 0;
+    const char ogg_magic[4] = "OggS";
+    pthread_mutex_lock(&ds->fifo->lock);
+
+    do {
+    	/* Need two buffer to access the real stream */
+    	while ( ds->fifo->start == NULL || ds->fifo->start->next == NULL ) {
+    	   _DSFYDEBUG ("Waiting for data (%d bytes)\n", ds->fifo->totbytes);
+    	   pthread_cond_wait (&ds->fifo->cs, &ds->fifo->lock);
+    	   _DSFYDEBUG ("Got data\n");
+    	}
+
+    	struct snd_buffer* b_start = ds->fifo->start;
+    	struct snd_buffer* b_head = ds->fifo->start->next;
+    	if (!b_start || !b_head) {
+    	    res = -2;
+    	    break;
+    	}
+
+    	_DSFYDEBUG("cmd:%d bytes:%d\n", b_start->cmd, ds->fifo->totbytes);
+
+    	if (b_start->cmd == SND_CMD_START) {
+    		/* First buffer in the stream. Detect OGG magic */
+    		if (memcmp(b_head->ptr,ogg_magic,sizeof(ogg_magic)) == 0 ) {
+    			res = 1;
+    		} else {
+    			res = 0;
+    		}
+    	} else {
+    		/* Errr.. Not called at start of stream */
+    		res = -1;
+    	}
+    } while ( 0 );
+    pthread_mutex_unlock(&ds->fifo->lock);
+    return res;
+}
+
+
+
+int snd_get_pcm(struct despotify_session* ds, struct pcm_data* pcm)
+{
+    if (!ds || !ds->fifo || !ds->fifo->start) {
+        pcm->len = 0;
+        shortsleep();
+        return 0;
+    }
+
+    /* top up fifo */
+    snd_fill_fifo(ds);
+
+    if (!ds->vf && !ds->mf) {
+    	int res = snd_stream_is_vorbis(ds);
+    	if ( res == 1 ) {
+    		/* Vorbis */
+    		return snd_do_vorbis(ds,pcm);
+    	} else if ( res == 0 ) {
+    		/* Probably mpeg */
+    		return snd_do_mpeg(ds,pcm);
+    	} else {
+    		/* Errr */
+    		return res;
+    	}
+    }
+    
+    if (ds->vf ) {
+    	return snd_do_vorbis(ds,pcm);
+    } else if ( ds->mf ) {
+    	return snd_do_mpeg(ds,pcm);
+    } else {
+    	/* err */
+    	return -3;
+    }
+}
-- 
1.7.6

