logo

drewdevault.com

[mirror] blog and personal website of Drew DeVault git clone https://hacktivis.me/git/mirror/drewdevault.com.git

Self-hosted-livestreaming.md (6065B)


  1. ---
  2. date: 2018-08-26
  3. layout: post
  4. title: How to make a self-hosted video livestream
  5. tags: [instructive, ffmpeg]
  6. ---
  7. I have seen some articles in the past which explain how to build the ecosystem
  8. *around* your video streaming, such as live chat and forums, but which leave the
  9. actual video streaming to Twitch.tv. I made a note the last time I saw one of
  10. these articles to write one of my own explaining the video bit. As is often the
  11. case with video, we'll be using the excellent [ffmpeg](http://ffmpeg.org/) tool
  12. for this. If it's A/V-related, ffmpeg can probably do it.
  13. *Note: a demonstration video was previously shown here, but as traffic on this
  14. article died down I took it offline to reduce unnecessary load.*
  15. ffmpeg has a built-in [DASH](https://dashif.org/) output format, which is the
  16. current industry standard for live streaming video to web browsers. It works by
  17. splitting the output up into discrete files and using an [XML
  18. file](/dash/live.mpd) (an MPD playlist) to tell the player where they are. Few
  19. browsers support DASH natively, but
  20. [dash.js](https://github.com/Dash-Industry-Forum/dash.js/wiki) can polyfill it
  21. by periodically downloading the latest manifest and driving the video element
  22. itself.
  23. Getting the source video into ffmpeg is a little bit beyond the scope of this
  24. article, but I know some readers won't be familiar with ffmpeg so I'll have
  25. mercy. Let's say you want to play some static video files like I'm doing above:
  26. ```sh
  27. ffmpeg \
  28. -re \
  29. -stream_loop -1 \
  30. -i my-video.mkv \
  31. ```
  32. This will tell ffmpeg to read the input (-i) in real time (-re), and loop it
  33. indefinitely. If instead you want to, for example, use x11grab instead to
  34. capture your screen and pulse to capture desktop audio, try this:
  35. ```sh
  36. -f x11grab \
  37. -r 30 \
  38. -video_size 1920x1080 \
  39. -i $DISPLAY \
  40. -f pulse \
  41. -i alsa_input.usb-Blue_Microphones_Yeti_Stereo_Microphone_REV8-00.analog-stereo
  42. ```
  43. This sets the framerate to 30 FPS and the video resolution to 1080p, then reads
  44. from the X11 display `$DISPLAY` (usually :0). Then we add pulseaudio and use my
  45. microphone source name, which I obtained with `pactl list sources`.
  46. Let's add some arguments describing the output format. Your typical web browser
  47. is a finicky bitch and has some very specific demands from your output format if
  48. you want maximum compatability:
  49. ```sh
  50. -codec:v libx264 \
  51. -profile:v baseline \
  52. -level 4 \
  53. -pix_fmt yuv420p \
  54. -preset veryfast \
  55. -codec:a aac \
  56. ```
  57. This specifices the libx264 video encoder with the baseline level 4 profile, the
  58. most broadly compatible x264 profile, with the yuv420p pixel format, the most
  59. broadly compatible pixel format, the veryfast preset to make sure we can encode
  60. it in realtime, the aac audio codec. Now that we've specified the parameters for
  61. the output, let's configure the output format: DASH.
  62. ```sh
  63. -f dash \
  64. -window_size 5 \
  65. -remove_at_exit 1 \
  66. /tmp/dash/live.mpd
  67. ```
  68. The window_size specifies the maximum number of A/V segments to keep in the
  69. manifest at any time, and remove_at_exit will clean up all of the files when
  70. ffmpeg exits. The output file is the path to the playlist to write to disk, and
  71. the segments will be written next to it. The last step is to serve this with
  72. nginx:
  73. ```nginx
  74. location /dash {
  75. types {
  76. application/dash+xml mpd;
  77. video/mp4 m4v;
  78. audio/mp4 m4a;
  79. }
  80. add_header Access-Control-Allow-Origin *;
  81. root /tmp;
  82. }
  83. ```
  84. You can now point the [DASH reference
  85. player](http://reference.dashif.org/dash.js/nightly/samples/dash-if-reference-player/index.html)
  86. at `http://your-server.org/dash/live.mpd` and see your video streaming there.
  87. Neato! You can add dash.js to your website and you know have a fully self-hosted
  88. video live streaming setup ready to rock.
  89. Perhaps the ffmpeg swiss army knife isn't your cup of tea. If you want to, for
  90. example, use [OBS Studio](https://obsproject.com/), you might want to take a
  91. somewhat different approach. The
  92. [nginx-rtmp-module](https://github.com/arut/nginx-rtmp-module) provides an RTMP
  93. (real-time media protocol) server that integrates with nginx. After adding
  94. the DASH output, you'll end up with something like this:
  95. ```sh
  96. rtmp {
  97. server {
  98. listen 1935;
  99. application live {
  100. dash on;
  101. dash_path /tmp/dash;
  102. dash_fragment 15s;
  103. }
  104. }
  105. }
  106. ```
  107. Then you can stream to `rtmp://your-server.org/live` and your dash segments
  108. will show up in `/tmp/dash`. There's no password protection here, so put it in
  109. the stream URL (e.g. `application R9AyTRfguLK8`) or use an IP whitelist:
  110. ```
  111. application live {
  112. allow publish your-ip;
  113. deny publish all;
  114. }
  115. ```
  116. If you want to get creative with it you can use
  117. [`on_publish`](https://github.com/arut/nginx-rtmp-module/wiki/Directives#on_publish)
  118. to hit an web service with some details and return a non-2xx code to forbid
  119. streaming. Have fun!
  120. I learned all of this stuff by making a bot which livestreamed Google hangouts
  121. over the LAN to get around the participant limit at work. I'll do a full writeup
  122. about that one later!
  123. ---
  124. Here's the full script I'm using to generate the live stream on this
  125. page:
  126. ```sh
  127. #!/bin/sh
  128. rm -f /tmp/playlist
  129. mkdir -p /tmp/dash
  130. for file in /var/www/mirror.sr.ht/hacksway-2018/*
  131. do
  132. echo "file '$file'" >> /tmp/playlist
  133. done
  134. ffmpeg \
  135. -re \
  136. -loglevel error \
  137. -stream_loop -1 \
  138. -f concat \
  139. -safe 0 \
  140. -i /tmp/playlist \
  141. -vf "drawtext=\
  142. fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:\
  143. text='%{gmtime\:%Y-%m-%d %T} UTC':\
  144. fontcolor=white:\
  145. x=(w-text_w)/2:y=128:\
  146. box=1:boxcolor=black:\
  147. fontsize=72,
  148. drawtext=\
  149. fontfile=/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf:\
  150. text='REBROADCAST':\
  151. fontcolor=white:\
  152. x=(w-text_w)/2:y=16:\
  153. box=1:boxcolor=black:\
  154. fontsize=48" \
  155. -codec:v libx264 \
  156. -profile:v baseline \
  157. -pix_fmt yuv420p \
  158. -level 4 \
  159. -preset veryfast \
  160. -codec:a aac \
  161. -f dash \
  162. -window_size 5 \
  163. -remove_at_exit 1 \
  164. /tmp/dash/live.mpd
  165. ```