EME, UAF?

Introdução às extensões de mídia criptografada

O Encrypted Media Extensions fornece uma API que permite que aplicativos da Web interajam com sistemas de proteção de conteúdo para permitir a reprodução de áudio e vídeo criptografados.

O EME foi projetado para permitir que o mesmo app e os arquivos criptografados sejam usados em qualquer navegador, independentemente do sistema de proteção subjacente. O primeiro é possibilitado pelos fluxos e APIs padronizados, enquanto o segundo é possibilitado pelo conceito de criptografia comum.

O EME é uma extensão da especificação HTMLMediaElement, daí o nome. Ser uma "extensão" significa que o suporte do navegador para EME é opcional: se um navegador não oferece suporte a mídia criptografada, ele não pode reproduzir mídia criptografada, mas o EME não é necessário para conformidade com as especificações HTML. A partir das especificações da EME:

As implementações do EME usam os seguintes componentes externos:

  • Sistema de chaves:um mecanismo de proteção de conteúdo (DRM). O EME não define Key Systems em si, além da Clear Key (mais sobre isso abaixo).
  • Módulo de descriptografia de conteúdo (CDM): um software ou mecanismo de hardware do lado do cliente que permite a reprodução de mídia criptografada. Assim como no Key Systems, o EME não define CDMs, mas fornece uma interface para que os aplicativos interajam com os CDMs disponíveis.
  • Servidor de licença (chave):interage com um CDM para fornecer chaves para descriptografar mídia. A negociação com o servidor de licenças é de responsabilidade do aplicativo.
  • Serviço de empacotamento:codifica e criptografa a mídia para distribuição/consumo.

Observe que um aplicativo que usa o EME interage com um servidor de licença para obter chaves e ativar a descriptografia, mas a identidade e a autenticação do usuário não fazem parte do EME. A recuperação de chaves para ativar a reprodução de mídia acontece depois (opcionalmente) da autenticação de um usuário. Serviços como o Netflix devem autenticar os usuários dentro de seu aplicativo da web: quando um usuário faz login no aplicativo, o aplicativo determina a identidade e os privilégios do usuário.

Como o EME funciona?

Veja como os componentes do EME interagem, correspondendo ao exemplo de código abaixo:

  1. Um aplicativo da Web tenta reproduzir áudio ou vídeo que tenha um ou mais streams criptografados.
  2. O navegador reconhece que a mídia está criptografada (consulte a caixa abaixo para saber o que acontece) e dispara um evento encrypted com metadados (initData) recebidos da mídia sobre a criptografia.
  3. O aplicativo processa o evento encrypted:
    1. Se nenhum objeto MediaKeys tiver sido associado ao elemento de mídia, primeiro selecione um sistema de chaves disponível usando navigator.requestMediaKeySystemAccess() para conferir quais sistemas de chaves estão disponíveis. Em seguida, crie um objeto MediaKeys para um sistema de chaves disponível usando um objeto MediaKeySystemAccess. A inicialização do objeto MediaKeys precisa acontecer antes do primeiro evento encrypted. A obtenção de um URL do servidor de licença é feita pelo aplicativo independentemente da seleção de um sistema de chaves disponível. Um objeto MediaKeys representa todas as chaves disponíveis para descriptografar a mídia de um elemento de áudio ou vídeo. Ele representa uma instância do CDM e fornece acesso ao CDM, especificamente para criar sessões importantes, que são usadas para receber chaves de um servidor de licenças.
    2. Depois que o objeto MediaKeys for criado, atribua-o ao elemento de mídia: setMediaKeys() associa o objeto MediaKeys a um HTMLMediaElement para que as chaves dele possam ser usadas durante a reprodução, ou seja, durante a decodificação.
  4. O app cria um MediaKeySession chamando createSession() na MediaKeys. Isso cria uma MediaKeySession, que representa o ciclo de vida de uma licença e as chaves dela.
  5. O app gera uma solicitação de licença transmitindo os dados de mídia recebidos no gerenciador encrypted para o CDM chamando generateRequest() no MediaKeySession.
  6. O CDM dispara um evento message: uma solicitação para adquirir uma chave de um servidor de licença.
  7. O objeto MediaKeySession recebe o evento message, e o aplicativo envia uma mensagem ao servidor de licença (via XHR, por exemplo).
  8. O aplicativo recebe uma resposta do servidor de licença e transmite os dados ao CDM usando o método update() da MediaKeySession.
  9. O CDM descriptografa a mídia usando as chaves da licença. Uma chave válida pode ser usada em qualquer sessão nos MediaKeys associados ao elemento de mídia. O CDM vai acessar a chave e a política indexadas pelo ID da chave.
  10. A reprodução de mídia é retomada.

Ufa...

É possível que haja várias mensagens entre o CDM e o servidor de licença, e toda a comunicação nesse processo é opaca para o navegador e o aplicativo. As mensagens são compreendidas somente pelo CDM e pelo servidor de licença, embora a camada do app possa ver que tipo de mensagem o CDM está enviando. A solicitação de licença contém prova da validade e da relação de confiança do CDM, além de uma chave a ser usada ao criptografar as chaves de conteúdo na licença resultante.

... mas o que os CDMs realmente fazem?

Uma implementação de EME não oferece, por si só, uma maneira de descriptografar mídia, ela simplesmente fornece uma API para que um aplicativo da Web interaja com os módulos de descriptografia de conteúdo.

O que os CDMs realmente fazem não é definido pela especificação da EME, e um CDM pode lidar com a decodificação (descompressão) de mídia, bem como a descriptografia. Do menos ao mais robusto, existem várias opções potenciais para a funcionalidade do CDM:

  • Somente descriptografia, permitindo a reprodução usando o pipeline de mídia normal, por exemplo, com um elemento <video>.
  • Descriptografia e decodificação, passando quadros de vídeo ao navegador para renderização.
  • Descriptografia e decodificação, renderizando diretamente no hardware (por exemplo, a GPU).

Há várias maneiras de disponibilizar um CDM para um app da Web:

  • Agrupar um CDM com o navegador.
  • Distribua um CDM separadamente.
  • Crie um CDM no sistema operacional.
  • Inclua um CDM no firmware.
  • Incorporar um CDM ao hardware.

A especificação da EME não define como o CDM é disponibilizado. No entanto, em todos os casos, o navegador é responsável por examinar e expor o CDM.

O EME não exige um sistema de chaves específico. Entre os navegadores atuais para computadores e dispositivos móveis, o Chrome é compatível com o Widevine e o IE11 é compatível com o PlayReady.

Como obter uma chave de um servidor de licença

Em uso comercial típico, o conteúdo é criptografado e codificado usando um serviço ou ferramenta de empacotamento. Depois que a mídia criptografada é disponibilizada on-line, um cliente da Web pode receber uma chave (contida em uma licença) de um servidor de licenças e usá-la para ativar a descriptografia e a reprodução do conteúdo.

O código a seguir (adaptado dos exemplos de especificação) mostra como um aplicativo pode selecionar um sistema de chaves adequado e receber uma chave de um servidor de licença.

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')
  );
}

Criptografia comum

Com as soluções de criptografia comuns, os provedores de conteúdo podem criptografar e empacotar o conteúdo uma vez por contêiner/codec e usá-lo com vários sistemas de chaves, CDMs e clientes, ou seja, qualquer CDM compatível com criptografia comum. Por exemplo, um vídeo empacotado com Playready pode ser reproduzido em um navegador usando um CDM Widevine obtendo uma chave de um servidor de licenças Widevine.

Isso é diferente em soluções legadas que só funcionariam com uma pilha vertical completa, incluindo um único cliente que geralmente também incluía um ambiente de execução de aplicativo.

A Criptografia comum (CENC) é um padrão ISO que define um esquema de proteção para o ISO BMFF. Um conceito semelhante se aplica ao WebM.

Limpar chave

Embora o EME não defina a funcionalidade DRM, a especificação atualmente determina que todos os navegadores compatíveis com o EME devem implementar a tecla Clear Key. Com o uso desse sistema, a mídia pode ser criptografada com uma chave e reproduzida com o simples fornecimento dela. A Clear Key pode ser integrada ao navegador: ela não exige o uso de um módulo de descriptografia separado.

Embora não seja provável que seja usada para muitos tipos de conteúdo comercial, a Clear Key é totalmente interoperável em todos os navegadores compatíveis com o EME. Ele também é útil para testar implementações do EME e aplicativos que usam o EME, sem a necessidade de solicitar uma chave de conteúdo de um servidor de licença. Há um exemplo simples de "Clear Key" em simpl.info/ck. Confira abaixo um tutorial do código, que é paralelo às etapas descritas acima, mas sem interação com o servidor de licença.

// 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]
  }));
}

Para testar este código, você precisa de um vídeo criptografado para reproduzir. É possível criptografar um vídeo para uso com Clear Key no WebM de acordo com as instruções webm_crypt. Serviços comerciais também estão disponíveis (pelo menos para ISO BMFF/MP4) e outras soluções estão sendo desenvolvidas.

Extensões de origem de mídia (EQM)

O HTMLMediaElement é uma criatura de beleza simples.

Para carregar, decodificar e reproduzir mídia, basta fornecer um URL src:

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

A API Media Source é uma extensão do HTMLMediaElement que permite um controle mais refinado sobre a fonte de mídia, permitindo que o JavaScript crie fluxos para reprodução de "blocos" de vídeo. Isso, por sua vez, permite técnicas como streaming adaptável e mudança de tempo.

Por que o MSE é importante para o EME? Porque, além de distribuir conteúdo protegido, os provedores de conteúdo comercial precisam adaptar o envio de conteúdo às condições da rede e a outros requisitos. A Netflix, por exemplo, muda dinamicamente a taxa de bits do stream conforme as condições da rede mudam. O EME funciona com a reprodução de streams de mídia fornecidos por uma implementação de MSE, assim como com mídia fornecida por um atributo src.

Como dividir em partes e reproduzir mídia codificada em diferentes taxas de bits? Consulte a seção DASH abaixo.

Veja o MSE em ação em simpl.info/mse. Para os fins deste exemplo, um vídeo WebM é dividido em cinco partes usando as APIs File. Em um aplicativo de produção, os blocos de vídeo são recuperados via Ajax.

Primeiro, um SourceBuffer é criado:

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

O filme inteiro é então "transmitido" a um elemento de vídeo, anexando cada fragmento usando o método anexarBuffer():

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);
  }
};

Saiba mais sobre o MSE no artigo HTML5 Rocks.

Streaming adaptável dinâmico sobre HTTP (DASH)

Vários dispositivos, plataformas e dispositivos móveis. Seja qual for o nome, a Web muitas vezes é experimentada sob condições de conectividade variável. A entrega dinâmica e adaptável é essencial para lidar com as restrições e a variabilidade de largura de banda no mundo com vários dispositivos.

O DASH (também conhecido como MPEG-DASH) foi desenvolvido para permitir a melhor entrega de mídia possível em um mundo instável, tanto para streaming quanto para download. Várias outras tecnologias fazem algo semelhante, como o HTTP Live Streaming (HLS) da Apple e o Smooth Streaming da Microsoft, mas o DASH é o único método de streaming de taxa de bits adaptável via HTTP baseado em um padrão aberto. O DASH já está em uso por sites como o YouTube.

Qual é a relação com o EME e o MSE? As implementações DASH com base no MSE podem analisar um manifesto, fazer o download de segmentos de vídeo com uma taxa de bits adequada e alimentá-los a um elemento de vídeo quando ele ficar com fome, usando a infraestrutura HTTP existente.

Em outras palavras, o DASH permite que os provedores de conteúdo comercial façam streaming adaptável de conteúdo protegido.

O DASH faz o que diz:

  • Dinâmico:responde a mudanças de condições.
  • Adaptável:adapta-se para fornecer uma taxa de bits de áudio ou vídeo adequada.
  • Streaming:permite streaming e download.
  • HTTP: permite o envio de conteúdo usando o HTTP, sem as desvantagens de um servidor de streaming tradicional.

A BBC começou a disponibilizar transmissões de teste usando o DASH:

Para resumir:

  1. A mídia é codificada em diferentes taxas de bits.
  2. Os diferentes arquivos de taxa de bits são disponibilizados a partir de um servidor HTTP.
  3. Um app da Web cliente escolhe qual taxa de bits recuperar e reproduzir com DASH.

Como parte do processo de segmentação de vídeo, um manifesto XML conhecido como Descrição de apresentação de mídia (MPD, na sigla em inglês) é criado de forma programática. Isso descreve conjuntos de adaptação e representações, com durações e URLs. Uma MPD tem a seguinte aparência:

<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>

Esse XML é retirado do arquivo .mpd usado no player de demonstração do DASH do YouTube.

De acordo com as especificações do DASH, um arquivo MPD pode, em teoria, ser usado como o src de um vídeo. No entanto, para dar mais flexibilidade aos desenvolvedores da Web, os fornecedores de navegadores optaram por deixar a compatibilidade com DASH para bibliotecas JavaScript usando MSE, como dash.js. A implementação do DASH em JavaScript permite que o algoritmo de adaptação evolua sem precisar de atualizações do navegador. O uso do MSE também permite testar formatos alternativos de manifesto e mecanismos de entrega sem exigir mudanças no navegador. O Shaka Player do Google implementa um cliente DASH com suporte para EME.

A Mozilla Developer Network tem instruções (em inglês) sobre como usar as ferramentas WebM e o FFmpeg para segmentar vídeos e criar uma MPD.

Conclusão

O uso da Web para oferecer vídeos e áudios pagos está crescendo a uma enorme taxa. Parece que todos os dispositivos novos, sejam tablets, consoles de jogos, smart TVs ou conversores, são capazes de fazer streaming de mídia dos principais provedores de conteúdo por HTTP. Mais de 85% dos navegadores para computadores e dispositivos móveis agora são compatíveis com <video> e <audio>, e a Cisco estima que o vídeo será de 80 a 90% do tráfego global da Internet até 2017. Nesse contexto, o suporte dos navegadores à distribuição de conteúdo protegido provavelmente será cada vez mais significativo, já que os fornecedores de navegadores oferecem suporte a APIs usadas pela maioria dos plug-ins de mídia.

Sugestões de leitura

Especificações e padrões

Artigos