Patchwork [Try2] Initial implementation of a mpeg1 layer2 streaming audio driver.

login
register
mail settings
Submitter François Revol
Date Nov. 7, 2010, 11:08 p.m.
Message ID <CD87BA0A-DD64-4D66-BBF0-A8A4AE0B325D@free.fr>
Download mbox | patch
Permalink /patch/70385/
State New
Headers show

Comments

François Revol - Nov. 7, 2010, 11:08 p.m.
Initial implementation of a mpeg1 layer2 streaming audio driver.
2nd try: Style & other fixes from malc's comments.
It is based on the twolame library <http://www.twolame.org/>.
Since twolame is very similar to lame (on purpose), one might easily create a lame version from it for better quality.
It allows one to listen to the audio produced by a VM from an mp3 http streaming client (layer2 is compatible).
I noticed esdaudio.c which I used as template on was under BSD licence, which is fine by me for this one as well.
For now it almost works with a Haiku guest (with HDA at 22050Hz and the WAKEEN patch I just sent), except with a 20s delay and missing frames, so it's possible buffers get queued up somewhere.

From ee55900f8ceb86a96878a60086e8a4da19c645a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Revol?= <revol@free.fr>
Date: Mon, 8 Nov 2010 00:01:43 +0100
Subject: [PATCH] Initial implementation of a mpeg1 layer2 streaming audio driver.
 It is based on the twolame library <http://www.twolame.org/>.
 - added a check for libtwolame to configure.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit


Signed-off-by: François Revol <revol@free.fr>
---
 Makefile.objs        |    1 +
 audio/audio.c        |    3 +
 audio/audio_int.h    |    1 +
 audio/twolameaudio.c |  417 ++++++++++++++++++++++++++++++++++++++++++++++++++
 configure            |   20 +++
 5 files changed, 442 insertions(+), 0 deletions(-)
 create mode 100644 audio/twolameaudio.c
François Revol - Nov. 8, 2010, 12:11 a.m.
Le 8 nov. 2010 à 01:02, malc a écrit :

>> +
>> +static void qtwolame_listen_read(void *opaque)
> 
> No space here.

?

>> +    if (csock != -1) {
>> +        twolame->sock = csock;
>> +        dolog ("Accepted peer\n");
>> +        if (write (twolame->sock, http_header, sizeof(http_header) - 1) < sizeof(http_header) - 1) {
> 
> Line is too long.

Dang, wrote it after the 80col pass.

> 
>> +            qtwolame_logerr (errno, "write failed for http headers\n");
>> +            /* sending headers failed, just close the connection */
>> +            closesocket (twolame->sock);
>> +            twolame->sock = -1;
>> +        }
>> +    }
> 
> twolame->csock is not set to -1 (the condition which is checked for everywhere)

There is no twolame->csock, just twolame->lsock (the listen socket), and twolame->sock (=csock temporary var) which is the accepted socket.

>> +        again:
>> +            if (twolame->sock > -1) {
>> +                written = write (twolame->sock, twolame->mpg_buf, converted);
>> +                if (written == -1) {
>> +                    if (errno == EPIPE) {
>> +                        dolog ("Lost peer\n");
>> +                        closesocket (twolame->sock);
> 
> This is actually no better than jumping before, reading this code requires
> more analysis than needed, and no i'm not saying it's not correct just hard
> to read.

I'll have to sleep on this then.

>> +    twolame_set_mode(twolame->options,
>> +        (as->nchannels == 2) ? TWOLAME_STEREO : TWOLAME_MONO);
>> +    twolame_set_num_channels(twolame->options, as->nchannels);
>> +    twolame_set_in_samplerate(twolame->options, as->freq);
>> +    twolame_set_out_samplerate(twolame->options, as->freq);
>> +    twolame_set_bitrate(twolame->options, conf.rate);
>> +
>> +    if (twolame_init_params(twolame->options)) {
>> +        dolog ("Could not set twolame options\n");
>> +        goto fail1;
>> +    }
> 
> Once again, if you don't like the space before paren by all means do not use
> it, but either way do things consistently.

Ah, forgot this one.

>> +    qemu_set_fd_handler2(twolame->lsock, NULL, NULL, NULL, NULL);
>> +    if (closesocket (twolame->lsock)) {
> 
> Here we go again, closesocket is cheked here but not elsewhere.

Because elsewhere it's done in an error path and it'd won't do much better, but oh well.


> And can you, please, elaborate some more on usage scenarios of this thing?


cf.
http://dev.haiku-os.org/browser/haiku/trunk/3rdparty/mmu_man/onlinedemo/haiku.php

and possibly http://oszoo.org/wiki/index.php/Main_Page some day...

The idea is to use it along with the -vnc option and the VNC applet to present a VM on the web.

François.
malc - Nov. 8, 2010, 3:57 a.m.
On Mon, 8 Nov 2010, Fran?ois Revol wrote:

> 
> Le 8 nov. 2010 ? 01:02, malc a ?crit :
> 
[..snip..]

> 
> 
> > And can you, please, elaborate some more on usage scenarios of this thing?
> 
> 
> cf.
> http://dev.haiku-os.org/browser/haiku/trunk/3rdparty/mmu_man/onlinedemo/haiku.php

Sorry my PHP skills have lapsed aeons ago.

> 
> and possibly http://oszoo.org/wiki/index.php/Main_Page some day...
> 
> The idea is to use it along with the -vnc option and the VNC applet
> to present a VM on the web.

While not very sexy, but can't you just use wav output to a fifo and
compress it via separate process.
François Revol - Nov. 8, 2010, 6:15 a.m.
Le 8 nov. 2010 à 04:57, malc a écrit :

>>> And can you, please, elaborate some more on usage scenarios of this thing?
>> 
>> cf.
>> http://dev.haiku-os.org/browser/haiku/trunk/3rdparty/mmu_man/onlinedemo/haiku.php
> 
> Sorry my PHP skills have lapsed aeons ago.

Sorry I don't have it installed on a publically accessible machine atm.

>> 
>> and possibly http://oszoo.org/wiki/index.php/Main_Page some day...
>> 
>> The idea is to use it along with the -vnc option and the VNC applet
>> to present a VM on the web.
> 
> While not very sexy, but can't you just use wav output to a fifo and
> compress it via separate process.

I did try years ago, but at least the current wav driver really didn't like fifos back then. I recall trying for hours to get it pipe to ffmpeg or others without much luck.

Also, this poses several problems about the control of the external process (respawn on listener disconnection, close on exit...).

François.
Anthony Liguori - Nov. 12, 2010, 2:32 p.m.
On 11/08/2010 12:15 AM, François Revol wrote:
> Le 8 nov. 2010 à 04:57, malc a écrit :
>
>    
>>>> And can you, please, elaborate some more on usage scenarios of this thing?
>>>>          
>>> cf.
>>> http://dev.haiku-os.org/browser/haiku/trunk/3rdparty/mmu_man/onlinedemo/haiku.php
>>>        
>> Sorry my PHP skills have lapsed aeons ago.
>>      
> Sorry I don't have it installed on a publically accessible machine atm.
>
>    
>>> and possibly http://oszoo.org/wiki/index.php/Main_Page some day...
>>>
>>> The idea is to use it along with the -vnc option and the VNC applet
>>> to present a VM on the web.
>>>        
>> While not very sexy, but can't you just use wav output to a fifo and
>> compress it via separate process.
>>      
> I did try years ago, but at least the current wav driver really didn't like fifos back then. I recall trying for hours to get it pipe to ffmpeg or others without much luck.
>
> Also, this poses several problems about the control of the external process (respawn on listener disconnection, close on exit...).
>    

Doing encoding in QEMU is going to starve the guest pretty bad.  For an 
x86 guest, that's going to result in issues with time drift, etc.

Making reencoding with an external process work a bit more nicely also 
has the advantage of making this work with other formats like Ogg which 
are a bit more Open Source friendly.

Regards,

Anthony Liguori

> François.
>
François Revol - Nov. 12, 2010, 4:54 p.m.
Le 12 nov. 2010 à 15:32, Anthony Liguori a écrit :

>> I did try years ago, but at least the current wav driver really didn't like fifos back then. I recall trying for hours to get it pipe to ffmpeg or others without much luck.
>> 
>> Also, this poses several problems about the control of the external process (respawn on listener disconnection, close on exit...).
> 
> Doing encoding in QEMU is going to starve the guest pretty bad.  For an x86 guest, that's going to result in issues with time drift, etc.
> 
> Making reencoding with an external process work a bit more nicely also has the advantage of making this work with other formats like Ogg which are a bit more Open Source friendly.

The current patch uses a separate thread, but since I clone this part from the esdaudio code I didn't check what it was doing. It seems it's not really queueing anything though except the single mix buffer, which kind of defeats the purpose of having a separate thread. This might explain why it lags so much here... I tried increasing the buffer size and lowering the threshold but it doesn't seem to help.

I agree it'd be better to use external programs when possible but as I said it's a bit harder to handle the errors and such, and I wanted to have something working.
Also, it requires more work to set it up for users, they must install the externals, figure out the command line options...
Possibly we can provide default templates for known programs, either text files with % escaping for args, or just a shell script passing env vars maybe...

Besides, external or not, IIRC a pipe is by default 4kB max, which isn't much better for decoupling the processing on its own, if the encoder is too slow it will still starve the audio thread, and the rest. Also it all requires more context switching and IPC, which increase the total processing time.

So I think it might be interesting to have both.

I'll see if I can buffer a bit more in the twolame code and if it helps, then I'll try to merge with the failed attempts I have around at using external progs.

François.
Anthony Liguori - Nov. 15, 2010, 7:30 p.m.
On 11/12/2010 10:54 AM, François Revol wrote:
> Le 12 nov. 2010 à 15:32, Anthony Liguori a écrit :
>
>    
>>> I did try years ago, but at least the current wav driver really didn't like fifos back then. I recall trying for hours to get it pipe to ffmpeg or others without much luck.
>>>
>>> Also, this poses several problems about the control of the external process (respawn on listener disconnection, close on exit...).
>>>        
>> Doing encoding in QEMU is going to starve the guest pretty bad.  For an x86 guest, that's going to result in issues with time drift, etc.
>>
>> Making reencoding with an external process work a bit more nicely also has the advantage of making this work with other formats like Ogg which are a bit more Open Source friendly.
>>      
> The current patch uses a separate thread, but since I clone this part from the esdaudio code I didn't check what it was doing. It seems it's not really queueing anything though except the single mix buffer, which kind of defeats the purpose of having a separate thread. This might explain why it lags so much here... I tried increasing the buffer size and lowering the threshold but it doesn't seem to help.
>
> I agree it'd be better to use external programs when possible but as I said it's a bit harder to handle the errors and such, and I wanted to have something working.
> Also, it requires more work to set it up for users, they must install the externals, figure out the command line options...
> Possibly we can provide default templates for known programs, either text files with % escaping for args, or just a shell script passing env vars maybe...
>
> Besides, external or not, IIRC a pipe is by default 4kB max, which isn't much better for decoupling the processing on its own, if the encoder is too slow it will still starve the audio thread, and the rest. Also it all requires more context switching and IPC, which increase the total processing time.
>
> So I think it might be interesting to have both.
>
> I'll see if I can buffer a bit more in the twolame code and if it helps, then I'll try to merge with the failed attempts I have around at using external progs.
>    

Okay, but my thinking was that we'd do something like:

audio_capture "capture_command -opt=val -opt2=val" ...

Which would make it very easy to tie into lame, oggenc, etc.

Having simple alias commands like mp3_capture and ogg_capture that 
invoke common tools with reasonable options also would be a bonus.

Regards,

Anthony Liguori

> François.
François Revol - Nov. 15, 2010, 9:53 p.m.
Le 15 nov. 2010 à 20:30, Anthony Liguori a écrit :

>> I'll see if I can buffer a bit more in the twolame code and if it helps, then I'll try to merge with the failed attempts I have around at using external progs.
>>   
> 
> Okay, but my thinking was that we'd do something like:
> 
> audio_capture "capture_command -opt=val -opt2=val" ...
> 
> Which would make it very easy to tie into lame, oggenc, etc.
> 
> Having simple alias commands like mp3_capture and ogg_capture that invoke common tools with reasonable options also would be a bonus.

You mean in the console ? I hardly use it (beside the idea is to not have it accessible to avoid the user committing snapshots as it's just for demos).

Btw, we should really convert all those audio env vars to QemuOpts someday, it seems like a duplicate. It should still be possible to map env vars to them for the time being.

Anyway I'm still having a hard time getting sound to work reliably in Haiku guests regardless the output or emulated card. OTH it works perfectly in VirtualBox with almost no cpu overhead. If anyone has a clue...
It might be related to the use of RDTSC as primary time source, still it's the same in vbox.
We do have some support for HPET, I should try it someday, maybe.
Maybe it has to do with vbox using VT-x and me not even building QEMU with kvm... (does it even work on OSX ?)

François.
Andreas Färber - Nov. 16, 2010, 7:22 p.m.
Am 15.11.2010 um 22:53 schrieb François Revol:

> I'm still having a hard time getting sound to work reliably in Haiku  
> guests regardless the output or emulated card. OTH it works  
> perfectly in VirtualBox with almost no cpu overhead. If anyone has a  
> clue...
> It might be related to the use of RDTSC as primary time source,  
> still it's the same in vbox.

Did you check the time drift command line options? Iirc Gleb needed  
some hacks for timer-related issues on Windows.

> (does [KVM] even work on OSX ?)

No.

Andreas
Anthony Liguori - Nov. 16, 2010, 7:33 p.m.
On 11/16/2010 01:22 PM, Andreas Färber wrote:
> Am 15.11.2010 um 22:53 schrieb François Revol:
>
>> I'm still having a hard time getting sound to work reliably in Haiku 
>> guests regardless the output or emulated card. OTH it works perfectly 
>> in VirtualBox with almost no cpu overhead. If anyone has a clue...
>> It might be related to the use of RDTSC as primary time source, still 
>> it's the same in vbox.
>
> Did you check the time drift command line options? Iirc Gleb needed 
> some hacks for timer-related issues on Windows.

Time drift correction is only applicable to the RTC and PIT and deals 
with catching up the periodic timers.

The issue with the TSC as a time source is that it skips around during 
CPU migration.  The easiest way to work around this is to simply pin 
your guest to a single physical CPU (assuming you're using KVM).

With TCG, it depends, but you can actually end up accessing the host TSC 
in which case, your SOL because the code in QEMU that uses the host TSC 
assumes a UP host which aren't all that common anymore :-)

Regards,

Anthony Liguori

>> (does [KVM] even work on OSX ?)
>
> No.
>
> Andreas
François Revol - Nov. 16, 2010, 8:10 p.m.
Le 16 nov. 2010 à 20:33, Anthony Liguori a écrit :

> On 11/16/2010 01:22 PM, Andreas Färber wrote:
>> Am 15.11.2010 um 22:53 schrieb François Revol:
>> 
>>> I'm still having a hard time getting sound to work reliably in Haiku guests regardless the output or emulated card. OTH it works perfectly in VirtualBox with almost no cpu overhead. If anyone has a clue...
>>> It might be related to the use of RDTSC as primary time source, still it's the same in vbox.
>> 
>> Did you check the time drift command line options? Iirc Gleb needed some hacks for timer-related issues on Windows.
> 
> Time drift correction is only applicable to the RTC and PIT and deals with catching up the periodic timers.

Except Haiku, just like BeOS 15 years ago (Linux didn't invent it) does tickless, so it likely has other side effects from the PIT emulation...
It should be possible to use IO-APIC for timers now, and it seems vbox has it enabled, but I don't remember if the code is totally working yet.

IIRC the HPET timer support also is about running timers for now, not as a timesource.

> The issue with the TSC as a time source is that it skips around during CPU migration.  The easiest way to work around this is to simply pin your guest to a single physical CPU (assuming you're using KVM).

Isn't there a POSIX API now for pinning down processes on cpus ?
Hmm seems pthread_attr_setaffinity_np is non portable...
Can't seem to find an equivalent on OSX...
Oh wait, maybe this:
http://developer.apple.com/library/mac/#releasenotes/Performance/RN-AffinityAPI/

I'll see if I can make use of this.

>>> (does [KVM] even work on OSX ?)
>> 
>> No.

Won't even try to write a kext for it anyway.

François.

Patch

diff --git a/Makefile.objs b/Makefile.objs
index faf485e..370d59a 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -109,6 +109,7 @@  audio-obj-$(CONFIG_FMOD) += fmodaudio.o
 audio-obj-$(CONFIG_ESD) += esdaudio.o
 audio-obj-$(CONFIG_PA) += paaudio.o
 audio-obj-$(CONFIG_WINWAVE) += winwaveaudio.o
+audio-obj-$(CONFIG_TWOLAME) += twolameaudio.o
 audio-obj-$(CONFIG_AUDIO_PT_INT) += audio_pt_int.o
 audio-obj-$(CONFIG_AUDIO_WIN_INT) += audio_win_int.o
 audio-obj-y += wavcapture.o
diff --git a/audio/audio.c b/audio/audio.c
index ad51077..0c2c304 100644
--- a/audio/audio.c
+++ b/audio/audio.c
@@ -46,6 +46,9 @@ 
 static struct audio_driver *drvtab[] = {
     CONFIG_AUDIO_DRIVERS
     &no_audio_driver,
+#ifdef CONFIG_TWOLAME
+    &twolame_audio_driver,
+#endif
     &wav_audio_driver
 };
 
diff --git a/audio/audio_int.h b/audio/audio_int.h
index d8560b6..337188b 100644
--- a/audio/audio_int.h
+++ b/audio/audio_int.h
@@ -210,6 +210,7 @@  extern struct audio_driver dsound_audio_driver;
 extern struct audio_driver esd_audio_driver;
 extern struct audio_driver pa_audio_driver;
 extern struct audio_driver winwave_audio_driver;
+extern struct audio_driver twolame_audio_driver;
 extern struct mixeng_volume nominal_volume;
 
 void audio_pcm_init_info (struct audio_pcm_info *info, struct audsettings *as);
diff --git a/audio/twolameaudio.c b/audio/twolameaudio.c
new file mode 100644
index 0000000..4372fc4
--- /dev/null
+++ b/audio/twolameaudio.c
@@ -0,0 +1,417 @@ 
+/*
+ * QEMU twolame streaming audio driver
+ *
+ * Copyright (c) 2010 François Revol <revol@free.fr>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "config-host.h"
+#include "qemu-common.h"
+#include "qemu-char.h"
+#include "qemu_socket.h"
+#include "audio.h"
+
+#define AUDIO_CAP "twolame"
+#include "audio_int.h"
+#include "audio_pt_int.h"
+
+#include <twolame.h>
+
+typedef struct {
+    HWVoiceOut hw;
+    int done;
+    int live;
+    int decr;
+    int rpos;
+    void *pcm_buf;
+    void *mpg_buf;
+    int lsock;
+    int sock;
+    struct audio_pt pt;
+    twolame_options *options;
+} LAMEVoiceOut;
+
+static struct {
+    int samples;
+    int divisor;
+    int port;
+    int rate;
+} conf = {
+    .samples = 1024,
+    .divisor = 2,
+    .port = 8080,
+    .rate = 160
+};
+
+static const char http_header[] = "HTTP/1.1 200 OK\r\nServer: QEMU\r\n"
+	"Content-Type: audio/mpeg\r\n\r\n";
+
+static void GCC_FMT_ATTR (2, 3) qtwolame_logerr (int err, const char *fmt, ...)
+{
+    va_list ap;
+
+    va_start (ap, fmt);
+    AUD_vlog (AUDIO_CAP, fmt, ap);
+    va_end (ap);
+
+    AUD_log (AUDIO_CAP, "Reason: %s\n", strerror (err));
+}
+
+static void qtwolame_listen_read(void *opaque)
+{
+    LAMEVoiceOut *twolame = opaque;
+    struct sockaddr_in addr;
+    socklen_t addrlen = sizeof(addr);
+    int csock;
+
+    if (twolame->sock > -1) {
+        return;
+    }
+
+    csock = qemu_accept(twolame->lsock, (struct sockaddr *)&addr, &addrlen);
+    if (csock != -1) {
+        twolame->sock = csock;
+        dolog ("Accepted peer\n");
+        if (write (twolame->sock, http_header, sizeof(http_header) - 1) < sizeof(http_header) - 1) {
+            qtwolame_logerr (errno, "write failed for http headers\n");
+            /* sending headers failed, just close the connection */
+            closesocket (twolame->sock);
+            twolame->sock = -1;
+        }
+    }
+}
+
+/* playback */
+static void *qtwolame_thread_out (void *arg)
+{
+    LAMEVoiceOut *twolame = arg;
+    HWVoiceOut *hw = &twolame->hw;
+    int threshold;
+
+    threshold = conf.divisor ? hw->samples / conf.divisor : 0;
+
+    if (audio_pt_lock (&twolame->pt, AUDIO_FUNC)) {
+        return NULL;
+    }
+
+    for (;;) {
+        int decr, to_mix, rpos;
+
+        for (;;) {
+            if (twolame->done) {
+                goto exit;
+            }
+
+            if (twolame->live > threshold) {
+                break;
+            }
+
+            if (audio_pt_wait (&twolame->pt, AUDIO_FUNC)) {
+                goto exit;
+            }
+
+        }
+
+        decr = to_mix = twolame->live;
+        rpos = hw->rpos;
+
+        if (audio_pt_unlock (&twolame->pt, AUDIO_FUNC)) {
+            return NULL;
+        }
+
+        while (to_mix) {
+            ssize_t converted, written;
+            int chunk = audio_MIN (to_mix, hw->samples - rpos);
+            struct st_sample *src = hw->mix_buf + rpos;
+
+            hw->clip (twolame->pcm_buf, src, chunk);
+
+            if (twolame->sock > -1) {
+                converted = twolame_encode_buffer_interleaved (twolame->options,
+                    twolame->pcm_buf, chunk, twolame->mpg_buf,
+                    hw->samples << hw->info.shift);
+                if (converted < 0) {
+                    qtwolame_logerr (converted, "twolame encode failed\n");
+                    return NULL;
+                }
+            }
+
+        again:
+            if (twolame->sock > -1) {
+                written = write (twolame->sock, twolame->mpg_buf, converted);
+                if (written == -1) {
+                    if (errno == EPIPE) {
+                        dolog ("Lost peer\n");
+                        closesocket (twolame->sock);
+                        twolame->sock = -1;
+                    } else if (errno == EINTR || errno == EAGAIN) {
+                        goto again;
+                    } else {
+                        qtwolame_logerr (errno, "write failed\n");
+                        return NULL;
+                    }
+                }
+            }
+
+            rpos = (rpos + chunk) % hw->samples;
+            to_mix -= chunk;
+        }
+
+        if (audio_pt_lock (&twolame->pt, AUDIO_FUNC)) {
+            return NULL;
+        }
+
+        twolame->rpos = rpos;
+        twolame->live -= decr;
+        twolame->decr += decr;
+    }
+
+ exit:
+    audio_pt_unlock (&twolame->pt, AUDIO_FUNC);
+    return NULL;
+}
+
+static int qtwolame_run_out (HWVoiceOut *hw, int live)
+{
+    int decr;
+    LAMEVoiceOut *twolame = (LAMEVoiceOut *) hw;
+
+    if (audio_pt_lock (&twolame->pt, AUDIO_FUNC)) {
+        return 0;
+    }
+
+    decr = audio_MIN (live, twolame->decr);
+    twolame->decr -= decr;
+    twolame->live = live - decr;
+    hw->rpos = twolame->rpos;
+    if (twolame->live > 0) {
+        audio_pt_unlock_and_signal (&twolame->pt, AUDIO_FUNC);
+    }
+    else {
+        audio_pt_unlock (&twolame->pt, AUDIO_FUNC);
+    }
+    return decr;
+}
+
+static int qtwolame_write (SWVoiceOut *sw, void *buf, int len)
+{
+    return audio_pcm_sw_write (sw, buf, len);
+}
+
+static int qtwolame_init_out (HWVoiceOut *hw, struct audsettings *as)
+{
+    LAMEVoiceOut *twolame = (LAMEVoiceOut *) hw;
+    struct audsettings obt_as = *as;
+    char listen_port[256];
+
+    twolame->sock = -1;
+
+    switch (as->fmt) {
+    case AUD_FMT_S8:
+    case AUD_FMT_U8:
+        dolog ("Will use 16 instead of 8 bit samples\n");
+        goto deffmt;
+
+    case AUD_FMT_S32:
+    case AUD_FMT_U32:
+        dolog ("Will use 16 instead of 32 bit samples\n");
+
+    case AUD_FMT_S16:
+    case AUD_FMT_U16:
+    deffmt:
+        obt_as.fmt = AUD_FMT_S16;
+        break;
+
+    default:
+        dolog ("Internal logic error: Bad audio format %d\n", as->fmt);
+        goto deffmt;
+
+    }
+    obt_as.endianness = AUDIO_HOST_ENDIANNESS;
+
+    audio_pcm_init_info (&hw->info, &obt_as);
+
+    twolame->options = twolame_init();
+    if (twolame->options == NULL) {
+        dolog ("Could not initialize twolame\n");
+        return -1;
+    }
+    twolame_set_mode(twolame->options,
+        (as->nchannels == 2) ? TWOLAME_STEREO : TWOLAME_MONO);
+    twolame_set_num_channels(twolame->options, as->nchannels);
+    twolame_set_in_samplerate(twolame->options, as->freq);
+    twolame_set_out_samplerate(twolame->options, as->freq);
+    twolame_set_bitrate(twolame->options, conf.rate);
+
+    if (twolame_init_params(twolame->options)) {
+        dolog ("Could not set twolame options\n");
+        goto fail1;
+    }
+
+    hw->samples = conf.samples;
+    twolame->pcm_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
+    if (!twolame->pcm_buf) {
+        dolog ("Could not allocate buffer (%d bytes)\n",
+               hw->samples << hw->info.shift);
+        goto fail1;
+    }
+
+    twolame->mpg_buf = audio_calloc (AUDIO_FUNC, hw->samples, 1 << hw->info.shift);
+    if (!twolame->mpg_buf) {
+        dolog ("Could not allocate mpeg buffer (%d bytes)\n",
+               hw->samples << hw->info.shift);
+        goto fail2;
+    }
+
+    sprintf(listen_port, ":%d", conf.port);
+    twolame->lsock = inet_listen (listen_port, listen_port, 256, SOCK_STREAM, 0);
+    if (twolame->lsock == -1) {
+        dolog ("Could not listen on port %d\n", conf.port);
+        goto fail3;
+    }
+
+    qemu_set_fd_handler2(twolame->lsock, NULL, qtwolame_listen_read, NULL, twolame);
+
+    if (audio_pt_init (&twolame->pt, qtwolame_thread_out, twolame, AUDIO_CAP,
+        AUDIO_FUNC)) {
+        goto fail4;
+    }
+
+    return 0;
+
+ fail4:
+    qemu_set_fd_handler2(twolame->lsock, NULL, NULL, NULL, NULL);
+    if (closesocket (twolame->lsock)) {
+        qtwolame_logerr (socket_error(), "%s: close on socket(%d) failed\n",
+                     AUDIO_FUNC, twolame->sock);
+    }
+    twolame->lsock = -1;
+
+ fail3:
+    qemu_free (twolame->mpg_buf);
+    twolame->mpg_buf = NULL;
+
+ fail2:
+    qemu_free (twolame->pcm_buf);
+    twolame->pcm_buf = NULL;
+
+ fail1:
+    twolame_close (&twolame->options);
+
+    return -1;
+}
+
+static void qtwolame_fini_out (HWVoiceOut *hw)
+{
+    void *ret;
+    LAMEVoiceOut *twolame = (LAMEVoiceOut *) hw;
+
+    audio_pt_lock (&twolame->pt, AUDIO_FUNC);
+    twolame->done = 1;
+    audio_pt_unlock_and_signal (&twolame->pt, AUDIO_FUNC);
+    audio_pt_join (&twolame->pt, &ret, AUDIO_FUNC);
+
+    if (twolame->sock >= 0) {
+        if (closesocket (twolame->sock)) {
+            qtwolame_logerr (socket_error(), "failed to close socket\n");
+        }
+        twolame->sock = -1;
+    }
+
+    if (twolame->options) {
+        twolame_close(&twolame->options);
+    }
+    twolame->options = NULL;
+
+    audio_pt_fini (&twolame->pt, AUDIO_FUNC);
+
+    qemu_free (twolame->pcm_buf);
+    twolame->pcm_buf = NULL;
+    qemu_free (twolame->mpg_buf);
+    twolame->mpg_buf = NULL;
+}
+
+static int qtwolame_ctl_out (HWVoiceOut *hw, int cmd, ...)
+{
+    (void) hw;
+    (void) cmd;
+    return 0;
+}
+
+/* common */
+static void *qtwolame_audio_init (void)
+{
+    return &conf;
+}
+
+static void qtwolame_audio_fini (void *opaque)
+{
+    (void) opaque;
+    ldebug ("twolame_fini");
+}
+
+struct audio_option qtwolame_options[] = {
+    {
+        .name  = "SAMPLES",
+        .tag   = AUD_OPT_INT,
+        .valp  = &conf.samples,
+        .descr = "buffer size in samples"
+    },
+    {
+        .name  = "DIVISOR",
+        .tag   = AUD_OPT_INT,
+        .valp  = &conf.divisor,
+        .descr = "threshold divisor"
+    },
+    {
+        .name  = "PORT",
+        .tag   = AUD_OPT_INT,
+        .valp  = &conf.port,
+        .descr = "streamer port"
+    },
+    {
+        .name  = "RATE",
+        .tag   = AUD_OPT_INT,
+        .valp  = &conf.rate,
+        .descr = "bitrate"
+    },
+    { /* End of list */ }
+};
+
+static struct audio_pcm_ops qtwolame_pcm_ops = {
+    .init_out = qtwolame_init_out,
+    .fini_out = qtwolame_fini_out,
+    .run_out  = qtwolame_run_out,
+    .write    = qtwolame_write,
+    .ctl_out  = qtwolame_ctl_out,
+};
+
+struct audio_driver twolame_audio_driver = {
+    .name           = "twolame",
+    .descr          = "mpeg1 layer2 streamer http://www.twolame.org/",
+    .options        = qtwolame_options,
+    .init           = qtwolame_audio_init,
+    .fini           = qtwolame_audio_fini,
+    .pcm_ops        = &qtwolame_pcm_ops,
+    .can_be_default = 0,
+    .max_voices_out = 1,
+    .max_voices_in  = 0,
+    .voice_size_out = sizeof (LAMEVoiceOut),
+    .voice_size_in  = 0
+};
diff --git a/configure b/configure
index 7025d2b..ca8e980 100755
--- a/configure
+++ b/configure
@@ -285,6 +285,7 @@  vnc_jpeg=""
 vnc_png=""
 vnc_thread="no"
 xen=""
+twolame=""
 linux_aio=""
 attr=""
 vhost_net=""
@@ -1155,6 +1156,21 @@  EOF
 fi
 
 ##########################################
+#
+
+cat > $TMPC <<EOF
+#include <twolame.h>
+int main(void) { twolame_options *encodeOptions; encodeOptions = twolame_init(); return 0; }
+EOF
+if compile_prog "" "-ltwolame" ; then
+  twolame="yes"
+  audio_pt_int="yes"
+  libs_softmmu="-ltwolame $libs_softmmu"
+else
+  twolame="no"
+fi
+
+##########################################
 # pkgconfig probe
 
 pkgconfig="${cross_prefix}pkg-config"
@@ -2314,6 +2330,7 @@  if test -n "$sparc_cpu"; then
     echo "Target Sparc Arch $sparc_cpu"
 fi
 echo "xen support       $xen"
+echo "twolame streaming $twolame"
 echo "brlapi support    $brlapi"
 echo "bluez  support    $bluez"
 echo "Documentation     $docs"
@@ -2551,6 +2568,9 @@  fi
 if test "$xen" = "yes" ; then
   echo "CONFIG_XEN=y" >> $config_host_mak
 fi
+if test "$twolame" = "yes" ; then
+  echo "CONFIG_TWOLAME=y" >> $config_host_mak
+fi
 if test "$io_thread" = "yes" ; then
   echo "CONFIG_IOTHREAD=y" >> $config_host_mak
   echo "CONFIG_THREAD=y" >> $config_host_mak