O FFmpeg é um grande projeto de código aberto, uma espécie de enciclopédia multimídia. Com o FFmpeg, você pode resolver um grande número de tarefas de multimídia de computadores. Mas, ainda assim, às vezes é necessário expandir o FFmpeg. A maneira padrão é fazer alterações no código do projeto e compilar a nova versão. O artigo detalha como adicionar um novo codec. Alguns recursos para conectar funções externas ao FFmpeg também são considerados. Se não for necessário adicionar um codec, o artigo poderá ser útil para uma melhor compreensão da arquitetura dos codecs FFmpeg e suas configurações. Supõe-se que o leitor esteja familiarizado com a arquitetura do FFmpeg, o processo de compilação do FFmpeg, e também tenha experiência em programação usando a API do FFmpeg. A descrição é válida para FFmpeg 4.2 "Ada", agosto de 2019.
Sumário
1. Introdução
O codec (codec, vem da combinação dos termos COder e DECoder) é um termo muito comum e, como geralmente acontece nesses casos, seu significado varia um pouco, dependendo do contexto. O significado principal é software ou hardware para compactar / descomprimir dados de mídia. Em vez dos termos compressão / descompressão, os termos codificação / decodificação são frequentemente usados. Mas, em alguns casos, entende-se que um codec significa simplesmente um formato de compactação (eles também dizem o formato de codec), independentemente dos meios usados para compactação / descompactação. Vamos ver como o termo codec é usado no FFmpeg.
1. Identificação do codec
Os codecs FFmpeg são compilados na biblioteca libavcodec .
1.1 ID do codec
A enum AVCodecID
definida no enum AVCodecID
libavcodec/avcodec.h
. Cada elemento dessa enumeração identifica o formato de compactação. Os elementos dessa enumeração devem ter o formato AV_CODEC_ID_XXX
, em que XXX
nome do identificador de codec exclusivo em maiúsculas. Aqui estão exemplos de identificadores de codec: AV_CODEC_ID_H264
, AV_CODEC_ID_AAC
. Para uma descrição mais detalhada do identificador de codec, use a estrutura AVCodecDescriptor
(declarada em libavcodec/avcodec.h
, fornecida de forma abreviada):
typedef struct AVCodecDescriptor { enum AVCodecID id; enum AVMediaType type; const char *name; const char *long_name;
O membro principal dessa estrutura é id
, o restante dos membros fornece informações adicionais sobre o identificador de codec. Cada identificador de codec está associado exclusivamente a um tipo de mídia (membro do type
) e possui um nome exclusivo (membro do name
), escrito em letras minúsculas. Uma matriz do tipo AVCodecDescriptor
definida no arquivo libavcodec/codec_desc.c
AVCodecDescriptor
. Para cada identificador de codec, há um elemento de matriz correspondente. Os elementos dessa matriz devem ser ordenados por valores de id
, pois a pesquisa binária é usada para procurar elementos. Para obter informações sobre o identificador de codec, você pode usar as funções:
const AVCodecDescriptor* avcodec_descriptor_get(enum AVCodecID id); const AVCodecDescriptor* avcodec_descriptor_get_by_name(const char *name); enum AVMediaType avcodec_get_type(enum AVCodecID codec_id); const char* avcodec_get_name(enum AVCodecID id);
1.2 Codec
O próprio codec - um conjunto de ferramentas necessárias para executar a codificação / decodificação de dados de mídia, combina a estrutura AVCodec
(declarada em libavcodec/avcodec.h
). Aqui está sua versão resumida, mais completa será discutida abaixo.
typedef struct AVCodec { const char *name; const char *long_name; enum AVMediaType type; enum AVCodecID id;
O membro mais importante dessa estrutura é id
, o identificador de codec, também existe um membro que define o tipo de mídia ( type
), mas seu valor deve corresponder ao valor do mesmo membro do AVCodecDescriptor
. Os codecs são divididos em duas categorias: codificadores, que compactam ou codificam mídia, e decodificadores, que executam a operação oposta - descomprimir ou decodificar. (Às vezes, em textos em russo, em vez do termo, o codificador usa papel vegetal do inglês - o codificador.) Não há nenhum membro especial no AVCodec
que define a categoria de codec (embora a categoria possa ser determinada indiretamente usando as funções av_codec_is_encoder()
e av_codec_is_decoder()
, essa categoria é determinada durante o registro. Como isso será feito será mostrado abaixo: Vários codecs podem ter o mesmo identificador de codec.Se eles tiverem a mesma categoria, eles devem diferir por nome ( name
membro) .Um codificador e decodificador com o mesmo identificador de codec pode ter um o mesmo nome, que também pode coincidir com o nome do identificador de codec (mas essas correspondências são opcionais). Tal situação pode levar a alguma confusão, mas não há nada a ser feito, você deve entender claramente a que entidade o nome pertence. Em uma categoria, o nome O codec deve ser exclusivo.Para procurar codecs registrados, existem funções:
AVCodec* avcodec_find_encoder_by_name(const char *name); AVCodec* avcodec_find_decoder_by_name(const char *name); AVCodec* avcodec_find_encoder(enum AVCodecID id); AVCodec* avcodec_find_decoder(enum AVCodecID id);
Como vários codecs podem ter o mesmo identificador, as duas últimas funções retornam uma delas, que pode ser considerada o codec padrão para um determinado identificador de codec.
Uma lista de todos os codecs registrados pode ser solicitada com o comando
ffmpeg -codecs >codecs.txt
Após executar o comando, o arquivo codecs.txt
conterá esta lista. Cada identificador de codec será representado por um registro (linha) separado. Aqui, por exemplo, a entrada para o identificador de codec AV_CODEC_ID_H264
:
DEV.LS
h264
H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
(decoders: h264 h264_qsv h264_cuvid)
(encoders: libx264 libx264rgb h264_amf h264_nvenc h264_qsv nvenc nvenc_h264)
No início da gravação, existem caracteres especiais que determinam os recursos comuns disponíveis para esse identificador de codec: decodificadores D
são registrados, codificadores E
são registrados, V
- é usado para vídeo, L
- existe a possibilidade de compressão com perdas, S
- existe a possibilidade de compressão sem perdas. A seguir, vem o nome do identificador de codec ( h264
), seguido por um nome longo de identificador de codec ( H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
) e, em seguida, uma lista de nomes de decodificadores e codificadores registrados.
2. Adicionando um novo codec ao FFmpeg
Vamos considerar o procedimento para adicionar um novo codec ao FFmpeg usando um exemplo de codec de áudio, que chamaremos de FROX
.
Etapa 1. Adicione um novo elemento à enum AVCodecID
.
Esta listagem está no libavcodec/avcodec.h
. Ao adicionar, você deve seguir as regras:
- O valor de um elemento não deve corresponder aos valores dos elementos de enumeração existentes;
- Não altere os valores dos elementos de enumeração existentes;
- Poste um novo valor em um grupo de codecs semelhantes.
De acordo com o modelo, o identificador desse elemento deve ser AV_CODEC_ID_FROX
. Coloque-o antes de AV_CODEC_ID_PCM_S64LE
e forneça o valor 0x10700
.
Etapa 2. Adicione o item à matriz codec_descriptors
(arquivo libavcodec/codec_desc.c
).
static const AVCodecDescriptor codec_descriptors[] = {
Você precisa adicionar o elemento ao local “certo”, a monotonicidade dos elementos da matriz pelo valor do id
não deve ser violada.
Etapa 3. Defina instâncias do AVCodec
separadamente para o codificador e o decodificador.
Para fazer isso, primeiro você precisa determinar a estrutura para o contexto do codec e várias funções que executarão a codificação / decodificação real e algumas outras operações necessárias. Nesta seção, essas definições serão feitas de maneira extremamente esquemática; uma descrição mais detalhada será feita posteriormente. libavcodec/frox.c
o código no arquivo libavcodec/frox.c
#include "avcodec.h"
Para simplificar, neste exemplo, o codificador e o decodificador têm o mesmo contexto - FroxContext
, mas na maioria das vezes o codificador e o decodificador têm contextos diferentes. Observe também que os AVCodec
instância do AVCodec
devem seguir um padrão especial.
Etapa 4. Adicione instâncias do AVCodec
à lista de registro.
Vá para o arquivo libavcodec/allcodecs.c
. No início deste arquivo, há uma lista de declarações de todos os codecs registrados. Adicione nossos codecs a esta lista:
extern AVCodec ff_frox_decoder; extern AVCodec ff_frox_encoder;
Durante a execução, o script configure
localiza todas essas declarações e gera o libavcodec/codec_list.c
, que contém uma matriz de ponteiros para os codecs declarados em libavcodec/allcodecs.c
. Depois de executar o script no arquivo libavcodec/codec_list.c
, veremos:
static const AVCodec * const codec_list[] = {
Além disso, durante a execução do script configure
, o arquivo config.h
é config.h
, no qual encontramos as declarações
#define CONFIG_FROX_DECODER 1 #define CONFIG_FROX_ENCODER 1
Etapa 5. Edite libavcodec/Makefile
Abra o libavcodec/Makefile
. Encontramos a seção # decoders/encoders
e adicionamos lá
OBJS-$(CONFIG_FROX_DECODER) += frox.o OBJS-$(CONFIG_FROX_ENCODER) += frox.o
Etapa 6. Edite o código do multiplexador e do desmultiplexador.
O multiplexador (muxer) e o desmultiplexador (desmuxer) devem "conhecer" o novo codec. Ao gravar, é necessário registrar as informações de identificação desse codec, enquanto lê, determina o identificador do codec a partir das informações de identificação. Aqui está o que você precisa fazer para o formato matroska
( *.mkv
).
1. No arquivo libavformat/matroska.c
, adicione um elemento para o novo codec à matriz libavformat/matroska.c
:
const CodecTags ff_mkv_codec_tags[] = {
String "A_FROX"
e será gravada pelo multiplexador no arquivo como informação de identificação. Nesta matriz, ele é associado ao identificador de codec; portanto, ao ler, o desmultiplexador pode determiná-lo facilmente. O desmultiplexador grava o identificador de codec no membro codec_id
estrutura codec_id
. Um ponteiro para essa estrutura é um membro da estrutura AVStream
.
2. No arquivo libavformat/matroskaenc.c
, adicione o elemento à matriz additional_audio_tags
:
static const AVCodecTag additional_audio_tags[] = {
Então está tudo pronto. Primeiro, execute o script de configure
. Depois disso, é necessário garantir que as alterações descritas acima nos arquivos libavcodec/codec_list.c
e config.h
sejam feitas. Então você pode executar a compilação:
make clean
make
Se a compilação ocorreu ffmpeg.exe
, o executável ffmpeg
(ou ffmpeg.exe
, se o sistema operacional de destino for Windows) será exibido. Execute o comando
./ffmpeg -codecs >codecs.txt
e verifique se o FFmpeg “vê” nossos novos codecs, encontramos a entrada no arquivo codecs.txt
DEA..S frox FROX audio (decoders: frox_dec) (encoders: frox_enc)
3. Descrição detalhada do contexto e funções necessárias
Nesta seção, descrevemos com mais detalhes a aparência da estrutura de contexto do codec e das funções necessárias.
3.1 Contexto do codec
O contexto do codec pode suportar a instalação de opções. Para codificadores, esse suporte é usado com bastante frequência, para decodificadores com menos frequência. A estrutura que suporta a instalação das opções deve ter um ponteiro para a estrutura AVClass
como o primeiro membro e depois as próprias opções.
#include "libavutil/opt.h" typedef struct FroxContext { const AVClass *av_class; int frox_int; char *frox_str; uint8_t *frox_bin; int bin_size; } FroxContext;
Em seguida, você precisa definir uma matriz do tipo AVOption
, cada um dos quais descreve uma opção específica.
static const AVOption frox_options[] = { { "frox_int", "This is a demo option of int type.", offsetof(FroxContext, frox_int), AV_OPT_TYPE_INT, { .i64 = -1 }, 1, SHRT_MAX }, { "frox_str", "This is a demo option of string type.", offsetof(FroxContext, frox_str), AV_OPT_TYPE_STRING }, { "frox_bin", "This is a demo option of binary type.", offsetof(FroxContext, frox_bin), AV_OPT_TYPE_BINARY }, { NULL }, };
Para cada opção, você deve definir um nome, descrição, deslocamento na estrutura, tipo. Você também pode definir um valor padrão e, para opções inteiras, um intervalo de valores válidos.
Em seguida, você precisa definir uma instância do tipo AVClass
.
static const AVClass frox_class = { .class_name = "FroxContext", .item_name = av_default_item_name, .option = frox_options, .version = LIBAVUTIL_VERSION_INT, };
Um ponteiro para esta instância deve ser usado para inicializar o membro AVCodec
correspondente.
AVCodec ff_frox_decoder = {
Agora, ao executar a função
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
uma instância da estrutura AVCodecContext
é AVCodecContext
e o membro do codec
é inicializado. Em seguida, com base no codec->priv_data_size
, a memória necessária será alocada para a instância FroxContext
, usando o codec->priv_class
primeiro membro dessa instância será inicializado e a função av_opt_set_defaults()
será chamada, que definirá os valores padrão para as opções. Um ponteiro para uma instância do FroxContext
estará disponível através do membro priv_data
estrutura priv_data
.
Ao trabalhar com a API FFmpeg, os valores para as opções podem ser definidos diretamente.
const AVCodec *codec;
Outra maneira é usar o dicionário de opções, que será passado como o terceiro argumento ao chamar avcodec_open2()
(veja abaixo).
Usando a função
const AVOption* av_opt_next(const void* ctx, const AVOption* prev);
Você pode obter uma lista de todas as opções suportadas pelo contexto do codec. Isso é útil ao examinar um codec. Porém, antes disso, verifique se codec_ctx->codec->priv_class
definido como um valor diferente de zero, caso contrário, o contexto não suporta opções e qualquer operação com opções travará o programa.
3.2 Funções
Vamos agora examinar mais detalhadamente como as funções usadas na inicialização do codec e a codificação / decodificação real são organizadas. Geralmente, eles sempre precisam obter um ponteiro para um FroxContext
.
AVCodecContext *codec_ctx;
As frox_decode_init()
e frox_encode_init()
serão chamadas quando a função for executada
int avcodec_open2( AVCodecContext *codec_ctx, const AVCodec *codec, AVDictionary **options);
Eles precisam alocar os recursos necessários para o codec funcionar e, se necessário, inicializar alguns membros da estrutura AVCodecContext
, por exemplo, frame_size
para um frame_size
áudio.
As frox_decode_close()
e frox_encode_close()
serão chamadas quando executadas
int avcodec_close(AVCodecContext *codec_ctx);
Eles precisam liberar os recursos alocados.
Considere uma função para implementar a decodificação
int frox_decode( AVCodecContext *codec_ctx, void *outdata, int *outdata_size, AVPacket *pkt);
Ela deve implementar as seguintes operações:
- Decodificação real;
- Alocação do buffer necessário para o quadro de saída;
- Copie os dados decodificados para o buffer do quadro.
Considere como alocar o buffer necessário para o quadro de saída. O parâmetro outdata
realmente aponta para um AVFrame
, portanto, você deve primeiro executar uma conversão de tipo:
AVFrame* frm = outdata;
Em seguida, você precisa alocar um buffer para armazenar dados do quadro. Para fazer isso, inicialize os membros AVFrame
que determinam o tamanho do buffer do quadro. Para áudio, são nb_samples
, channel_layout
, format
(para width
, height
e format
vídeo).
Depois disso, você precisa chamar a função
int av_frame_get_buffer(AVFrame* frm, int alignment);
O ponteiro para o quadro, que é o parâmetro outdata
convertido, é usado como o primeiro argumento; é recomendável passar zero como o segundo argumento. Depois de usar o quadro (isso já acontece fora do codec), o buffer alocado por esta função é liberado pela função
void av_frame_unref(AVFrame* frm);
A função frox_decode()
deve retornar o número de bytes usados para decodificação do pacote apontado por pkt
. Se a formação do quadro for concluída, a variável apontada por outdata_size
recebe um valor diferente de zero, caso contrário, essa variável obtém o valor 0
.
Considere uma função para implementar a codificação
int frox_encode( AVCodecContext *codec_ctx, AVPacket *pkt, const AVFrame *frame, int *got_pkt_ptr);
Ela deve implementar as seguintes operações:
- Codificação real;
- Alocação do buffer necessário para o pacote de saída;
- Copie dados codificados para o buffer de pacote.
Para selecionar o buffer necessário, use a função
int av_new_packet(AVPacket *pkt, int pack_size);
O parâmetro pkt
usado como o primeiro argumento e o tamanho dos dados codificados é o segundo. Depois de usar o pacote (isso já acontece fora do codec), os buffers alocados por essa função são liberados pela função
void av_packet_unref(AVPacket *pkt);
Se o pacote for concluído, a variável apontada por got_pkt_ptr
um valor diferente de zero, caso contrário, essa variável got_pkt_ptr
o valor 0
. Se não houver erro, a função retornará zero, caso contrário, um código de erro.
Ao implementar o codec, o log é geralmente usado (para erros, isso pode ser considerado um requisito obrigatório). Aqui está um exemplo:
static int frox_decode_close(AVCodecContext *codec_ctx) { av_log(codec_ctx, AV_LOG_INFO, "FROX decode close\n");
Nesse caso, ao enviar para o log, o nome do codec será usado como o nome do contexto.
3.3 Carimbos de tempo
Para definir o tempo no FFmpeg, é usada uma base de tempo, especificada em segundos usando o número racional representado pelo tipo AVRational
. (Uma abordagem semelhante é usada no C ++ 11. Por exemplo, 1/1000 define o milissegundo.) Os quadros e pacotes têm registros de data e hora do tipo int64_t
, seus valores contêm tempo nas unidades de tempo correspondentes. Um quadro, ou seja, uma estrutura AVFrame
, possui um membro pts
(registro de data e hora da apresentação), cujo valor determina o tempo relativo da cena capturada no quadro. Um pacote, ou seja, uma estrutura AVPacket
, possui membros pts
(registro de data e hora da apresentação) e dts
(registro de data e hora de descompactação). O valor dts
determina o tempo relativo de transmissão do pacote para decodificação. Para codecs simples, é o mesmo que pts
, mas para codecs complexos pode ser diferente (por exemplo, para h264
ao usar quadros B), ou seja, pacotes podem ser decodificados na ordem errada na qual os quadros devem ser usados.
A unidade de tempo é definida para o fluxo e o codec, a estrutura AVStream
possui um membro correspondente - time_base
, o mesmo membro possui a estrutura AVCodecContext
.
Os carimbos de data e hora do pacote extraído do fluxo usando av_read_frame()
serão especificados em unidades de tempo desse fluxo. Ao decodificar, a unidade de tempo do codec não é usada. Para um decodificador de vídeo, ele geralmente não está configurado, para um decodificador de áudio ele possui um valor padrão - o inverso da frequência de amostragem. O decodificador deve definir um registro de data e hora para o quadro de saída com base no registro de data e hora do pacote. O FFmpeg define independentemente esse rótulo e o grava no membro best_effort_timestamp
estrutura best_effort_timestamp
. Todos esses registros de data e hora usarão a unidade de tempo do fluxo do qual o pacote é extraído.
Para o codificador, você deve especificar a unidade de tempo. No código do cliente que organiza a decodificação, você deve definir o valor do membro time_base
estrutura time_base
antes de chamar avcodec_open2()
. Normalmente, dedique a unidade de tempo usada para carimbos de data e hora do quadro codificado. Se isso não for feito, os codificadores de vídeo geralmente emitem um erro; os codificadores de áudio definem o valor padrão - o inverso da frequência de amostragem. Se um codec pode alterar uma determinada unidade de tempo não está totalmente claro. Por precaução, é melhor sempre verificar o valor time_base
depois de chamar avcodec_open2()
e, se tiver sido alterado, recalcular os registros de data e hora dos quadros de entrada por unidade de tempo do codec. No processo de codificação, você deve instalar os pts
e dts
pacote. Após a codificação, antes de gravar um pacote no fluxo de saída, é necessário recalcular os registros de data e hora do pacote da unidade de tempo do codec para a unidade de tempo do fluxo. Para fazer isso, use a função
void av_packet_rescale_ts( AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
Ao gravar pacotes no fluxo, é necessário garantir que os valores dts
aumentem estritamente, caso contrário, o multiplexador gerará um erro. (Para obter mais informações, consulte a documentação da função av_interleaved_write_frame()
.)
3.4 Outras funções usadas pelo codec
Quando você inicializa uma instância do AVCodec
, mais duas funções podem ser registradas. Aqui estão os membros relevantes da AVCodec
:
typedef struct AVCodec {
O primeiro deles é chamado uma vez após o registro do codec.
O segundo redefine o estado interno do codec, será chamado durante a execução da função
void avcodec_flush_buffers(AVCodecContext *codec_ctx);
Essa chamada é necessária, por exemplo, quando forçar a alteração da posição atual de reprodução.
4. Implementação externa do codec
4.1 Conexão de função externa
Considere a seguinte organização de codecs: o codec registrado no FFmpeg desempenha o papel de uma estrutura e delega o procedimento real de codificação / decodificação para funções externas (algum tipo de plug-in) implementadas fora do FFmpeg.
. Aqui estão alguns deles:
- , FFmpeg ;
- C, , C++;
- framework, FFmpeg.
, FFmpeg «», FFmpeg API. «» FFmpeg ( , ), . — . .
typedef int(*dec_extern_t)(const void*, int, void*); static int frox_decode( AVCodecContext* codec_ctx, void* outdata, int *outdata_size, AVPacket* pkt) { int ret = -1; void* out_buff;
FFmpeg API ( C++) .
extern "C" { int DecodeFroxData(const void* buff, int size, void* outBuff); typedef int(*dec_extern_t)(const void*, int, void*); #include <libavcodec/avcodec.h> #include <libavutil/opt.h> } // ... AVCodecContext* ctx; // ... dec_extern_t dec = DecodeFroxData; void* pv = &dec; auto pb = static_cast<const uint8_t*>(pv); auto sz = sizeof(dec); av_opt_set_bin(ctx->priv_data, "frox_bin", pb, sz, 0);
4.2.
— . , . , . , , FFmpeg , «» , . . , . FFmpeg API - , , . . , . PC (Windows) DirectShow AVI . PC - DirectShow. 32- FourCC. ( biCompression
BITMAPINFOHEADER
.) , DirectShow , PC -. FFmpeg , , , codec_tag
AVCodecParameters
FourCC, . FFmpeg API , . FFmpeg FFmpeg API.
, *.mkv
FFmpeg ( ENCODER
).
Conclusão
, , FFmpeg: , changelog, .. «» FFmpeg, , .
Recursos
FFmpeg
[1] FFmpeg —
[2] FFmpeg —
[3] FFmpeg —
[4] FFmpeg — Ubuntu
[5] FFmpeg Compilation Guide
[6] Compilation of FFmpeg 4.0 in Windows 10
FFmpeg API
[7] ffmpeg
[8] FFmpeg codec HOWTO
[9] FFmpeg video codec tutorial