Видео не просто файл на диске. Это живой поток кадров, звука и метаданных, который проходит сложный путь от источника к зрителю. Декодер раскрывает сжатые данные, фильтры меняют картинку по желанию, энкодер упаковывает все заново. FFmpeg C bindings дают полный контроль над этим процессом. libavformat разбирает контейнеры, libavcodec управляет кодеками, libavfilter строит цепочки обработки. Когда стандартная команда ffmpeg не справляется с кастомными задачами, разработчики берут API в свои руки и собирают транскодинг-пайплайн точно под нужды проекта.

Многие начинают с простого конвертера, а заканчивают сложными системами стриминга или анализа видео. Параметры кодеков передают характеристики потока аккуратно, графы фильтров вставляются как модули в конвейер. Такой подход позволяет менять все на лету, добавлять аппаратное ускорение или интеграцию с другими библиотеками.

AVCodecParameters как надежный переносчик метаданных

В старых версиях FFmpeg характеристики кодека хранились прямо в AVCodecContext. Это создавало путаницу при работе с несколькими потоками. AVCodecParameters решили проблему: легкая структура, которая отделяет описание от контекста выполнения.

При открытии входного файла avformat_find_stream_info заполняет AVStream->codecpar всеми данными: codec_id, width, height, pix_fmt, bit_rate, sample_rate для аудио, extradata с профилями H.264 или HEVC. Эти параметры копируются в декодер одной функцией.

Пример настройки декодера:

C
 
AVCodec *dec = avcodec_find_decoder(stream->codecpar->codec_id);
if (!dec) return error;

AVCodecContext *dec_ctx = avcodec_alloc_context3(dec);
if (avcodec_parameters_to_context(dec_ctx, stream->codecpar) < 0) return error;

if (avcodec_open2(dec_ctx, dec, NULL) < 0) return error;
 
 

Функция avcodec_parameters_to_context переносит не только базовые поля, но и sample_aspect_ratio, field_order, color_range, chroma_location. Если энкодер требует другой формат, разработчик корректирует контекст вручную до открытия.

Для вывода процесс обратный. После настройки энкодера параметры переносятся в выходной поток:

C
 
AVCodecContext *enc_ctx = avcodec_alloc_context3(enc);
// ... настройка width, height, time_base, gop_size, bitrate и т.д.

avcodec_open2(enc_ctx, enc, &opts);

AVStream *out_stream = avformat_new_stream(out_fmt_ctx, NULL);
avcodec_parameters_from_context(out_stream->codecpar, enc_ctx);
 
 

Без этого муксер не знал бы, как писать заголовки контейнера. Параметры гарантируют совместимость: MP4 получит правильные avcC или hvcC атомы для H.264/HEVC.

Технически extradata часто содержит SPS/PPS для видео или channel layout для аудио. Копирование сохраняет все нюансы исходного кодека.

Построение графа фильтров шаг за шагом

libavfilter - это конструктор для обработки медиа. Граф состоит из источников, фильтров и приемников. buffersrc принимает сырые кадры от декодера, buffersink выдает их энкодеру после трансформаций.

Создание графа начинается просто:

C
 
AVFilterGraph *graph = avfilter_graph_alloc();
if (!graph) return error;

AVFilterContext *src_ctx, *sink_ctx;
 
 

Источник настраивается под декодер:

C
 
char args[512];
snprintf(args, sizeof(args),
    "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d",
    dec_ctx->width, dec_ctx->height, dec_ctx->pix_fmt,
    dec_ctx->time_base.num, dec_ctx->time_base.den,
    dec_ctx->sample_aspect_ratio.num,
    dec_ctx->sample_aspect_ratio.den ? dec_ctx->sample_aspect_ratio.den : 1);

const AVFilter *buffer = avfilter_get_by_name("buffer");
avfilter_graph_create_filter(&src_ctx, buffer, "in", args, NULL, graph);
 
 

Приемник привязывается к энкодеру:

C
 
const AVFilter *sink = avfilter_get_by_name("buffersink");
avfilter_graph_create_filter(&sink_ctx, sink, "out", NULL, NULL, graph);

enum AVPixelFormat pix_fmts[] = { enc_ctx->pix_fmt, AV_PIX_FMT_NONE };
av_opt_set_int_list(sink_ctx, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN);
 
 

Цепочка фильтров задается строкой, как в командной строке:

C
 
AVFilterInOut *outputs = NULL, *inputs = NULL;
const char *filter_desc = "scale=1280:720:flags=lanczos,format=yuv420p,fps=30";

if (avfilter_graph_parse_ptr(graph, filter_desc, &inputs, &outputs, NULL) < 0) return error;

if (avfilter_link(inputs->filter_ctx, 0, src_ctx, 0) < 0) return error;
if (avfilter_link(sink_ctx, 0, outputs->filter_ctx, 0) < 0) return error;

avfilter_inout_free(&inputs);
avfilter_inout_free(&outputs);

if (avfilter_graph_config(graph, NULL) < 0) return error;
 
 

Граф проверяет совместимость и вставляет необходимые конвертеры. Кадры подаются:

C
 
av_buffersrc_add_frame_flags(src_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF);
while (av_buffersink_get_frame(sink_ctx, filtered_frame) >= 0) {
    // отправка в энкодер
}
 
 

Один набор популярных фильтров для транскодинга:

  • scale и format - базовая адаптация разрешения и цвета
  • yadif или fieldmatch - деинтерлейсинг старого видео
  • noise или denoise_nlmeans - очистка от шума
  • overlay - наложение логотипов или субтитров
  • asetpts и afade - обработка аудио в том же графе

Аудиофильтры добавляются аналогично с abuffer и abuffersink.

Полный цикл транскодинга с обработкой

Пайплайн работает в цикле. av_read_frame читает пакеты, avcodec_send_packet/receive_frame декодирует. Кадры идут в buffersrc, вытаскиваются из buffersink, отправляются в энкодер avcodec_send_frame/receive_packet. Пакеты пишутся av_interleaved_write_frame.

Обработка задержанных кадров обязательна: после flush декодера (NULL пакет) и энкодера (NULL фрейм) нужно вытянуть все остатки.

Аппаратное ускорение вписывается естественно:

C
 
AVBufferRef *hw_device_ctx = NULL;
av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_CUDA, NULL, NULL, 0);

dec_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
enc_ctx->hw_device_ctx = av_buffer_ref(hw_device_ctx);
 
 

Фильтры вроде scale_cuda или hwupload_cuda берут нагрузку на GPU. Для Intel QSV или VAAPI тип устройства меняется.

Динамические изменения возможны через avfilter_graph_send_command - например, менять силу фильтра sharpen на лету.

Практические наблюдения и тонкости работы

Разработчики часто ловят ошибки на несоответствии time_base. Рекомендуется унифицировать: выходной time_base ставить как 1/framerate или stream->time_base копировать с корректировкой.

Память важна: кадры передаются по ссылке, флаги AV_BUFFERSRC_FLAG_KEEP_REF помогают избежать лишних копий. Для больших видео лучше использовать hw_frames_ctx.

Один случай из практики: проект стриминга адаптировал битрейт на лету, меняя enc_ctx->bit_rate и переконфигурируя граф. С API это делается без перезапуска пайплайна.

Если вдуматься, FFmpeg C bindings превращают видеообработку в точную инженерию. Параметры кодеков держат основу совместимости, графы фильтров добавляют гибкость. Многие проекты - от серверов транскодинга до плееров - выросли именно на этом фундаменте.

Природа API здесь раскрывает глубину. Каждый кадр проходит через руки разработчика, меняясь по пути. Стоит ли удивляться, что опытные инженеры предпочитают bindings командной строке? Полный контроль открывает двери к решениям, которых не добиться иначе. Видеопоток оживает, и каждый шаг в пайплайне становится осознанным выбором.