Ответ 1
После некоторой напряженной борьбы мне удалось, наконец, немного поработать, изучив немного, как использовать FFmpeg и libx264 C API для моей конкретной цели, благодаря полезной информации, которую некоторые пользователи предоставили на этом сайте и некоторых других, так как а также некоторые примеры документации FFmpeg. Для иллюстрации подробности будут представлены ниже.
Прежде всего, библиотека libx264 C была скомпилирована, а затем FFmpeg с параметрами configure --enable-gpl --enable-libx264. Теперь перейдем к кодированию. Соответствующая часть кода, которая достигла заданной цели, следующая:
Включает:
#include <stdint.h>
extern "C"{
#include <x264.h>
#include <libswscale/swscale.h>
#include <libavcodec/avcodec.h>
#include <libavutil/mathematics.h>
#include <libavformat/avformat.h>
#include <libavutil/opt.h>
}
LDFLAGS на Makefile:
-lx264 -lswscale -lavutil -lavformat -lavcodec
Внутренний код (для простоты проверки ошибок будут опущены, а объявления переменных будут выполняться, когда это необходимо, вместо начала для лучшего понимания):
av_register_all(); // Loads the whole database of available codecs and formats.
struct SwsContext* convertCtx = sws_getContext(width, height, AV_PIX_FMT_RGB24, width, height, AV_PIX_FMT_YUV420P, SWS_FAST_BILINEAR, NULL, NULL, NULL); // Preparing to convert my generated RGB images to YUV frames.
// Preparing the data concerning the format and codec in order to write properly the header, frame data and end of file.
char *fmtext="mp4";
char *filename;
sprintf(filename, "GeneratedVideo.%s", fmtext);
AVOutputFormat * fmt = av_guess_format(fmtext, NULL, NULL);
AVFormatContext *oc = NULL;
avformat_alloc_output_context2(&oc, NULL, NULL, filename);
AVStream * stream = avformat_new_stream(oc, 0);
AVCodec *codec=NULL;
AVCodecContext *c= NULL;
int ret;
codec = avcodec_find_encoder_by_name("libx264");
// Setting up the codec:
av_dict_set( &opt, "preset", "slow", 0 );
av_dict_set( &opt, "crf", "20", 0 );
avcodec_get_context_defaults3(stream->codec, codec);
c=avcodec_alloc_context3(codec);
c->width = width;
c->height = height;
c->pix_fmt = AV_PIX_FMT_YUV420P;
// Setting up the format, its stream(s), linking with the codec(s) and write the header:
if (oc->oformat->flags & AVFMT_GLOBALHEADER) // Some formats require a global header.
c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
avcodec_open2( c, codec, &opt );
av_dict_free(&opt);
stream->time_base=(AVRational){1, 25};
stream->codec=c; // Once the codec is set up, we need to let the container know which codec are the streams using, in this case the only (video) stream.
av_dump_format(oc, 0, filename, 1);
avio_open(&oc->pb, filename, AVIO_FLAG_WRITE);
ret=avformat_write_header(oc, &opt);
av_dict_free(&opt);
// Preparing the containers of the frame data:
AVFrame *rgbpic, *yuvpic;
// Allocating memory for each RGB frame, which will be lately converted to YUV:
rgbpic=av_frame_alloc();
rgbpic->format=AV_PIX_FMT_RGB24;
rgbpic->width=width;
rgbpic->height=height;
ret=av_frame_get_buffer(rgbpic, 1);
// Allocating memory for each conversion output YUV frame:
yuvpic=av_frame_alloc();
yuvpic->format=AV_PIX_FMT_YUV420P;
yuvpic->width=width;
yuvpic->height=height;
ret=av_frame_get_buffer(yuvpic, 1);
// After the format, code and general frame data is set, we write the video in the frame generation loop:
// std::vector<uint8_t> B(width*height*3);
Вышеуказанный прокомментированный вектор имеет ту же структуру, что и тот, который я выставил в своем вопросе; однако данные RGB хранятся на AVFrames определенным образом. Поэтому, ради изложения, предположим, что вместо этого мы указали на структуру формы uint8_t [3] Matrix (int, int), способ доступа к значениям цветов пикселей для заданной координаты (x, y) - матрица (x, y) → Red, Matrix (x, y) → Green и Matrix (x, y) → Blue, чтобы получить соответственно красные, зеленые и синие значения координата (x, y). Первый аргумент означает горизонтальное положение слева направо по мере увеличения x, а второе для вертикального положения сверху вниз с увеличением y.
Будучи указанным, цикл for для передачи данных, кодирования и записи каждого кадра будет следующим:
Matrix B(width, height);
int got_output;
AVPacket pkt;
for (i=0; i<N; i++)
{
generateframe(B, i); // This one is the function that generates a different frame for each i.
// The AVFrame data will be stored as RGBRGBRGB... row-wise, from left to right and from top to bottom, hence we have to proceed as follows:
for (y=0; y<height; y++)
{
for (x=0; x<width; x++)
{
// rgbpic->linesize[0] is equal to width.
rgbpic->data[0][y*rgbpic->linesize[0]+3*x]=B(x, y)->Red;
rgbpic->data[0][y*rgbpic->linesize[0]+3*x+1]=B(x, y)->Green;
rgbpic->data[0][y*rgbpic->linesize[0]+3*x+2]=B(x, y)->Blue;
}
}
sws_scale(convertCtx, rgbpic->data, rgbpic->linesize, 0, height, yuvpic->data, yuvpic->linesize); // Not actually scaling anything, but just converting the RGB data to YUV and store it in yuvpic.
av_init_packet(&pkt);
pkt.data = NULL;
pkt.size = 0;
yuvpic->pts = i; // The PTS of the frame are just in a reference unit, unrelated to the format we are using. We set them, for instance, as the corresponding frame number.
ret=avcodec_encode_video2(c, &pkt, yuvpic, &got_output);
if (got_output)
{
fflush(stdout);
av_packet_rescale_ts(&pkt, (AVRational){1, 25}, stream->time_base); // We set the packet PTS and DTS taking in the account our FPS (second argument) and the time base that our selected format uses (third argument).
pkt.stream_index = stream->index;
printf("Write frame %6d (size=%6d)\n", i, pkt.size);
av_interleaved_write_frame(oc, &pkt); // Write the encoded frame to the mp4 file.
av_packet_unref(&pkt);
}
}
// Writing the delayed frames:
for (got_output = 1; got_output; i++) {
ret = avcodec_encode_video2(c, &pkt, NULL, &got_output);
if (got_output) {
fflush(stdout);
av_packet_rescale_ts(&pkt, (AVRational){1, 25}, stream->time_base);
pkt.stream_index = stream->index;
printf("Write frame %6d (size=%6d)\n", i, pkt.size);
av_interleaved_write_frame(oc, &pkt);
av_packet_unref(&pkt);
}
}
av_write_trailer(oc); // Writing the end of the file.
if (!(fmt->flags & AVFMT_NOFILE))
avio_closep(oc->pb); // Closing the file.
avcodec_close(stream->codec);
// Freeing all the allocated memory:
sws_freeContext(convertCtx);
av_frame_free(&rgbpic);
av_frame_free(&yuvpic);
avformat_free_context(oc);
Боковые заметки:
В будущем, поскольку доступная информация о сети, касающаяся временных меток (PTS/DTS), выглядит настолько запутанной, и я буду объяснять, как мне удалось решить проблемы, установив правильные значения. Установка этих значений неправильно привела к тому, что размер вывода был намного больше, чем размер, полученный с помощью встроенного инструментальной функции командной строки ffmpeg, поскольку данные кадра были избыточно записаны с меньшими временными интервалами, чем фактически установленные FPS.
Прежде всего, следует отметить, что при кодировании существуют два типа штампов времени: один связан с кадром (PTS) (этап предварительного кодирования) и два связанных с пакетом (PTS и DTS) (post- этап кодирования). В первом случае похоже, что значения PTS фрейма могут быть назначены с использованием настраиваемой единицы ссылки (с единственным ограничением, что они должны быть равномерно распределены, если требуется постоянный FPS), поэтому можно взять, например, номер кадра, поскольку мы в приведенном выше коде. Во втором мы должны учитывать следующие параметры:
- Временная база контейнера выходного формата, в нашем случае mp4 (= 12800 Гц), информация которой хранится в stream- > time_base.
- Желаемый FPS видео.
- Если кодер генерирует B-кадры или нет (во втором случае значения PTS и DTS для фрейма должны быть одинаковыми, но это сложнее, если мы в первом случае, как в этом примере). См. Этот ответ на другой связанный вопрос для получения дополнительных ссылок.
Ключевым моментом здесь является то, что, к счастью, нет необходимости бороться с вычислением этих величин, поскольку libav предоставляет функцию для вычисления правильных временных меток, связанных с пакетом, зная вышеупомянутые данные:
av_packet_rescale_ts(AVPacket *pkt, AVRational FPS, AVRational time_base)
Благодаря этим соображениям я, наконец, смог создать разумный выходной контейнер и, по сути, такую же скорость сжатия, как и полученную с помощью инструмента командной строки, которые были двумя оставшимися проблемами, прежде чем более глубоко изучить, как заголовок и трейлер формата и как правильно настроены метки времени.