[Parte 1/2] Guia para FFmpeg e SDL ou Como gravar um player de vídeo em menos de 1000 linhas


Embora essa informação já esteja desatualizada, o material original ainda é uma fonte popular de inspiração para vários conteúdos úteis sobre o tema do FFmpeg. No entanto, ainda não há tradução completa do original para o russo. Corrigimos a omissão irritante, pois é melhor tarde do que nunca.

E, embora tenhamos tentado, dificuldades de tradução são inevitáveis ​​em um texto tão volumoso . Relatar bugs (de preferência em mensagens privadas) - juntos faremos melhor.

Índice

EDISON Software - desenvolvimento web
EDISON.

, C C++.

! ;-)


UPD: Este guia foi atualizado em fevereiro de 2015.

O FFmpeg é uma ótima biblioteca para criar aplicativos de vídeo e utilitários de uso geral. O FFmpeg cuida de toda a rotina de processamento de vídeo, executando toda a decodificação, codificação, multiplexação e desmultiplexação. O que simplifica bastante a criação de aplicativos de mídia. Tudo é bastante simples e rápido, escrito em C, você pode decodificar quase qualquer codec disponível hoje, bem como codificar para outros formatos.

O único problema é que a documentação está praticamente ausente. Há um tutorial ( no original, aqui está um link para uma página da web já inexistente - note translator), que aborda os conceitos básicos do FFmpeg e a geração automática de docas doxygen. E nada mais. Portanto, decidi descobrir de forma independente como usar o FFmpeg para criar aplicativos de áudio e vídeo digital funcionais e, ao mesmo tempo, documentar o processo e apresentá-lo na forma de um livro didático.

Existe um programa FFplay que acompanha o FFmpeg. É simples, escrito em C, implementa um reprodutor de vídeo completo usando o FFmpeg. Minha primeira lição é uma versão atualizada da lição original de Martin Boehme ( no original, um link para uma página da web já extinta - uma nota do tradutor ) - arrastei algumas peças de lá. E também em uma série de minhas lições, mostrarei o processo de criação de um reprodutor de vídeo funcional baseado no ffplay.cFabrice Bellard. Cada lição apresentará uma nova idéia (ou até duas) com uma explicação de sua implementação. Cada capítulo vem com uma lista em C, que você pode compilar e executar por conta própria. Os arquivos de origem mostrarão como este programa funciona, como suas partes individuais funcionam e também demonstrarão pequenos detalhes técnicos que não são abordados neste guia. Quando terminarmos, teremos um reprodutor de vídeo de trabalho escrito em menos de 1000 linhas de código!

Ao criar o player, usaremos o SDL para gerar arquivos de mídia de áudio e vídeo. SDL é uma excelente biblioteca multimídia multiplataforma usada em programas de reprodução MPEG, emuladores e muitos videogames. Você precisará baixar e instalar as bibliotecas SDL em seu sistema para compilar os programas deste guia.

Este tutorial é para pessoas com boa experiência em programação. No mínimo, você precisa conhecer C e também ter um entendimento de conceitos como filas, mutexes etc. Deve haver alguma compreensão de multimídia; por exemplo, coisas como formas de onda e coisas do gênero. No entanto, não é necessário ser um guru nesses assuntos, pois muitos conceitos serão explicados no decorrer das lições.

Sinta-se à vontade para me enviar mensagens de erro, perguntas, comentários, idéias, recursos, o que for, no Dranger Doggy Gmail dot Com.







Leia também no blog da
empresa EDISON:


Manual do FFmpeg libav






Lição 1: Criando Screencaps


Lista completa: tutorial01.c
// tutorial01.c
// Code based on a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101 
// on GCC 4.7.2 in Debian February 2015

// A small sample program that shows how to use libavformat and libavcodec to
// read video from a file.
//
// Use
//
// gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lswscale -lz
//
// to build (assuming libavformat and libavcodec are correctly installed
// your system).
//
// Run using
//
// tutorial01 myvideofile.mpg
//
// to write the first five frames from "myvideofile.mpg" to disk in PPM
// format.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <stdio.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;
  
  // Open file
  sprintf(szFilename, "frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;
  
  // Write header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  
  // Write pixel data
  for(y=0; y<height; y++)
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  
  // Close file
  fclose(pFile);
}

int main(int argc, char *argv[]) {
  // Initalizing these to NULL prevents segfaults!
  AVFormatContext   *pFormatCtx = NULL;
  int               i, videoStream;
  AVCodecContext    *pCodecCtxOrig = NULL;
  AVCodecContext    *pCodecCtx = NULL;
  AVCodec           *pCodec = NULL;
  AVFrame           *pFrame = NULL;
  AVFrame           *pFrameRGB = NULL;
  AVPacket          packet;
  int               frameFinished;
  int               numBytes;
  uint8_t           *buffer = NULL;
  struct SwsContext *sws_ctx = NULL;

  if(argc < 2) {
    printf("Please provide a movie file\n");
    return -1;
  }
  // Register all formats and codecs
  av_register_all();
  
  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
  
  // Find the first video stream
  videoStream=-1;
  for(i=0; i<pFormatCtx->nb_streams; i++)
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
      videoStream=i;
      break;
    }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  
  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }
  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();
  
  // Allocate an AVFrame structure
  pFrameRGB=av_frame_alloc();
  if(pFrameRGB==NULL)
    return -1;

  // Determine required buffer size and allocate buffer
  numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
			      pCodecCtx->height);
  buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));
  
  // Assign appropriate parts of buffer to image planes in pFrameRGB
  // Note that pFrameRGB is an AVFrame, but AVFrame is a superset
  // of AVPicture
  avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
		 pCodecCtx->width, pCodecCtx->height);
  
  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_RGB24,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );

  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	// Convert the image from its native format to RGB
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pFrameRGB->data, pFrameRGB->linesize);
	
	// Save the frame to disk
	if(++i<=5)
	  SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, 
		    i);
      }
    }
    
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
  }
  
  // Free the RGB image
  av_free(buffer);
  av_frame_free(&pFrameRGB);
  
  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codecs
  avcodec_close(pCodecCtx);
  avcodec_close(pCodecCtxOrig);

  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}

Visão geral


Os arquivos de filme têm vários componentes principais. Primeiro, o próprio arquivo é chamado de contêiner e o tipo de contêiner determina como os dados são representados no arquivo. Exemplos de contêineres são AVI e Quicktime . Além disso, existem vários threads no arquivo; em particular, geralmente há um fluxo de áudio e um fluxo de vídeo . ("Fluxo" é uma palavra engraçada para "uma sequência de itens de dados disponíveis de acordo com a linha do tempo".) Os itens de dados em um fluxo são chamados de quadros . Cada fluxo é codificado por um ou outro tipo de codec . O codec determina como os dados reais para diruyutsya e dezembroAuditado - daí o nome do codec. Exemplos de codecs são DivX e MP3. Os pacotes são então lidos a partir do fluxo. Pacotes são pedaços de dados que podem conter bits de dados decodificados em quadros brutos, que podemos finalmente manipular em nosso aplicativo. Para nossos propósitos, cada pacote contém quadros completos (ou vários quadros, se for áudio).

Trabalhar com fluxos de vídeo e áudio é muito simples, mesmo no nível mais básico:

10 OPEN video_stream FROM video.avi
20 READ packet FROM video_stream INTO frame
30 IF frame NOT COMPLETE GOTO 20
40 DO SOMETHING WITH frame
50 GOTO 20

Trabalhar com multimídia usando o FFmpeg é quase tão simples quanto neste programa, embora em alguns programas a etapa "MAKE [...]" possa ser muito difícil. Neste tutorial, abriremos o arquivo, contaremos o fluxo de vídeo dentro dele e nosso "MAKE [...]" gravará o quadro no arquivo PPM.

Abrir arquivo


Primeiramente, vamos ver o que acontece primeiro quando você abre o arquivo. Usando o FFmpeg, primeiro inicializamos a biblioteca desejada:

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <ffmpeg/swscale.h>
...
int main(int argc, charg *argv[]) {
av_register_all();

Isso registra todos os formatos de arquivo e codecs disponíveis na biblioteca, para que sejam usados ​​automaticamente ao abrir um arquivo com o formato / codec apropriado. Note que você precisa chamar av_register_all () apenas uma vez, então fazemos isso aqui em main (). Se desejar, você pode registrar apenas formatos de arquivo e codecs seletivos, mas geralmente não há motivo específico para fazê-lo.

Agora abra o arquivo:

AVFormatContext *pFormatCtx = NULL;

// Open video file
if(avformat_open_input(&pFormatCtx, argv[1], NULL, 0, NULL)!=0)
  return -1; // Couldn't open file

Obtenha o nome do arquivo no primeiro argumento. Essa função lê o cabeçalho do arquivo e armazena as informações de formato de arquivo na estrutura AVFormatContext que passamos. Os últimos três argumentos são usados ​​para especificar o formato do arquivo, o tamanho do buffer e os parâmetros de formato. Ao defini-los como NULL ou 0, o libavformat detectará tudo automaticamente.

Essa função olha apenas para o cabeçalho, então agora precisamos verificar as informações do fluxo no arquivo:

// Retrieve stream information
if(avformat_find_stream_info(pFormatCtx, NULL)<0)
  return -1; // Couldn't find stream information

Esta função passa dados válidos para pFormatCtx -> fluxos . Nós nos familiarizamos com uma função de depuração conveniente, mostrando o que está dentro:

// Dump information about file onto standard error
av_dump_format(pFormatCtx, 0, argv[1], 0);

Agora pFormatCtx -> streams é apenas uma matriz de ponteiros de tamanho pFormatCtx -> nb_streams . Vamos passar por isso até encontrar o fluxo de vídeo:

int i;
AVCodecContext *pCodecCtxOrig = NULL;
AVCodecContext *pCodecCtx = NULL;

// Find the first video stream
videoStream=-1;
for(i=0; i<pFormatCtx->nb_streams; i++)
  if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
    videoStream=i;
    break;
  }
if(videoStream==-1)
  return -1; // Didn't find a video stream

// Get a pointer to the codec context for the video stream
pCodecCtx=pFormatCtx->streams[videoStream]->codec;

As informações sobre o codec no fluxo estão localizadas em um local chamado " contexto do codec ". Ele contém todas as informações sobre o codec que o fluxo usa e agora temos um ponteiro para ele. Mas ainda precisamos encontrar o codec real e abri-lo:

AVCodec *pCodec = NULL;

// Find the decoder for the video stream
pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
if(pCodec==NULL) {
  fprintf(stderr, "Unsupported codec!\n");
  return -1; // Codec not found
}
// Copy context
pCodecCtx = avcodec_alloc_context3(pCodec);
if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
  fprintf(stderr, "Couldn't copy codec context");
  return -1; // Error copying codec context
}
// Open codec
if(avcodec_open2(pCodecCtx, pCodec)<0)
  return -1; // Could not open codec

Observe que você não pode usar diretamente o AVCodecContext a partir do fluxo de vídeo! Portanto, você precisa usar um vcodec_copy_context () para copiar o contexto para um novo local (é claro, depois que a memória é alocada para ele).

Armazenamento de dados


Agora precisamos de um local para armazenar o quadro:

AVFrame *pFrame = NULL;

// Allocate video frame
pFrame=av_frame_alloc();

Como planejamos produzir arquivos PPM armazenados em RGB de 24 bits, precisaremos converter nosso quadro de seu próprio formato para RGB. O FFmpeg fará isso por nós. Para a maioria dos projetos (incluindo este), você precisa converter o quadro inicial para um formato específico. Selecione um quadro para o quadro convertido:

// Allocate an AVFrame structure
pFrameRGB=av_frame_alloc();
if(pFrameRGB==NULL)
  return -1;

Apesar de termos selecionado o quadro, ainda precisamos de um local para acomodar os dados brutos ao convertê-los. Usamos avpicture_get_size para obter os tamanhos corretos e alocar o espaço necessário manualmente:

uint8_t *buffer = NULL;
int numBytes;
// Determine required buffer size and allocate buffer
numBytes=avpicture_get_size(PIX_FMT_RGB24, pCodecCtx->width,
                            pCodecCtx->height);
buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t));

av_malloc é um análogo do malloc da função C do FFmpeg, que é um invólucro simples do malloc que fornece alinhamento de endereços de memória, etc. A propósito, isso não protege contra vazamentos de memória, liberação dupla ou outros problemas que ocorrem com o malloc .

Agora usamos avpicture_fill para associar o quadro ao nosso buffer recém-alocado. Em relação ao AVPicture : a estrutura AVPicture é um subconjunto da estrutura AVFrame - o início da estrutura AVFrame é idêntico à estrutura AVPicture .

// Assign appropriate parts of buffer to image planes in pFrameRGB
// Note that pFrameRGB is an AVFrame, but AVFrame is a superset
// of AVPicture
avpicture_fill((AVPicture *)pFrameRGB, buffer, PIX_FMT_RGB24,
                pCodecCtx->width, pCodecCtx->height);

Já estamos na linha de chegada! Agora estamos prontos para ler a partir do fluxo!

Lendo dados


Agora, para ler todo o fluxo de vídeo, lemos o próximo pacote, descriptografamos em nosso quadro e, assim que a descriptografia estiver concluída, converta o quadro e salve-o:

struct SwsContext *sws_ctx = NULL;
int frameFinished;
AVPacket packet;
// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
    pCodecCtx->height,
    pCodecCtx->pix_fmt,
    pCodecCtx->width,
    pCodecCtx->height,
    PIX_FMT_RGB24,
    SWS_BILINEAR,
    NULL,
    NULL,
    NULL
    );

i=0;
while(av_read_frame(pFormatCtx, &packet)>=0) {
  // Is this a packet from the video stream?
  if(packet.stream_index==videoStream) {
	// Decode video frame
    avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
    
    // Did we get a video frame?
    if(frameFinished) {
    // Convert the image from its native format to RGB
        sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pFrameRGB->data, pFrameRGB->linesize);
	
        // Save the frame to disk
        if(++i<=5)
          SaveFrame(pFrameRGB, pCodecCtx->width, 
                    pCodecCtx->height, i);
    }
  }
    
  // Free the packet that was allocated by av_read_frame
  av_free_packet(&packet);
}

Nada complicado: av_read_frame () lê o pacote e o salva na estrutura do AVPacket . Observe que distribuímos apenas a estrutura do pacote - o FFmpeg nos fornece os dados internos aos quais o pacote.data aponta . Isso libera av_free_packet () um pouco mais tarde . avcodec_decode_video () converte o pacote em quadro. No entanto, podemos não ter todas as informações necessárias para o quadro depois de decodificar o pacote, portanto avcodec_decode_video () define o frameFinished quando tivermos o próximo quadro. Finalmente, usamos sws_scale () para converter de nosso próprio formato ( pCodecCtx ->pix_fmt ) em RGB. Lembre-se de que você pode converter umponteiro AVFrame em um ponteiro AVPicture . Finalmente, passamos as informações sobre o quadro, altura e largura da nossa função SaveFrame .

Falando em pacotes. Tecnicamente, um pacote pode conter apenas parte de um quadro, além de outros bits de dados. No entanto, o analisador FFmpeg garante que os pacotes que recebemos contenham um quadro completo ou até vários quadros.

Agora tudo o que precisa ser feito é usar a função SaveFrame para gravar as informações RGB em um arquivo PPM. Embora estejamos lidando superficialmente com o próprio formato PPM; acredite em mim, tudo funciona aqui:

void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) {
  FILE *pFile;
  char szFilename[32];
  int  y;
  
  // Open file
  sprintf(szFilename, "frame%d.ppm", iFrame);
  pFile=fopen(szFilename, "wb");
  if(pFile==NULL)
    return;
  
  // Write header
  fprintf(pFile, "P6\n%d %d\n255\n", width, height);
  
  // Write pixel data
  for(y=0; y<height; y++)
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  
  // Close file
  fclose(pFile);
}

Executamos um arquivo padrão aberto, etc., e depois registramos os dados RGB. O arquivo é gravado linha por linha. Um arquivo PPM é simplesmente um arquivo no qual as informações RGB são apresentadas como uma linha longa. Se você conhece as cores do HTML, será como marcar as cores de cada pixel do começo ao fim, algo como # ff0000 # ff0000 .... , como em uma tela vermelha. (De fato, ele é armazenado em formato binário e sem separador, mas espero que você entenda.) O título indica a largura e a altura da imagem, assim como o tamanho máximo dos valores RGB.

Agora, de volta à nossa função main (). Assim que terminamos de ler o fluxo de vídeo, precisamos limpar tudo:

// Free the RGB image
av_free(buffer);
av_free(pFrameRGB);

// Free the YUV frame
av_free(pFrame);

// Close the codecs
avcodec_close(pCodecCtx);
avcodec_close(pCodecCtxOrig);

// Close the video file
avformat_close_input(&pFormatCtx);

return 0;

Como você pode ver, usamos av_free para a memória alocada usando avcode_alloc_frame e av_malloc .

Esse é todo o código! Agora, se você estiver usando Linux ou uma plataforma semelhante, execute:

gcc -o tutorial01 tutorial01.c -lavutil -lavformat -lavcodec -lz -lavutil -lm

Se você possui uma versão mais antiga do FFmpeg, pode ser necessário remover -lavutil :

gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lz -lm

A maioria dos programas gráficos deve abrir o formato PPM. Confira em alguns arquivos de filme cujas capturas de tela foram feitas usando nosso programa.






Lição 2: Exibindo a tela


Lista completa: tutorial02.c
// tutorial02.c
// A pedagogical video player that will stream through every video frame as fast as it can.
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
// 
// gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial02 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

int main(int argc, char *argv[]) {
  AVFormatContext *pFormatCtx = NULL;
  int             i, videoStream;
  AVCodecContext  *pCodecCtxOrig = NULL;
  AVCodecContext  *pCodecCtx = NULL;
  AVCodec         *pCodec = NULL;
  AVFrame         *pFrame = NULL;
  AVPacket        packet;
  int             frameFinished;
  float           aspect_ratio;
  struct SwsContext *sws_ctx = NULL;

  SDL_Overlay     *bmp;
  SDL_Surface     *screen;
  SDL_Rect        rect;
  SDL_Event       event;

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
  
  // Find the first video stream
  videoStream=-1;
  for(i=0; i<pFormatCtx->nb_streams; i++)
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) {
      videoStream=i;
      break;
    }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  
  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }
  
  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
				 pCodecCtx->height,
				 SDL_YV12_OVERLAY,
				 screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_YUV420P,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );



  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	SDL_LockYUVOverlay(bmp);

	AVPicture pict;
	pict.data[0] = bmp->pixels[0];
	pict.data[1] = bmp->pixels[2];
	pict.data[2] = bmp->pixels[1];

	pict.linesize[0] = bmp->pitches[0];
	pict.linesize[1] = bmp->pitches[2];
	pict.linesize[2] = bmp->pitches[1];

	// Convert the image into YUV format that SDL uses
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pict.data, pict.linesize);

	SDL_UnlockYUVOverlay(bmp);
	
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
      
      }
    }
    
    // Free the packet that was allocated by av_read_frame
    av_free_packet(&packet);
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

  }
  
  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codec
  avcodec_close(pCodecCtx);
  avcodec_close(pCodecCtxOrig);
  
  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}

SDL e vídeo


Para desenhar na tela, usaremos SDL. SDL significa Simple Direct Layer . É uma excelente biblioteca multimídia multiplataforma usada em muitos projetos. Você pode obter a biblioteca no site oficial ou fazer o download do pacote de desenvolvedor para o seu sistema operacional, se houver. Você precisará de bibliotecas para compilar o código desta lição (todas as outras lições, a propósito, isso também se aplica).

O SDL tem muitos métodos para desenhar na tela. Uma maneira de exibir filmes é o que é chamado de sobreposição YUV .

Formalmente, nem mesmo YUV, mas YCbCr. Algumas pessoas, a propósito, ficam muito queimadas quando "YCbCr" é chamado de "YUV". De um modo geral, YUV é um formato analógico e YCbCr é um formato digital. FFmpeg e SDL em seu código e em macros designam YCbCr como YUV, mas é isso.

YUV é um método de armazenamento de dados brutos de imagem, como RGB. Grosso modo, Y é um componente do brilho e U e V são componentes da cor . (Isso é mais complicado que o RGB porque parte das informações de cores são descartadas e você pode ter apenas 1 medida de U e V para cada 2 medidas de Y ). Sobreposição de YUVno SDL aceita um conjunto de dados YUV bruto e o exibe. Ele aceita 4 tipos diferentes de formatos YUV, mas o YV12 é o mais rápido deles. Há outro formato YUV chamado YUV420P que corresponde a YV12, exceto que as matrizes de U e V são trocadas. 420 significa que é amostrada na proporção de 4: 2: 0, ou seja, para cada 4 medições de brilho, há 1 medição de cor, de modo que as informações sobre cores são distribuídas em quartos. Essa é uma boa maneira de economizar largura de banda, porque o olho humano ainda não percebe essas alterações. A letra latina "P" no nome indica que o formato é "plano", o que significa simplesmente que os componentes são Y ,U e V estão em matrizes separadas. O FFmpeg pode converter imagens em YUV420P , o que é muito útil, porque muitos fluxos de vídeo já estão armazenados nesse formato ou são facilmente convertidos nele.

Portanto, nosso plano atual é substituir a função SaveFrame () da lição anterior e exibir nosso quadro. Mas primeiro você precisa se familiarizar com os recursos básicos da biblioteca SDL. Para começar, conecte as bibliotecas e inicialize o SDL:

#include <SDL.h>
#include <SDL_thread.h>

if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
  fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
  exit(1);
}

SDL_Init () essencialmente diz à biblioteca quais funções vamos usar. SDL_GetError (), é claro, esta é a nossa função conveniente para depuração.

Criação de exibição


Agora precisamos de um lugar na tela para organizar os elementos. A área principal para exibir imagens com SDL é chamada de superfície :

SDL_Surface *screen;

screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
if(!screen) {
  fprintf(stderr, "SDL: could not set video mode - exiting\n");
  exit(1);
}

Então montamos uma tela com uma largura e altura especificadas. A próxima opção é a profundidade de bits da tela - 0 - este é um valor especial que significa "o mesmo que a exibição atual".

Agora, criamos uma sobreposição de YUV nessa tela para que possamos produzir vídeo para ele e configuramos nosso SWSContext para converter dados de imagem em YUV420 :

SDL_Overlay     *bmp = NULL;
struct SWSContext *sws_ctx = NULL;

bmp = SDL_CreateYUVOverlay(pCodecCtx->width, pCodecCtx->height,
                           SDL_YV12_OVERLAY, screen);

// initialize SWS context for software scaling
sws_ctx = sws_getContext(pCodecCtx->width,
                         pCodecCtx->height,
			 pCodecCtx->pix_fmt,
			 pCodecCtx->width,
			 pCodecCtx->height,
			 PIX_FMT_YUV420P,
			 SWS_BILINEAR,
			 NULL,
			 NULL,
			 NULL
			 );

Como mencionado, usamos o YV12 para exibir a imagem e obter os dados do YUV420 no FFmpeg.

Exibição de imagem


Bem, isso foi fácil! Agora só precisamos mostrar a imagem. Vamos até o local onde tiramos a foto final. Podemos nos livrar de tudo o que tínhamos para o quadro RGB e vamos substituir SaveFrame () pelo nosso código de exibição. Para exibir a imagem, vamos criar uma estrutura AVPicture e definir os ponteiros de dados e o tamanho da linha para a nossa sobreposição YUV :

  if(frameFinished) {
    SDL_LockYUVOverlay(bmp);

    AVPicture pict;
    pict.data[0] = bmp->pixels[0];
    pict.data[1] = bmp->pixels[2];
    pict.data[2] = bmp->pixels[1];

    pict.linesize[0] = bmp->pitches[0];
    pict.linesize[1] = bmp->pitches[2];
    pict.linesize[2] = bmp->pitches[1];

    // Convert the image into YUV format that SDL uses
    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, pCodecCtx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(bmp);

Inicialmente, bloqueamos a sobreposição, porque planejamos gravar nela. Este é um bom hábito, para que mais tarde não haja problemas. A estrutura AVPicture , como mostrado acima, possui um ponteiro de dados, que é uma matriz de 4 ponteiros. Como aqui estamos lidando com o YUV420P , temos apenas 3 canais e, portanto, apenas 3 conjuntos de dados. Outros formatos podem ter um quarto ponteiro para o canal alfa ou outra coisa. O tamanho da linha é o que parece. Estruturas semelhantes em nossa sobreposição de YUV são variáveis ​​para pixels e alturas. (Lances, lances - se expressos em termos de SDL para indicar a largura de uma determinada linha de dados.) Portanto, indicamos três matrizes pict.data em nossa sobreposição, portanto, quando escrevemos empict , na verdade estamos gravando em nossa sobreposição, o que, naturalmente, já tem o espaço necessário alocado especificamente para ele. Da mesma forma, obtemos informações de tamanho de linha diretamente de nossa sobreposição. Alteramos o formato de conversão para PIX_FMT_YUV420P e usamos sws_scale como antes.

Desenho da imagem


Mas ainda precisamos especificar o SDL para que ele realmente mostre os dados que fornecemos a ele. Também passamos um retângulo para essa função, que indica para onde o filme deve ir, em que largura e altura ele deve ser dimensionado. Assim, o SDL é escalável para nós, e isso pode ajudar sua GPU a escalar mais rapidamente:

SDL_Rect rect;

  if(frameFinished) {
    /* ... code ... */
    // Convert the image into YUV format that SDL uses
    sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
              pFrame->linesize, 0, pCodecCtx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(bmp);
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
  }

Agora nosso vídeo é exibido!

Vamos mostrar mais um recurso do SDL: sistema de eventos . O SDL é configurado de tal maneira que quando você insere ou move o mouse no aplicativo SDL ou envia um sinal para ele, um evento é gerado. Seu programa verifica esses eventos se se destina a processar a entrada do usuário. Seu programa também pode criar eventos para enviar eventos SDL ao sistema. Isso é especialmente útil para programação multithread com SDL, que veremos na lição número 4. Em nosso programa, vamos verificar os eventos imediatamente após o processamento do pacote. No momento, trataremos o evento SDL_QUIT para que possamos sair:

SDL_Event       event;

    av_free_packet(&packet);
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

E assim vivemos! Nós nos livramos de todo o lixo antigo e estamos prontos para compilar. Se você usa Linux ou algo como Linux, a melhor maneira de compilar usando as bibliotecas SDL é:

gcc -o tutorial02 tutorial02.c -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

O sdl-config simplesmente exibe os sinalizadores necessários para que o gcc habilite corretamente as bibliotecas SDL. Talvez você precise fazer outra coisa para compilar isso no seu sistema; por favor, verifique a documentação SDL do seu sistema para qualquer bombeiro. Depois de compilado, continue e execute.

O que acontece quando você executa este programa? O vídeo parece estar ficando louco! De fato, simplesmente exibimos todos os quadros de vídeo o mais rápido que podemos extraí-los do arquivo de filme. No momento, não temos o código para descobrir quando precisamos mostrar o vídeo. No final (na lição número 5), começaremos a sincronizar o vídeo. Mas no momento estamos perdendo algo igualmente importante: o som!






Lição 3: reproduzir som


Lista completa: tutorial03.c
// tutorial03.c
// A pedagogical video player that will stream through every video frame as fast as it can
// and play audio (out of sync).
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
//
// Use
//
// gcc -o tutorial03 tutorial03.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial03 myvideofile.mpg
//
// to play the stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

PacketQueue audioq;

int quit = 0;

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(av_dup_packet(pkt) < 0) {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;
  
  
  SDL_LockMutex(q->mutex);
  
  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;
  
  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(quit) {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1) {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
	q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf, int buf_size) {

  static AVPacket pkt;
  static uint8_t *audio_pkt_data = NULL;
  static int audio_pkt_size = 0;
  static AVFrame frame;

  int len1, data_size = 0;

  for(;;) {
    while(audio_pkt_size > 0) {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
      if(len1 < 0) {
	/* if error, skip frame */
	audio_pkt_size = 0;
	break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1;
      data_size = 0;
      if(got_frame) {
	data_size = av_samples_get_buffer_size(NULL, 
					       aCodecCtx->channels,
					       frame.nb_samples,
					       aCodecCtx->sample_fmt,
					       1);
	assert(data_size <= buf_size);
	memcpy(audio_buf, frame.data[0], data_size);
      }
      if(data_size <= 0) {
	/* No data yet, get more frames */
	continue;
      }
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt.data)
      av_free_packet(&pkt);

    if(quit) {
      return -1;
    }

    if(packet_queue_get(&audioq, &pkt, 1) < 0) {
      return -1;
    }
    audio_pkt_data = pkt.data;
    audio_pkt_size = pkt.size;
  }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {

  AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  int len1, audio_size;

  static uint8_t audio_buf[(MAX_AUDIO_FRAME_SIZE * 3) / 2];
  static unsigned int audio_buf_size = 0;
  static unsigned int audio_buf_index = 0;

  while(len > 0) {
    if(audio_buf_index >= audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(aCodecCtx, audio_buf, sizeof(audio_buf));
      if(audio_size < 0) {
	/* If error, output silence */
	audio_buf_size = 1024; // arbitrary?
	memset(audio_buf, 0, audio_buf_size);
      } else {
	audio_buf_size = audio_size;
      }
      audio_buf_index = 0;
    }
    len1 = audio_buf_size - audio_buf_index;
    if(len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
    len -= len1;
    stream += len1;
    audio_buf_index += len1;
  }
}

int main(int argc, char *argv[]) {
  AVFormatContext *pFormatCtx = NULL;
  int             i, videoStream, audioStream;
  AVCodecContext  *pCodecCtxOrig = NULL;
  AVCodecContext  *pCodecCtx = NULL;
  AVCodec         *pCodec = NULL;
  AVFrame         *pFrame = NULL;
  AVPacket        packet;
  int             frameFinished;
  struct SwsContext *sws_ctx = NULL;
  
  AVCodecContext  *aCodecCtxOrig = NULL;
  AVCodecContext  *aCodecCtx = NULL;
  AVCodec         *aCodec = NULL;

  SDL_Overlay     *bmp;
  SDL_Surface     *screen;
  SDL_Rect        rect;
  SDL_Event       event;
  SDL_AudioSpec   wanted_spec, spec;

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Open video file
  if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0)
    return -1; // Couldn't open file
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, argv[1], 0);
    
  // Find the first video stream
  videoStream=-1;
  audioStream=-1;
  for(i=0; i<pFormatCtx->nb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       videoStream < 0) {
      videoStream=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audioStream < 0) {
      audioStream=i;
    }
  }
  if(videoStream==-1)
    return -1; // Didn't find a video stream
  if(audioStream==-1)
    return -1;
   
  aCodecCtxOrig=pFormatCtx->streams[audioStream]->codec;
  aCodec = avcodec_find_decoder(aCodecCtxOrig->codec_id);
  if(!aCodec) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  // Copy context
  aCodecCtx = avcodec_alloc_context3(aCodec);
  if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Set audio settings from codec info
  wanted_spec.freq = aCodecCtx->sample_rate;
  wanted_spec.format = AUDIO_S16SYS;
  wanted_spec.channels = aCodecCtx->channels;
  wanted_spec.silence = 0;
  wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
  wanted_spec.callback = audio_callback;
  wanted_spec.userdata = aCodecCtx;
  
  if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
    fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
    return -1;
  }

  avcodec_open2(aCodecCtx, aCodec, NULL);

  // audio_st = pFormatCtx->streams[index]
  packet_queue_init(&audioq);
  SDL_PauseAudio(0);

  // Get a pointer to the codec context for the video stream
  pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec;
  
  // Find the decoder for the video stream
  pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id);
  if(pCodec==NULL) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1; // Codec not found
  }

  // Copy context
  pCodecCtx = avcodec_alloc_context3(pCodec);
  if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }

  // Open codec
  if(avcodec_open2(pCodecCtx, pCodec, NULL)<0)
    return -1; // Could not open codec
  
  // Allocate video frame
  pFrame=av_frame_alloc();

  // Make a screen to put our video

#ifndef __DARWIN__
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 0, 0);
#else
        screen = SDL_SetVideoMode(pCodecCtx->width, pCodecCtx->height, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }
  
  // Allocate a place to put our YUV image on that screen
  bmp = SDL_CreateYUVOverlay(pCodecCtx->width,
				 pCodecCtx->height,
				 SDL_YV12_OVERLAY,
				 screen);

  // initialize SWS context for software scaling
  sws_ctx = sws_getContext(pCodecCtx->width,
			   pCodecCtx->height,
			   pCodecCtx->pix_fmt,
			   pCodecCtx->width,
			   pCodecCtx->height,
			   PIX_FMT_YUV420P,
			   SWS_BILINEAR,
			   NULL,
			   NULL,
			   NULL
			   );

  // Read frames and save first five frames to disk
  i=0;
  while(av_read_frame(pFormatCtx, &packet)>=0) {
    // Is this a packet from the video stream?
    if(packet.stream_index==videoStream) {
      // Decode video frame
      avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
      
      // Did we get a video frame?
      if(frameFinished) {
	SDL_LockYUVOverlay(bmp);

	AVPicture pict;
	pict.data[0] = bmp->pixels[0];
	pict.data[1] = bmp->pixels[2];
	pict.data[2] = bmp->pixels[1];

	pict.linesize[0] = bmp->pitches[0];
	pict.linesize[1] = bmp->pitches[2];
	pict.linesize[2] = bmp->pitches[1];

	// Convert the image into YUV format that SDL uses	
	sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data,
		  pFrame->linesize, 0, pCodecCtx->height,
		  pict.data, pict.linesize);
	
	SDL_UnlockYUVOverlay(bmp);
	
	rect.x = 0;
	rect.y = 0;
	rect.w = pCodecCtx->width;
	rect.h = pCodecCtx->height;
	SDL_DisplayYUVOverlay(bmp, &rect);
	av_free_packet(&packet);
      }
    } else if(packet.stream_index==audioStream) {
      packet_queue_put(&audioq, &packet);
    } else {
      av_free_packet(&packet);
    }
    // Free the packet that was allocated by av_read_frame
    SDL_PollEvent(&event);
    switch(event.type) {
    case SDL_QUIT:
      quit = 1;
      SDL_Quit();
      exit(0);
      break;
    default:
      break;
    }

  }

  // Free the YUV frame
  av_frame_free(&pFrame);
  
  // Close the codecs
  avcodec_close(pCodecCtxOrig);
  avcodec_close(pCodecCtx);
  avcodec_close(aCodecCtxOrig);
  avcodec_close(aCodecCtx);
  
  // Close the video file
  avformat_close_input(&pFormatCtx);
  
  return 0;
}

Áudio


Agora gostaríamos que o som tocasse no aplicativo. O SDL também nos fornece métodos para reproduzir som. A função SDL_OpenAudio () é usada para abrir o próprio dispositivo de áudio. Toma como argumento a estrutura SDL_AudioSpec , que contém todas as informações sobre o áudio que vamos reproduzir.

Antes de mostrarmos como configurar isso, primeiro explicamos como o computador processa o áudio em geral. O áudio digital consiste em um longo fluxo de amostras, cada um dos quais representa um significado específico de uma onda sonora. Os sons são gravados em uma taxa de amostragem específica, o que simplesmente indica a rapidez com que cada amostra é reproduzida e medidos pelo número de amostras por segundo. As frequências aproximadas de amostragem são 22.050 e 44.100 amostras por segundo, que são as velocidades usadas para rádio e CD, respectivamente. Além disso, a maioria do áudio pode ter mais de um canal para som estéreo ou surround; portanto, por exemplo, se a amostra estiver em estéreo, as amostras serão apresentadas duas por vez. Quando obtemos os dados do arquivo do filme, não sabemos quantas amostras obteremos, mas o FFmpeg não produz amostras quebradas - isso também significa que também não separará a amostra estéreo.

O método para reproduzir áudio em SDL é o seguinte. Os parâmetros de som estão configurados: frequência de amostragem, número de canais, etc. E também defina a função de retorno de chamada e os dados do usuário. Quando começamos a reproduzir som, o SDL chama constantemente essa função de retorno de chamada e solicita que preencha o buffer de áudio com um determinado número de bytes. Depois de colocar essas informações na estrutura SDL_AudioSpec , chamamos SDL_OpenAudio (), que abrirá o dispositivo de áudio e retornará outra estrutura AudioSpec para nós . Essas são as características que realmente usaremos - não há garantia de que obteremos exatamente o que pedimos!

Configuração de áudio


Lembre-se por enquanto, porque ainda não temos informações sobre fluxos de áudio! Vamos voltar ao local do código onde encontramos o fluxo de vídeo e descobrir qual fluxo é o fluxo de áudio:

// Find the first video stream
videoStream=-1;
audioStream=-1;
for(i=0; i < pFormatCtx->nb_streams; i++) {
  if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO
     &&
       videoStream < 0) {
    videoStream=i;
  }
  if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
     audioStream < 0) {
    audioStream=i;
  }
}
if(videoStream==-1)
  return -1; // Didn't find a video stream
if(audioStream==-1)
  return -1;

Aqui podemos obter todas as informações que queremos do AVCodecContext a partir do fluxo, assim como fizemos com o fluxo de vídeo:

AVCodecContext *aCodecCtxOrig;
AVCodecContext *aCodecCtx;

aCodecCtxOrig=pFormatCtx->streams[audioStream]->codec;

Se você se lembra, nas lições anteriores, ainda precisamos abrir o próprio codec de áudio. É simples:

AVCodec         *aCodec;

aCodec = avcodec_find_decoder(aCodecCtx->codec_id);
if(!aCodec) {
  fprintf(stderr, "Unsupported codec!\n");
  return -1;
}
// Copy context
aCodecCtx = avcodec_alloc_context3(aCodec);
if(avcodec_copy_context(aCodecCtx, aCodecCtxOrig) != 0) {
  fprintf(stderr, "Couldn't copy codec context");
  return -1; // Error copying codec context
}
/* set up SDL Audio here */

avcodec_open2(aCodecCtx, aCodec, NULL);

No contexto do codec contém todas as informações necessárias para configurar nosso áudio:

wanted_spec.freq = aCodecCtx->sample_rate;
wanted_spec.format = AUDIO_S16SYS;
wanted_spec.channels = aCodecCtx->channels;
wanted_spec.silence = 0;
wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
wanted_spec.callback = audio_callback;
wanted_spec.userdata = aCodecCtx;

if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
  fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
  return -1;
}

Vamos examinar cada item:
  • freq (frequência): taxa de amostragem, conforme explicado anteriormente.
  • format (): SDL , . «S» «S16SYS» «», 16 , 16 , «SYS» , , . , avcodec_decode_audio2 .
  • channels (): .
  • silence (): , . 0.
  • samples (): , , SDL , . - 512 8192; FFplay, , 1024.
  • retorno de chamada (callback): aqui passamos a função real de retorno de chamada. Falaremos mais sobre a função de retorno de chamada mais tarde.
  • userdata : o SDL dará ao nosso retorno de chamada um ponteiro nulo para todos os dados do usuário que desejamos. Queremos que ele saiba sobre o nosso contexto de codec; um pouco mais baixo, ficará claro o porquê.

Por fim, abra o áudio com SDL_OpenAudio .

Filas


E é necessário! Agora estamos prontos para extrair informações de áudio do fluxo. Mas o que fazer com esta informação? Receberemos continuamente pacotes do arquivo do filme, mas, ao mesmo tempo, o SDL chamará a função de retorno de chamada! A solução será criar algum tipo de estrutura global na qual possamos inserir pacotes de áudio para que nosso audio_callback tenha algo para receber dados de áudio! Então, aqui está o que faremos para criar a fila de pacotes. O FFmpeg ainda tem uma estrutura para ajudar com isso: AVPacketList , que é apenas uma lista vinculada de pacotes. Aqui está a nossa estrutura de filas:

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;

Primeiro, devemos indicar que nb_packets é diferente em tamanho - o tamanho refere-se ao tamanho do byte que obtemos do pacote-> tamanho . Observe que temos um mutex e uma variável de condição. Isso ocorre porque o SDL executa o processo de áudio como um fluxo separado. Se não bloquearmos a fila corretamente, podemos realmente arruinar nossos dados. Vamos ver como a fila é implementada. Todo programador que se preze deve saber como criar filas, mas também mostraremos como fazer isso, para que seja mais fácil aprender as funções SDL.

Primeiro, criamos uma função para inicializar a fila:

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}

Em seguida, crie uma função para colocar objetos em nossa fila:

int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(av_dup_packet(pkt) < 0) {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;
  
  
  SDL_LockMutex(q->mutex);
  
  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}

SDL_LockMutex () bloqueia o mutex na fila para que possamos adicionar algo e, em seguida, SDL_CondSignal () envia um sinal para nossa função get (se ela espera) através de nossa variável condicional para informar que existem dados e que podem ser continuados, para mais informações. desbloquear mutex.

Aqui está a função get correspondente . Observe como SDL_CondWait () cria o bloco de funções (ou seja, pausa até obtermos os dados) se solicitarmos que faça isso:

int quit = 0;

static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block) {
  AVPacketList *pkt1;
  int ret;
  
  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(quit) {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1) {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
	q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

Como você pode ver, envolvemos a função em um ciclo eterno; portanto, obteremos definitivamente alguns dados, se quisermos bloqueá-lo. Evitamos fazer um loop para sempre usando a função SDL_CondWait (). Essencialmente, tudo o que o CondWait faz é aguardar um sinal de SDL_CondSignal () (ou SDL_CondBroadcast ()) e continuar. No entanto, parece que o pegamos em um mutex - se mantivermos o bloqueio, nossa função put não poderá enfileirar nada! No entanto, o que SDL_CondWait () também faz por nós é desbloquear o mutex que fornecemos e, em seguida, tente bloqueá-lo assim que recebermos o sinal.

Para todo bombeiro


Você também pode ver que temos um mundial sair variável que certifique-se de que nós não definir um sinal de saída no programa (SDL processa automaticamente PRAZO sinais , etc.). Caso contrário, o encadeamento continuará para sempre e teremos que matar o programa com kill -9 :

  SDL_PollEvent(&event);
  switch(event.type) {
  case SDL_QUIT:
    quit = 1;

Definiremos o sinalizador de saída como 1.

Alimentamos pacotes


Resta apenas configurar nossa fila:

PacketQueue audioq;
main() {
...
  avcodec_open2(aCodecCtx, aCodec, NULL);

  packet_queue_init(&audioq);
  SDL_PauseAudio(0);

SDL_PauseAudio () finalmente inicia a unidade de áudio. Ele reproduz o silêncio se não receber dados; mas isso não acontece imediatamente.

Então, temos uma fila configurada, agora estamos prontos para enviar pacotes para ela. Passamos ao nosso ciclo de leitura de pacotes:

while(av_read_frame(pFormatCtx, &packet)>=0) {
  // Is this a packet from the video stream?
  if(packet.stream_index==videoStream) {
    // Decode video frame
    ....
    }
  } else if(packet.stream_index==audioStream) {
    packet_queue_put(&audioq, &packet);
  } else {
    av_free_packet(&packet);
  }

Observe que não liberamos o pacote após colocá-lo na fila. Nós o liberaremos mais tarde quando descriptografarmos.

Recuperando pacotes


Agora vamos finalmente fazer nossa função audio_callback buscar pacotes da fila. O retorno de chamada deve se parecer com:

void callback(void *userdata, Uint8 *stream, int len)

userdata , é o ponteiro que fornecemos ao SDL, stream é o buffer no qual gravaremos dados de áudio e len é o tamanho desse buffer. Aqui está o código:

void audio_callback(void *userdata, Uint8 *stream, int len) {

  AVCodecContext *aCodecCtx = (AVCodecContext *)userdata;
  int len1, audio_size;

  static uint8_t audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  static unsigned int audio_buf_size = 0;
  static unsigned int audio_buf_index = 0;

  while(len > 0) {
    if(audio_buf_index >= audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(aCodecCtx, audio_buf,
                                      sizeof(audio_buf));
      if(audio_size < 0) {
	/* If error, output silence */
	audio_buf_size = 1024;
	memset(audio_buf, 0, audio_buf_size);
      } else {
	audio_buf_size = audio_size;
      }
      audio_buf_index = 0;
    }
    len1 = audio_buf_size - audio_buf_index;
    if(len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)audio_buf + audio_buf_index, len1);
    len -= len1;
    stream += len1;
    audio_buf_index += len1;
  }
}

Na verdade, esse é um loop simples que extrai dados de outra função que escrevemos, audio_decode_frame (), salva o resultado em um buffer intermediário, tenta gravar bytes de len no fluxo e recebe mais dados se ainda não tivermos o suficiente ou salvá-lo para mais tarde, se tivermos algo sobrando. O tamanho do audio_buf é 1,5 vezes o tamanho do maior quadro de áudio que o FFmpeg nos dará, o que nos dá uma boa margem.

Descriptografia final de áudio


Vejamos o interior do decodificador audio_decode_frame :

int audio_decode_frame(AVCodecContext *aCodecCtx, uint8_t *audio_buf,
                       int buf_size) {

  static AVPacket pkt;
  static uint8_t *audio_pkt_data = NULL;
  static int audio_pkt_size = 0;
  static AVFrame frame;

  int len1, data_size = 0;

  for(;;) {
    while(audio_pkt_size > 0) {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(aCodecCtx, &frame, &got_frame, &pkt);
      if(len1 < 0) {
	/* if error, skip frame */
	audio_pkt_size = 0;
	break;
      }
      audio_pkt_data += len1;
      audio_pkt_size -= len1;
      data_size = 0;
      if(got_frame) {
	data_size = av_samples_get_buffer_size(NULL, 
					       aCodecCtx->channels,
					       frame.nb_samples,
					       aCodecCtx->sample_fmt,
					       1);
	assert(data_size <= buf_size);
	memcpy(audio_buf, frame.data[0], data_size);
      }
      if(data_size <= 0) {
	/* No data yet, get more frames */
	continue;
      }
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt.data)
      av_free_packet(&pkt);

    if(quit) {
      return -1;
    }

    if(packet_queue_get(&audioq, &pkt, 1) < 0) {
      return -1;
    }
    audio_pkt_data = pkt.data;
    audio_pkt_size = pkt.size;
  }
}

Todo o processo começa realmente perto do final da função, onde chamamos packet_queue_get (). Pegamos o pacote da fila e salvamos as informações. Então, quando temos o pacote para trabalhar, chamamos avcodec_decode_audio4 (), que é muito semelhante à sua função irmã avcodec_decode_video (), exceto que, neste caso, o pacote pode ter mais de um quadro. Portanto, pode ser necessário chamá-lo várias vezes para obter todos os dados do pacote. Depois de receber o quadro, simplesmente o copiamos para nosso buffer de áudio, certificando-nos de que o tamanho do dado seja menor que o buffer de áudio. Lembre-se também de transmitir audio_bufpara o tipo correto, porque SDL fornece um buffer int de 8 bits e FFmpeg nos fornece dados em um buffer int de 16 bits. Você também deve considerar a diferença entre len1 e data_size . len1 é o tamanho do pacote que usamos e data_size é a quantidade de dados brutos retornados.

Quando temos alguns dados, retornamos imediatamente para descobrir se precisamos obter mais dados da fila ou se estamos prontos. Se ainda precisamos processar o pacote, atenha-se a ele. Se você concluiu o pacote, libere-o finalmente.

E é tudo! Temos o áudio transferido do loop de leitura principal para a fila, que é então lida pela função audio_callback, que transfere esses dados para o SDL e o SDL transfere para sua placa de som. Vá em frente e compile:

gcc -o tutorial03 tutorial03.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

Gip-gip-hooray! O vídeo ainda é carregado na velocidade máxima, mas o som já está sendo reproduzido como deveria. Por que é que? Sim, porque as informações de áudio têm uma frequência de amostragem - bombeamos as informações de áudio o mais rápido possível, mas o áudio é simplesmente reproduzido nesse fluxo de acordo com a frequência de amostragem.

Estamos quase prontos para a sincronização de vídeo e áudio, mas primeiro precisamos realizar uma pequena reorganização do programa. O método de enfileirar o som e reproduzi-lo usando um fluxo separado funcionou muito bem: tornou o código mais gerenciável e mais modular. Antes de começarmos a sincronizar o vídeo com o áudio, precisamos simplificar o código. Na próxima série, produziremos fluxos de controle!






Lição 4: Vários Threads


Lista completa tutorial04.c
// tutorial04.c
// A pedagogical video player that will stream through every video frame as fast as it can,
// and play audio (out of sync).
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial04 tutorial04.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>
#include <math.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)

#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)

#define VIDEO_PICTURE_QUEUE_SIZE 1

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;


typedef struct VideoPicture {
  SDL_Overlay *bmp;
  int width, height; /* source height & width */
  int allocated;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;
  AVStream        *audio_st;
  AVCodecContext  *audio_ctx;
  PacketQueue     audioq;
  uint8_t         audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  unsigned int    audio_buf_size;
  unsigned int    audio_buf_index;
  AVFrame         audio_frame;
  AVPacket        audio_pkt;
  uint8_t         *audio_pkt_data;
  int             audio_pkt_size;
  AVStream        *video_st;
  AVCodecContext  *video_ctx;
  PacketQueue     videoq;
  struct SwsContext *sws_ctx;

  VideoPicture    pictq[VIDEO_PICTURE_QUEUE_SIZE];
  int             pictq_size, pictq_rindex, pictq_windex;
  SDL_mutex       *pictq_mutex;
  SDL_cond        *pictq_cond;
  
  SDL_Thread      *parse_tid;
  SDL_Thread      *video_tid;

  char            filename[1024];
  int             quit;
} VideoState;

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
VideoState *global_video_state;

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(av_dup_packet(pkt) < 0) {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;
  
  SDL_LockMutex(q->mutex);

  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;

  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(global_video_state->quit) {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1) {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
	q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size) {

  int len1, data_size = 0;
  AVPacket *pkt = &is->audio_pkt;

  for(;;) {
    while(is->audio_pkt_size > 0) {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
      if(len1 < 0) {
	/* if error, skip frame */
	is->audio_pkt_size = 0;
	break;
      }
      data_size = 0;
      if(got_frame) {
	data_size = av_samples_get_buffer_size(NULL, 
					       is->audio_ctx->channels,
					       is->audio_frame.nb_samples,
					       is->audio_ctx->sample_fmt,
					       1);
	assert(data_size <= buf_size);
	memcpy(audio_buf, is->audio_frame.data[0], data_size);
      }
      is->audio_pkt_data += len1;
      is->audio_pkt_size -= len1;
      if(data_size <= 0) {
	/* No data yet, get more frames */
	continue;
      }
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt->data)
      av_free_packet(pkt);

    if(is->quit) {
      return -1;
    }
    /* next packet */
    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    is->audio_pkt_data = pkt->data;
    is->audio_pkt_size = pkt->size;
  }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {

  VideoState *is = (VideoState *)userdata;
  int len1, audio_size;

  while(len > 0) {
    if(is->audio_buf_index >= is->audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf));
      if(audio_size < 0) {
	/* If error, output silence */
	is->audio_buf_size = 1024;
	memset(is->audio_buf, 0, is->audio_buf_size);
      } else {
	is->audio_buf_size = audio_size;
      }
      is->audio_buf_index = 0;
    }
    len1 = is->audio_buf_size - is->audio_buf_index;
    if(len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
    len -= len1;
    stream += len1;
    is->audio_buf_index += len1;
  }
}

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
  SDL_Event event;
  event.type = FF_REFRESH_EVENT;
  event.user.data1 = opaque;
  SDL_PushEvent(&event);
  return 0; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
  SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

void video_display(VideoState *is) {

  SDL_Rect rect;
  VideoPicture *vp;
  float aspect_ratio;
  int w, h, x, y;
  int i;

  vp = &is->pictq[is->pictq_rindex];
  if(vp->bmp) {
    if(is->video_ctx->sample_aspect_ratio.num == 0) {
      aspect_ratio = 0;
    } else {
      aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
	is->video_ctx->width / is->video_ctx->height;
    }
    if(aspect_ratio <= 0.0) {
      aspect_ratio = (float)is->video_ctx->width /
	(float)is->video_ctx->height;
    }
    h = screen->h;
    w = ((int)rint(h * aspect_ratio)) & -3;
    if(w > screen->w) {
      w = screen->w;
      h = ((int)rint(w / aspect_ratio)) & -3;
    }
    x = (screen->w - w) / 2;
    y = (screen->h - h) / 2;
    
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;
    SDL_LockMutex(screen_mutex);
    SDL_DisplayYUVOverlay(vp->bmp, &rect);
    SDL_UnlockMutex(screen_mutex);

  }
}

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];
      /* Now, normally here goes a ton of code
	 about timing, etc. we're just going to
	 guess at a delay for now. You can
	 increase and decrease this value and hard code
	 the timing - but I don't suggest that ;)
	 We'll learn how to do it for real later.
      */
      schedule_refresh(is, 40);
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}
      
void alloc_picture(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;

  vp = &is->pictq[is->pictq_windex];
  if(vp->bmp) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  SDL_LockMutex(screen_mutex);
  vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
				 is->video_ctx->height,
				 SDL_YV12_OVERLAY,
				 screen);
  SDL_UnlockMutex(screen_mutex);

  vp->width = is->video_ctx->width;
  vp->height = is->video_ctx->height;
  vp->allocated = 1;

}

int queue_picture(VideoState *is, AVFrame *pFrame) {

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

  /* wait until we have space for a new pic */
  SDL_LockMutex(is->pictq_mutex);
  while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
	!is->quit) {
    SDL_CondWait(is->pictq_cond, is->pictq_mutex);
  }
  SDL_UnlockMutex(is->pictq_mutex);

  if(is->quit)
    return -1;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  if(!vp->bmp ||
     vp->width != is->video_ctx->width ||
     vp->height != is->video_ctx->height) {
    SDL_Event event;

    vp->allocated = 0;
    alloc_picture(is);
    if(is->quit) {
      return -1;
    }
  }

  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    pict.data[0] = vp->bmp->pixels[0];
    pict.data[1] = vp->bmp->pixels[2];
    pict.data[2] = vp->bmp->pixels[1];
    
    pict.linesize[0] = vp->bmp->pitches[0];
    pict.linesize[1] = vp->bmp->pitches[2];
    pict.linesize[2] = vp->bmp->pitches[1];
    
    // Convert the image into YUV format that SDL uses
    sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, is->video_ctx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(vp->bmp);
    /* now we inform our display thread that we have a pic ready */
    if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
      is->pictq_windex = 0;
    }
    SDL_LockMutex(is->pictq_mutex);
    is->pictq_size++;
    SDL_UnlockMutex(is->pictq_mutex);
  }
  return 0;
}

int video_thread(void *arg) {
  VideoState *is = (VideoState *)arg;
  AVPacket pkt1, *packet = &pkt1;
  int frameFinished;
  AVFrame *pFrame;

  pFrame = av_frame_alloc();

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    // Decode video frame
    avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);
    // Did we get a video frame?
    if(frameFinished) {
      if(queue_picture(is, pFrame) < 0) {
	break;
      }      
    }
    av_free_packet(packet);
  }
  av_frame_free(&pFrame);
  return 0;
}

int stream_component_open(VideoState *is, int stream_index) {

  AVFormatContext *pFormatCtx = is->pFormatCtx;
  AVCodecContext *codecCtx = NULL;
  AVCodec *codec = NULL;
  SDL_AudioSpec wanted_spec, spec;

  if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
    return -1;
  }

  codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
  if(!codec) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  codecCtx = avcodec_alloc_context3(codec);
  if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    wanted_spec.freq = codecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = codecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = is;
    
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
      fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
      return -1;
    }
  }
  if(avcodec_open2(codecCtx, codec, NULL) < 0) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  switch(codecCtx->codec_type) {
  case AVMEDIA_TYPE_AUDIO:
    is->audioStream = stream_index;
    is->audio_st = pFormatCtx->streams[stream_index];
    is->audio_ctx = codecCtx;
    is->audio_buf_size = 0;
    is->audio_buf_index = 0;
    memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
    packet_queue_init(&is->audioq);
    SDL_PauseAudio(0);
    break;
  case AVMEDIA_TYPE_VIDEO:
    is->videoStream = stream_index;
    is->video_st = pFormatCtx->streams[stream_index];
    is->video_ctx = codecCtx;
    packet_queue_init(&is->videoq);
    is->video_tid = SDL_CreateThread(video_thread, is);
    is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
				 is->video_ctx->pix_fmt, is->video_ctx->width,
				 is->video_ctx->height, PIX_FMT_YUV420P,
				 SWS_BILINEAR, NULL, NULL, NULL
				 );
    break;
  default:
    break;
  }
}

int decode_thread(void *arg) {

  VideoState *is = (VideoState *)arg;
  AVFormatContext *pFormatCtx;
  AVPacket pkt1, *packet = &pkt1;

  int video_index = -1;
  int audio_index = -1;
  int i;

  is->videoStream=-1;
  is->audioStream=-1;

  global_video_state = is;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

  for(i=0; i<pFormatCtx->nb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       video_index < 0) {
      video_index=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audio_index < 0) {
      audio_index=i;
    }
  }
  if(audio_index >= 0) {
    stream_component_open(is, audio_index);
  }
  if(video_index >= 0) {
    stream_component_open(is, video_index);
  }   

  if(is->videoStream < 0 || is->audioStream < 0) {
    fprintf(stderr, "%s: could not open codecs\n", is->filename);
    goto fail;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    if(is->audioq.size > MAX_AUDIOQ_SIZE ||
       is->videoq.size > MAX_VIDEOQ_SIZE) {
      SDL_Delay(10);
      continue;
    }
    if(av_read_frame(is->pFormatCtx, packet) < 0) {
      if(is->pFormatCtx->pb->error == 0) {
	SDL_Delay(100); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    if(packet->stream_index == is->videoStream) {
      packet_queue_put(&is->videoq, packet);
    } else if(packet->stream_index == is->audioStream) {
      packet_queue_put(&is->audioq, packet);
    } else {
      av_free_packet(packet);
    }
  }
  /* all done - wait for it */
  while(!is->quit) {
    SDL_Delay(100);
  }

 fail:
  if(1){
    SDL_Event event;
    event.type = FF_QUIT_EVENT;
    event.user.data1 = is;
    SDL_PushEvent(&event);
  }
  return 0;
}

int main(int argc, char *argv[]) {

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
        screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  screen_mutex = SDL_CreateMutex();

  av_strlcpy(is->filename, argv[1], sizeof(is->filename));

  is->pictq_mutex = SDL_CreateMutex();
  is->pictq_cond = SDL_CreateCond();

  schedule_refresh(is, 40);

  is->parse_tid = SDL_CreateThread(decode_thread, is);
  if(!is->parse_tid) {
    av_free(is);
    return -1;
  }
  for(;;) {

    SDL_WaitEvent(&event);
    switch(event.type) {
    case FF_QUIT_EVENT:
    case SDL_QUIT:
      is->quit = 1;
      SDL_Quit();
      return 0;
      break;
    case FF_REFRESH_EVENT:
      video_refresh_timer(event.user.data1);
      break;
    default:
      break;
    }
  }
  return 0;

}

Visão geral


Na última vez, adicionamos suporte de áudio usando os recursos de áudio SDL. A SDL lançou um encadeamento retornando chamadas para a função que definimos cada vez que um som era necessário. Agora vamos fazer o mesmo com a exibição do vídeo. Isso torna o código mais modular e mais fácil de trabalhar - especialmente se você deseja adicionar a sincronização. Então por onde começamos?

Observe que nossa função principal lida muito: ela percorre o loop de eventos, lê pacotes e decodifica o vídeo. O que vamos fazer é dividir tudo em partes: teremos um fluxo responsável pela decodificação de pacotes; esses pacotes são adicionados à fila e lidos pelos fluxos de áudio e vídeo correspondentes. Já sintonizamos o fluxo de áudio conforme necessário; com um fluxo de vídeo, será um pouco mais difícil, pois teremos que garantir que o vídeo seja exibido por nós mesmos. Adicionaremos o código de exibição real ao loop principal. Mas, em vez de mostrar o vídeo toda vez que executamos o loop, integramos a exibição do vídeo ao loop de eventos. A idéia é decodificar o vídeo, salvar o quadro recebido em outra fila e criar seu próprio evento ( FF_REFRESH_EVENT), que adicionamos ao sistema de eventos e, quando nosso loop de eventos vir esse evento, ele exibirá o próximo quadro na fila. Aqui está uma ilustração ASCII conveniente do que está acontecendo:


O principal motivo para mover o controle de exibição de vídeo através do loop de eventos é que, com o fluxo SDL_Delay , podemos controlar com precisão quando o próximo quadro de vídeo aparece na tela. Quando finalmente sincronizarmos o vídeo na próxima lição, basta adicionar um código que agende a próxima atualização de vídeo para que a imagem correta apareça na tela na hora certa.

Simplifique o código


Vamos limpar um pouco o código. Temos todas essas informações sobre codecs de áudio e vídeo e vamos adicionar filas, buffers e Deus sabe o que mais. Todas essas coisas são para uma certa unidade lógica, a saber - para o filme. Portanto, pretendemos criar uma grande estrutura contendo todas essas informações chamadas VideoState .

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;
  AVStream        *audio_st;
  AVCodecContext  *audio_ctx;
  PacketQueue     audioq;
  uint8_t         audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  unsigned int    audio_buf_size;
  unsigned int    audio_buf_index;
  AVPacket        audio_pkt;
  uint8_t         *audio_pkt_data;
  int             audio_pkt_size;
  AVStream        *video_st;
  AVCodecContext  *video_ctx;
  PacketQueue     videoq;

  VideoPicture    pictq[VIDEO_PICTURE_QUEUE_SIZE];
  int             pictq_size, pictq_rindex, pictq_windex;
  SDL_mutex       *pictq_mutex;
  SDL_cond        *pictq_cond;
  
  SDL_Thread      *parse_tid;
  SDL_Thread      *video_tid;

  char            filename[1024];
  int             quit;
} VideoState;

Aqui vemos dicas do que vamos conseguir no final. Primeiro, vemos as informações básicas - o contexto do formato e os índices do fluxo de áudio e vídeo, bem como os objetos AVStream correspondentes . Então vemos que alguns desses buffers de áudio são movidos para essa estrutura. Eles ( audio_buf , audio_buf_size etc.) destinavam-se a informações sobre o áudio que ainda estava lá (ou estava ausente). Adicionamos outra fila para vídeo e um buffer (que será usado como fila; para isso, não precisamos de filas extravagantes) para quadros decodificados (salvos como uma sobreposição). Estrutura VideoPicture- esta é nossa própria criação (veremos o que haverá nela quando chegarmos a ela). Você também pode notar que alocamos ponteiros para dois fluxos adicionais que criaremos, além de um sinalizador de saída e um nome de arquivo de filme.

Então, agora voltamos à função principal para ver como isso muda nosso programa. Vamos configurar nossa estrutura VideoState :

int main(int argc, char *argv[]) {

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

av_mallocz () é uma boa função que irá alocar memória para nós e zerá-la.

Em seguida, inicializamos nossos bloqueios para o buffer de exibição ( pictq ), porque, como o loop de eventos chama nossa função de exibição - lembre-se, a função de exibição recupera quadros pré-decodificados do pictq . Ao mesmo tempo, nosso decodificador de vídeo incluirá informações - não sabemos quem chega primeiro. Espero que você entenda que esta é uma condição clássica de corrida. Portanto, nós o distribuímos agora antes de iniciar qualquer tópico. Vamos também copiar o nome do nosso filme no VideoState :

av_strlcpy(is->filename, argv[1], sizeof(is->filename));

is->pictq_mutex = SDL_CreateMutex();
is->pictq_cond = SDL_CreateCond();

av_strlcpy é uma função do FFmpeg que executa algumas verificações adicionais de borda além do strncpy .

Nosso primeiro tópico


Vamos executar nossos threads e fazer algo real:

schedule_refresh(is, 40);

is->parse_tid = SDL_CreateThread(decode_thread, is);
if(!is->parse_tid) {
  av_free(is);
  return -1;
}

schedule_refresh é uma função que definiremos mais adiante. O que ela faz é dizer ao sistema para produzir FF_REFRESH_EVENT após o número especificado de milissegundos. Isso, por sua vez, chama a função de atualização de vídeo quando a vemos na fila de eventos. Mas agora vamos ver SDL_CreateThread ().

SDL_CreateThread () faz exatamente isso - gera um novo thread que tem acesso total a toda a memória do processo original e inicia o thread executado pela função que fornecemos. Esta função também transmitirá dados definidos pelo usuário. Nesse caso, chamamos decode_thread () e anexamos nossa estrutura VideoState. Não há nada de novo na primeira metade da função; apenas faz o trabalho de abrir o arquivo e encontrar o índice dos fluxos de áudio e vídeo. A única coisa que fazemos de maneira diferente é manter o contexto do formato em nossa grande estrutura. Depois de encontrarmos nossos índices de fluxo, chamamos outra função que definimos, stream_component_open (). Essa é uma maneira bastante natural de separar e, como fazemos muitas coisas semelhantes para configurar o codec de vídeo e áudio, reutilizamos algum código, tornando-o uma função.

Função Stream_component_open() É o local onde descobrimos nosso decodificador de codec, configuramos os parâmetros de som, salvamos informações importantes em nossa grande estrutura e lançamos nossos fluxos de áudio e vídeo. Aqui também inserimos outros parâmetros, como o uso forçado do codec em vez de sua detecção automática, etc. Como isso:

int stream_component_open(VideoState *is, int stream_index) {

  AVFormatContext *pFormatCtx = is->pFormatCtx;
  AVCodecContext *codecCtx;
  AVCodec *codec;
  SDL_AudioSpec wanted_spec, spec;

  if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
    return -1;
  }

  codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
  if(!codec) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  codecCtx = avcodec_alloc_context3(codec);
  if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    wanted_spec.freq = codecCtx->sample_rate;
    /* ...etc... */
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = is;
    
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
      fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
      return -1;
    }
  }
  if(avcodec_open2(codecCtx, codec, NULL) < 0) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  switch(codecCtx->codec_type) {
  case AVMEDIA_TYPE_AUDIO:
    is->audioStream = stream_index;
    is->audio_st = pFormatCtx->streams[stream_index];
    is->audio_ctx = codecCtx;
    is->audio_buf_size = 0;
    is->audio_buf_index = 0;
    memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
    packet_queue_init(&is->audioq);
    SDL_PauseAudio(0);
    break;
  case AVMEDIA_TYPE_VIDEO:
    is->videoStream = stream_index;
    is->video_st = pFormatCtx->streams[stream_index];
    is->video_ctx = codecCtx;
    
    packet_queue_init(&is->videoq);
    is->video_tid = SDL_CreateThread(video_thread, is);
    is->sws_ctx = sws_getContext(is->video_st->codec->width, is->video_st->codec->height,
				 is->video_st->codec->pix_fmt, is->video_st->codec->width,
				 is->video_st->codec->height, PIX_FMT_YUV420P,
				 SWS_BILINEAR, NULL, NULL, NULL
				 );
    break;
  default:
    break;
  }
}

É quase o mesmo que o código que tínhamos antes, exceto que agora ele é generalizado para áudio e vídeo. Observe que, em vez de aCodecCtx, configuramos nossa grande estrutura como dados do usuário para nosso retorno de chamada em áudio. Também salvamos os fluxos como audio_st e video_st . Também adicionamos nossa fila de vídeos e a configuramos exatamente como nossa fila de áudio. A linha inferior é executar fluxos de vídeo e áudio. Esses bits fazem o seguinte:

    SDL_PauseAudio(0);
    break;

/* ...... */

    is->video_tid = SDL_CreateThread(video_thread, is);

Lembre-se de SDL_PauseAudio () da última lição. SDL_CreateThread () é usado da mesma maneira. De volta à nossa função video_thread ().

Antes disso, vamos voltar para a segunda metade da nossa função decode_thread (). Essencialmente, é apenas um loop for que lê um pacote e o coloca na fila certa:

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    if(is->audioq.size > MAX_AUDIOQ_SIZE ||
       is->videoq.size > MAX_VIDEOQ_SIZE) {
      SDL_Delay(10);
      continue;
    }
    if(av_read_frame(is->pFormatCtx, packet) < 0) {
      if((is->pFormatCtx->pb->error) == 0) {
	SDL_Delay(100); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    if(packet->stream_index == is->videoStream) {
      packet_queue_put(&is->videoq, packet);
    } else if(packet->stream_index == is->audioStream) {
      packet_queue_put(&is->audioq, packet);
    } else {
      av_free_packet(packet);
    }
  }

Não há nada realmente novo aqui, exceto que agora temos um tamanho máximo para nossa fila de áudio e vídeo e adicionamos a verificação de erros de leitura. O contexto do formato possui uma estrutura ByteIOContext chamada pb . O ByteIOContext é uma estrutura que basicamente armazena todas as informações sobre arquivos de baixo nível.

Após o nosso loop for, temos todo o código para aguardar o restante do programa concluir ou informar sobre ele. Esse código é instrutivo porque mostra como enviamos eventos - algo que precisaremos posteriormente para exibir o vídeo:

  while(!is->quit) {
    SDL_Delay(100);
  }

 fail:
  if(1){
    SDL_Event event;
    event.type = FF_QUIT_EVENT;
    event.user.data1 = is;
    SDL_PushEvent(&event);
  }
  return 0;

Obtemos valores para eventos personalizados usando a constante SDL SDL_USEREVENT . O primeiro evento do usuário deve ser definido como SDL_USEREVENT , o próximo SDL_USEREVENT + 1 etc. FF_QUIT_EVENT é definido em nosso programa como SDL_USEREVENT + 1 . Também podemos passar dados do usuário, se necessário, e aqui passamos o ponteiro para uma grande estrutura. Finalmente, chamamos SDL_PushEvent (). No nosso comutador de loop de eventos, colocamos isso na seção SDL_QUIT_EVENTque tínhamos antes. Veremos nosso ciclo de eventos com mais detalhes; por enquanto, apenas certifique-se de que, quando pressionamos FF_QUIT_EVENT , o capturamos mais tarde e alternamos o sinalizador de saída.

Quadro de recebimento: video_thread


Depois de preparar o codec, você pode iniciar o fluxo de vídeo. Esse fluxo lê pacotes da fila de vídeos, decodifica o vídeo em quadros e chama a função queue_picture para colocar o quadro processado na fila de imagens:

int video_thread(void *arg) {
  VideoState *is = (VideoState *)arg;
  AVPacket pkt1, *packet = &pkt1;
  int frameFinished;
  AVFrame *pFrame;

  pFrame = av_frame_alloc();

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    // Decode video frame
    avcodec_decode_video2(is->video_st->codec, pFrame, &frameFinished, packet);

    // Did we get a video frame?
    if(frameFinished) {
      if(queue_picture(is, pFrame) < 0) {
	break;
      }
    }
    av_free_packet(packet);
  }
  av_free(pFrame);
  return 0;
}

A maior parte dessa função deve ser entendida até agora. Acabamos de copiar a função avcodec_decode_video2 aqui , simplesmente substituindo alguns argumentos; por exemplo, temos um AVStream armazenado em nossa grande estrutura, portanto, obtemos nosso codec a partir daí. Apenas continuamos a receber pacotes da nossa fila de vídeos até que alguém nos diga para sair ou encontrarmos um erro.

Quadro de fila


Vamos dar uma olhada na função que armazena nosso pFrame decodificado em nossa fila de imagens. Como nossa fila de imagens é uma sobreposição de SDL (presumivelmente para permitir que a função de exibição de vídeo execute o mínimo possível de computação), precisamos converter nosso quadro nela. Os dados que armazenamos na fila de imagens são a estrutura que criamos:

typedef struct VideoPicture {
  SDL_Overlay *bmp;
  int width, height; /* source height & width */
  int allocated;
} VideoPicture;

Nossa grande estrutura contém um buffer desses arquivos, onde podemos armazená-los. No entanto, precisamos distribuir o SDL_Overlay por conta própria (preste atenção ao sinalizador atribuído, que mostra se o fizemos ou não).

Para usar essa fila, temos dois ponteiros - o índice de gravação e o índice de leitura. Também rastreamos quantas imagens reais existem no buffer. Para gravar na fila, primeiro esperamos até que nosso buffer seja limpo, para termos um local para armazenar nossa VideoPicture . Em seguida, verificamos se definimos a sobreposição em nosso índice de registros? Caso contrário, você precisará alocar memória. Também devemos realocar o buffer se o tamanho da janela mudou!

int queue_picture(VideoState *is, AVFrame *pFrame) {

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

  /* wait until we have space for a new pic */
  SDL_LockMutex(is->pictq_mutex);
  while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
	!is->quit) {
    SDL_CondWait(is->pictq_cond, is->pictq_mutex);
  }
  SDL_UnlockMutex(is->pictq_mutex);

  if(is->quit)
    return -1;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  if(!vp->bmp ||
     vp->width != is->video_st->codec->width ||
     vp->height != is->video_st->codec->height) {
    SDL_Event event;

    vp->allocated = 0;
    alloc_picture(is);
    if(is->quit) {
      return -1;
    }
  }

Vamos dar uma olhada na função assign_picture ():

void alloc_picture(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;

  vp = &is->pictq[is->pictq_windex];
  if(vp->bmp) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  SDL_LockMutex(screen_mutex);
  vp->bmp = SDL_CreateYUVOverlay(is->video_st->codec->width,
				 is->video_st->codec->height,
				 SDL_YV12_OVERLAY,
				 screen);
  SDL_UnlockMutex(screen_mutex);
  vp->width = is->video_st->codec->width;
  vp->height = is->video_st->codec->height;  
  vp->allocated = 1;
}

Você deve reconhecer a função SDL_CreateYUVOverlay , que movemos do nosso loop principal para esta seção. Este código deve estar razoavelmente claro até agora. No entanto, agora temos um bloqueio mutex, porque dois threads não podem gravar informações simultaneamente na tela! Isso não permitirá que nossa função assign_picture interfira em outra função que exibirá a imagem. (Criamos esse bloqueio como uma variável global e o inicializamos em main (); veja o código.) Lembre-se de que mantemos a largura e a altura na estrutura VideoPicture , porque precisamos garantir que o tamanho do vídeo não seja alterado por algum motivo.
Ok, nós resolvemos isso e temos nossa sobreposição YUV, dedicado e pronto para receber a imagem. Vamos voltar para queue_picture e ver o código para copiar o quadro para a sobreposição. Esta parte deve ser familiar para você:

int queue_picture(VideoState *is, AVFrame *pFrame) {

  /* Allocate a frame if we need it... */
  /* ... */
  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    pict.data[0] = vp->bmp->pixels[0];
    pict.data[1] = vp->bmp->pixels[2];
    pict.data[2] = vp->bmp->pixels[1];
    
    pict.linesize[0] = vp->bmp->pitches[0];
    pict.linesize[1] = vp->bmp->pitches[2];
    pict.linesize[2] = vp->bmp->pitches[1];
    
    // Convert the image into YUV format that SDL uses
    sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, is->video_st->codec->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(vp->bmp);
    /* now we inform our display thread that we have a pic ready */
    if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
      is->pictq_windex = 0;
    }
    SDL_LockMutex(is->pictq_mutex);
    is->pictq_size++;
    SDL_UnlockMutex(is->pictq_mutex);
  }
  return 0;
}

Aqui, a maioria é apenas o código que usamos anteriormente para preencher a sobreposição de YUV com nosso quadro. O último bit simplesmente "adiciona" nosso valor à fila. A fila funciona, os valores são adicionados a ela até estarem cheios e a leitura ocorre enquanto há pelo menos algo nela. Portanto, tudo depende do valor de is -> pictq_size , o que exige que seja bloqueado. Então, o que estamos fazendo aqui: aumente o ponteiro do registro (e, se necessário, inicie novamente), depois bloqueie a fila e aumente seu tamanho. Agora, nosso leitor saberá que há mais informações sobre a fila e, se isso deixar nossa fila cheia, e nosso gravador saberá sobre isso.

Exibição de vídeo


Isso é tudo para o nosso segmento de vídeo! Agora, concluímos todos os threads gratuitos, exceto um - lembra como chamamos a função schedule_refresh () há muito tempo ? Veja o que realmente aconteceu:

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
  SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

SDL_AddTimer () é uma função SDL que simplesmente executa um retorno de chamada para uma função definida pelo usuário após um determinado número de milissegundos (e, se necessário, transfere alguns dados definidos pelo usuário). Usaremos essa função para agendar atualizações de vídeo - cada vez que a chamamos, ele define um timer que aciona um evento, o que, por sua vez, fará com que nossa função main () chame uma função que extrai um quadro da imagem da fila e exibe dela! Ufa! Três "quais / quais / quais" em uma frase! Então, vamos fazer a primeira coisa a fazer - disparar este evento. Isso nos envia para:

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
  SDL_Event event;
  event.type = FF_REFRESH_EVENT;
  event.user.data1 = opaque;
  SDL_PushEvent(&event);
  return 0; /* 0 means stop timer */
}

O evento é lançado pelo nosso velho amigo. FF_REFRESH_EVENT é definido aqui como SDL_USEREVENT + 1 . Deve-se notar que, quando retornamos 0, o SDL para o temporizador, para que o retorno de chamada não seja executado novamente.

Agora que chamamos FF_REFRESH_EVENT novamente , precisamos processá-lo em nosso loop de eventos:

for(;;) {

  SDL_WaitEvent(&event);
  switch(event.type) {
  /* ... */
  case FF_REFRESH_EVENT:
    video_refresh_timer(event.user.data1);
    break;

o que nos envia aqui a essa função, que realmente extrai dados da nossa fila de imagens:

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];
      /* Timing code goes here */

      schedule_refresh(is, 80);
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}

No momento, essa função é bem simples: processa a fila enquanto temos algo, define um temporizador para exibir o próximo quadro de vídeo, chama video_display para realmente mostrar o vídeo na tela, depois aumenta o contador na fila e reduz seu tamanho. Você pode perceber que não estamos realmente fazendo nada com o vp nessa função, e aqui está o porquê: isso está à frente. Mas um pouco depois. Vamos usá-lo para acessar informações de tempo quando começarmos a sincronizar vídeo com áudio. Aqui, dê uma olhada no local no código onde está escrito o comentário "Código de temporização aqui". Nesta seção, descobriremos quanto tempo devemos mostrar o próximo quadro de vídeo e, em seguida, insira esse valor na função schedule_refresh() No momento, apenas inserimos um valor fictício de 80. Tecnicamente, você pode adivinhar e verificar esse valor e recompilá-lo para cada filme, mas: 1) ele começará a desacelerar depois de um tempo e 2) é bem estúpido. Embora, no futuro, retornaremos a esse ponto.

Estamos quase terminando. Resta fazer apenas uma coisa: mostrar o vídeo! Aqui está a função video_display :

void video_display(VideoState *is) {

  SDL_Rect rect;
  VideoPicture *vp;
  float aspect_ratio;
  int w, h, x, y;
  int i;

  vp = &is->pictq[is->pictq_rindex];
  if(vp->bmp) {
    if(is->video_st->codec->sample_aspect_ratio.num == 0) {
      aspect_ratio = 0;
    } else {
      aspect_ratio = av_q2d(is->video_st->codec->sample_aspect_ratio) *
	is->video_st->codec->width / is->video_st->codec->height;
    }
    if(aspect_ratio <= 0.0) {
      aspect_ratio = (float)is->video_st->codec->width /
	(float)is->video_st->codec->height;
    }
    h = screen->h;
    w = ((int)rint(h * aspect_ratio)) & -3;
    if(w > screen->w) {
      w = screen->w;
      h = ((int)rint(w / aspect_ratio)) & -3;
    }
    x = (screen->w - w) / 2;
    y = (screen->h - h) / 2;
    
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;
    SDL_LockMutex(screen_mutex);
    SDL_DisplayYUVOverlay(vp->bmp, &rect);
    SDL_UnlockMutex(screen_mutex);
  }
}

Como a tela pode ter qualquer tamanho (instalamos 640x480 e existem maneiras de configurá-la para que o usuário seja redimensionado), você precisa determinar dinamicamente o tamanho da área retangular do filme. Então, primeiro você precisa descobrir a proporção do filme, apenas a largura dividida pela altura. Alguns codecs terão uma proporção estranha da amostra, que é simplesmente a largura / altura de um pixel ou amostra. Como os valores de altura e largura em nosso contexto de codec são medidos em pixels, a proporção real é igual à proporção multiplicada pela proporção da amostra. Alguns codecs mostrarão uma proporção de 0, o que significa que cada pixel simplesmente tem um tamanho de 1x1. Então escalamos o filme de tal maneirapara que caiba na tela o máximo possível. Reversão de bits& -3 simplesmente arredonda o valor para o múltiplo mais próximo de quatro. Em seguida, centralize o filme e chame SDL_DisplayYUVOverlay () para garantir que o mutex da tela seja usado para acessá-lo.

E é tudo? Nós terminamos? Você ainda precisa reescrever o código de áudio para usar o novo VideoStruct , mas essas são alterações triviais que podem ser vistas no código de amostra. A última coisa que precisamos fazer é alterar nosso retorno de chamada para a função de retorno de chamada de saída interna no FFmpeg:

VideoState *global_video_state;

int decode_interrupt_cb(void) {
  return (global_video_state && global_video_state->quit);
}

Defina global_video_state como uma estrutura grande em main ().

Então é isso! Nós compilamos:

gcc -o tutorial04 tutorial04.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

e curta o filme sem sincronizar! Na próxima etapa, finalmente criaremos um reprodutor de vídeo realmente funcional !






Lição 5: sincronização de vídeo


Lista completa tutorial05.c
// tutorial05.c
// A pedagogical video player that really works!
//
// Code based on FFplay, Copyright (c) 2003 Fabrice Bellard, 
// and a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de)
// Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1
// With updates from https://github.com/chelyaev/ffmpeg-tutorial
// Updates tested on:
// LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101, SDL 1.2.15
// on GCC 4.7.2 in Debian February 2015
// Use
//
// gcc -o tutorial05 tutorial05.c -lavformat -lavcodec -lswscale -lz -lm `sdl-config --cflags --libs`
// to build (assuming libavformat and libavcodec are correctly installed, 
// and assuming you have sdl-config. Please refer to SDL docs for your installation.)
//
// Run using
// tutorial04 myvideofile.mpg
//
// to play the video stream on your screen.

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>

#include <SDL.h>
#include <SDL_thread.h>

#ifdef __MINGW32__
#undef main /* Prevents SDL from overriding main() */
#endif

#include <stdio.h>
#include <assert.h>
#include <math.h>

// compatibility with newer API
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
#define av_frame_alloc avcodec_alloc_frame
#define av_frame_free avcodec_free_frame
#endif

#define SDL_AUDIO_BUFFER_SIZE 1024
#define MAX_AUDIO_FRAME_SIZE 192000

#define MAX_AUDIOQ_SIZE (5 * 16 * 1024)
#define MAX_VIDEOQ_SIZE (5 * 256 * 1024)

#define AV_SYNC_THRESHOLD 0.01
#define AV_NOSYNC_THRESHOLD 10.0

#define FF_REFRESH_EVENT (SDL_USEREVENT)
#define FF_QUIT_EVENT (SDL_USEREVENT + 1)

#define VIDEO_PICTURE_QUEUE_SIZE 1

typedef struct PacketQueue {
  AVPacketList *first_pkt, *last_pkt;
  int nb_packets;
  int size;
  SDL_mutex *mutex;
  SDL_cond *cond;
} PacketQueue;


typedef struct VideoPicture {
  SDL_Overlay *bmp;
  int width, height; /* source height & width */
  int allocated;
  double pts;
} VideoPicture;

typedef struct VideoState {

  AVFormatContext *pFormatCtx;
  int             videoStream, audioStream;

  double          audio_clock;
  AVStream        *audio_st;
  AVCodecContext  *audio_ctx;
  PacketQueue     audioq;
  uint8_t         audio_buf[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2];
  unsigned int    audio_buf_size;
  unsigned int    audio_buf_index;
  AVFrame         audio_frame;
  AVPacket        audio_pkt;
  uint8_t         *audio_pkt_data;
  int             audio_pkt_size;
  int             audio_hw_buf_size;  
  double          frame_timer;
  double          frame_last_pts;
  double          frame_last_delay;
  double          video_clock; ///<pts of last decoded frame / predicted pts of next decoded frame
  AVStream        *video_st;
  AVCodecContext  *video_ctx;
  PacketQueue     videoq;
  struct SwsContext *sws_ctx;

  VideoPicture    pictq[VIDEO_PICTURE_QUEUE_SIZE];
  int             pictq_size, pictq_rindex, pictq_windex;
  SDL_mutex       *pictq_mutex;
  SDL_cond        *pictq_cond;
  
  SDL_Thread      *parse_tid;
  SDL_Thread      *video_tid;

  char            filename[1024];
  int             quit;
} VideoState;

SDL_Surface     *screen;
SDL_mutex       *screen_mutex;

/* Since we only have one decoding thread, the Big Struct
   can be global in case we need it. */
VideoState *global_video_state;

void packet_queue_init(PacketQueue *q) {
  memset(q, 0, sizeof(PacketQueue));
  q->mutex = SDL_CreateMutex();
  q->cond = SDL_CreateCond();
}
int packet_queue_put(PacketQueue *q, AVPacket *pkt) {

  AVPacketList *pkt1;
  if(av_dup_packet(pkt) < 0) {
    return -1;
  }
  pkt1 = av_malloc(sizeof(AVPacketList));
  if (!pkt1)
    return -1;
  pkt1->pkt = *pkt;
  pkt1->next = NULL;
  
  SDL_LockMutex(q->mutex);

  if (!q->last_pkt)
    q->first_pkt = pkt1;
  else
    q->last_pkt->next = pkt1;
  q->last_pkt = pkt1;
  q->nb_packets++;
  q->size += pkt1->pkt.size;
  SDL_CondSignal(q->cond);
  
  SDL_UnlockMutex(q->mutex);
  return 0;
}
static int packet_queue_get(PacketQueue *q, AVPacket *pkt, int block)
{
  AVPacketList *pkt1;
  int ret;

  SDL_LockMutex(q->mutex);
  
  for(;;) {
    
    if(global_video_state->quit) {
      ret = -1;
      break;
    }

    pkt1 = q->first_pkt;
    if (pkt1) {
      q->first_pkt = pkt1->next;
      if (!q->first_pkt)
	q->last_pkt = NULL;
      q->nb_packets--;
      q->size -= pkt1->pkt.size;
      *pkt = pkt1->pkt;
      av_free(pkt1);
      ret = 1;
      break;
    } else if (!block) {
      ret = 0;
      break;
    } else {
      SDL_CondWait(q->cond, q->mutex);
    }
  }
  SDL_UnlockMutex(q->mutex);
  return ret;
}

double get_audio_clock(VideoState *is) {
  double pts;
  int hw_buf_size, bytes_per_sec, n;
  
  pts = is->audio_clock; /* maintained in the audio thread */
  hw_buf_size = is->audio_buf_size - is->audio_buf_index;
  bytes_per_sec = 0;
  n = is->audio_ctx->channels * 2;
  if(is->audio_st) {
    bytes_per_sec = is->audio_ctx->sample_rate * n;
  }
  if(bytes_per_sec) {
    pts -= (double)hw_buf_size / bytes_per_sec;
  }
  return pts;
}

int audio_decode_frame(VideoState *is, uint8_t *audio_buf, int buf_size, double *pts_ptr) {

  int len1, data_size = 0;
  AVPacket *pkt = &is->audio_pkt;
  double pts;
  int n;

  for(;;) {
    while(is->audio_pkt_size > 0) {
      int got_frame = 0;
      len1 = avcodec_decode_audio4(is->audio_ctx, &is->audio_frame, &got_frame, pkt);
      if(len1 < 0) {
	/* if error, skip frame */
	is->audio_pkt_size = 0;
	break;
      }
      data_size = 0;
      if(got_frame) {
	data_size = av_samples_get_buffer_size(NULL, 
					       is->audio_ctx->channels,
					       is->audio_frame.nb_samples,
					       is->audio_ctx->sample_fmt,
					       1);
	assert(data_size <= buf_size);
	memcpy(audio_buf, is->audio_frame.data[0], data_size);
      }
      is->audio_pkt_data += len1;
      is->audio_pkt_size -= len1;
      if(data_size <= 0) {
	/* No data yet, get more frames */
	continue;
      }
      pts = is->audio_clock;
      *pts_ptr = pts;
      n = 2 * is->audio_ctx->channels;
      is->audio_clock += (double)data_size /
	(double)(n * is->audio_ctx->sample_rate);
      /* We have data, return it and come back for more later */
      return data_size;
    }
    if(pkt->data)
      av_free_packet(pkt);

    if(is->quit) {
      return -1;
    }
    /* next packet */
    if(packet_queue_get(&is->audioq, pkt, 1) < 0) {
      return -1;
    }
    is->audio_pkt_data = pkt->data;
    is->audio_pkt_size = pkt->size;
    /* if update, update the audio clock w/pts */
    if(pkt->pts != AV_NOPTS_VALUE) {
      is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
    }
  }
}

void audio_callback(void *userdata, Uint8 *stream, int len) {

  VideoState *is = (VideoState *)userdata;
  int len1, audio_size;
  double pts;

  while(len > 0) {
    if(is->audio_buf_index >= is->audio_buf_size) {
      /* We have already sent all our data; get more */
      audio_size = audio_decode_frame(is, is->audio_buf, sizeof(is->audio_buf), &pts);
      if(audio_size < 0) {
	/* If error, output silence */
	is->audio_buf_size = 1024;
	memset(is->audio_buf, 0, is->audio_buf_size);
      } else {
	is->audio_buf_size = audio_size;
      }
      is->audio_buf_index = 0;
    }
    len1 = is->audio_buf_size - is->audio_buf_index;
    if(len1 > len)
      len1 = len;
    memcpy(stream, (uint8_t *)is->audio_buf + is->audio_buf_index, len1);
    len -= len1;
    stream += len1;
    is->audio_buf_index += len1;
  }
}

static Uint32 sdl_refresh_timer_cb(Uint32 interval, void *opaque) {
  SDL_Event event;
  event.type = FF_REFRESH_EVENT;
  event.user.data1 = opaque;
  SDL_PushEvent(&event);
  return 0; /* 0 means stop timer */
}

/* schedule a video refresh in 'delay' ms */
static void schedule_refresh(VideoState *is, int delay) {
  SDL_AddTimer(delay, sdl_refresh_timer_cb, is);
}

void video_display(VideoState *is) {

  SDL_Rect rect;
  VideoPicture *vp;
  float aspect_ratio;
  int w, h, x, y;
  int i;

  vp = &is->pictq[is->pictq_rindex];
  if(vp->bmp) {
    if(is->video_ctx->sample_aspect_ratio.num == 0) {
      aspect_ratio = 0;
    } else {
      aspect_ratio = av_q2d(is->video_ctx->sample_aspect_ratio) *
	is->video_ctx->width / is->video_ctx->height;
    }
    if(aspect_ratio <= 0.0) {
      aspect_ratio = (float)is->video_ctx->width /
	(float)is->video_ctx->height;
    }
    h = screen->h;
    w = ((int)rint(h * aspect_ratio)) & -3;
    if(w > screen->w) {
      w = screen->w;
      h = ((int)rint(w / aspect_ratio)) & -3;
    }
    x = (screen->w - w) / 2;
    y = (screen->h - h) / 2;
    
    rect.x = x;
    rect.y = y;
    rect.w = w;
    rect.h = h;
    SDL_LockMutex(screen_mutex);
    SDL_DisplayYUVOverlay(vp->bmp, &rect);
    SDL_UnlockMutex(screen_mutex);

  }
}

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  double actual_delay, delay, sync_threshold, ref_clock, diff;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];

      delay = vp->pts - is->frame_last_pts; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;

      /* update delay to sync to audio */
      ref_clock = get_audio_clock(is);
      diff = vp->pts - ref_clock;

      /* Skip or repeat the frame. Take delay into account
	 FFPlay still doesn't "know if this is the best guess." */
      sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
      if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
	if(diff <= -sync_threshold) {
	  delay = 0;
	} else if(diff >= sync_threshold) {
	  delay = 2 * delay;
	}
      }
      is->frame_timer += delay;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}
      
void alloc_picture(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;

  vp = &is->pictq[is->pictq_windex];
  if(vp->bmp) {
    // we already have one make another, bigger/smaller
    SDL_FreeYUVOverlay(vp->bmp);
  }
  // Allocate a place to put our YUV image on that screen
  SDL_LockMutex(screen_mutex);
  vp->bmp = SDL_CreateYUVOverlay(is->video_ctx->width,
				 is->video_ctx->height,
				 SDL_YV12_OVERLAY,
				 screen);
  SDL_UnlockMutex(screen_mutex);

  vp->width = is->video_ctx->width;
  vp->height = is->video_ctx->height;
  vp->allocated = 1;

}

int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {

  VideoPicture *vp;
  int dst_pix_fmt;
  AVPicture pict;

  /* wait until we have space for a new pic */
  SDL_LockMutex(is->pictq_mutex);
  while(is->pictq_size >= VIDEO_PICTURE_QUEUE_SIZE &&
	!is->quit) {
    SDL_CondWait(is->pictq_cond, is->pictq_mutex);
  }
  SDL_UnlockMutex(is->pictq_mutex);

  if(is->quit)
    return -1;

  // windex is set to 0 initially
  vp = &is->pictq[is->pictq_windex];

  /* allocate or resize the buffer! */
  if(!vp->bmp ||
     vp->width != is->video_ctx->width ||
     vp->height != is->video_ctx->height) {
    SDL_Event event;

    vp->allocated = 0;
    alloc_picture(is);
    if(is->quit) {
      return -1;
    }
  }

  /* We have a place to put our picture on the queue */

  if(vp->bmp) {

    SDL_LockYUVOverlay(vp->bmp);
    vp->pts = pts;
    
    dst_pix_fmt = PIX_FMT_YUV420P;
    /* point pict at the queue */

    pict.data[0] = vp->bmp->pixels[0];
    pict.data[1] = vp->bmp->pixels[2];
    pict.data[2] = vp->bmp->pixels[1];
    
    pict.linesize[0] = vp->bmp->pitches[0];
    pict.linesize[1] = vp->bmp->pitches[2];
    pict.linesize[2] = vp->bmp->pitches[1];
    
    // Convert the image into YUV format that SDL uses
    sws_scale(is->sws_ctx, (uint8_t const * const *)pFrame->data,
	      pFrame->linesize, 0, is->video_ctx->height,
	      pict.data, pict.linesize);
    
    SDL_UnlockYUVOverlay(vp->bmp);
    /* now we inform our display thread that we have a pic ready */
    if(++is->pictq_windex == VIDEO_PICTURE_QUEUE_SIZE) {
      is->pictq_windex = 0;
    }
    SDL_LockMutex(is->pictq_mutex);
    is->pictq_size++;
    SDL_UnlockMutex(is->pictq_mutex);
  }
  return 0;
}

double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {

  double frame_delay;

  if(pts != 0) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_ctx->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
  is->video_clock += frame_delay;
  return pts;
}

int video_thread(void *arg) {
  VideoState *is = (VideoState *)arg;
  AVPacket pkt1, *packet = &pkt1;
  int frameFinished;
  AVFrame *pFrame;
  double pts;

  pFrame = av_frame_alloc();

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;

    // Decode video frame
    avcodec_decode_video2(is->video_ctx, pFrame, &frameFinished, packet);

    if((pts = av_frame_get_best_effort_timestamp(pFrame)) == AV_NOPTS_VALUE) {
      pts = 0;
    }
    pts *= av_q2d(is->video_st->time_base);

    // Did we get a video frame?
    if(frameFinished) {
      pts = synchronize_video(is, pFrame, pts);
      if(queue_picture(is, pFrame, pts) < 0) {
	break;
      }
    }
    av_free_packet(packet);
  }
  av_frame_free(&pFrame);
  return 0;
}

int stream_component_open(VideoState *is, int stream_index) {

  AVFormatContext *pFormatCtx = is->pFormatCtx;
  AVCodecContext *codecCtx = NULL;
  AVCodec *codec = NULL;
  SDL_AudioSpec wanted_spec, spec;

  if(stream_index < 0 || stream_index >= pFormatCtx->nb_streams) {
    return -1;
  }

  codec = avcodec_find_decoder(pFormatCtx->streams[stream_index]->codec->codec_id);
  if(!codec) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  codecCtx = avcodec_alloc_context3(codec);
  if(avcodec_copy_context(codecCtx, pFormatCtx->streams[stream_index]->codec) != 0) {
    fprintf(stderr, "Couldn't copy codec context");
    return -1; // Error copying codec context
  }


  if(codecCtx->codec_type == AVMEDIA_TYPE_AUDIO) {
    // Set audio settings from codec info
    wanted_spec.freq = codecCtx->sample_rate;
    wanted_spec.format = AUDIO_S16SYS;
    wanted_spec.channels = codecCtx->channels;
    wanted_spec.silence = 0;
    wanted_spec.samples = SDL_AUDIO_BUFFER_SIZE;
    wanted_spec.callback = audio_callback;
    wanted_spec.userdata = is;
    
    if(SDL_OpenAudio(&wanted_spec, &spec) < 0) {
      fprintf(stderr, "SDL_OpenAudio: %s\n", SDL_GetError());
      return -1;
    }
    is->audio_hw_buf_size = spec.size;
  }
  if(avcodec_open2(codecCtx, codec, NULL) < 0) {
    fprintf(stderr, "Unsupported codec!\n");
    return -1;
  }

  switch(codecCtx->codec_type) {
  case AVMEDIA_TYPE_AUDIO:
    is->audioStream = stream_index;
    is->audio_st = pFormatCtx->streams[stream_index];
    is->audio_ctx = codecCtx;
    is->audio_buf_size = 0;
    is->audio_buf_index = 0;
    memset(&is->audio_pkt, 0, sizeof(is->audio_pkt));
    packet_queue_init(&is->audioq);
    SDL_PauseAudio(0);
    break;
  case AVMEDIA_TYPE_VIDEO:
    is->videoStream = stream_index;
    is->video_st = pFormatCtx->streams[stream_index];
    is->video_ctx = codecCtx;

    is->frame_timer = (double)av_gettime() / 1000000.0;
    is->frame_last_delay = 40e-3;
    
    packet_queue_init(&is->videoq);
    is->video_tid = SDL_CreateThread(video_thread, is);
    is->sws_ctx = sws_getContext(is->video_ctx->width, is->video_ctx->height,
				 is->video_ctx->pix_fmt, is->video_ctx->width,
				 is->video_ctx->height, PIX_FMT_YUV420P,
				 SWS_BILINEAR, NULL, NULL, NULL
				 );
    break;
  default:
    break;
  }
}

int decode_thread(void *arg) {

  VideoState *is = (VideoState *)arg;
  AVFormatContext *pFormatCtx;
  AVPacket pkt1, *packet = &pkt1;

  int video_index = -1;
  int audio_index = -1;
  int i;

  is->videoStream=-1;
  is->audioStream=-1;

  global_video_state = is;

  // Open video file
  if(avformat_open_input(&pFormatCtx, is->filename, NULL, NULL)!=0)
    return -1; // Couldn't open file

  is->pFormatCtx = pFormatCtx;
  
  // Retrieve stream information
  if(avformat_find_stream_info(pFormatCtx, NULL)<0)
    return -1; // Couldn't find stream information
  
  // Dump information about file onto standard error
  av_dump_format(pFormatCtx, 0, is->filename, 0);
  
  // Find the first video stream

  for(i=0; i<pFormatCtx->nb_streams; i++) {
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO &&
       video_index < 0) {
      video_index=i;
    }
    if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_AUDIO &&
       audio_index < 0) {
      audio_index=i;
    }
  }
  if(audio_index >= 0) {
    stream_component_open(is, audio_index);
  }
  if(video_index >= 0) {
    stream_component_open(is, video_index);
  }   

  if(is->videoStream < 0 || is->audioStream < 0) {
    fprintf(stderr, "%s: could not open codecs\n", is->filename);
    goto fail;
  }

  // main decode loop

  for(;;) {
    if(is->quit) {
      break;
    }
    // seek stuff goes here
    if(is->audioq.size > MAX_AUDIOQ_SIZE ||
       is->videoq.size > MAX_VIDEOQ_SIZE) {
      SDL_Delay(10);
      continue;
    }
    if(av_read_frame(is->pFormatCtx, packet) < 0) {
      if(is->pFormatCtx->pb->error == 0) {
	SDL_Delay(100); /* no error; wait for user input */
	continue;
      } else {
	break;
      }
    }
    // Is this a packet from the video stream?
    if(packet->stream_index == is->videoStream) {
      packet_queue_put(&is->videoq, packet);
    } else if(packet->stream_index == is->audioStream) {
      packet_queue_put(&is->audioq, packet);
    } else {
      av_free_packet(packet);
    }
  }
  /* all done - wait for it */
  while(!is->quit) {
    SDL_Delay(100);
  }

 fail:
  if(1){
    SDL_Event event;
    event.type = FF_QUIT_EVENT;
    event.user.data1 = is;
    SDL_PushEvent(&event);
  }
  return 0;
}

int main(int argc, char *argv[]) {

  SDL_Event       event;

  VideoState      *is;

  is = av_mallocz(sizeof(VideoState));

  if(argc < 2) {
    fprintf(stderr, "Usage: test <file>\n");
    exit(1);
  }
  // Register all formats and codecs
  av_register_all();
  
  if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER)) {
    fprintf(stderr, "Could not initialize SDL - %s\n", SDL_GetError());
    exit(1);
  }

  // Make a screen to put our video
#ifndef __DARWIN__
        screen = SDL_SetVideoMode(640, 480, 0, 0);
#else
        screen = SDL_SetVideoMode(640, 480, 24, 0);
#endif
  if(!screen) {
    fprintf(stderr, "SDL: could not set video mode - exiting\n");
    exit(1);
  }

  screen_mutex = SDL_CreateMutex();

  av_strlcpy(is->filename, argv[1], sizeof(is->filename));

  is->pictq_mutex = SDL_CreateMutex();
  is->pictq_cond = SDL_CreateCond();

  schedule_refresh(is, 40);

  is->parse_tid = SDL_CreateThread(decode_thread, is);
  if(!is->parse_tid) {
    av_free(is);
    return -1;
  }
  for(;;) {

    SDL_WaitEvent(&event);
    switch(event.type) {
    case FF_QUIT_EVENT:
    case SDL_QUIT:
      is->quit = 1;
      SDL_Quit();
      return 0;
      break;
    case FF_REFRESH_EVENT:
      video_refresh_timer(event.user.data1);
      break;
    default:
      break;
    }
  }
  return 0;

}

ATENÇÃO


Quando acabei de escrever este guia, todo o meu código de sincronização foi retirado da versão do ffplay.c na época . Hoje é um programa completamente diferente, e as atualizações nas bibliotecas FFmpeg (e no próprio ffplay.c) levaram a mudanças fundamentais. Embora esse código ainda funcione, ele já está desatualizado e há muitas outras melhorias que podem ser usadas neste guia.

Como o vídeo é sincronizado


Até agora, tínhamos um reprodutor de filmes quase inútil. Sim, ele reproduz vídeo e sim, reproduz áudio, mas não é bem assim que chamaríamos de filme. Então o que fazemos então?

PTS e DTS


Felizmente, os fluxos de áudio e vídeo contêm informações sobre a rapidez e em que momentos eles devem ser reproduzidos. Os fluxos de áudio têm uma taxa de amostragem e os fluxos de vídeo têm quadros por segundo. No entanto, se apenas sincronizarmos o vídeo contando o número de quadros e multiplicando pela taxa de quadros, há uma boa chance de que ele não seja sincronizado com o som. Portanto, iremos para o outro lado. Pacotes do fluxo pode ter o chamado decodificação tempo selo ( DTS - a partir d ecoding t ime s tampo ) e tempo de apresentação de selo ( PTS - de p resentação t ime stamp ). Para entender esses dois significados, você precisa saber como os filmes são armazenados. Alguns formatos, como MPEG, usam o que chamam de quadros B ( cama e é bidirecional, Inglaterra. Bidirecional ). Dois outros tipos de quadros são chamados quadros I e quadros P ( Eu é interna , i nner , e P meios previsto , p redicted ). Os quadros I contêm a imagem completa. Quadros Pdependem dos quadros I e P anteriores e são diferentes dos quadros anteriores, ou você também pode nomear deltas. Os quadros B são semelhantes aos quadros P, mas dependem das informações contidas nos quadros anteriores e subsequentes! O fato de um quadro não conter a imagem em si, mas diferir de outros quadros - explica por que podemos não ter um quadro finalizado após chamar avcodec_decode_video2 .

Digamos que tenhamos um filme em que 4 quadros nesta sequência: IBBP . Em seguida, precisamos descobrir as informações do último quadro P antes de podermos exibir qualquer um dos dois quadros B anteriores. Por esse motivo , os quadros podem ser armazenados em uma sequência que não corresponde à ordem de exibição real: IPBB. É para isso que servem o carimbo de data / hora de decodificação e o carimbo de hora da apresentação para cada quadro. O registro de data e hora da decodificação nos diz quando precisamos decodificar algo, e o registro de data e hora da apresentação nos diz quando precisamos exibir algo. Portanto, nesse caso, nosso fluxo pode ser assim:

   PTS: 1 4 2 3
   DTS: 1 2 3 4
Fluxo: IPBB

Como regra, PTS e DTS são diferentes apenas quando o fluxo sendo reproduzido contém quadros B.

Quando recebemos um pacote de av_read_frame (), ele contém os valores de PTS e DTS para as informações que estão dentro do pacote. Mas o que realmente precisamos é do PTS de nosso quadro bruto decodificado, caso em que sabemos quando ele precisa ser exibido.

Felizmente, o FFmpeg nos fornece o "melhor carimbo de data / hora possível" que podemos obter usando a função av_frame_get_best_effort_timestamp ().

Sincronização


Para que os quadros sejam exibidos por vez, seria bom saber quando exibir um quadro de vídeo específico. Mas como exatamente fazemos isso? A idéia é a seguinte: depois de mostrarmos o quadro, descobrimos quando o próximo quadro deve ser mostrado. Depois, basta fazer uma pausa, após o que atualizamos o vídeo após esse período de tempo. Como esperado, verificamos o valor PTS do próximo quadro no relógio do sistema para ver quanto tempo deve ser o tempo de espera. Essa abordagem funciona, mas há dois problemas que precisam ser abordados.

Primeiro, a pergunta é: quando será o próximo PTS? Você dirá que pode simplesmente adicionar a frequência de vídeo ao PTS atual - e, em princípio, estará certo. No entanto, algumas variedades de vídeo exigirão a repetição de quadros. Isso significa que você deve repetir o quadro atual um certo número de vezes. Isso pode fazer com que o programa exiba o próximo quadro muito cedo. Isso deve ser levado em consideração.

O segundo problema é que, no programa que escrevemos no momento, o vídeo e o áudio avançam com alegria até que eles se preocupem em sincronizar. Não precisaríamos nos preocupar se tudo por si só funcionasse perfeitamente. Mas seu computador não é perfeito, assim como muitos arquivos de vídeo. Assim, temos três opções: sincronizar áudio com vídeo, sincronizar vídeo com áudio ou sincronizar áudio e vídeo com um relógio externo (por exemplo, com o seu computador). Agora vamos sincronizar o vídeo com o áudio.

Codificação: recebendo um quadro PTS


Agora vamos escrever algo diretamente. Precisamos adicionar mais algumas peças à nossa grande estrutura e faremos da maneira que precisamos. Primeiro, vamos dar uma olhada no nosso tópico de vídeo. Lembre-se de que aqui coletamos pacotes que foram enfileirados pelo nosso fluxo de decodificação? Nesta parte do código, precisamos obter o PTS para o quadro que avcodec_decode_video2 nos forneceu . A primeira maneira de falarmos é obter o DTS do último pacote processado, o que é bastante simples:

  double pts;

  for(;;) {
    if(packet_queue_get(&is->videoq, packet, 1) < 0) {
      // means we quit getting packets
      break;
    }
    pts = 0;
    // Decode video frame
    len1 = avcodec_decode_video2(is->video_st->codec,
                                pFrame, &frameFinished, packet);
    if(packet->dts != AV_NOPTS_VALUE) {
      pts = av_frame_get_best_effort_timestamp(pFrame);
    } else {
      pts = 0;
    }
    pts *= av_q2d(is->video_st->time_base);

Definimos o PTS como zero se não pudermos determinar seu valor.

Bem, isso foi fácil. Nota técnica: como você pode ver, usamos int64 para PTS. Isso ocorre porque o PTS é armazenado como um número inteiro. Este valor é um registro de data e hora que corresponde à dimensão de tempo no timebb . Por exemplo, se o fluxo tiver 24 quadros por segundo, o PTS de 42 indicará que o quadro deve ser usado onde o 42º quadro deve estar, desde que substituamos quadros a cada 1/24 segundo (é claro, isso não será necessariamente o caso). de fato).

Podemos converter esse valor em segundos dividindo pela taxa de quadros. Valor Time_baseo fluxo será igual a 1 dividido pela taxa de quadros (para conteúdo com taxa de quadros fixa); portanto, para obter o PTS em segundos, multiplicamos por time_base .

Código adicional: sincronização e uso do PTS


Então agora temos todo o PTS pronto. Agora vamos cuidar desses dois problemas de sincronização, que foram discutidos um pouco mais alto. Vamos definir uma função synchronize_video que atualizará o PTS para sincronizar com tudo. Esta função, finalmente, também tratará de casos em que não obtemos o valor de PTS para o nosso quadro. Ao mesmo tempo, precisamos rastrear quando o próximo quadro é esperado, para que possamos definir corretamente a taxa de atualização. Podemos fazer isso usando o valor interno video_clock , que rastreia quanto tempo passou para o vídeo. Acrescentamos esse valor à nossa grande estrutura:

typedef struct VideoState {
  double          video_clock; // pts of last decoded frame / predicted pts of next decoded frame

Aqui está a função synchronize_video , que é bastante clara:

double synchronize_video(VideoState *is, AVFrame *src_frame, double pts) {

  double frame_delay;

  if(pts != 0) {
    /* if we have pts, set video clock to it */
    is->video_clock = pts;
  } else {
    /* if we aren't given a pts, set it to the clock */
    pts = is->video_clock;
  }
  /* update the video clock */
  frame_delay = av_q2d(is->video_st->codec->time_base);
  /* if we are repeating a frame, adjust clock accordingly */
  frame_delay += src_frame->repeat_pict * (frame_delay * 0.5);
  is->video_clock += frame_delay;
  return pts;
}

Como você pode ver, levamos em conta quadros repetidos nesta função.

Agora, vamos obter nosso PTS correto e enfileirar o quadro usando queue_picture adicionando um novo argumento pts :

    // Did we get a video frame?
    if(frameFinished) {
      pts = synchronize_video(is, pFrame, pts);
      if(queue_picture(is, pFrame, pts) < 0) {
	break;
      }
    }

A única coisa que muda em queue_picture é que armazenamos esse valor de pontos na estrutura VideoPicture que colocamos na fila. Portanto, devemos adicionar a variável pts à estrutura e adicionar estas linhas de código:

typedef struct VideoPicture {
  ...
  double pts;
}
int queue_picture(VideoState *is, AVFrame *pFrame, double pts) {
  ... stuff ...
  if(vp->bmp) {
    ... convert picture ...
    vp->pts = pts;
    ... alert queue ...
  }

Então agora temos as imagens na fila com os valores corretos de PTS, então vamos dar uma olhada no nosso recurso de atualização de vídeo. Você pode se lembrar da última lição que simplesmente falsificamos e instalamos uma atualização de 80 ms. Bem, agora vamos descobrir o que realmente deveria estar lá.

Nossa estratégia é prever a hora do próximo PTS, simplesmente medindo o tempo entre os pontos atuais e o anterior. Ao mesmo tempo, precisamos sincronizar o vídeo com o áudio. Nós vamos fazer um relógio de áudio.: Um valor interno que monitora a posição do áudio que estamos reproduzindo. É como uma leitura digital em qualquer mp3 player. Como sincronizamos o vídeo com o som, o fluxo de vídeo usa esse valor para descobrir se está muito adiantado ou muito atrasado.

Voltaremos à implementação mais tarde; Agora, vamos assumir que temos a função get_audio_clocko que nos dará tempo no relógio de áudio. Assim que obtemos esse valor, o que precisa ser feito se o vídeo e o áudio não forem sincronizados? Seria tolice apenas tentar pular para o pacote certo através de uma pesquisa ou qualquer outra coisa. Em vez disso, simplesmente ajustamos o valor que calculamos para a próxima atualização: se o PTS estiver muito atrasado em relação ao tempo de áudio, dobraremos o atraso estimado. Se o PTS estiver muito adiantado em relação ao tempo de reprodução, atualizamos o mais rápido possível. Agora que temos a atualização configurada ou o tempo de atraso, comparamos com o relógio do nosso computador, deixando o frame_timer em execução . Este temporizador de quadro resume todos os atrasos estimados durante a reprodução do filme. Em outras palavras, esse frame_timer- Este é o tempo que indica quando exibir o próximo quadro. Simplesmente adicionamos um novo atraso ao temporizador de quadros, comparamos com o horário no relógio do nosso computador e usamos esse valor para planejar a próxima atualização. Isso pode ser um pouco confuso, então leia o código com atenção:

void video_refresh_timer(void *userdata) {

  VideoState *is = (VideoState *)userdata;
  VideoPicture *vp;
  double actual_delay, delay, sync_threshold, ref_clock, diff;
  
  if(is->video_st) {
    if(is->pictq_size == 0) {
      schedule_refresh(is, 1);
    } else {
      vp = &is->pictq[is->pictq_rindex];

      delay = vp->pts - is->frame_last_pts; /* the pts from last time */
      if(delay <= 0 || delay >= 1.0) {
	/* if incorrect delay, use previous one */
	delay = is->frame_last_delay;
      }
      /* save for next time */
      is->frame_last_delay = delay;
      is->frame_last_pts = vp->pts;

      /* update delay to sync to audio */
      ref_clock = get_audio_clock(is);
      diff = vp->pts - ref_clock;

      /* Skip or repeat the frame. Take delay into account
	 FFPlay still doesn't "know if this is the best guess." */
      sync_threshold = (delay > AV_SYNC_THRESHOLD) ? delay : AV_SYNC_THRESHOLD;
      if(fabs(diff) < AV_NOSYNC_THRESHOLD) {
	if(diff <= -sync_threshold) {
	  delay = 0;
	} else if(diff >= sync_threshold) {
	  delay = 2 * delay;
	}
      }
      is->frame_timer += delay;
      /* computer the REAL delay */
      actual_delay = is->frame_timer - (av_gettime() / 1000000.0);
      if(actual_delay < 0.010) {
	/* Really it should skip the picture instead */
	actual_delay = 0.010;
      }
      schedule_refresh(is, (int)(actual_delay * 1000 + 0.5));
      /* show the picture! */
      video_display(is);
      
      /* update queue for next picture! */
      if(++is->pictq_rindex == VIDEO_PICTURE_QUEUE_SIZE) {
	is->pictq_rindex = 0;
      }
      SDL_LockMutex(is->pictq_mutex);
      is->pictq_size--;
      SDL_CondSignal(is->pictq_cond);
      SDL_UnlockMutex(is->pictq_mutex);
    }
  } else {
    schedule_refresh(is, 100);
  }
}

Fazemos várias verificações: em primeiro lugar, garantimos que o atraso entre o PTS atual e o PTS anterior faça sentido. Se não houver necessidade de atraso, o áudio e o vídeo apenas coincidiram nesse ponto e apenas usaram o último atraso. Em seguida, garantimos que o limite de sincronização seja atingido, porque a sincronização perfeita nunca acontece. O FFplay usa um valor de 0,01 para o limite. Também garantimos que o limite de sincronização nunca seja menor que os intervalos entre os valores do PTS. Por fim, defina o valor mínimo de atualização para 10 milissegundos (de fato, parece que eles devem pular o quadro aqui, mas não vamos nos preocupar com isso).

Adicionamos várias variáveis ​​à estrutura grande, portanto, não esqueça de verificar o código. Além disso, não esqueça de inicializar o temporizador de quadros e o atraso inicial do quadro anterior em stream_component_open :

    is->frame_timer = (double)av_gettime() / 1000000.0;
    is->frame_last_delay = 40e-3;

Sincronização: relógio de áudio


Chegou a hora de realizar o relógio de áudio. Podemos atualizar o horário em nossa função audio_decode_frame , onde decodificamos o áudio. Agora lembre-se de que nem sempre processamos um novo pacote toda vez que chamamos essa função; portanto, há duas áreas em que você precisa atualizar o relógio. O primeiro lugar é onde obtemos o novo pacote: basta instalar o relógio de som no pacote PTS. Então, se o pacote tiver vários quadros, economizamos o tempo de reprodução do áudio contando o número de amostras e multiplicando-as por uma determinada frequência de amostragem por segundo. Então, quando temos o pacote:

    /* if update, update the audio clock w/pts */
    if(pkt->pts != AV_NOPTS_VALUE) {
      is->audio_clock = av_q2d(is->audio_st->time_base)*pkt->pts;
    }

E assim que processarmos o pacote:

      /* Keep audio_clock up-to-date */
      pts = is->audio_clock;
      *pts_ptr = pts;
      n = 2 * is->audio_st->codec->channels;
      is->audio_clock += (double)data_size /
	(double)(n * is->audio_st->codec->sample_rate);

Algumas pequenas nuances: o modelo da função foi alterado e agora inclui pts_ptr , portanto, certifique-se de alterá-lo. pts_ptr é o ponteiro que usamos para informar ao audio_callback os pacotes de áudio pts . Isso será usado na próxima vez para sincronizar o áudio com o vídeo.

Agora podemos finalmente implementar nossa função get_audio_clock . Não é tão simples quanto obter o valor é -> audio_clock , se você pensar sobre isso. Observe que definimos o áudio PTS toda vez que o processamos, mas se você observar a função audio_callback, levará algum tempo para mover todos os dados do nosso pacote de áudio para o buffer de saída. Isso significa que o valor em nosso relógio de áudio pode estar muito à frente. Portanto, precisamos verificar quanto precisamos escrever. Aqui está o código completo:

double get_audio_clock(VideoState *is) {
  double pts;
  int hw_buf_size, bytes_per_sec, n;
  
  pts = is->audio_clock; /* maintained in the audio thread */
  hw_buf_size = is->audio_buf_size - is->audio_buf_index;
  bytes_per_sec = 0;
  n = is->audio_st->codec->channels * 2;
  if(is->audio_st) {
    bytes_per_sec = is->audio_st->codec->sample_rate * n;
  }
  if(bytes_per_sec) {
    pts -= (double)hw_buf_size / bytes_per_sec;
  }
  return pts;
}

Agora você deve entender por que essa função funciona;)

Então é isso! Nós compilamos:

gcc -o tutorial05 tutorial05.c -lavutil -lavformat -lavcodec -lswscale -lz -lm \
`sdl-config --cflags --libs`

Aconteceu! Você pode assistir ao filme em um reprodutor próprio. Na próxima lição, veremos a sincronização de áudio e aprenderemos a pesquisar.

Guia FFmpeg e SDL ou como gravar um player de vídeo em menos de 1000 linhas - Parte 2



Traduções no Blog Edison:


All Articles