savonet / liquidsoap

Liquidsoap is a statically typed scripting general-purpose language with dedicated operators and backend for all thing media, streaming, file generation, automation, HTTP backend and more.
http://liquidsoap.info
GNU General Public License v2.0
1.39k stars 128 forks source link

can liquidsoap listen/produce for RTP streams? #109

Open anarcat opened 10 years ago

anarcat commented 10 years ago

I have come to like RTP to produce a low-latency, high quality (but incidentally high bandwidth) stream within the house. I currently use MPD to stream audio to icecast and RTP, as described here. I know I can make liquidsoap stream to/from icecast fairly easily, but then I can't make it stream RTP in a meaningful way. I have tried various hacks with mplayer, pipes and all sorts of hacks, but couldn't get anything reliable going.

toots commented 10 years ago

This is not supported for now, unfortunately. I would love to add RTP support but I don't think we've ever come to a library simple enough to be used. Do you know of one that you would recommend?

anarcat commented 10 years ago

So far I have used:

I am guessing gstreamer may be the solution since liquidsoap already supports it, but it doesn't work at all in Debian right now (or is this 1.0?): http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=727044

I am not sure a library would suffice either, as it may not appear as "playing" if no stream is available... I guess this should be tested...

smimram commented 10 years ago

I also think that

input.gstreamer.audio("udpsrc multicast-group=224.0.0.56 port=5004 ! "application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10" ! rtpL16depay ! audioconvert")

is the right solution. I am currently using 1.1.1-6 version of liquidsoap in Debian (testing) and just tested that gstreamer is working. I think that Sourcefabric has updated packages for currenst stable Debian here: http://apt.sourcefabric.org/

anarcat commented 10 years ago

Okay for that's for listening on a multicast RTP stream, how about generating one?

anarcat commented 10 years ago

Also, while this works:

gst-launch udpsrc multicast-group=224.0.0.56 port=5004 ! "application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10" ! rtpL16depay ! audioconvert ! alsasink

This doesn't:

liquidsoap 'out(input.gstreamer.audio(pipeline="udpsrc multicast-group=224.0.0.56 port=5004 ! \"application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10\" ! rtpL16depay ! audioconvert ! alsasink sync=false"))'

Because:

gst-launch audiotestsrc ! audioconvert ! alsasink

and:

liquidsoap 'out(input.gstreamer.audio(pipeline="audiotestsrc"))'

yield the same result, I would have assumed this would work, but whereas the latter eventually yields:

2014/02/04 23:18:25 [clock.wallclock_pulse:3] Streaming loop starts, synchronized by active sources.
2014/02/04 23:18:25 [mksafe:3] Switch to safe_blank.
2014/02/04 23:18:25 [mksafe:3] Switch to input(dot)gstreamer(dot)audio_video_9110 with transition.

the other never switches to the gstreamer input:

anarcat@marcos:~$ liquidsoap 'out(input.gstreamer.audio(pipeline="udpsrc multicast-group=224.0.0.56 port=5004 ! \"application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10\" ! rtpL16depay"))'
ERROR: Could not load classifier cascade /usr/share/opencv/haarcascades/haarcascade_frontalface_alt2.xml
Failed to register plugin /usr/lib/frei0r-1/facedetect.so: Frei0r.Failure
2014/02/04 23:19:06 >>> LOG START
2014/02/04 23:19:06 [protocols.external:3] Found "/usr/bin/wget".
2014/02/04 23:19:06 [main:3] Liquidsoap 1.1.1
2014/02/04 23:19:06 [main:3] Using: graphics=[distributed with Ocaml] pcre=7.0.2 dtools=0.3.1 duppy=0.5.1 duppy.syntax=0.5.1 cry=0.2.2 mm=0.2.1 xmlplaylist=0.1.3 lastfm=0.3.0 ogg=0.4.5 vorbis=0.6.1 opus=0.1.0 speex=0.2.0 mad=0.4.4 flac=0.1.1 flac.ogg=0.1.1 dynlink=[distributed with Ocaml] lame=0.3.2 shine=0.1.1 gstreamer=0.2.0 frei0r=0.1.0 voaacenc=0.1.0 theora=0.3.0 schroedinger=0.1.0 gavl=0.1.5 bjack=0.1.4 alsa=0.2.1 ao=0.2.0 samplerate=0.1.2 taglib=0.3.1 magic=0.7.3 camomile=0.8.4 inotify=1.0 faad=0.3.2 soundtouch=0.1.7 portaudio=0.2.0 pulseaudio=0.1.2 ladspa=0.1.4 dssi=0.1.1 sdl=0.9.1 camlimages=4.0.0 lo=0.1.0 yojson=1.1.7 gd=1.0a5
2014/02/04 23:19:06 [dynamic.loader:3] Could not find dynamic module for fdkaac encoder.
2014/02/04 23:19:06 [dynamic.loader:3] Could not find dynamic module for aacplus encoder.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/mad.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/shine.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/frei0r.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/oss.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/ao.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/ladspa.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/ogg.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/dssi.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/pulseaudio.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/bjack.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/speex.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/xmlplaylist.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/samplerate.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/soundtouch.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/gavl.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/portaudio.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/theora.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/vorbis.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/taglib.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/lo.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/lame.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/gstreamer.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/camlimages.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/flac_ogg.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/cry.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/voaacenc.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/sdl.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/opus.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/graphics.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/flac.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/faad.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/schroedinger.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/gd.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/alsa.cmxs.
2014/02/04 23:19:06 [dynamic.loader:2] Loaded plugin file /usr/lib/liquidsoap/1.1.1/plugins/lastfm.cmxs.
2014/02/04 23:19:06 [frame:3] Using 44100Hz audio, 25Hz video, 44100Hz master.
2014/02/04 23:19:06 [frame:3] Frame size must be a multiple of 1764 ticks = 1764 audio samples = 1 video samples.
2014/02/04 23:19:06 [frame:3] Targetting 'frame.duration': 0.04s = 1764 audio samples = 1764 ticks.
2014/02/04 23:19:06 [frame:3] Frames last 0.04s = 1764 audio samples = 1 video samples = 1764 ticks.
2014/02/04 23:19:06 [threads:3] Created thread "generic queue #1".
2014/02/04 23:19:06 [threads:3] Created thread "generic queue #2".
2014/02/04 23:19:06 [threads:3] Created thread "wallclock_pulse" (1 total).
2014/02/04 23:19:06 [clock.wallclock_pulse:3] Streaming loop starts, synchronized by active sources.
2014/02/04 23:19:06 [mksafe:3] Switch to safe_blank.
anarcat commented 10 years ago

oh oh oh, i found it:

liquidsoap 'out(input.gstreamer.audio(pipeline="udpsrc multicast-group=224.0.0.56 port=5004 caps=\"application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10\" ! rtpL16depay"))'

i was missing the caps= bit, which seems to be optional on the commandline! --debug helped quite a bit in debugging that.

And now it works and fallbacks as well! ie. this also works:

rtpsrc = input.gstreamer.audio(pipeline="udpsrc multicast-group=224.0.0.56 port=5004 caps=\"application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10\" ! rtpL16depay")
security = single('/srv/mp3/misc/smash.mp3')
radio= fallback([rtpsrc,security])
out(radio)

Unfortunately the fallback starts first, and there's a ~5 seconds delay when the rtp stream goes blank, but it works.

anarcat commented 10 years ago

I also found out how to produce the RTP stream, so here's everything from the top:

generating the stream

liquidsoap 'output.gstreamer.audio(single("/srv/mp3/misc/smash.mp3"), pipeline="audioconvert ! rtpL16pay ! udpsink host=224.0.0.56 auto-multicast=true port=5004")'

listening to the stream

liquidsoap 'out(input.gstreamer.audio(pipeline="udpsrc multicast-group=224.0.0.56 port=5004 caps=\"application/x-rtp, media=(string)audio, clock-rate=44100, payload=(int)10\" ! rtpL16depay"))'

boo-ya. i guess we can close this now, although having that documentation somewhere would just rock.

anarcat commented 10 years ago

So while this works in and out, it won't work with VLC or other clients, because the udpsink doesn't seem to send SDP announcements to describe the stream. VLC complains with:

anarcat@marcos:~$ cvlc rtp://@224.0.0.1:5004
VLC media player 2.1.2 Rincewind (revision 2.1.2-0-ga4c4876)
[0x1ac9588] dummy interface: using the dummy interface module...
[0x7eff48000e28] rtp demux error: unspecified payload format (type 96)
[0x7eff48000e28] rtp demux: A valid SDP is needed to parse this RTP stream.
[0x7eff48000e28] main demux error: SDP required
[0x7ff7c4000e38] main demux error: A description in SDP format is required to receive the RTP stream. Note that rtp:// URIs cannot work with dynamic RTP payload format (96).
anarcat commented 10 years ago

The fix is of course to work pulseaudio:

output.pulseaudio(final)

and then set a default sink in PA:

# we need the following config appended to a default.pa:
load-module module-null-sink sink_name=rtp format=s16be channels=2 rate=44100 sink_properties="device.description='RTP Multicast Sink'"
load-module module-rtp-send source=rtp.monitor port=5004 destination_ip=224.0.0.1 loop=1
set-default-sink rtp
dbaelde commented 9 years ago

What's the status here? I the recipe stable enough? If so, could you please document it as an extra page in doc/content and create a pull request?

anarcat commented 9 years ago

the recipe works, but i have given up on the whole stack because of latency problems after an upgrade to debian jessie... not sure what happened there. the jukebox is just turned off now... but my last comment here worked and could be reused.

kmahelona commented 7 years ago

According to this documentation , you need to create a .sdp file and open that in VLC:

v=0 m=video 5000 RTP/AVP 96 c=IN IP4 127.0.0.1 a=rtpmap:96 MP4V-ES/90000

While that's for video, perhaps if you change it for audio it might work? I'm gonna give this a try. I'd like Airtime to have a low latency stream back to our station.

anarcat commented 7 years ago

for the record, i turned off RTP here because of the interference it does over wifi - it completely destroys the network here...