icecast是一个音频直播流媒体服务器,支持Ogg(Vorbis和Theora),Opus,WebM和MP3流。他可以被用来创建网络电台。官方网站 Gitlab源码地址
Icecast本身只是个直播服务器,一般使用方式是使用推流客户端(例如IceS,liquidsoap等)推流到icecast server,然后用户从icecast收听。
Icecast server安装使用
下载icecast包,下载地址
解压,进入解压后目录icecast-2.4.4,执行./configure
并根据提示安装缺失的包
成功后执行make && make install
安装完成
会自动生成一个配置文件在/usr/local/etc/icecast.xml
,编辑配置文件,主要是服务ip、端口、流地址和密码
启动命令su - appuser -c "icecast -b -c /usr/local/etc/icecast.xml"
,即可在后台启动
IceS推流 IceS作为推流端使用简便,问题是指支持Ogg Vorbis编码和MP3编码
下载ices,下载地址
解压,进入解压后目录ices-2.0.2,执行./configure
并根据提示安装缺失的包
成功后执行make && make install
安装完成
几个示例配置文件在当前目录下的conf目录中,ices-xxx.xml
复制ices-playlist.xml
模板,修改icecast服务器参数,包括ip、端口、密码和stream名字
修改playlist.txt
文件,每行一个ogg vorbis编码的音频文件路径
执行ices conf/ices-playlist.xml
,即可把流推到icecast的服务器了
使用icecast服务器的ip:port/streamname就可以开始收听广播
liquidsoap推流 liquidsoap是使用ocaml编译的成宿,编译安装好之后会生成liquidsoap指令,使用其自定义的脚本语言可以编写复杂的推流方式以及使用外部编解码器 由于支持外部编解码器,所以理论上liquidsoap可以支持所有格式和编码官方网站 Github源码地址
安装流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 yum install ocaml bubblewrap libao bzip2 wget https://li.nux.ro/download/nux/dextop/el7/x86_64/faad2-libs-2.7-5.el7.nux.x86_64.rpm rpm -Uvh faad2-libs-2.7-5.el7.nux.x86_64.rpm wget https://li.nux.ro/download/nux/dextop/el7/x86_64/faad2-devel-2.7-5.el7.nux.x86_64.rpm rpm -Uvh faad2-devel-2.7-5.el7.nux.x86_64.rpm rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm yum install -y ffmpeg ffmpeg-devel fdk-aac-devel sh <(curl -sL https://raw.githubusercontent.com/ocaml/opam/master/shell/install.sh) opam init opam switch create 4.08.0 opam depext taglib mad lame vorbis cry samplerate faad opus fdkaac liquidsoap opam install taglib mad lame vorbis cry samplerate faad opus fdkaac liquidsoap
使用方式 可以使用一行式指令,如下会把从http://c16.radioboss.fm/stream/5 直播流,转码成opus编码推给icecast服务器
1 2 3 4 5 liquidsoap ' output.icecast(%opus(bitrate=16), host = "172.31.34.113", port = 8000, password = "hackme", mount = "mystream.ogg", mksafe(input.https("https://c16.radioboss.fm/stream/5")))'
python-shout 这个包是用python语言实现的读取数据流,发送到icecast server的工具,使用简单,不包含转码,需要手动转码
1 pip install python-shout
下方脚本是连续下载url中音频存为mp3,然后使用ffmpeg转换为opus编码的ogg文件,最后把ogg文件读取推送到icecast server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 import loggingimport osimport threadingimport urlliblogging.basicConfig(format ='%(asctime)s [PID:%(process)d] %(name)s %(threadName)s \"%(filename)s\"[:%(lineno)d] ' '%(levelname)s %(message)s' , level=logging.INFO) logger = logging.getLogger(__name__) logger.info('loading %s' , __name__) import datetimeimport shoutimport timeimport queuefrom pydub import AudioSegmentclass Converter : def __init__ (self, url, delete_file=True ) -> None : self .url = url self .delete_file = delete_file self .convert_q = queue.Queue() self .send_q = queue.Queue() self .read_thread = threading.Thread(target=self .write_to_temp_file) self .convert_thread = threading.Thread(target=self .convert_opus) self .send_thread = threading.Thread(target=self .send_to_icecast) def write_to_temp_file (self ): response = urllib.request.urlopen(self .url) while True : f = open (self .get_file_name("1111rdio0001" ), 'wb' ) video_file_size_start = 0 video_file_size_end = 100 * 1024 block_size = 1024 logger.info(f"ready to read from url={response.url} to file={f.name} " ) st = time.time() while True : try : buffer = response.readline() if not buffer: break video_file_size_start += len (buffer) if video_file_size_start > video_file_size_end: break f.write(buffer) except Exception as e: logger.exception(e) et = time.time() logger.info(f"read {video_file_size_start / 1024 } k data in {round (et - st, 2 )} s" ) f.close() self .convert_q.put(f.name) def convert_opus (self ): while True : fa = self .convert_q.get(block=True ) opus_fa = "re_" + fa.split("." )[0 ] + ".ogg" AudioSegment.from_mp3(fa).export(opus_fa, format ='opus' , bitrate="16k" ) self .send_q.put(opus_fa) if self .delete_file and os.path.exists(fa): os.remove(fa) def send_to_icecast (self ): s = shout.Shout() s.host = "172.31.34.113" s.port = 8000 s.password = "hackme" s.mount = "/mystream.ogg" s.open () while True : try : total = 0 st = time.time() fa = self .send_q.get(block=True ) logger.info("send_to_icecast opening file %s" % fa) f = open (fa, 'rb' ) nbuf = f.read(4096 ) while 1 : buf = nbuf nbuf = f.read(4096 ) total = total + len (buf) if len (buf) == 0 : break s.send(buf) f.close() et = time.time() br = total * 0.008 / (et - st) logger.info("Sent %d bytes in %d seconds (%f kbps)" % (total, et - st, br)) if self .delete_file and os.path.exists(fa): os.remove(fa) except Exception: logger.exception("fail to send data to icecast" ) def get_file_name (self, radio_id, suffix="mp3" ): return f"{radio_id} _{int (datetime.datetime.now().timestamp() * 1000 )} .{suffix} " def start (self ): self .read_thread.start() self .convert_thread.start() self .send_thread.start() if __name__ == "__main__" : Converter("https://onlineradiobox.com/json/ng/christovibes/play?platform=web" ).start()