Я хочу использовать библиотеку C FFMPEG для создания файла Matroska Video .mkv только с потоком h264, но полученный файл .mkv оказывается поврежденным.
Файл невозможно воспроизвести с помощью проигрывателя Windows Media, ffplay или VLC, и когда я пытаюсь ffprobe
получить полученный файл, появляются следующие сообщения об ошибках:
[h264 @ 0000015060d8f5c0] No start code is found.
Last message repeated 1 times
[h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced
Last message repeated 1 times
[h264 @ 0000015060d8f5c0] decode_slice_header error
[h264 @ 0000015060d8f5c0] no frame!
[h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced
Last message repeated 1 times
[h264 @ 0000015060d8f5c0] decode_slice_header error
[h264 @ 0000015060d8f5c0] no frame!
[h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced
Last message repeated 1 times
# [...]
# this continues for a long time
Я выполнил другие шаги по устранению неполадок при кодировании h264 в Matroska, но ни один из них, похоже, не помог мне:
AV_CODEC_FLAG_GLOBAL_HEADER
: Неверные данные при создании контейнера MKV с потоком h264, поскольку дополнительные данные имеют значение NULLЭто мой код C:
#include <stdio.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
int main(void) {
char *out_file_path = "./video.mkv";
AVFormatContext *format_context;
AVStream *video_stream;
AVCodecContext *codec_context;
avformat_alloc_output_context2(&format_context, NULL, NULL, out_file_path);
const AVCodec *codec = avcodec_find_encoder(AV_CODEC_ID_H264);
video_stream = avformat_new_stream(format_context, NULL);
codec_context = avcodec_alloc_context3(codec);
av_opt_set(codec_context->priv_data, "preset", "superfast", 0);
av_opt_set(codec_context->priv_data, "crf", "22", 0);
codec_context->width = 1920;
codec_context->height = 1080;
codec_context->time_base = av_make_q(1, 30);
codec_context->pix_fmt = AV_PIX_FMT_YUV420P;
if (strncmp("./video.mkv", out_file_path, 11) == 0) {
printf("Writing .mkv...\n");
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
codec_context->extradata = (uint8_t*)av_mallocz(1024 * 1024);
codec_context->extradata_size = 1024 * 1024;
} else {
printf("Writing .mp4...\n");
}
// XXX avcodec_parameters_from_context is potentially superfluous (?)
int ret = avcodec_parameters_from_context(video_stream->codecpar, codec_context);
ret = avcodec_open2(codec_context, codec, NULL);
avio_open(&format_context->pb, out_file_path, AVIO_FLAG_WRITE);
ret = avformat_write_header(format_context, NULL);
// create a black input frame
AVFrame *input_frame = av_frame_alloc();
input_frame->width = 1920;
input_frame->height = 1080;
input_frame->format = AV_PIX_FMT_YUV420P;
ret = av_image_alloc(input_frame->data, input_frame->linesize, input_frame->width, input_frame->height, input_frame->format, 32);
ptrdiff_t linesize[4] = { input_frame->linesize[0], input_frame->linesize[1], input_frame->linesize[2], input_frame->linesize[3] };
ret = av_image_fill_black(input_frame->data, linesize, input_frame->format, 0, 1920, 1080);
// write 2 seconds of video, all black
for (size_t current_pts = 0; current_pts < 60; current_pts++) {
input_frame->pts = av_rescale_q(current_pts, av_make_q(1, 30), codec_context->time_base);;
ret = avcodec_send_frame(codec_context, input_frame);
AVPacket* packet = av_packet_alloc();
while (1) {
ret = avcodec_receive_packet(codec_context, packet);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
printf("avcodec_receive_packet failed");
} else {
av_packet_rescale_ts(packet, codec_context->time_base, video_stream->time_base);
ret = av_interleaved_write_frame(format_context, packet);
}
}
av_packet_free(&packet);
}
av_frame_free(&input_frame);
// flush encoder
avcodec_send_frame(codec_context, NULL);
AVPacket *flush_packet = av_packet_alloc();
while (avcodec_receive_packet(codec_context, flush_packet) != AVERROR_EOF) {
//int ret = av_interleaved_write_frame(format_context_, packet);
av_packet_rescale_ts(flush_packet, codec_context->time_base, video_stream->time_base);
ret = av_write_frame(format_context, flush_packet);
}
av_packet_free(&flush_packet);
ret = av_write_trailer(format_context);
ret = avio_close(format_context->pb);
return 0;
}
Обработка ошибок удалена, но она не вызывает никаких ошибок.
Этот код работает, когда я записываю в файл mp4, но создает поврежденный файл при записи в .mkv. Вы можете изменить формат выходного контейнера на mp4, установив
char *out_file_path = "./video.mp4";
Вы можете скомпилировать это, запустив
clang -I$(FFMPEG_DIR)/include -L$(FFMPEG_DIR)/lib -lavformat -lavcodec -lavutil main.c -o makemkv
Вывод, который я получаю во время выполнения приведенного выше кода:
Writing .mkv...
[libx264 @ 0x139804c40] using cpu capabilities: ARMv8 NEON
[libx264 @ 0x139804c40] profile High, level 4.0, 4:2:0, 8-bit
[libx264 @ 0x139804c40] 264 - core 164 r3108 31e19f9 - H.264/MPEG-4 AVC codec - Copyleft 2003-2023 - http://www.videolan.org/x264.html - options: cabac=1 ref=1 deblock=1:0:0 analyse=0x3:0x3 me=dia subme=1 psy=1 psy_rd=1.00:0.00 mixed_ref=0 me_range=16 chroma_me=1 trellis=0 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=0 threads=15 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=1 keyint=250 keyint_min=25 scenecut=40 intra_refresh=0 rc=crf mbtree=0 crf=22.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 pb_ratio=1.30 aq=1:1.00
Когда я использую CLI ffmpeg, я могу создать рабочий .mkv из моего рабочего .mp4 следующим образом:
ffmpeg -i video.mp4 -c:v copy created-with-ffmpeg-cli.mkv
Полученные видеофайлы я загрузил сюда: https://drive.google.com/drive/folders/1FS-0fBAwKBbO-tyxC0VrFqcCyyqd0BR_?usp=sharing
Хорошо. Я добавил тег c++, иначе не думаю, что у вас будет много читателей :-)
Просто мысль (сомневаюсь, что это настоящая проблема), вы настроили GOP на 1, по сути, конвертируя H264 в MJPEG. Возможно, это вызывает некоторую путаницу?
Пожалуйста, опубликуйте весь свой код, почему существует рамка RGB и масштабирование, когда вы просто пытаетесь мультиплексировать поток juv h264 в контейнер mkv? В выводе декодера говорится, что конечных единиц нет, например, нет данных h264, таких как SPS, PPS и т. д.
@MarvinKilling (1) Хорошо ли работает версия для командной строки с точно таким же вводом H264 для создания работающего MKV? (2) Судя по коду C++, имеет ли выходной MKV меньший размер, чем входные данные H264? Просто интересно, правильно ли настроен контейнер, но почему-то данные кадра на самом деле не были записаны. (3) Можете ли вы создать тестовый файл, чтобы поделиться им для нашего собственного тестирования воспроизведения? Достаточно простого цветного квадрата 64x64. Пожалуйста, закодируйте это изображение в h264 (это будет один кадр), а затем мультиплексируйте с вашим кодом. Поделитесь 3 файлами: (а) сам H264, (б) как мультиплексированный в MKV и (в) как мультиплексированный в MP4.
@MarvinKilling PS: Если вы сможете выполнить третий пункт выше, вы быстрее получите ответ о проблеме в данных выходного файла от других, кто может проверить тот же вывод. PPS: Вы открыты для советов/ответов по поводу ручного мультиплексирования H264 в MKV? Я имею в виду, что для вас является более приоритетным в этой задаче? Речь идет об обучении правильному использованию FFmpeg в C++? Или вы просто хотите знать, как вчера доставить MKV с байтами H264 (готовы написать собственный код)?
Эй, спасибо всем! Я переписал свой код выше, чтобы он стал полным примером на простом C (больше не на C++), и загрузил видеофайлы, которые он создает, на Google Диск. Интересно то, что создание .mp4 с почти такой же конфигурацией потока h264 работает. Я почти уверен, что проблема вызвана этим кодом: codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; codec_context->extradata = (uint8_t*)av_mallocz(1024 * 1024); codec_context->extradata_size = 1024 * 1024;
Однако, если я его оставлю, avformat_write_header
потерпит неудачу.
[h264 @ 0000015060d8f5c0] No start code is found. Last message repeated 1 times [h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced Last message repeated 1 times [h264 @ 0000015060d8f5c0] decode_slice_header error [h264 @ 0000015060d8f5c0] no frame! [h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced Last message repeated 1 times [h264 @ 0000015060d8f5c0] decode_slice_header error [h264 @ 0000015060d8f5c0] no frame! [h264 @ 0000015060d8f5c0] non-existing PPS 0 referenced Last message repeated 1 times
Похоже, ваш кодер H.264 не устанавливает ключевые кадры с этими настройками. Пожалуйста, изучите пример кода здесь: https://ffmpeg.org//doxygen/trunk/encode_video_8c-example.html
Меня беспокоит эта строка:
codec_context_->time_base = av_make_q(1, 90000);
Почему бы вам не использовать тип AVRational
, как это предложено в документации?
time_base = (AVRational){1, 25};
framerate = (AVRational){25, 1};
Нет никаких препятствий, чтобы проверить, кажется ли ваша частота кадров постоянной.
Для H.264 с потерями размер GOP будет больше 1:
c->gop_size = 10;
c->max_b_frames = 1;
c->pix_fmt = AV_PIX_FMT_YUV420P;
Больше всего меня беспокоит то, что вы никогда не увеличиваете поле pts
, поэтому каждый кадр в последовательности должен иметь одинаковую временную метку, поскольку функция av_packet_rescale_ts только масштабирует действительную временную метку, но не увеличивает и не пересчитывает ее. Таким образом, все кадры имеют временную метку 0, поскольку она никогда не устанавливалась в соответствии с существующей частотой кадров.
Привет, извините, если мой код, опубликованный выше, вызвал недоразумения. Я правильно настроил точки и временные рамки, код без проблем работал при создании потока h264 в контейнере mp4. av_make_q
возвращает AVRational и должно быть эквивалентным. time_base
был выбран из-за варианта использования моего кода: входящие кадры имеют метки времени в микросекундах и в противном случае могут перекрываться. Но это не относится к данной проблеме. Теперь я сократил свой код до минимального воспроизводимого примера и удалил неинтуитивные части.
Проблема оказалась в этой строке:
codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
Если его удалить, то все работает нормально.
Важно выделить немного места для таких дополнительных данных:
codec_context->extradata = (uint8_t*)av_mallocz(32);
codec_context->extradata_size = 32;
Вы должны обнулить дополнительные данные (например, присвоив их av_mallocz
) и убедиться, что их размер равен 6 или больше. Фактический размер, похоже, не имеет значения, если он равен 6 или больше.
Это отличается от записи в контейнер mp4, где дополнительные данные не имеют значения.
Я не понимаю, почему эта строка соотносится с 0, написанным в pts
. Требует ли спецификация MKV AV_CODEC_FLAG_GLOBAL_HEADER
и расширенных метаданных?
@TedLyngmo Я неправильно понял, как следует использовать систему тегов, сейчас я удалил тег.