MPEG DASH - team-wedev/wedev GitHub Wiki

DASH

image (μ΄λ―Έμ§€μΆœμ²˜: http://abt.net/multimedia.php)

Dynamic Adaptive Streaming over HTTP λ˜λŠ”, MPEG-DASHλŠ” web serverμ—μ„œ media contentλ₯Ό μ œκ³΅ν•  λ•Œ client의 λ„€νŠΈμ›Œν¬ 상황에 따라 그에 λ§žλŠ” quality의 media contentλ₯Ό μ œκ³΅ν•˜λŠ” 것을 μ˜λ―Έν•©λ‹ˆλ‹€. μ• ν”Œμ˜ HLS(HTTP Live Streaming)κ³Ό μœ μ‚¬ν•©λ‹ˆλ‹€.

ν”„λ‘œμ„ΈμŠ€

λΉ„λ””μ˜€ 인코딩

ffmpegλ₯Ό μ΄μš©ν•˜μ—¬ client μ—μ„œ μž…λ ₯ν•œ 원본 λ™μ˜μƒμ„ μ—¬λŸ¬ ν•΄μƒλ„μ˜ λ™μ˜μƒμœΌλ‘œ μΈμ½”λ”©ν•©λ‹ˆλ‹€.

wget --no-check-certificate "https://docs.google.com/uc?export=download&id=17leZjmhYETsCl67vXBlc3_FO0W0UElyc" -O sample2.mp4

ffmpeg -y -i ./sample/sample.mp4 -c:a aac -ac 2 -ab 256k -ar 48000 -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 1500k -maxrate 1500k -bufsize 1000k -vf scale=-1:720 -strict -2 ./sample/sample720.mp4
ffmpeg -y -i ./sample/sample.mp4 -c:a aac -ac 2 -ab 128k -ar 44100 -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 800k -maxrate 800k -bufsize 500k -vf scale=-1:540 -strict -2  ./sample/sample540.mp4 -strict -2
ffmpeg -y -i ./sample/sample.mp4 -c:a aac -ac 2 -ab 64k -ar 22050 -c:v libx264 -x264opts 'keyint=24:min-keyint=24:no-scenecut' -b:v 400k -maxrate 400k -bufsize 400k -vf scale=-1:360 -strict -2  ./sample/sample360.mp4 -strict -2

MANIFEST 파일 생성

./shaka_packager/src/out/Release/packager \
  input=./sample/sample720.mp4,stream=audio,output=./dest/sample720_audio.mp4 \
  input=./sample/sample720.mp4,stream=video,output=./dest/sample720_video.mp4 \
  input=./sample/sample360.mp4,stream=audio,output=./dest/sample360_audio.mp4 \
  input=./sample/sample360.mp4,stream=video,output=./dest/sample360_video.mp4 \
--profile on-demand \
--mpd_output ./manifest/sample-manifest-full.mpd \
--min_buffer_time 3 \
--segment_duration 3

./shaka_packager/src/out/Release/packager \
  input=./sample/sample720.mp4,stream=audio,output=./dest/sample720_audio.mp4 \
  input=./sample/sample720.mp4,stream=video,output=./dest/sample720_video.mp4 \
--profile on-demand \
--mpd_output ./manifest/sample-720-full.mpd \
--min_buffer_time 3 \
--segment_duration 3

Shaka packager λ₯Ό μ΄μš©ν•˜μ—¬ 각각 λ‹€λ₯Έ ν’ˆμ§ˆμ˜ λΉ„λ””μ˜€ 파일과 μ˜€λ””μ˜€ νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€. Shaka packagerλŠ” 이λ₯Ό ν† λŒ€λ‘œ manifest νŒŒμΌμ„ μƒμ„±ν•©λ‹ˆλ‹€.

MDN-DASH에 이에 λŒ€ν•œ μ •ν™•ν•œ 흐름이 λͺ…μ‹œλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

Structure of an MPEG-DASH MPD


Client

Clientμ—μ„œλŠ” Shaka playerλ₯Ό μ΄μš©ν•˜μ—¬ μ‚¬μš©μžμ˜ λ„€νŠΈμ›Œν¬ μƒνƒœμ— λ”°λ₯Έ λ™μ˜μƒ μš”μ²­μ„ μ‹€μ‹œν•©λ‹ˆλ‹€.

예제 μ½”λ“œ

Git hub μ£Όμ†Œ

  • index.js shaka-player.compiled.js λŠ” shaka player μ„€μΉ˜λ₯Ό ν•˜λ©΄ 받을 수 μžˆμŠ΅λ‹ˆλ‹€.
<!DOCTYPE html>
<html>
  <head>
    <script src="/js/shaka-player.compiled.js"></script>
    <script>console.log("test");</script>
  </head>
  <body>
    <video id="video" width="640"
          controls autoplay"></video>

    <script src="/js/myapp.js"></script>
  </body>
</html>
  • myapp.js
const manifestUrl = "/manifest";

function initApp() {
  shaka.polyfill.installAll();

  if (shaka.Player.isBrowserSupported()) {
    initPlayer();
  } else {
    console.error("Browser not supported!");
  }
}

function initPlayer() {
  const video = document.getElementById("video");
  const player = new shaka.Player(video);

  window.player = player;

  player.addEventListener("error", onErrorEvent);

  player
    .load(manifestUrl)
    .then(function() {
      console.log("The video has now been loaded!");
    })
    .catch(onError);
}

function onErrorEvent(event) {
  onError(event.detail);
}

function onError(error) {
  console.error("Error code", error.code, "object", error);
}

document.addEventListener("DOMContentLoaded", initApp);
  • sample-manifest-full.mpd
<?xml version="1.0" encoding="UTF-8"?>
<!--Generated with https://github.com/google/shaka-packager version 1bcaf0a-release-->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:mpeg:dash:schema:mpd:2011 DASH-MPD.xsd" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011" minBufferTime="PT3S" type="static" mediaPresentationDuration="PT27.33333396911621S">
  <Period id="0">
    <AdaptationSet id="0" contentType="video" maxWidth="1278" maxHeight="720" frameRate="12288/512" subsegmentAlignment="true" par="16:9">
      <Representation id="0" bandwidth="430715" codecs="avc1.64001e" mimeType="video/mp4" sar="639:640" width="640" height="360">
        <BaseURL>./dest/sample360_video.mp4</BaseURL>
        <SegmentBase indexRange="875-1026" timescale="12288">
          <Initialization range="0-874"/>
        </SegmentBase>
      </Representation>
      <Representation id="1" bandwidth="1576446" codecs="avc1.64001f" mimeType="video/mp4" sar="1:1" width="1278" height="720">
        <BaseURL>./dest/sample720_video.mp4</BaseURL>
        <SegmentBase indexRange="855-1006" timescale="12288">
          <Initialization range="0-854"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
    <AdaptationSet id="1" contentType="audio">
      <Representation id="2" bandwidth="79354" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="22050">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
        <BaseURL>./dest/sample360_audio.mp4</BaseURL>
        <SegmentBase indexRange="789-940" timescale="22050">
          <Initialization range="0-788"/>
        </SegmentBase>
      </Representation>
      <Representation id="3" bandwidth="260620" codecs="mp4a.40.2" mimeType="audio/mp4" audioSamplingRate="48000">
        <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
        <BaseURL>./dest/sample720_audio.mp4</BaseURL>
        <SegmentBase indexRange="789-940" timescale="48000">
          <Initialization range="0-788"/>
        </SegmentBase>
      </Representation>
    </AdaptationSet>
  </Period>
</MPD>

Server

예제 μ½”λ“œ
const express = require("express");
const fs = require("fs");
const path = require("path");
const app = express();
const PORT = process.env.PORT || 4040;

app.use("/manifest/dest", express.static("public/videos"));
app.use("/js", express.static("public/js"));

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.get("/", (req, res) => {
  console.log("test");
  // const html = fs.readFileSync(path.resolve(__dirname, "./view/index.html"));
  res.sendFile(path.join(__dirname, "/view/index.html"));
});

app.get("/manifest", (req, res) => {
  console.log("manifest");

  res.sendFile(
    path.join(__dirname, "/public/manifest/sample-manifest-full.mpd")
  );
});

app.post("/test", (req, res) => {
  console.log(req.body);
  console.log("TEST");
  res.send("OK");
});

app.listen(PORT, () => {
  console.log(`Listening on ${PORT}...........`);
});
⚠️ **GitHub.com Fallback** ⚠️