Skip to main content

[Media] 影音傳輸-實作篇

以下內容完全為整理自 30 天之即時網路影音開發攻略(小白本) by 我是小馬克 @ iThome 的筆記,無原創內容。

建立串流點播工具#

將影音檔轉成 HLS 用的串流檔#

透過 ffmpeg 即可將影音檔(例如,mp4)轉成 .m3u8.ts 適用於 HLS 的串流檔。

在 Node.js 中可以使用 fluent-ffmpeg 這個工具進行轉換:

// ./ffmpeg-helper.js
// 把 mp4 檔轉換成 .m3u8 索引檔和多支 .ts 檔
const ffmpeg = require('fluent-ffmpeg');
module.exports = {
convertToHls: async (file) => {
return new Promise((resolve) => {
ffmpeg(file, { timeout: 432000 })
.addOptions([
'-profile:v baseline', // for H264 video codec
'-level 3.0',
'-s 640x360', // 640px width, 360px height
'-start_number 0', // start the first .ts segment at index 0
'-hls_time 10', // 10 second segment duration
'-hls_list_size 0', // Maximum number of playlist entries
'-f hls', // HLS format
])
.output('./source-m3u8/output.m3u8')
.on('end', () => {
console.log('finish');
resolve();
})
.run();
});
},
};

並且建立一支 convert.js 來執行:

// ./convert.js
const ffmpegHelper = require('./ffmpeg-helper');
(async () => {
await ffmpegHelper.convertToHls('./source.mp4');
})();

轉換好的檔案會被放置到 source-m3u8 的資料夾內:

imgur

建立支援 HLS 的 Server#

要建立 HLS 的 Node 伺服器可以使用套件 hls-server

// index.js
const HLSServer = require('hls-server');
const http = require('http');
const hls = new HLSServer(server, {
path: '/streams', // Base URI to output HLS streams
dir: 'source-m3u8', // Directory that input files are stored
});
server.listen(8000);

使用 ffplay 播放串流檔#

$ node index.js # 於專案資料夾下執行
$ ffplay http://localhost:8000/streams/output.m3u8

如此即可開始播放:

imgur

在瀏覽器進行播放#

若要在瀏覽器進行播放,一樣需要額外的套件,這裡用的是 hls.js

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>HLS Client</title>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head>
<body>
<div id="app">
<video controls id="video"></video>
<input type="text" />
<button type="button" id="load">Load</button>
</div>
<script>
const video = document.getElementById('video');
const button = document.querySelector('#load');
if (Hls.isSupported()) {
var hls = new Hls();
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
button.addEventListener('click', function () {
hls.loadSource(document.querySelector('input').value);
});
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.addEventListener('canplay', function () {
video.play();
});
button.addEventListener('click', function () {
video.src = document.querySelector('input').value;
});
}
</script>
</body>
</html>

Server 的部分做一些修改,讓它可以 serve 這支 index.html

// index.js
const HLSServer = require('hls-server');
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
const html = fs.readFileSync('index.html', 'utf8');
res.write(html);
res.end();
});
const hls = new HLSServer(server, {
path: '/streams', // Base URI to output HLS streams
dir: 'source-m3u8', // Directory that input files are stored
});
server.listen(8000);

完成後,順利的話只要在表單中輸入 http://localhost:8000/streams/output.m3u8 就可以在網頁上載到影片:

imgur

建立直播工具#

直播的部分會分成「推流」和「拉流」兩個部分:

imgur

  • 在 server 的部分,這裡會使用到 Node-Media-Server 這個套件來接收從直撥主送過來的推流,並提供 .flv 檔給 Client。
  • 在 client 的部分,會使用 bilibili 提供的 flv.js 來拉流。

要建立直播,首先要把 media server 給 on 起來:

$ node app.js

接著透過 ffmpeg 假裝是直播主透過 RTMP 協定進行推流:

$ ffmpeg -re -i source.mp4 -c copy -f flv rtmp://localhost/live/mark

這時候 server 就可以收到直播主的推流,client 的部分則可以使用 flv.js 來拉流:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Live Streaming</title>
<script src="https://cdn.bootcss.com/flv.js/1.4.2/flv.min.js"></script>
</head>
<body>
<video controls id="video" width="100%"></video>
<input type="text" />
<button type="button" id="load">Load</button>
<script>
const button = document.querySelector('#load');
if (flvjs.isSupported()) {
button.addEventListener('click', function () {
const video = document.getElementById('video');
const target = document.querySelector('input').value;
const flvPlayer = flvjs.createPlayer({
type: 'flv',
url: `http://localhost:8000/live/${target}.flv`,
});
flvPlayer.attachMediaElement(video);
flvPlayer.load();
flvPlayer.play();
});
}
</script>
</body>
</html>

影音串流伺服器常見問題#

作為一個影音串流伺服器,常見的問題包含「伺服器連線數限制」、「流量頻寬問題」、「效能消耗問題」、「遠距離封包遺失問題」。

透過 Load Balance 可以解決「伺服器連線數限制」和「效能消耗問題」;而「流量頻寬問題」和「遠距離封包遺失問題」則可以透過 CDN 解決。

沒有 CDN 的協助基本上是很難完成影音串流伺服器的。

Content Delivery Network (CDN)#

透過內容交付網路(CDN, Content Delivery Network),即使伺服器架設在美國,而使用者人在台灣,依然可以用更短的時間取得這些資源。

CDN 主要可以分成三個部分:

  • 智能 DNS(Intelligent DNS):告訴 client 最近的 Edge CDN 位址在哪
  • 邊緣 CDN(Edge CDN):散佈在世界各地的 CDN 節點
  • 來源伺服器(Origin Server):原始資料存放的位址

imgur

關於如何在 AWS 上設定 CDN 可以參考 30-23 之 CDN 的說話島 ( AWS CloudFront CDN 實作 ) by 我是小馬克 @ iThome

WebRTC 與 SDP#

透過 WebRTC 可以讓瀏覽器不需要任何外掛的情況下直接進行 P2P 的溝通,因此這裡示範如何用 WebRTC 建立 P2P 的影音連線。

雖然 WebRTC 可以讓使用者直接透過瀏覽器進行 P2P 連線,但在雙方建立連線前仍需要有一個伺服器讓他們知道雙方的位址,才能進行溝通,這個伺服器稱作 Signaling Server,這並沒有規範在 WebRTC 內,可以使用 HTTP 輪詢(一直打 request 詢問)或 websocket 的方式實作。這裡會使用到 peer.js 這個套件來實作。

當雙方還不認識彼此時,需要透過傳送自己的 SDP( Session Description Protocol,會話描述協議) 到 Signaling Server 來認識彼此,SDP 中會包含有發送者的地址、媒體類型、傳輸協議、媒體格式。

WebRTC 加上 SDP 後的流程如下:

  1. 用戶 A 向 Server 發送進行會話,內容包含 A 的 SDP。
  2. Server 將會話 SDP A 請求發送給用戶 B。
  3. 用戶 B 向 Server 進行應答,並回應 B 的 SDP。
  4. Server 向用戶 A 發送 用戶 B 的 SDP。
  5. A、B 雙方使用 SDP 開始建立連線。

imgur

影音串流在 P2P 上的困難與解法#

當透過 P2P 的方式進行影音傳輸,因此當使用者有使用「網路位址轉換(NAT, Network Address Translation)」或防火牆時,會沒辦法直接找到目標電腦的位址。

在 WebRTC 則使用了 ICE (Interactive Connectivity Establishment) 這個框架來解決這個問題,ICE 整合了 STUN (Session Traversal Utilities for NAT)TURN (Traversal Using Relay NAT ) 兩個協定,讓 NAT 內的用戶一樣可以找到它的位置。

參考資源#

Last updated on