EME WTF?

מבוא לתוספי מדיה מוצפנים

תוספי מדיה מוצפנים מספקים ממשק API שמאפשר לאפליקציות אינטרנט לקיים אינטראקציה עם מערכות להגנה על תוכן, כדי לאפשר הפעלה של אודיו ווידאו מוצפנים.

הכלי EME נועד לאפשר את השימוש באותה האפליקציה ובקבצים מוצפנים בכל דפדפן, ללא קשר למערכת ההגנה הבסיסית. הדרך הראשונה מתאפשרת על ידי הזרימה וממשקי ה-API הסטנדרטיים, והשניה אפשריים באמצעות העיקרון של הצפנה משותפת.

EME הוא הרחבה למפרט של HTMLMediaElement – ומכאן השם. המשמעות של 'תוסף' היא שתמיכה בדפדפן ב-EME היא אופציונלית: אם דפדפן לא תומך במדיה מוצפנת, הוא לא יוכל להפעיל מדיה מוצפנת, אבל EME לא נדרש לתאימות למפרט של HTML. מהמפרט של EME:

הטמעות EME משתמשות ברכיבים החיצוניים הבאים:

  • מערכת מפתחות: מנגנון של הגנה על תוכן (DRM). EME לא מגדיר את מערכות המפתחות עצמן מלבד 'מפתח ברור' (מידע נוסף בנושא מופיע בהמשך).
  • מודול לפענוח תוכן (CDM): תוכנה בצד הלקוח או מנגנון חומרה שמאפשרים הפעלה של מדיה מוצפנת. בדומה למערכות מפתחות, EME לא מגדיר כל CDM, אלא מספק ממשק לאינטראקציה עם אפליקציות CDM זמינות.
  • שרת רישיונות (מפתחות): מקיים אינטראקציה עם CDM כדי לספק מפתחות לפענוח מדיה. המשא ומתן עם שרת הרישיונות הוא באחריות האפליקציה.
  • שירות אריזה: קידוד והצפנה של מדיה לצורך הפצה/צריכה.

חשוב לשים לב שאפליקציה שמשתמשת ב-EME יוצרת אינטראקציה עם שרת רישיונות כדי לקבל מפתחות שיאפשרו פענוח, אבל זהות ואימות של משתמשים אינם חלק מ-EME. אחזור המפתחות כדי לאפשר הפעלת מדיה מתבצע לאחר (אופציונלי) אימות של משתמש. שירותים כמו Netflix חייבים לאמת משתמשים בתוך אפליקציית האינטרנט שלהם: כשמשתמש נכנס לאפליקציה, האפליקציה קובעת את זהותו ואת ההרשאות שלו.

איך פועל EME?

זו האינטראקציה בין הרכיבים של EME, בהתאם לקוד לדוגמה הבא:

  1. אפליקציית אינטרנט מנסה להפעיל אודיו או וידאו שיש בהם שידור מוצפן אחד או יותר.
  2. הדפדפן מזהה שהמדיה מוצפנת (בהמשך מוסבר איך זה קורה) ומפעיל אירוע encrypted עם המטא-נתונים (initData) שהתקבלו מהמדיה לגבי ההצפנה.
  3. האפליקציה מטפלת באירוע encrypted:
    1. אם לא שויך אובייקט MediaKeys לרכיב המדיה, קודם בוחרים מערכת מפתחות זמינה באמצעות navigator.requestMediaKeySystemAccess() כדי לבדוק אילו מערכות מפתחות זמינות, ואז יוצרים אובייקט MediaKeys עבור מערכת מפתחות זמינה באמצעות אובייקט MediaKeySystemAccess. שים לב שהאתחול של אובייקט MediaKeys צריך להתרחש לפני אירוע encrypted הראשון. האפליקציה יוצרת כתובת URL של שרת רישיונות בנפרד לבחירה של מערכת מפתחות זמינה. אובייקט MediaKeys מייצג את כל המפתחות הזמינים לפענוח המדיה של רכיב אודיו או וידאו. הוא מייצג מכונת CDM ומספק גישה ל-CDM, במיוחד ליצירת סשנים עם מפתחות, המשמשים להשגת מפתחות משרת רישיונות.
    2. לאחר יצירת האובייקט MediaKeys, מקצים אותו לרכיב המדיה: setMediaKeys() משייך את האובייקט MediaKeys ל-HTMLMediaElement, כך שניתן יהיה להשתמש במפתחות שלו במהלך ההפעלה, כלומר במהלך פענוח הקידוד.
  4. האפליקציה יוצרת MediaKeySession על ידי קריאה ל-createSession() ב-MediaKeys. הפעולה הזו יוצרת MediaKeySession, שמייצג את משך החיים של רישיון ואת המפתחות שלו.
  5. האפליקציה יוצרת בקשת רישיון על ידי העברת נתוני המדיה שהתקבלו ב-handler של encrypted אל ה-CDM על ידי קריאה ל-generateRequest() ב-MediaKeySession.
  6. ה-CDM מפעיל אירוע message: בקשה לקבלת מפתח משרת רישיונות.
  7. האובייקט MediaKeySession מקבל את האירוע message והאפליקציה שולחת הודעה לשרת הרישיונות (לדוגמה, באמצעות XHR).
  8. האפליקציה מקבלת תגובה משרת הרישיונות ומעבירה את הנתונים ל-CDM באמצעות שיטת update() של MediaKeySession.
  9. ה-CDM מפענח את המדיה באמצעות המפתחות שברישיון. ניתן להשתמש במפתח חוקי מכל סשן ב-MediaKey שמשויך לרכיב המדיה. ה-CDM ייגש למפתח ולמדיניות, שנוספו לאינדקס לפי מזהה המפתח.
  10. הפעלת המדיה תמשיך.

איזה מזל...

שימו לב שיכולות להיות הודעות מרובות בין ה-CDM לבין שרת הרישיונות, וכל התקשורת בתהליך הזה תהיה אטומה לדפדפן ולאפליקציה: ההודעות מובנת רק על ידי ה-CDM ושרת הרישיונות, למרות ששכבת האפליקציה יכולה לראות איזה סוג הודעה ה-CDM שולח. בקשת הרישיון מכילה הוכחה לתוקף (ויחסי האמון) של ה-CDM, וכן מפתח לשימוש בעת הצפנת מפתחות התוכן ברישיון שנוצר.

...אבל מה למעשה CDMs עושים?

הטמעה של EME לא מספקת כשלעצמה דרך לפענח מדיה: היא רק מספקת API שמאפשר לאפליקציית אינטרנט לתקשר עם מודולים של פענוח תוכן.

מה ש-CDMs למעשה לא מוגדר על ידי מפרט ה-EME, ו-CDM עשוי לטפל בפענוח קידוד (פתיחת דחיסה) של מדיה וגם בפענוח. מהרמה הנמוכה ביותר ועד הגבוהה ביותר, יש מספר אפשרויות לפונקציונליות של CDM.

  • פענוח בלבד, מאפשר הפעלה באמצעות צינור המדיה הרגיל, לדוגמה באמצעות רכיב <video>.
  • פענוח ופענוח, המעבירים פריימים של סרטון לדפדפן לצורך רינדור.
  • פענוח ופענוח, רינדור ישירות בחומרה (לדוגמה, GPU).

יש מספר דרכים להפוך CDM לזמין לאפליקציית אינטרנט:

  • אגד CDM עם הדפדפן.
  • יש להפיץ CDM בנפרד.
  • צור CDM לתוך מערכת ההפעלה.
  • כולל CDM בקושחה.
  • הטמעת CDM בחומרה.

האופן שבו CDM הופך לזמין לא מוגדר במפרט ה-EME, אבל בכל המקרים הדפדפן אחראי על הבדיקה והחשיפה של ה-CDM.

EME לא מחייב מערכת מפתחות מסוימת. בין הדפדפנים שזמינים למחשבים ולניידים, Chrome תומך ב-Widevine ו-IE11 תומך ב-PlayReady.

קבלת מפתח משרת רישיונות

בשימוש מסחרי טיפוסי, התוכן יוצפן ויקודד באמצעות שירות אריזה או כלי אריזה. לאחר שהמדיה המוצפנת הופכת לזמינה באינטרנט, לקוח אינטרנט יכול להשיג מפתח (שכלול ברישיון) משרת רישיונות ולהשתמש במפתח כדי לאפשר פענוח והפעלה של התוכן.

הקוד הבא (מותאם לדוגמאות של המפרט) מראה איך אפליקציה יכולה לבחור מערכת מפתחות מתאימה ולקבל מפתח משרת רישיונות.

var video = document.querySelector('video');

var config = [{initDataTypes: ['webm'],
  videoCapabilities: [{contentType: 'video/webm; codecs="vp9"'}]}];

if (!video.mediaKeys) {
  navigator.requestMediaKeySystemAccess('org.w3.clearkey',
      config).then(
    function(keySystemAccess) {
      var promise = keySystemAccess.createMediaKeys();
      promise.catch(
        console.error.bind(console, 'Unable to create MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          return video.setMediaKeys(createdMediaKeys);
        }
      ).catch(
        console.error.bind(console, 'Unable to set MediaKeys')
      );
      promise.then(
        function(createdMediaKeys) {
          var initData = new Uint8Array([...]);
          var keySession = createdMediaKeys.createSession();
          keySession.addEventListener('message', handleMessage,
              false);
          return keySession.generateRequest('webm', initData);
        }
      ).catch(
        console.error.bind(console,
          'Unable to create or initialize key session')
      );
    }
  );
}

function handleMessage(event) {
  var keySession = event.target;
  var license = new Uint8Array([...]);
  keySession.update(license).catch(
    console.error.bind(console, 'update() failed')
  );
}

הצפנה נפוצה

פתרונות הצפנה נפוצים מאפשרים לספקי תוכן להצפין ולארוז את התוכן שלהם פעם אחת לכל קונטיינר/קודק ולהשתמש בו עם מגוון של מערכות מפתח, CDM ולקוחות: כלומר כל CDM שתומך בהצפנה משותפת. לדוגמה, ניתן להפעיל בדפדפן סרטון שנארז באמצעות Play Ready באמצעות CDM של Widevine המקבל מפתח משרת רישיונות של Widevine.

זאת בניגוד לפתרונות מדור קודם, שיפעלו רק עם סטאק אנכי מלא, כולל לקוח יחיד שלרוב כלל גם סביבת זמן ריצה של אפליקציה.

הצפנה משותפת (CENC) הוא תקן ISO המגדיר סכימת הגנה עבור ISO BMFF. עיקרון דומה חל על WebM.

הסרת המפתח

אף על פי ש-EME לא מגדיר פונקציונליות של DRM, אך כרגע המפרט קובע שכל הדפדפנים שתומכים ב-EME חייבים להטמיע את Clear Key. באמצעות המערכת הזו, אפשר להצפין את המדיה באמצעות מפתח, ואז להפעיל אותה שוב על ידי אספקת המפתח הזה. אפשר לכלול מפתח ניקוי מובנה בדפדפן: הוא לא מחייב שימוש במודול פענוח נפרד.

סביר להניח שלא ייעשה שימוש בסוגים רבים של תוכן מסחרי, אבל ניתן להשתמש ב-Clear Key עם יכולת פעולה הדדית מלאה בכל הדפדפנים שתומכים ב-EME. הוא גם שימושי לבדיקת הטמעות EME ואפליקציות באמצעות EME, ללא צורך לבקש מפתח תוכן משרת רישיונות. יש דוגמה פשוטה של Clear Key בכתובת simpl.info/ck. בהמשך מופיע הדרכה מפורטת על הקוד, שמקבילה לשלבים המתוארים למעלה, אך ללא אינטראקציה עם שרת הרישיונות.

// Define a key: hardcoded in this example
// – this corresponds to the key used for encryption
var KEY = new Uint8Array([
  0xeb, 0xdd, 0x62, 0xf1, 0x68, 0x14, 0xd2, 0x7b,
  0x68, 0xef, 0x12, 0x2a, 0xfc, 0xe4, 0xae, 0x3c
]);

var config = [{
  initDataTypes: ['webm'],
  videoCapabilities: [{
    contentType: 'video/webm; codecs="vp8"'
  }]
}];

var video = document.querySelector('video');
video.addEventListener('encrypted', handleEncrypted, false);

navigator.requestMediaKeySystemAccess('org.w3.clearkey', config).then(
  function(keySystemAccess) {
    return keySystemAccess.createMediaKeys();
  }
).then(
  function(createdMediaKeys) {
    return video.setMediaKeys(createdMediaKeys);
  }
).catch(
  function(error) {
    console.error('Failed to set up MediaKeys', error);
  }
);

function handleEncrypted(event) {
  var session = video.mediaKeys.createSession();
  session.addEventListener('message', handleMessage, false);
  session.generateRequest(event.initDataType, event.initData).catch(
    function(error) {
      console.error('Failed to generate a license request', error);
    }
  );
}

function handleMessage(event) {
  // If you had a license server, you would make an asynchronous XMLHttpRequest
  // with event.message as the body.  The response from the server, as a
  // Uint8Array, would then be passed to session.update().
  // Instead, we will generate the license synchronously on the client, using
  // the hard-coded KEY at the top.
  var license = generateLicense(event.message);

  var session = event.target;
  session.update(license).catch(
    function(error) {
      console.error('Failed to update the session', error);
    }
  );
}

// Convert Uint8Array into base64 using base64url alphabet, without padding.
function toBase64(u8arr) {
  return btoa(String.fromCharCode.apply(null, u8arr)).
      replace(/\+/g, '-').replace(/\//g, '_').replace(/=*$/, '');
}

// This takes the place of a license server.
// kids is an array of base64-encoded key IDs
// keys is an array of base64-encoded keys
function generateLicense(message) {
  // Parse the clearkey license request.
  var request = JSON.parse(new TextDecoder().decode(message));
  // We only know one key, so there should only be one key ID.
  // A real license server could easily serve multiple keys.
  console.assert(request.kids.length === 1);

  var keyObj = {
    kty: 'oct',
    alg: 'A128KW',
    kid: request.kids[0],
    k: toBase64(KEY)
  };
  return new TextEncoder().encode(JSON.stringify({
    keys: [keyObj]
  }));
}

כדי לבדוק את הקוד הזה, צריך להפעיל סרטון מוצפן. ניתן לבצע הצפנת סרטון לשימוש באמצעות מפתח ניקוי עבור WebM בהתאם להוראות Webm_crypt. יש גם שירותים מסחריים (לגבי ISO BMFF/MP4 לפחות) ואנחנו מפתחים פתרונות נוספים.

תוספים של מקור מדיה (MSE)

ה-HTMLMediaElement הוא יצור בעל יופי פשוט.

אנחנו יכולים לטעון, לפענח ולהפעיל מדיה פשוט על ידי אספקת כתובת URL מסוג src:

<video src='foo.webm'></video>

ה-Media Source API הוא תוספת ל-HTMLMediaElement המאפשר שליטה פרטנית יותר במקור המדיה, על ידי מתן אפשרות ל-JavaScript לבנות זרמים להפעלה מ 'קטעי וידאו'. לאחר מכן התכונות האלה מאפשרות טכניקות כמו סטרימינג דינמי והזזת מועד הצפייה.

למה MSE חשוב ל-EME? מכיוון שבנוסף להפצת תוכן מוגן, ספקי תוכן מסחרי חייבים להיות מסוגלים להתאים את העברת התוכן לתנאי הרשת ולדרישות אחרות. לדוגמה, ב-Netflix, קצב העברת הנתונים של הסטרימינג משתנה באופן דינמי בהתאם לשינויים בתנאי הרשת. EME פועל עם הפעלה של שידורי מדיה שמסופקים על ידי הטמעת MSE, בדיוק כמו עם מדיה שמסופקת באמצעות מאפיין src.

כיצד לפצל ולהפעיל מדיה שמקודדת בקצבי העברת נתונים שונים? מידע נוסף מפורט בקטע DASH שבהמשך.

אפשר לראות את ה-MSE בפעולה ב-simpl.info/mse. לצורך הדוגמה הזו, סרטון WebM מחולק לחמישה קטעים באמצעות ממשקי ה-API של הקבצים. באפליקציה בסביבת ייצור, מקטעי סרטון יאוחזרו באמצעות Ajax.

תחילה נוצר SourceBuffer:

var sourceBuffer = mediaSource.addSourceBuffer('video/webm; codecs="vorbis,vp8"');

לאחר מכן, הסרט כולו 'משודר' לרכיב וידאו. כדי לעשות זאת, מוסיפים כל מקטע באמצעות שיטת appendBuffer() :

reader.onload = function (e) {
  sourceBuffer.appendBuffer(new Uint8Array(e.target.result));
  if (i === NUM_CHUNKS - 1) {
    mediaSource.endOfStream();
  } else {
    if (video.paused) {
      // start playing after first chunk is appended
      video.play();
    }
    readChunk_(++i);
  }
};

למד עוד על MSE במאמר על HTML5 Rocks.

סטרימינג דינמי דינמי ב-HTTP (DASH)

מרובה מכשירים, פלטפורמות מרובות וניידים - לא משנה מה תקראו, האינטרנט חווה לעיתים קרובות בתנאים של קישוריות שניתנת לשינוי. מסירה דינמית ודינמית חיונית להתמודדות עם מגבלות רוחב פס ועם גיוון בעולם מרובה מכשירים.

DASH (המכונה גם MPEG-DASH) מיועד לאפשר את העברת המדיה הטובה ביותר בעולם רעוע, הן לסטרימינג והן להורדה. כמה טכנולוגיות אחרות עושות משהו דומה – כמו HTTP Live Streaming (HLS) של Apple ו-Smooth Streaming של Microsoft – אבל DASH היא השיטה היחידה לסטרימינג בקצב העברת נתונים דינמי דרך HTTP, המבוססת על תקן פתוח. DASH כבר נמצא בשימוש באתרים כגון YouTube.

מה זה קשור ל-EME ול-MSE? הטמעות DASH המבוססות על MSE יכולות לנתח מניפסט, להוריד קטעי וידאו בקצב העברת נתונים מתאים ולהזין אותם לרכיב וידאו כשזה נעשה רעב – באמצעות תשתית ה-HTTP הקיימת.

במילים אחרות, DASH מאפשר לספקי תוכן מסחרי לבצע סטרימינג דינמי של תוכן מוגן.

DASH עושה את מה שכתוב על הפח:

  • דינמית: בתגובה לתנאים משתנים.
  • ניתן להתאמה: התאמות שמותאמות לספק קצב העברת נתונים מתאים של אודיו או וידאו.
  • סטרימינג: מאפשר סטרימינג וגם הורדות.
  • HTTP: מאפשר העברת תוכן עם היתרון של HTTP, ללא החסרונות של שרת סטרימינג מסורתי.

הערוץ BBC התחיל לספק שידורי בדיקה באמצעות DASH:

לסיכום:

  1. המדיה מקודדת בקצבי העברת נתונים שונים.
  2. הקבצים השונים בקצב העברת הנתונים זמינים משרת HTTP.
  3. אפליקציית אינטרנט של לקוח בוחרת את קצב העברת הנתונים לאחזור ולהפעיל אותו באמצעות DASH.

כחלק מתהליך פילוח הסרטונים, מניפסט XML שנקרא תיאור מצגת מדיה (MPD) נבנה באופן פרוגרמטי. התיאור הזה מתאר ייצוגים וקבוצות התאמה, עם משכי זמן וכתובות URL. MPD נראה כך:

<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011" mediaPresentationDuration="PT0H3M1.63S" minBufferTime="PT1.5S" profiles="urn:mpeg:dash:profile:isoff-on-demand:2011"
type="static">
  <Period duration="PT0H3M1.63S" start="PT0S">
    <AdaptationSet>
      <ContentComponent contentType="video" id="1" />
      <Representation bandwidth="4190760" codecs="avc1.640028" height="1080" id="1" mimeType="video/mp4" width="1920">
        <BaseURL>car-20120827-89.mp4</BaseURL>
        <SegmentBase indexRange="674-1149">
          <Initialization range="0-673" />
        </SegmentBase>
      </Representation>
      <Representation bandwidth="2073921" codecs="avc1.4d401f" height="720" id="2" mimeType="video/mp4" width="1280">
        <BaseURL>car-20120827-88.mp4</BaseURL>
        <SegmentBase indexRange="708-1183">
          <Initialization range="0-707" />
        </SegmentBase>
      </Representation>

      …

    </AdaptationSet>
  </Period>
</MPD>

(XML זה לקוח מקובץ .mpd המשמש עבור נגן ההדגמה של YouTube DASH)

לפי מפרט DASH, קובץ MPD יכול לשמש באופן תיאורטי כ-src של סרטון. עם זאת, כדי לספק למפתחי אתרים גמישות רבה יותר, ספקי דפדפנים בחרו להשאיר תמיכה ב-DASH בספריות JavaScript באמצעות MSE כמו dash.js. היישום של DASH ב-JavaScript מאפשר לאלגוריתם ההתאמה להתפתח ללא צורך בעדכונים של הדפדפן. השימוש ב-MSE מאפשר גם ניסויים עם פורמטים חלופיים של מניפסטים ומנגנונים אחרים להצגת מודעות, ללא צורך בביצוע שינויים בדפדפן. Shaka Player של Google מטמיע לקוח DASH עם תמיכה ב-EME.

Mozilla Developer Network מציעה הוראות לשימוש בכלים של WebM וב-FFmpeg כדי לפלח סרטונים וליצור MPD.

סיכום

השימוש באינטרנט כדי להציג וידאו ואודיו בתשלום גדל בקצב עצום. נראה שכל מכשיר חדש, בין אם מדובר בטאבלט, בקונסולת משחקים, בטלוויזיה מחוברת או בממיר, יכול לבצע סטרימינג של מדיה מספקי התוכן הגדולים באמצעות HTTP. יותר מ-85% מהדפדפנים בנייד ובמחשבים תומכים עכשיו ב-<video> וב-<audio>, ולפי ההערכה של Cisco, הסרטונים יהיו 80 עד 90 אחוזים מהתנועה הגלובלית של צרכנים באינטרנט עד 2017. בהקשר הזה, התמיכה בדפדפן להפצת תוכן מוגן צפויה להיות משמעותית יותר ויותר, מכיוון שספקי דפדפנים מצמצמים את התמיכה בממשקי API שעליהם מסתמכים רוב יישומי הפלאגין של המדיה.

קריאה נוספת

מפרטים וסטנדרטים

מאמרים