πŸ“œ ⬆️ ⬇️

Internet radio station on Liquidsoap + IceCast

liquidsoap Quite a lot on HabrΓ© said about Internet broadcasting from the inside. There are even well-written theoretical foundations of Internet broadcasting, which I advise you to read. In this article I would like to talk about the organization of another amateur Internet radio station, built on a bundle of undeservedly little-known Liquidsoap 1.0.1 and the omnipresent IceCast 2.3.2 . The article is designed for those who at least approximately know what an audio stream is, IceCast, the Linux console, and what he wants to get. However, it was written by a novice user, so my decision does not even bail at the title of optimal.

Required final functionality


  1. The ability to assign your playlist for an arbitrary time period
  2. Support OGG, MP3, FLAC pickup as a source for audio stream
  3. Configuration agility
  4. Ease of editing content for the radio
  5. Linux work
  6. The ability for a novice Linux user to install and configure it

After quite a long trial of various ways of obtaining this functionality, I stopped on Liquidsoap + IceCast. The latter was taken for compliance with the requirements and widespread (in principle, I did not even look for analogues), and Liquidsoap for truly amazing opportunities available through its functional scripting language. Before him, I considered ices, ices + ardj, AirTime, something else that I didn’t even remember, but all of them didn’t suit me anyway. In general, I decided to use Liquidsoap. Of the shortcomings, I noticed only the issuance of an empty stream under unknown circumstances (after a restart) - it is decided by a reboot. Unfortunately, I will not be able to feel all his power - all the documentation is written in English, and with him everything is not smooth - however, I learned something and will try to describe everything I can operate with.

Installation


Without hesitation, I decided to raise all this miracle on my laptop under openSUSE 12.2 x64, so that it was convenient to study the functionality of Liquidsoap, and only then transfer it to a working machine. Only IceCast was present in the repositories, Liquidsoap had to be collected. Debian / Ubuntu, Windows, Mac OS X, FreeBSD, and ArchLinux users can get ready-made packages on the official website .

Icecast

I installed IceCast from the standard repositories:
# zypper in icecast 

I will not describe the configuration of IceCast in detail, I will only give you a working example of what is spinning around me:
')
/etc/icecast.xml
 <icecast> <limits> <clients>100</clients> <sources>2</sources> <threadpool>5</threadpool> <queue-size>524288</queue-size> <client-timeout>30</client-timeout> <header-timeout>15</header-timeout> <source-timeout>10</source-timeout> <burst-on-connect>1</burst-on-connect> <burst-size>65535</burst-size> </limits> <authentication> <source-password>mypass</source-password> <relay-password>mysecondpass</relay-password> <admin-user>adminuser</admin-user> <admin-password>mythirdpass</admin-password> </authentication> <hostname>localhost</hostname> <listen-socket> <port>8000</port> </listen-socket> <fileserve>1</fileserve> #_____________________________________________________________ #   .      ,          ,      .      ices     secure. <mount> <mount-name>/secure</mount-name> <hidden>1</hidden> #   -      <charset>UTF8</charset> #  </mount> #   mount' <mount> <fallback-mount>/secure</fallback-mount> #   ,    <fallback-override>1</fallback-override> #           <fallback-when-full>1</fallback-when-full> #    ,       <mount-name>/HabraRadio_192</mount-name> #  mount'. <charset>UTF8</charset> #  </mount> <mount> <fallback-mount>/secure</fallback-mount> <fallback-override>1</fallback-override> <fallback-when-full>1</fallback-when-full> <mount-name>/HabraRadio_320</mount-name> <charset>UTF8</charset> </mount> <mount> <fallback-mount>/secure</fallback-mount> <fallback-override>1</fallback-override> <fallback-when-full>1</fallback-when-full> <mount-name>/HabraRadio_vorbis_avg_128</mount-name> <charset>UTF8</charset> </mount> #_____________________________________________________________ <paths> <basedir>/usr/share/icecast</basedir> <logdir>/var/log/icecast</logdir> <webroot>/usr/share/icecast/web</webroot> <adminroot>/usr/share/icecast/admin</adminroot> <alias source="/" dest="/status.xsl"/> </paths> <logging> <accesslog>access.log</accesslog> <errorlog>error.log</errorlog> <loglevel>3</loglevel> <logsize>10000</logsize> </logging> <security> <chroot>0</chroot> <changeowner> <user>icecast</user> <group>icecast</group> </changeowner> </security> </icecast> 


Liquidsoap

Swing, prepare:
 $ git clone https://github.com/savonet/liquidsoap-full.git liquidsoap $ cd liquidsoap $ make init $ cp PACKAGES.minimal PACKAGES 

For me, the minimum set is enough, but if anyone needs support for anything else, then you should edit the PACKAGES file.
To compile the program normally, I installed the following packages:
 # zypper in make autoconf automake ocaml libao-devel libmad-devel libmp3lame-devel flac-devel libgavl-devel ocaml-camomile-devel ocaml-camlimages-devel ocaml-camomile-data libtheora-devel ocaml-findlib-devel libsamplerate-devel libtag-devel libvorbis-devel gcc-c++ ocaml-pcre-devel libtiff-devel libjpeg62-devel libXpm-devel 

We collect:
 $ ./bootstrap $ ./configure --with-user=user --with-group=users $ make # make install 

By the way, after performing "./configure ..." it is worth checking whether all the functionality we need will be available in the assembled program. You can do this simply by looking at the table displayed at the completion of "./configure ..."
Everything, you can check the operation of the program by executing β€œliquidsoap --version” - there should be no errors.

Oriented with the schedule of broadcast


Suppose we need to get something like this:
  1. 02: 00-06: 00 - night playlist
  2. 06: 00-09: 00 - morning playlist
  3. 09: 00-19: 00 - daily playlist
  4. 19: 00-02: 00 - evening playlist
  5. Mon, Wed, Fri - 21: 00-22: 00 - one program
  6. Mon, Wed, Thu, Fri - 18: 00-19: 00 - second program

File system location:
 radio β”œβ”€β”€ collection |  β”‚ β”œβ”€β”€ efir |    β”‚ β”‚ β”œβ”€β”€ daytime |   β”‚ β”‚ β”‚ β”œβ”€β”€ jingles | ,   β”‚ β”‚ β”‚ └── music | ,   β”‚ β”‚ β”œβ”€β”€ evening | ,  β”‚ β”‚ β”‚ β”œβ”€β”€ jingles β”‚ β”‚ β”‚ └── music β”‚ β”‚ β”œβ”€β”€ morning |  β”‚ β”‚ β”‚ β”œβ”€β”€ jingles β”‚ β”‚ β”‚ └── music β”‚ β”‚ └── night |   β”‚ β”‚ β”œβ”€β”€ jingles β”‚ β”‚ └── music β”‚ β”œβ”€β”€ programs β”‚ β”‚ β”œβ”€β”€ 1_prog | 1  β”‚ β”‚ β”œβ”€β”€ 2_prog | 2  β”‚ β”œβ”€β”€ promo |   β”‚ └── security |     β”œβ”€β”€ technical | ,  └──  |    

Liquidsoap configuration


Immediately bring the finished configuration:

./radio/technical/start_liquidsoap
 #!/usr/local/bin/liquidsoap #          #      out = output.icecast( #   icecast host = "127.0.0.1", #   port = 8000, #  user = "source", #   password = "mypass", #  name = "-", #  genre = "Rock", #    url = "http://habrahabr.ru", #  encoding = "UTF-8" ) #  telnet- set("server.telnet.bind_addr","127.0.0.1") set("server.telnet",true) # _____________________________________ #     . #    ,       ,       ,       .   ,   . #      wd = "/home/user/radio" #      pl = "#{wd}/collection" #   tech = "#{wd}/technical" #  set("log.file.path","#{tech}/liquidsoap.log") #     set("log.level", 3) #   #     promo_dir = "#{pl}/promo" #    progr_dir = "#{pl}/programs" #     ef = "#{pl}/efir" #    ni = "#{ef}/night" mo = "#{ef}/morning" da = "#{ef}/daytime" ev = "#{ef}/evening" #    mus_ni_dir = "#{ni}/music" mus_mo_dir = "#{mo}/music" mus_da_dir = "#{da}/music" mus_ev_dir = "#{ev}/music" #    jin_ni_dir = "#{ni}/jingles" jin_mo_dir = "#{mo}/jingles" jin_da_dir = "#{da}/jingles" jin_ev_dir = "#{ev}/jingles" #   .   -      ,   -    . 1_prog_pl = "#{progr_dir}/1_prog.pl" 2_prog_pl = "#{progr_dir}/2_prog.pl" # _____________________________________ #    "source",     . #   "reload"    360     ,  . #  ,   ,  <code>mode = "normal"</code>     . #  , , ,  mus_ni = playlist (reload = 360, "#{mus_ni_dir}") mus_mo = playlist (reload = 360, "#{mus_mo_dir}") mus_da = playlist (reload = 360, "#{mus_da_dir}") mus_ev = playlist (reload = 360, "#{mus_ev_dir}") jin_ni = playlist (reload = 360, "#{jin_ni_dir}") jin_mo = playlist (reload = 360, "#{jin_mo_dir}") jin_da = playlist (reload = 360, "#{jin_da_dir}") jin_ev = playlist (reload = 360, "#{jin_ev_dir}") promo = playlist (reload = 360, "#{promo_dir}") 1_prog = playlist (reload = 360, "#{1_prog_pl}", mode = "normal") 2_prog = playlist (reload = 360, "#{2_prog_pl}", mode = "normal") # _____________________________________ #  4 ,    #   ins_ni = rotate (weights = [2, 1], [jin_ni, promo]) ins_mo = rotate (weights = [2, 1], [jin_mo, promo]) ins_da = rotate (weights = [2, 1], [jin_da, promo]) ins_ev = rotate (weights = [2, 1], [jin_ev, promo]) #     ni = rotate (weights = [3, 1], [mus_ni, ins_ni]) mo = rotate (weights = [3, 1], [mus_mo, ins_mo]) da = rotate (weights = [3, 1], [mus_da, ins_da]) ev = rotate (weights = [3, 1], [mus_ev, ins_ev]) #_______________________________________________________________________ #    radio = switch (track_sensitive = true, [ ({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), ({ (1w18h - 1w19h) or (3w18h - 3w19h) or (4w18h - 4w19h) or (5w18h - 5w19h)}, 2_prog), ({ 2h - 6h }, ni), ({ 6h - 9h }, mo), ({ 9h - 19h }, da), ({ 19h - 2h }, ev) ]) #_______________________________________________________________________ #  crossfade radio = crossfade(start_next=1., fade_out=1., fade_in=1., radio) # , ,      out( %vorbis.abr(samplerate = 44100, channels = 2, bitrate = 128, max_bitrate = 192, min_bitrate = 96), description = "Average vorbis 96-128-192 Kbps", mount = "HabraRadio_vorbis_avg_128", mksafe(radio) ) out( %mp3(bitrate = 320, id3v2 = true), description = "MP3 320 Kbps", mount = "HabraRadio_320", mksafe(radio) ) out( %mp3(bitrate = 192, id3v2 = true), description = "MP3 192 Kbps", mount = "HabraRadio_192", mksafe(radio) ) 


A few comments:
1) These lines:
 wd = "/home/user/radio" pl = "#{wd}/collection" ef = "#{pl}/efir" ni = "#{ef}/night" mus_ni_dir = "#{ni}/music" mus_ni = playlist (reload = 360, "#{mus_ni_dir}") 

quite successfully can be replaced by
 mus_ni = playlist (reload = 360, "/home/user/radio/collection/efir/night/music") 

and you won't get anything for it - Liquidsoap simply replaces # {wd} with the value of the variable wd.

2) In the place where we inserted the inserts, there were lines:
 ins_ni = rotate (weights = [2, 1], [jin_ni, promo]) ni = rotate (weights = [3, 1], [mus_ni, ins_ni]) 

rotate() - allows you to adjust the queue
weights = [2, 1], [jin_ni, promo] - indicates to take 2 tracks from jin_ni, then 1 from the promo, after again 2 tracks from jin_ni and so on.
weights = [3, 1], [mus_ni, ins_ni] - indicates to take 3 tracks from mus_ni, then 1 track from that already mixed playlist, which turned out to be a line earlier (ins_ni).

3) When configuring the broadcast schedule, we used the following lines:
 radio = switch (track_sensitive = true, [ ({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), ({ (1w18h - 1w19h) or (3w18h - 3w19h) or (4w18h - 4w19h) or (5w18h - 5w19h)}, 2_prog), ({ 2h - 6h }, ni), ({ 6h - 9h }, mo), ({ 9h - 19h }, da), ({ 19h - 2h }, ev) ]) 


switch() - switches audio sources at a specified time.
track_sensitive = true - allows you to not interrupt the current track, even if the active playlist has timed out. Those. if the night track started at 05:59, then until it ends, the morning playlist will not take effect.
({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), - on Mondays, Wednesdays and Fridays from 21 to 22 hours to play the source 1_prog.
As I understand it, those lines in the switch () list that are located above have the highest priority.

Forming a playlist radio program


The principle is simple: in the prog_1 folder are broadcast files of the form β€œ01_ProgName”, where the voices of radio broadcasters are recorded. Playlist will be:

01_ProgName
Music152
02_ProgName
Music241
03_ProgName
Music937
...

I think it would be more appropriate for bash to write a generator of such a playlist, but I recently picked up Python, so I quickly wrote on it:

./radio/technical/generatorProg1.py
 #!/usr/bin/env python2 # -*- coding: utf-8 -*- import os import random finalPaylist = '/home/user/radio/collection/programs/1_prog.pl' music = '/home/user/radio/collection/efir/evening/music/' show = '/home/user/radio/collection/programs/1_prog/' myShow = sorted(os.listdir(show)) myMusic = os.listdir(music) listOfTracks = [] def getRandomTrack(list): i = 0 buf = random.choice(myMusic) while (buf in list) & (not i == 100): i += 1 buf = random.choice(myMusic) return buf for i in range(60): if not i%2: try: listOfTracks.append(show + myShow[i/2]) except : listOfTracks.append(music + getRandomTrack(listOfTracks)) else: listOfTracks.append(music + getRandomTrack(listOfTracks)) myFile = open(finalPaylist, 'w') for i in range(len(listOfTracks)): myFile.write(listOfTracks[i]+'\n') 

The result is a playlist that covers at least an hour of broadcast, even without radio files. The main thing is not to forget to pick up this playlist in a non-random mode. Well, for the second program, the playlist is also worth generating.

Final touches


We bring in the crontab generation of playlists:

crontab -e
 0 19 * * * /home/user/radio/technical/generatorProg1.py 0 16 * * * /home/user/radio/technical/generatorProg2.py 

Add a couple of lines to the KDE autorun:

/home/user/.kde4/Autostart/start_liquidsoap.sh
 #!/bin/sh cp /home/user/radio/technical/liquidsoap.log /home/user/radio/technical/liquidsoap_backup.log cat /dev/null > /home/user/radio/technical/liquidsoap.log liquidsoap /home/user/radio/technical/start_liquidsoap 

Autostarting IceCast would not hurt
 /etc/init.d/icecast start chkconfig --add icecast 

And, importantly, lastly it’s worth setting up NTP β€” it’s pretty much depends on the right time. I configured it from under YaST.

Finally


Now this radio works successfully, files are added by a responsible person remotely from under Windows, which has become possible with the help of the openVPN + Samba bundle, programs are played, logs are written. A couple of features are planned for the future, including: playing various inserts at the beginning of each hour, implementing randomization of playlists, remote conference calls on the air, combing everything and everyone, setting up fault tolerance, as well as searching for pitfalls. Generally deal with Liquidsoap - a pleasure. All good music.

UPD: Supplement from milar .

UPD 2: So that there is no silence on the air after the crossfade, you need to add a line before the crossfade:
 radio = mksafe(radio) 

Many thanks to kvaps for this solution.

Source: https://habr.com/ru/post/156591/


All Articles