Introdução ao HTTP Live Streaming: HLS no Android e mais

Publicados: 2022-03-11

O streaming de vídeo é parte integrante da experiência moderna da Internet. Está em todos os lugares: em telefones celulares, computadores de mesa, TVs e até mesmo wearables. Ele precisa funcionar perfeitamente em todos os dispositivos e tipos de rede, seja em conexões móveis lentas, WiFi, atrás de firewalls, etc. O HTTP Live Streaming (HLS) da Apple foi criado exatamente com esses desafios em mente.

Quase todos os dispositivos modernos vêm dotados de hardware moderno que é rápido o suficiente para reproduzir vídeo, de modo que a velocidade e a confiabilidade da rede surgem como o maior problema. Por que é que? Até alguns anos atrás, a maneira canônica de armazenar e publicar vídeo eram protocolos baseados em UDP, como RTP. Isso se mostrou problemático de várias maneiras, para listar apenas alguns:

  1. Você precisa de um serviço de servidor (daemon) para transmitir conteúdo.
  2. A maioria dos firewalls é configurada para permitir apenas portas padrão e tipos de tráfego de rede, como http, email, etc.
  3. Se seu público for global, você precisará de uma cópia do serviço de streaming daemon em execução em todas as principais regiões.

Claro, você pode pensar que todos esses problemas são fáceis de resolver. Basta armazenar arquivos de vídeo (por exemplo, arquivos mp4) em seu servidor http e usar seu serviço CDN favorito para servi-los em qualquer lugar do mundo.

Onde o streaming de vídeo legado fica aquém

Esta está longe de ser a melhor solução por algumas razões, sendo a eficiência uma delas. Se você armazenar arquivos de vídeo originais em resolução máxima, os usuários em áreas rurais ou partes do mundo com pouca conectividade terão dificuldade em aproveitá-los. Seus players de vídeo terão dificuldade para baixar dados suficientes para reproduzi-los em tempo de execução.

Portanto, você precisa de uma versão especial do arquivo para que a quantidade de vídeo baixada seja aproximadamente a mesma que pode ser reproduzida. Por exemplo, se a resolução e a qualidade do vídeo forem tais que em cinco segundos ele possa baixar outros cinco segundos de vídeo, isso é ótimo. No entanto, se levar cinco segundos para fazer o download de apenas três segundos de vídeo, o player irá parar e aguardar o download da próxima parte do fluxo.

Por outro lado, reduzir ainda mais a qualidade e a resolução só degradaria a experiência do usuário em conexões mais rápidas, pois você economizaria largura de banda desnecessariamente. No entanto, existe uma terceira via.

Streaming de taxa de bits adaptável

Embora você possa fazer upload de diferentes versões de vídeo para diferentes usuários, você precisa ter a capacidade de controlar seus players e calcular qual é o melhor fluxo para sua conexão e dispositivo. Em seguida, o jogador precisa alternar entre eles (por exemplo, quando um usuário muda de 3G para WiFi). E mesmo assim, e se o cliente alterar o tipo de rede? Em seguida, o player deve mudar para um vídeo diferente, mas deve começar a ser reproduzido não desde o início, mas em algum lugar no meio do vídeo. Então, como você calcula o intervalo de bytes a ser solicitado?

Uma coisa legal seria se os players de vídeo pudessem detectar mudanças no tipo de rede e largura de banda disponível, e então alternar de forma transparente entre diferentes fluxos (do mesmo vídeo preparado para diferentes velocidades) até encontrar o melhor.

É exatamente isso que o streaming de taxa de bits adaptável resolve.

Nota: Este tutorial HLS não abrange criptografia, reproduções sincronizadas e IMSC1.

O que é HLS?

HTTP Live Streaming é um protocolo de streaming de taxa de bits adaptável introduzido pela Apple em 2009. Ele usa arquivos m3u8 para descrever fluxos de mídia e usa HTTP para a comunicação entre o servidor e o cliente. É o protocolo de streaming de mídia padrão para todos os dispositivos iOS, mas pode ser usado em navegadores Android e web.

Ilustração da capa do HTTP Live Streaming

Os blocos de construção básicos de um fluxo HLS são:

  1. listas de reprodução M3U8
  2. Arquivos de mídia para vários fluxos

Listas de reprodução M3U8

Vamos começar respondendo a uma pergunta básica: O que são arquivos M3U8 ?

M3U (ou M3U8) é um formato de arquivo de texto simples originalmente criado para organizar coleções de arquivos MP3. O formato é estendido para HLS, onde é usado para definir fluxos de mídia. No HLS existem dois tipos de arquivos m3u8:

  • Lista de reprodução de mídia: contendo URLs dos arquivos necessários para streaming (ou seja, pedaços do vídeo original a serem reproduzidos).
  • Lista de reprodução principal: contém URLs para listas de reprodução de mídia que, por sua vez, contêm variantes do mesmo vídeo preparadas para diferentes larguras de banda.

A chamada URL de transmissão ao vivo M3U8 nada mais é do que URLs para arquivos M3U8, como: https://s3-us-west-2.amazonaws.com/hls-playground/hls.m3u8.

Exemplo de arquivo M3U8 para fluxo HLS

Um arquivo M3U8 contém uma lista de URLs ou caminhos de arquivos locais com alguns metadados adicionais. As linhas de metadados começam com #.

Este exemplo ilustra a aparência de um arquivo M3U8 para um fluxo HLS simples:

 #EXTM3U #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-ALLOW-CACHE:YES #EXT-X-TARGETDURATION:11 #EXTINF:5.215111, 00000.ts #EXTINF:10.344822, 00001.ts #EXTINF:10.344822, 00002.ts #EXTINF:9.310344, 00003.ts #EXTINF:10.344822, 00004.ts ... #EXT-X-ENDLIST
  • As primeiras quatro linhas são metadados globais (cabeçalho) para esta lista de reprodução M3U8.
  • O EXT-X-VERSION é a versão do formato M3U8 (deve ser pelo menos 3 se quisermos usar entradas EXTINF ).
  • A tag EXT-X-TARGETDURATION contém a duração máxima de cada “pedaço” de vídeo. Normalmente, esse valor é em torno de 10s.
  • O restante do documento contém pares de linhas, como:
 #EXTINF:10.344822, 00001.ts

Este é um "pedaço" de vídeo. Este representa o pedaço 00001.ts que tem exatamente 10,344822 segundos. Quando um player de vídeo cliente precisa iniciar um vídeo a partir de um determinado ponto do vídeo, ele pode calcular facilmente qual arquivo .ts ele precisa solicitar somando as durações dos blocos visualizados anteriormente. A segunda linha pode ser um nome de arquivo local ou uma URL para esse arquivo.

O arquivo M3U8 com seus arquivos .ts representa a forma mais simples de um fluxo HLS – uma lista de reprodução de mídia.

Lembre-se de que nem todos os navegadores podem reproduzir fluxos HLS por padrão.

Lista de reprodução principal ou arquivo de índice M3U8

O exemplo M3U8 anterior aponta para uma série de pedaços .ts . Eles são criados a partir do arquivo de vídeo original, que é redimensionado codificado e dividido em pedaços.

Isso significa que ainda temos o problema descrito na introdução – e os clientes em redes muito lentas (ou excepcionalmente rápidas)? Ou clientes em redes rápidas com tamanhos de tela muito pequenos? Não faz sentido transmitir um arquivo em resolução máxima se ele não puder ser exibido em toda a sua glória em seu telefone novo e brilhante.

M3U8 em HSL

O HLS resolve esse problema introduzindo outra “camada” de M3U8. Este arquivo M3U8 não conterá ponteiros para arquivos .ts , mas terá ponteiros para outros arquivos M3U8 que, por sua vez, contêm arquivos de vídeo preparados com antecedência para taxas de bits e resoluções específicas.

Aqui está um exemplo de um arquivo M3U8:

 #EXTM3U #EXT-X-STREAM-INF:BANDWIDTH=1296,RESOLUTION=640x360 https://.../640x360_1200.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=264,RESOLUTION=416x234 https://.../416x234_200.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=464,RESOLUTION=480x270 https://.../480x270_400.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=1628,RESOLUTION=960x540 https://.../960x540_1500.m3u8 #EXT-X-STREAM-INF:BANDWIDTH=2628,RESOLUTION=1280x720 https://.../1280x720_2500.m3u8

O player de vídeo escolherá pares de linhas, como:

 #EXT-X-STREAM-INF:BANDWIDTH=1296,RESOLUTION=640x360 https://.../640x360_1200.m3u8

Essas são chamadas de variantes do mesmo vídeo preparadas para diferentes velocidades de rede e resoluções de tela. Este arquivo M3U8 específico ( 640x360_1200.m3u8 ) contém os pedaços de arquivo de vídeo do vídeo redimensionados para 640x360 pixels e preparados para taxas de bits de 1296kbps . Observe que a taxa de bits relatada deve levar em consideração os fluxos de vídeo e áudio no vídeo.

O player de vídeo geralmente começará a ser reproduzido a partir da primeira variante de fluxo (no exemplo anterior, isso é 640x360_1200.m3u8). Por esse motivo, você deve ter um cuidado especial para decidir qual variante será a primeira da lista. A ordem das outras variantes não é importante.

Se o primeiro arquivo .ts demorar muito para ser baixado (causando “buffering”, ou seja, aguardando o próximo pedaço), o player de vídeo mudará para um fluxo com uma taxa de bits menor. E, claro, se for carregado rápido o suficiente, significa que pode mudar para uma variante de melhor qualidade, mas apenas se fizer sentido para a resolução da tela.

Se o primeiro fluxo na lista do índice M3U8 não for o melhor, o cliente precisará de um ou dois ciclos até que se acomode com a variante correta.

Então, agora temos três camadas de HLS:

  1. Arquivo de índice M3U8 (a lista de reprodução principal) contendo ponteiros (URLs) para variantes.
  2. Arquivos M3U8 variantes (a lista de reprodução de mídia) para diferentes fluxos para diferentes tamanhos de tela e velocidades de rede. Eles contêm ponteiros (URLs) para arquivos .ts.
  3. Arquivos .ts (pedaços) que são arquivos binários com partes do vídeo.

Você pode assistir a um arquivo de índice M3U8 de exemplo aqui (novamente, depende do seu navegador/SO).

Às vezes, você sabe de antemão que o cliente está em uma rede lenta ou rápida. Nesse caso, você pode ajudar o cliente a escolher a variante certa fornecendo um arquivo de índice M3U8 com uma primeira variante diferente. Há duas maneiras de fazer isso.

  • A primeira é ter vários arquivos de índice preparados para diferentes tipos de rede e preparar o cliente com antecedência para solicitar o correto. O cliente terá que verificar o tipo de rede e solicitar, por exemplo, http://.../index_wifi.m3u8 ou http://.../index_mobile.m3u8 .
  • Você também pode garantir que o cliente envie o tipo de rede como parte da solicitação http (por exemplo, se estiver conectado a um wifi ou móvel 2G/3G/…) e, em seguida, ter o arquivo de índice M3U8 preparado dinamicamente para cada solicitação. Apenas o arquivo de índice M3U8 precisa de uma versão dinâmica, os fluxos únicos (arquivos M3U8 variantes) ainda podem ser armazenados como arquivos estáticos.

Preparando arquivos de vídeo para HLS

Existem dois blocos de construção importantes do HTTP Live Streaming da Apple. Uma é a maneira como os arquivos de vídeo são armazenados (para serem servidos via HTTP posteriormente) e a outra são os arquivos de índice M3U8 que informam ao player (o aplicativo cliente de streaming) onde obter qual arquivo de vídeo.

Vamos começar com arquivos de vídeo. O protocolo HLS espera que os arquivos de vídeo sejam armazenados em partes menores de igual duração, normalmente 10 segundos cada. Originalmente, esses arquivos tinham que ser armazenados em arquivos MPEG-2 TS ( .ts ) e codificados com o formato H.264 com áudio em MP3, HE-AAC ou AC-3.

Vídeo HLS

Isso significa que um vídeo de 30 segundos será dividido em 3 arquivos .ts menores, cada um com aproximadamente 10s de duração.

Observe que a versão mais recente do HLS também permite arquivos .mp4 fragmentados. Como isso ainda é novo e alguns players de vídeo ainda precisam implementá-lo, os exemplos deste artigo usarão arquivos .ts .

Quadros-chave

Os pedaços devem ser codificados com um quadro-chave no início de cada arquivo. Cada vídeo contém frames. Quadros são imagens, mas os formatos de vídeo não armazenam imagens completas, o que consumiria muito espaço em disco. Eles codificam apenas a diferença do quadro anterior. Quando você pula para um ponto médio no vídeo, o player precisa de um “ponto inicial” de onde aplicar todos esses diffs para mostrar a imagem inicial e, em seguida, começar a reproduzir o vídeo.

É por isso que os blocos .ts devem ter um quadro-chave no início. Às vezes, os jogadores precisam começar no meio do pedaço. O jogador sempre pode calcular a imagem atual adicionando todos os “diffs” do primeiro quadro-chave. Mas, se começar 9 segundos desde o início, ele precisa calcular 9 segundos de “diffs”. Para tornar essa computação mais rápida, é melhor criar quadros-chave a cada poucos segundos (melhor cca 3s).

Pontos de interrupção HLS

Há situações em que você deseja que vários videoclipes sejam reproduzidos em sucessão. Uma maneira de fazer isso é mesclar os arquivos de vídeo originais e, em seguida, criar os fluxos HLS com esse arquivo, mas isso é problemático por vários motivos. E se você quiser exibir um anúncio antes ou depois do vídeo? Talvez você não queira fazer isso para todos os usuários e provavelmente queira anúncios diferentes para usuários diferentes. E, claro, você não quer preparar arquivos HLS com anúncios diferentes com antecedência.

Para corrigir esse problema, existe uma tag #EXT-X-DISCONTINUITY que pode ser usada na lista de reprodução m3u8. Esta linha basicamente diz ao player de vídeo para se preparar antecipadamente para o fato de que, a partir deste ponto, os arquivos .ts podem ser criados com uma configuração diferente (por exemplo, a resolução pode mudar). O jogador precisará recalcular tudo e possivelmente mudar para outra variante e precisa estar preparado para tais pontos de “descontinuidade”.

Transmissão ao vivo com HLS

Existem basicamente dois tipos de “streaming de vídeo”. Um deles é o Video On Demand ( VOD ) para vídeos gravados com antecedência e transmitidos para o usuário quando ele decidir. E há transmissão ao vivo . Embora HLS seja uma abreviação de HTTP Live Streaming, tudo explicado até agora foi centrado em VOD, mas também existe uma maneira de fazer streaming ao vivo com HLS.

Existem algumas alterações em seus arquivos M3U8. Primeiro, deve haver uma tag #EXT-X-MEDIA-SEQUENCE:1 no arquivo M3U8 variante. Então, o arquivo M3U8 não deve terminar com #EXT-X-ENDLIST (que de outra forma sempre deve ser colocado no final).

Enquanto você grava seu stream, você terá constantemente novos arquivos .ts . Você precisa anexá-los na lista de reprodução M3U8 e cada vez que adicionar um novo, o contador no #EXT-X-MEDIA-SEQUENCE:<counter> deve ser aumentado em 1.

O player de vídeo verificará o contador. Se alterado desde a última vez, ele sabe se há novos pedaços para serem baixados e reproduzidos. Certifique-se de que o arquivo M3U8 seja servido com os cabeçalhos sem cache, porque os clientes continuarão recarregando os arquivos M3U8 esperando que novos pedaços sejam reproduzidos.

VTT

Outro recurso interessante para fluxos HLS é que você pode incorporar arquivos Web Video Text Track (VTT) neles. Os arquivos VTT podem ser usados ​​para vários usos. Por exemplo, para um player HLS da Web, você pode especificar instantâneos de imagens para várias partes do vídeo. Quando o usuário move o mouse sobre a área do timer de vídeo (abaixo do player de vídeo), o player pode mostrar instantâneos dessa posição no vídeo.

Outro uso óbvio para arquivos VTT são as legendas. O fluxo HLS pode especificar várias legendas para vários idiomas:

 #EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-,NAME="English",DEFAULT=YES,AUTOSELECT=YES,FORCED=NO,LANGUAGE="en",CHARACTERISTICS="public.accessibility.transcribes-spoken-dialog, public.accessibility.describes-music-and-sound",URI="subtitles/eng/prog_index.m3u8"

Então, theprog_index.m3u8 se parece com:

 #EXTM3U #EXT-X-TARGETDURATION:30 #EXT-X-VERSION:3 #EXT-X-MEDIA-SEQUENCE:0 #EXT-X-PLAYLIST-TYPE:VOD #EXTINF:30, 0000.webvtt #EXTINF:30, 0001.webvtt ...

O VTT real (por exemplo 0000.webvtt ):

 WEBVTT X-TIMESTAMP-MAP=MPEGTS:900000,LOCAL:00:00:00.000 00:00:01.000 --> 00:00:03.000 Subtitle -Unforced- (00:00:01.000) 00:00:03.000 --> 00:00:05.000 <i>...text here... -Unforced- (00:00:03.000)</i> <i>...text here...</i>

Além dos arquivos VTT, a Apple anunciou recentemente que o HLS terá suporte para IMSC1, um novo formato de legenda otimizado para entrega de streaming. Sua vantagem mais importante é que ele pode ser estilizado usando CSS.

Ferramentas de transmissão ao vivo HTTP e problemas potenciais

A Apple introduziu uma série de ferramentas úteis de HSL, que são descritas com mais detalhes no guia oficial de HLS.

  • Para transmissões ao vivo, a Apple preparou uma ferramenta chamada mediastreamsegmenter para criar arquivos de segmento em tempo real a partir de um fluxo de vídeo em andamento.
  • Outra ferramenta importante é mediastreamvalidator . Ele verificará suas listas de reprodução M3U8, baixará os arquivos de vídeo e relatará vários problemas. Por exemplo, quando a taxa de bits informada não é a mesma calculada a partir dos arquivos .ts.
  • Claro, quando você deve codificar/decodificar/mux/demux/chunk/strip/merge/join/… arquivos de vídeo/áudio, existe o ffmpeg. Esteja pronto para compilar suas próprias versões personalizadas do ffmpeg para casos de uso específicos.

Um dos problemas mais frequentes encontrados no vídeo é a sincronização de áudio. Se você achar que o áudio em alguns de seus fluxos HLS está fora de sincronia com o vídeo (ou seja, um ator abre a boca, mas você percebe que a voz está alguns milissegundos adiantada ou atrasada), é possível que o arquivo de vídeo original tenha sido filmado usando uma taxa de quadros variável. Certifique-se de convertê-lo para taxa de bits constante.

Se possível, é ainda melhor certificar-se de que seu software esteja configurado para gravar vídeo em uma taxa de quadros constante.

Exemplo de transmissão ao vivo HTTP

Eu preparei um aplicativo HLS Android que transmite um HLS predefinido usando o player ExoPlayer do Google. Ele mostrará um vídeo e uma lista de “eventos” HLS abaixo dele. Esses eventos incluem: cada arquivo .ts baixado ou cada vez que o player decide mudar para um fluxo de taxa de bits maior ou menor.

Vamos passar pelas principais partes da inicialização do visualizador. Na primeira etapa, recuperaremos o tipo de conexão atual do dispositivo e usaremos essas informações para decidir qual arquivo m3u8 recuperar.

 String m3u8File = "hls.m3u8"; ConnectivityManager connectivity = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo(); if (activeNetwork != null && activeNetwork.isConnectedOrConnecting()) { int type = activeNetwork.getType(); int subType = activeNetwork.getSubtype(); if (type == ConnectivityManager.TYPE_MOBILE && subType == TelephonyManager.NETWORK_TYPE_GPRS) { m3u8File = "hls_gprs.m3u8"; } } String m3u8URL = "https://s3-us-west-2.amazonaws.com/hls-playground/" + m3u8File;

Observe que isso não é estritamente necessário. O player HLS sempre se ajustará à variante HLS correta após alguns trechos, mas isso significa que nos primeiros 5 a 20 segundos o usuário pode não assistir à variante ideal do fluxo.

Lembre-se, a primeira variante no arquivo m3u8 é aquela com a qual o visualizador começará. Como estamos do lado do cliente e podemos detectar o tipo de conexão, podemos pelo menos tentar evitar a alternância do player inicial entre as variantes solicitando o arquivo m3u8 que é preparado antecipadamente para esse tipo de conexão.

Na próxima etapa, inicializamos e iniciamos nosso player HLS:

 Handler mainHandler = new Handler(); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter.Builder() .setEventListener(mainHandler, bandwidthMeterEventListener) .build(); TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(bandwidthMeter); TrackSelector trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); LoadControl loadControl = new DefaultLoadControl(); SimpleExoPlayer player = ExoPlayerFactory.newSimpleInstance(this, trackSelector, loadControl);

Em seguida, preparamos o player e o alimentamos com o m3u8 certo para este tipo de conexão de rede:

 DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "example-hls-app"), bandwidthMeter); HlsMediaSource videoSource = new HlsMediaSource(Uri.parse(m3u8URL), dataSourceFactory, 5, mainHandler, eventListener); player.prepare(videoSource);

E aqui está o resultado:

Exemplo de streaming HLS no Android

Compatibilidade do navegador HLS, desenvolvimentos futuros

Há um requisito da Apple para aplicativos de streaming de vídeo no iOS que eles devem usar HLS se os vídeos tiverem mais de 10 minutos ou mais de 5 MB. Isso por si só é uma garantia de que a HLS veio para ficar. Houve algumas preocupações sobre HLS e MPEG-DASH e qual deles será o vencedor na arena de navegadores da web. O HLS não é implementado em todos os navegadores modernos (você provavelmente notou isso se clicou nos exemplos de URL m3u8 anteriores). No Android, por exemplo, em versões inferiores a 4.0 não funcionará. De 4.1 a 4.4, funciona apenas parcialmente (por exemplo, o áudio está ausente ou o vídeo está ausente, mas o áudio funciona).

Mas essa “batalha” ficou um pouco mais simples recentemente. A Apple anunciou que o novo protocolo HLS permitirá arquivos mp4 fragmentados ( fMP4 ). Anteriormente, se você quisesse ter suporte para HLS e MPEG-DASH, você tinha que codificar seus vídeos duas vezes. Agora, você poderá reutilizar os mesmos arquivos de vídeo e reempacotar apenas os arquivos de metadados ( .m3u8 para HLS e .mpd para MPEG-DASH).

Outro anúncio recente é o suporte para High Efficiency Video Codec (HEVC). Se usado, deve ser empacotado em arquivos mp4 fragmentados. E isso provavelmente significa que o futuro do HLS é fMP4 .

A situação atual no mundo dos navegadores é que apenas algumas implementações de navegador da tag <video> reproduzirão o HLS imediatamente. Mas existem soluções comerciais e de código aberto que oferecem compatibilidade com HLS. A maioria deles oferece HLS por ter um fallback Flash, mas existem algumas implementações completamente escritas em JavaScript.

Empacotando

Este artigo se concentra especificamente no HTTP Live Streaming, mas conceitualmente também pode ser lido como uma explicação de como o Adaptive Bitrate Streaming (ABS) funciona. Em conclusão, podemos dizer que o HLS é uma tecnologia que resolve inúmeros problemas importantes no streaming de vídeo:

  • Simplifica o armazenamento de arquivos de vídeo
  • CDN
  • Players de cliente lidando com diferentes larguras de banda de cliente e alternando entre fluxos
  • Legendas, criptografia, reproduções sincronizadas e outros recursos não abordados neste artigo

Independentemente de você acabar usando HLS ou MPEG-DASH, ambos os protocolos devem oferecer funcionalidades semelhantes e, com a introdução do mp4 fragmentado (fMP4) no HLS, você pode usar os mesmos arquivos de vídeo. Isso significa que, na maioria dos casos, você precisará entender o básico de ambos os protocolos. Felizmente, eles parecem estar se movendo na mesma direção, o que deve torná-los mais fáceis de dominar.