FFmpeg es un gran proyecto de código abierto, una especie de enciclopedia multimedia. Con FFmpeg puede resolver una gran cantidad de tareas multimedia de computadora. Pero aún así, a veces es necesario expandir FFmpeg. La forma estándar es realizar cambios en el código del proyecto y luego compilar la nueva versión. El artículo detalla cómo agregar un nuevo códec. También se consideran algunas características para conectar funciones externas a FFmpeg. Si no es necesario agregar un códec, el artículo puede ser útil para comprender mejor la arquitectura de los códecs FFmpeg y su configuración. Se supone que el lector está familiarizado con la arquitectura de FFmpeg, el proceso de compilación de FFmpeg, y también tiene experiencia en programación utilizando la API de FFmpeg. La descripción es válida para FFmpeg 4.2 "Ada", agosto de 2019.
Tabla de contenidos
Introduccion
El códec (códec, proviene de la combinación de los términos COder y DECoder) es un término muy común y, como suele suceder en tales casos, su significado varía un poco según el contexto. El significado principal es software o hardware para comprimir / descomprimir datos de medios. En lugar de los términos compresión / descompresión, a menudo se usan los términos codificación / decodificación. Pero en algunos casos, se entiende que un códec significa simplemente un formato de compresión (también dicen el formato de códec), independientemente de los medios utilizados para la compresión / descompresión. Veamos cómo se usa el término códec en FFmpeg.
1. Identificación del códec
Los códecs FFmpeg se compilan en la biblioteca libavcodec .
1.1. ID de códec
La enum AVCodecID
define en el enum AVCodecID
libavcodec/avcodec.h
. Cada elemento de esta enumeración identifica el formato de compresión. Los elementos de esta enumeración deben tener la forma AV_CODEC_ID_XXX
, donde XXX
nombre único del identificador de códec en mayúsculas. Aquí hay ejemplos de identificadores de códec: AV_CODEC_ID_H264
, AV_CODEC_ID_AAC
. Para obtener una descripción más detallada del identificador de códec, use la estructura AVCodecDescriptor
(declarada en libavcodec/avcodec.h
, en forma abreviada):
typedef struct AVCodecDescriptor { enum AVCodecID id; enum AVMediaType type; const char *name; const char *long_name;
El miembro clave de esta estructura es id
, el resto de los miembros proporcionan información adicional sobre el identificador de códec. Cada identificador de códec está asociado de forma exclusiva con un tipo de medio (miembro de type
) y tiene un nombre único (miembro de name
), escrito en minúsculas. Una matriz de tipo AVCodecDescriptor
define en el archivo libavcodec/codec_desc.c
AVCodecDescriptor
. Para cada identificador de códec, hay un elemento de matriz correspondiente. Los elementos de esta matriz deben ordenarse por valores de id
, ya que la búsqueda binaria se usa para buscar elementos. Para obtener información sobre el identificador de códec, puede usar las funciones:
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. Códec
El códec mismo, un conjunto de herramientas necesarias para realizar la codificación / decodificación de datos multimedia, combina la estructura AVCodec
(declarada en libavcodec/avcodec.h
). Aquí está su versión resumida, más completa se discutirá a continuación.
typedef struct AVCodec { const char *name; const char *long_name; enum AVMediaType type; enum AVCodecID id;
El miembro más importante de esta estructura es id
, el identificador de códec, también hay un miembro que define el tipo de medio ( type
), pero su valor debe coincidir con el valor del mismo miembro de AVCodecDescriptor
. Los códecs se dividen en dos categorías: codificadores, que comprimen o codifican los medios, y decodificadores, que realizan la operación opuesta: descomprimir o decodificar. (A veces, en textos rusos, en lugar del término, el codificador usa papel de calco del inglés: el codificador). No hay ningún miembro especial en AVCodec
defina la categoría de códec (aunque la categoría se puede determinar indirectamente utilizando las funciones av_codec_is_encoder()
y av_codec_is_decoder()
, esta categoría se determina durante el registro. A continuación se mostrará cómo se hará: Varios códecs pueden tener el mismo identificador de códec. Si tienen la misma categoría, deben diferir por nombre ( name
miembro). Un codificador y decodificador que tenga el mismo identificador de códec puede tener uno el mismo nombre, que también puede coincidir con el nombre del identificador de códec (pero estas coincidencias son opcionales). Tal situación puede generar cierta confusión, pero no hay nada que hacer, debe comprender claramente a qué entidad pertenece el nombre. Dentro de una categoría, el nombre El códec debe ser único. Para buscar códecs registrados, hay funciones:
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);
Dado que varios códecs pueden tener el mismo identificador, las dos últimas funciones devuelven uno de ellos, que puede considerarse el códec predeterminado para un identificador de códec dado.
Se puede solicitar una lista de todos los códecs registrados con el comando
ffmpeg -codecs >codecs.txt
Después de ejecutar el comando, el archivo codecs.txt
contendrá esta lista. Cada identificador de códec estará representado por un registro separado (línea). Aquí, por ejemplo, la entrada para el identificador de códec 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)
Al comienzo de la grabación, hay caracteres especiales que determinan las características comunes disponibles para este identificador de códec: D
- los decodificadores están registrados, E
- los codificadores están registrados, V
- se usa para video, L
- existe la posibilidad de compresión con pérdidas, S
- existe la posibilidad de compresión sin pérdidas. Luego viene el nombre del identificador de códec ( h264
), seguido de un nombre de identificador de códec largo ( H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10
), y luego una lista de nombres de decodificadores y codificadores registrados.
2. Agregar un nuevo códec a FFmpeg
Consideraremos el procedimiento para agregar un nuevo códec a FFmpeg usando un ejemplo de códec de audio, al que llamaremos FROX
.
Paso 1. Agregue un nuevo elemento a la enum AVCodecID
.
Este listado está en el libavcodec/avcodec.h
. Al agregar, debe seguir las reglas:
- El valor de un elemento no debe coincidir con los valores de los elementos de enumeración existentes;
- No cambie los valores de los elementos de enumeración existentes;
- Publique un nuevo valor en un grupo de códecs similares.
Según la plantilla, el identificador de este elemento debe ser AV_CODEC_ID_FROX
. Colóquelo antes de AV_CODEC_ID_PCM_S64LE
y dé el valor 0x10700
.
Paso 2. Agregue el elemento a la matriz codec_descriptors
(archivo libavcodec/codec_desc.c
).
static const AVCodecDescriptor codec_descriptors[] = {
Debe agregar el elemento al lugar "correcto", no se debe violar la monotonicidad de los elementos de la matriz por el valor de id
.
Paso 3. Defina instancias de AVCodec
por separado para el codificador y el decodificador.
Para hacer esto, primero debe determinar la estructura para el contexto del códec y varias funciones que realizarán la codificación / decodificación real y algunas otras operaciones necesarias. En esta sección, estas definiciones se realizarán de manera extremadamente esquemática; más adelante se realizará una descripción más detallada. libavcodec/frox.c
el código en el archivo libavcodec/frox.c
#include "avcodec.h"
Para simplificar, en este ejemplo, el codificador y el decodificador tienen el mismo contexto: FroxContext
, pero la mayoría de las veces el codificador y el decodificador tienen contextos diferentes. También tenga en cuenta que los AVCodec
instancia AVCodec
deben seguir un patrón especial.
Paso 4. Agregue instancias de AVCodec
a la lista de registro.
Vaya al archivo libavcodec/allcodecs.c
. Al principio de este archivo hay una lista de declaraciones de todos los códecs registrados. Agregue nuestros códecs a esta lista:
extern AVCodec ff_frox_decoder; extern AVCodec ff_frox_encoder;
Durante la ejecución, el script de configure
encuentra todas esas declaraciones y genera el libavcodec/codec_list.c
, que contiene una matriz de punteros a los códecs declarados en libavcodec/allcodecs.c
. Después de ejecutar el script en el archivo libavcodec/codec_list.c
veremos:
static const AVCodec * const codec_list[] = {
Además, durante la ejecución del script de configure
, se config.h
archivo config.h
, en el que encontramos las declaraciones
#define CONFIG_FROX_DECODER 1 #define CONFIG_FROX_ENCODER 1
Paso 5. Edite libavcodec/Makefile
Abra libavcodec/Makefile
. Encontramos la sección # decoders/encoders
, y agregamos allí
OBJS-$(CONFIG_FROX_DECODER) += frox.o OBJS-$(CONFIG_FROX_ENCODER) += frox.o
Paso 6. Edite el código del multiplexor y demultiplexor.
El multiplexor (muxer) y el demultiplexor (demuxer) deben "conocer" el nuevo códec. Al grabar, es necesario registrar la información de identificación para este códec, mientras lee, determinar el identificador del códec a partir de la información de identificación. Esto es lo que debe hacer para el formato matroska
( *.mkv
).
1. En el archivo libavformat/matroska.c
, agregue un elemento para el nuevo códec a la matriz libavformat/matroska.c
:
const CodecTags ff_mkv_codec_tags[] = {
Cadena "A_FROX"
y el multiplexor lo escribirá en el archivo como información de identificación. En esta matriz, está asociada con el identificador de códec, por lo tanto, al leer, el demultiplexor puede determinarlo fácilmente. El demultiplexor escribe el identificador de códec en el miembro codec_id
de la estructura codec_id
. Un puntero a esta estructura es un miembro de la estructura AVStream
.
2. En el archivo libavformat/matroskaenc.c
, agregue el elemento a la matriz Additional_audio_tags:
static const AVCodecTag additional_audio_tags[] = {
Entonces todo está listo. Primero, ejecute el script de configure
. Después de eso, debe asegurarse de que se realicen los cambios descritos anteriormente en los archivos libavcodec/codec_list.c
y config.h
. Entonces puedes ejecutar la compilación:
make clean
make
Si la compilación se realizó ffmpeg.exe
, aparece el ejecutable ffmpeg
(o ffmpeg.exe
, si el sistema operativo de destino es Windows). Ejecutar el comando
./ffmpeg -codecs >codecs.txt
y asegúrese de que FFmpeg "vea" nuestros nuevos códecs, encontramos la entrada en el archivo codecs.txt
DEA..S frox FROX audio (decoders: frox_dec) (encoders: frox_enc)
3. Descripción detallada del contexto y funciones requeridas.
En esta sección, describimos con más detalle cómo puede verse la estructura del contexto del códec y las funciones necesarias.
3.1. Contexto de códec
El contexto del códec puede admitir la instalación de opciones. Para los codificadores, este soporte se usa con la frecuencia suficiente, para los decodificadores con menos frecuencia. La estructura que soporta la instalación de opciones debe tener un puntero a la estructura AVClass
como primer miembro y luego las opciones mismas.
#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;
A continuación, debe definir una matriz de tipo AVOption
, cada elemento del cual describe una opción 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 opción, debe definir un nombre, descripción, desplazamiento en la estructura, tipo. También puede definir un valor predeterminado y para opciones enteras un rango de valores válidos.
A continuación, debe definir una instancia de tipo AVClass
.
static const AVClass frox_class = { .class_name = "FroxContext", .item_name = av_default_item_name, .option = frox_options, .version = LIBAVUTIL_VERSION_INT, };
Se debe utilizar un puntero a esta instancia para inicializar el miembro AVCodec
correspondiente.
AVCodec ff_frox_decoder = {
Ahora al ejecutar la función
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
se AVCodecContext
una instancia de la estructura AVCodecContext
y se inicializa el miembro del codec
. A continuación, en función del valor codec->priv_data_size
, se codec->priv_data_size
la memoria necesaria para la instancia de FroxContext
, utilizando el valor codec->priv_class
primer miembro de esta instancia y luego se av_opt_set_defaults()
función av_opt_set_defaults()
, que establecerá los valores predeterminados para las opciones. Un puntero a una instancia de FroxContext
estará disponible a través del miembro priv_data
de la estructura priv_data
.
Al trabajar con la API de FFmpeg, los valores para las opciones se pueden establecer directamente.
const AVCodec *codec;
Otra forma es usar el diccionario de opciones, que se pasará como el tercer argumento al llamar a avcodec_open2()
(ver más abajo).
Usando la función
const AVOption* av_opt_next(const void* ctx, const AVOption* prev);
Puede obtener una lista de todas las opciones admitidas por el contexto de códec. Esto es útil al examinar un códec. Pero antes de eso, debe asegurarse de que codec_ctx->codec->priv_class
configurado en un valor distinto de cero, de lo contrario, el contexto no admite opciones y cualquier operación con opciones bloqueará el programa.
3.2. Las funciones
Examinemos ahora con más detalle cómo se organizan las funciones utilizadas en la inicialización del códec y la codificación / decodificación real. Por lo general, siempre necesitan obtener un puntero a un FroxContext
.
AVCodecContext *codec_ctx;
Las frox_decode_init()
y frox_encode_init()
cuando se ejecute la función
int avcodec_open2( AVCodecContext *codec_ctx, const AVCodec *codec, AVDictionary **options);
Deben asignar los recursos necesarios para que funcione el códec y, si es necesario, inicializar algunos miembros de la estructura AVCodecContext
, por ejemplo, frame_size
para un frame_size
audio.
Las frox_decode_close()
y frox_encode_close()
cuando se ejecuten
int avcodec_close(AVCodecContext *codec_ctx);
Necesitan liberar los recursos asignados.
Considere una función para implementar la decodificación
int frox_decode( AVCodecContext *codec_ctx, void *outdata, int *outdata_size, AVPacket *pkt);
Ella debe implementar las siguientes operaciones:
- Decodificación real;
- Asignación del búfer necesario para la trama de salida;
- Copie los datos decodificados al buffer de cuadros
Considere cómo asignar el búfer necesario para el marco de salida. El parámetro outdata
realidad apunta a un AVFrame
, por lo que primero debe realizar una conversión de tipo:
AVFrame* frm = outdata;
A continuación, debe asignar un búfer para almacenar datos de trama. Para hacer esto, inicialice los miembros de AVFrame
que determinan el tamaño del búfer de trama. Para audio, esto es nb_samples
, channel_layout
, format
(para width
, height
, format
video).
Después de eso, debes llamar a la función
int av_frame_get_buffer(AVFrame* frm, int alignment);
El puntero al marco, que es el parámetro de datos outdata
convertidos, se usa como primer argumento; se recomienda pasar cero como segundo argumento. Después de usar el marco (esto ya ocurre fuera del códec), la función libera el búfer asignado por esta función
void av_frame_unref(AVFrame* frm);
La función frox_decode()
debería devolver el número de bytes utilizados para la decodificación del paquete al que apunta pkt
. Si se completa la formación de trama, la variable a la que apunta outdata_size
asigna un valor distinto de cero; de lo contrario, esta variable obtiene el valor 0
.
Considere una función para implementar la codificación
int frox_encode( AVCodecContext *codec_ctx, AVPacket *pkt, const AVFrame *frame, int *got_pkt_ptr);
Ella debe implementar las siguientes operaciones:
- Codificación real;
- Asignación del búfer necesario para el paquete de salida;
- Copie los datos codificados al búfer de paquetes.
Para seleccionar el búfer requerido, use la función
int av_new_packet(AVPacket *pkt, int pack_size);
El parámetro pkt
utiliza como primer argumento, y el tamaño de los datos codificados es el segundo. Después de usar el paquete (esto ya ocurre fuera del códec), los buffers asignados por esta función son liberados por la función
void av_packet_unref(AVPacket *pkt);
Si el paquete se completa, la variable a la que apunta got_pkt_ptr
asigna un valor distinto de cero; de lo contrario, esta variable obtiene el valor 0
. Si no hay error, la función devuelve cero, de lo contrario, un código de error.
Al implementar el códec, generalmente se usa el registro (para errores, esto puede considerarse un requisito obligatorio). Aquí hay un ejemplo:
static int frox_decode_close(AVCodecContext *codec_ctx) { av_log(codec_ctx, AV_LOG_INFO, "FROX decode close\n");
En este caso, cuando salga al registro, el nombre del códec se usará como nombre de contexto.
3.3. Sellos de tiempo
Para establecer el tiempo en FFmpeg, se usa una base de tiempo, especificada en segundos usando el número racional representado por el tipo AVRational
. (Se utiliza un enfoque similar en C ++ 11. Por ejemplo, 1/1000 establece el milisegundo). Los marcos y los paquetes tienen marcas de tiempo del tipo int64_t
, sus valores contienen tiempo en las unidades de tiempo correspondientes. Un cuadro, es decir, una estructura AVFrame
, tiene un miembro pts
(marca de tiempo de presentación), cuyo valor determina el tiempo relativo de la escena capturada en el cuadro. Un paquete, es decir, una estructura AVPacket
, tiene miembros pts
(marca de tiempo de presentación) y dts
(marca de tiempo de descompresión). El valor dts
determina el tiempo relativo de transmisión del paquete para la decodificación. Para los códecs simples, es lo mismo que pts
, pero para los códecs complejos puede ser diferente (por ejemplo, para h264
cuando se usan cuadros B), es decir, los paquetes se pueden decodificar en el orden incorrecto en el que se deben usar los cuadros.
La unidad de tiempo se define para la secuencia y el códec, la estructura AVStream
tiene un miembro correspondiente: time_base
, el mismo miembro tiene la estructura AVCodecContext
.
Las marcas de tiempo del paquete extraído de la secuencia utilizando av_read_frame()
se especificarán en unidades de tiempo de esta secuencia. Al decodificar, no se utiliza la unidad de tiempo del códec. Para un decodificador de video, por lo general simplemente no está configurado, para un decodificador de audio tiene un valor estándar: el inverso de la frecuencia de muestreo. El decodificador debe establecer una marca de tiempo para el marco de salida en función de la marca de tiempo del paquete. FFmpeg define de forma independiente dicha etiqueta y la escribe en el miembro best_effort_timestamp
de la estructura best_effort_timestamp
. Todas estas marcas de tiempo utilizarán la unidad de tiempo de la secuencia de la que se extrae el paquete.
Para el codificador, debe especificar la unidad de tiempo. En el código del cliente que organiza la decodificación, debe establecer el valor para el miembro time_base
de la estructura time_base
antes de llamar a avcodec_open2()
. Por lo general, toma la unidad de tiempo utilizada para las marcas de tiempo del marco codificado. Si esto no se hace, los codificadores de video generalmente dan un error, los codificadores de audio establecen el valor predeterminado: el inverso de la frecuencia de muestreo. No está del todo claro si un códec puede cambiar una unidad de tiempo dada. Por si acaso, es mejor verificar siempre el valor de time_base
después de llamar a avcodec_open2()
y, si ha cambiado, recalcular las marcas de tiempo de los marcos de entrada por unidad de tiempo del códec. En el proceso de codificación, debe instalar los pts
y dts
paquete. Después de la codificación, antes de escribir un paquete en el flujo de salida, es necesario volver a calcular las marcas de tiempo del paquete desde la unidad de tiempo del códec a la unidad de tiempo del flujo. Para hacer esto, use la función
void av_packet_rescale_ts( AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
Al escribir paquetes en la secuencia, es necesario asegurarse de que los valores dts
aumenten estrictamente; de lo contrario, el multiplexor arrojará un error. (Para obtener más información, consulte la documentación de la función av_interleaved_write_frame()
).
3.4. Otras funciones utilizadas por el códec
Cuando inicializa una instancia de AVCodec
, se pueden registrar dos funciones más. Estos son los miembros relevantes de AVCodec
:
typedef struct AVCodec {
El primero de ellos se llama una vez al registrarse el códec.
El segundo restablece el estado interno del códec, se llamará durante la ejecución de la función.
void avcodec_flush_buffers(AVCodecContext *codec_ctx);
Esta llamada es necesaria, por ejemplo, cuando se cambia por la fuerza la posición de reproducción actual.
4. Implementación externa del códec
4.1. Conexión de función externa
Considere la siguiente organización de códec: el códec registrado en FFmpeg desempeña el papel de un marco y delega el procedimiento de codificación / decodificación real a funciones externas (algún tipo de complementos) implementadas fuera de FFmpeg.
. Aquí hay algunos de ellos:
- , 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
).
Conclusión
, , 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