Интернет-радиостанция на Liquidsoap + IceCast

от автора

Довольно много на хабре сказано про интернет-радиовещание изнутри. Есть даже хорошо написанные теоретические основы интернет-радиовещания, с которыми советую ознакомиться. В данной статье я бы хотел рассказать об организации ещё одной любительской интернет-радиостанции, построенной на связке незаслуженно малоизвестного Liquidsoap 1.0.1 и вездесущего IceCast 2.3.2. Статья расчитана на тех, кто хотя бы приблизительно знает, что такое аудиопоток, IceCast, линуксовская консоль и таки что он вообще хочет получить. Однако она и написана начинающим пользователем, поэтому представленное решение даже не зарекается на звание оптимального.

Требуемый итоговый функционал

  1. Возможность назначения своего плейлиста для произвольного временного отрезка
  2. Поддержка подхвата OGG, MP3, FLAC в качестве источника для аудиопотока
  3. Динамичность конфигурирования
  4. Простота редактирования контента для радиоэфира
  5. Работа под Linux
  6. Возможность начинающему Linux-пользователю установить и настроить всё это

После довольно долгих проб различных способов получения данного функционала, я остановился именно на Liquidsoap + IceCast. Последний был взят за соответствие требованиям и широкую распространённость (в принципе, аналоги я даже не искал), а Liquidsoap за воистину потрясающие возможности, доступные посредством его функционального языка скриптования. До него я рассматривал ices, ices + ardj, AirTime, что-то ещё, что даже и не упомнить, но все они так или иначе мне не подходили. В общем, я решил воспользоваться Liquidsoap. К сожалению, всей мощи его я не смогу ощутить — вся документация написана на английском, а с ним у меня не всё гладко — однако что-то я усвоил и постараюсь описать всё, чем могу оперировать.

Установка

Не долго думая я решил всё это чудо поднять на своём ноутбуке под openSUSE 12.2 x64, дабы было удобно изучать функционал Liquidsoap, а уже потом перенести на рабочую машину. В репозиториях присутствовал лишь IceCast, Liquidsoap же пришлось собирать. Пользователи Debian/Ubuntu, Windows, Mac OS X, FreeBSD и ArchLinux могут взять готовые пакеты на официальном сайте.

IceCast

Установку IceCast я выполнил из стандартных репозиториев:

# zypper in icecast 

Подробно описывать конфигурацию IceCast я не буду, лишь приведу рабочий пример того, что крутится у меня:

/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

Качаем, подготавливаем:

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

Для меня достаточно минимального набора, но если кому нужна поддержка ещё чего-либо, то следует отредактировать файл 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 

Собираем:

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

К слову, после выполнения "./configure …" стоит проверить, весь ли функционал, нужный нам, будет доступен в собранной программе. Сделать это можно просто посмотрев на таблицу, выведенную при завершении "./configure …"
Всё, проверить работоспособность программы можно выполнив «liquidsoap —version» — ошибок быть не должно.

Ориентируемся с расписанием эфира

Предположим, что нам необходимо получить приблизительно следующее:

  1. 02:00-06:00 — ночной плейлист
  2. 06:00-09:00 — утренний плейлист
  3. 09:00-19:00 — дневной плейлист
  4. 19:00-02:00 — вечерний плейлист
  5. пн, ср, пт — 21:00-22:00 — одна программа
  6. пн, ср, чт, пт — 18:00-19:00 — вторая программа

Расположение в файловой системе:

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

Сразу приведу готовую конфигурацию:

./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) ) 

Несколько комментариев:
1) Эти строки:

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}")

вполне успешно можно заменить на

mus_ni = playlist (reload = 360, "/home/user/radio/collection/efir/night/music") 

и ничего вам за это не будет — Liquidsoap просто вместо #{wd} подставляет значение переменной wd.

2) В месте, где мы внедряли вставки, были строки:

ins_ni = rotate (weights = [2, 1], [jin_ni, promo]) ni = rotate (weights = [3, 1], [mus_ni, ins_ni]) 

rotate() — позволяет регулировать очередью
weights = [2, 1], [jin_ni, promo] — указывает брать 2 трека из jin_ni, затем 1 из promo, после повторно 2 трека из jin_ni и так далее.
weights = [3, 1], [mus_ni, ins_ni] — указывает брать 3 трека из mus_ni, затем 1 трек из того уже перемешанного плейлиста, что получился строкой ранее (ins_ni).

3) При конфигурировании расписания эфира мы использовали следующие строки:

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() — переключает аудиоисточники в заданное время.
track_sensitive = true — позволяет не прерывать текущий трек, даже если время активного плейлиста истекло. Т.е. если ночной трек начался в 05:59, то, пока он не закончится, в силу не вступит утренний плейлист.
({ (1w21h - 1w22h) or (3w21h - 3w22h) or (5w21h - 5w22h)}, 1_prog), — по понедельникам, средам и пятницам с 21 до 22 часов играть источник 1_prog.
Насколько я понял, те строкив списке switch(), что выше расположены, имеют высший приоритет.

Формирование плейлиста радиопрограммы

Принцип прост: в папке prog_1 лежат файлы радиопередачи вида «01_ProgName», где записаны голоса радиоведущих. Плейлист будет вида:

01_ProgName
Music152
02_ProgName
Music241
03_ProgName
Music937

Думаю, на bash было бы правильней писать генератор такого плейлиста, но я недавно дорвался до Python, посему на скорую руку написал на нём:

./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') 

В итоге получается плейлист, который покрывает как минимум час эфира даже без файлов радиопередачи. Главное не забыть подхватить этот плейлист в нерандомном режиме. Ну и для второй программы так же плейлист стоит генерировать.

Финальные штрихи

Заносим в crontab генерирование плейлистов:

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

Добавляем в автозапуск KDE пару строк:

/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 

Автозапустить IceCast тоже не помешало бы

/etc/init.d/icecast start chkconfig --add icecast 

И, что важно, на последок стоит настроить NTP — здесь довольно много зависит от верно установленного времени. Его я настроил из-под YaST.

В заключение

Сейчас это радио успешно работает, файлы добавляются ответственным человеком удалённо из-под Windows, что стало возможным с помощью связки openVPN+Samba, программы играют, логи пишутся. На будущее задумано ещё пара фич, среди которых: проигрывание различных вставок в начале каждого часа, реализация своей рандомизации плейлистов, удалённые конференц-звонки в прямой эфир, причёсывание всего и вся, настройка пущей отказоустойчивости, а так же поиск подводных камней. Вообще разбираться с Liquidsoap — одно удовольствие. Хорошей всем музыки.

ссылка на оригинал статьи http://habrahabr.ru/post/156591/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *