LINUX.ORG.RU

ffmpeg: как записать корректный заголовок?

 , ,


1

1

Здравствуйте. Уже неделю мучаю ffmpeg. На данный момент осилено декодирование видео в несжатый вариант (raw). Теперь нужно реализовать кодирование в тот формат, который мне нужен. И вроде бы даже что-то там у меня кодируется, но на выходе получается нечитаемый файл. Судя по всему, я просто пишу корявый заголовок и поэтому понять, удалось ли что-то там кодировать — невозможно, т.к. файл-то не проиграть.

Пробую пока тупо расжать и сжать обратно mp4-файл. Вот функция, которая открывает выходной файл и пишет заголовок. По сути, слегка переделанный код из примера transcoding.c, идущего вместе с исходниками ffmpeg.

int open_output_file(const char *filename)
{
	int result;
	avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename);
	if (!ofmt_ctx)
	{
		printf("can't create output context\n");
		return -1;
	}

	ostream = avformat_new_stream(ofmt_ctx, NULL);
	if (!ostream)
	{
		printf("can't create new stream\n");
		return -1;
	}

	ostream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
	ostream->codecpar->codec_id = AV_CODEC_ID_H264;
	ostream->codecpar->height = decoder_ctx->height;
	ostream->codecpar->width = decoder_ctx->width;

	encoder_ctx = ostream->codec;
	encoder = avcodec_find_encoder(decoder_ctx->codec_id);
	if (!encoder)
	{
		printf("can't find encoder\n");
		return -1;
	}
	encoder_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
	encoder_ctx->codec_id = AV_CODEC_ID_H264;
	encoder_ctx->height = decoder_ctx->height;
	encoder_ctx->width = decoder_ctx->width;
	encoder_ctx->qmin = 10;
	encoder_ctx->qmax = 51;
	encoder_ctx->max_qdiff = 4;
	encoder_ctx->sample_aspect_ratio = decoder_ctx->sample_aspect_ratio;
	encoder_ctx->time_base = decoder_ctx->time_base;
	ostream->time_base = decoder_ctx->time_base;
	if (encoder->pix_fmts)
	{
		encoder_ctx->pix_fmt = encoder->pix_fmts[0];
	}
	else
	{
		encoder_ctx->pix_fmt = decoder_ctx->pix_fmt;
	}
	result = avcodec_open2(encoder_ctx, encoder, NULL)
	if (result < 0)
	{
		printf("can't open encoder for video stream \n");
		return -1;
	}

	if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
		encoder_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;

	av_dump_format(ofmt_ctx, 0, filename, 1);

	result = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE);
	if (result < 0)
	{
		printf("can't open output file %s\n", filename);
		return -1;
	}

	result = avformat_write_header(ofmt_ctx, NULL);
	if (result < 0)
	{
		printf("can't write header\n");
		return -1;
	}

	return 0;
}

В результате работы функции получается файл размером 48 байт, с вот таким содержимым:

$ head -1 out.mp4
 ftypisomisomiso2avc1mp4freemdat

Если выполнить ffprobe на этот файл, в ответ получаем ругань:

[mov,mp4,m4a,3gp,3g2,mj2 @ 0x3530bc0] moov atom not found
out.mp4: Invalid data found when processing input

Если посмотреть корректный mp4-файл, то там в начале вот так (те же первые 48 байт):

▒ftypmp42isommp42�KmoovlmvhdМ�"М�"X�

Если отрезать эти самые 48 байт от корректного mp4-файла и скормить их ffprobe, то он хотя бы не матерится:

Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
  Metadata:
    major_brand     : mp42
    minor_version   : 0
    compatible_brands: isommp42
    creation_time   : 2014-11-27 16:04:50
  Duration: N/A, bitrate: N/A

Предположительно, что-то не так в параметре ofmt_ctx функции avformat_write_header. Посмотрел в дебагере значения некоторых полей структуры ofmt_ctx->oformat и с точки зрения человека, не разбирающегося в кодировании видео, все выглядит нормально:

136             if (avformat_write_header(ofmt_ctx, NULL) < 0)
(gdb) print ofmt_ctx->oformat->name
$1 = 0xfb258c "mp4"
(gdb) print ofmt_ctx->oformat->mime_type
$2 = 0xf858cd "video/mp4"
(gdb) print ofmt_ctx->oformat->video_codec
$3 = AV_CODEC_ID_H264

В общем, как говорится, куда рыть, что еще смотреть?

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


Ответ на: комментарий от Andrey_Utkin
$ ./ffmpeg -version
ffmpeg version 3.0.git Copyright (c) 2000-2016 the FFmpeg developers
built with gcc 4.9.2 (Debian 4.9.2-10)
configuration:
--prefix=/home/username/src/ffmpeg_build_light
--pkg-config-flags=--static
--extra-cflags=-I/home/username/src/ffmpeg_build_light/include
--extra-ldflags=-L/home/username/src/ffmpeg_build_light/lib
--bindir=/home/username/src/ffmpeg_build_light/bin
--enable-gpl
--enable-libx264
libavutil      55. 26.100 / 55. 26.100
libavcodec     57. 46.100 / 57. 46.100
libavformat    57. 39.100 / 57. 39.100
libavdevice    57.  0.101 / 57.  0.101
libavfilter     6. 46.102 /  6. 46.102
libswscale      4.  1.100 /  4.  1.100
libswresample   2.  1.100 /  2.  1.100
libpostproc    54.  0.100 / 54.  0.100

ffmpeg скомпилирован как раз из исходников с git'a. Примеры, соотв., тоже оттуда.

Разжималку получилось сделать на базе примера decoding_demuxing.c. Функция открытия выходного файла, по суди, взята из «transcoding.c» и убрано всякое, не относящееся непосредственно к видео. Ну и немного добавлено, чтобы ffmpeg не матерился.

Есть еще пример decoding_encoding.c, там я запись заголовка в файл не увидел, но там и код 2001 года, не уверен, что его целесообразно использовать.

s3rjke ()
Ответ на: комментарий от s3rjke

Вчитался в вашу тему. Ваша ошибка в том, что вы не финализируете файл (avformat_write_trailer()). Без него у вас не будет валидного файла, конкретно в случае mp4 контейнера. MPEG TS, Matroska, FLV - должны работать. MP4 - известно из практики, что нет.

Andrey_Utkin ★★ ()
Ответ на: комментарий от Andrey_Utkin

Получается, пока само видео не готово (не закодировано и не записано в файл), корректный заголовок и не сформируется?

avformat_write_trailer() у меня после цикла декодирования-кодирования стоит. Судя по всему, в процессе тасования кода все-таки что-то срослось и, хоть программа и падает в конце с сегфолтом, но некий видеофайл все-таки получается и даже проигрывается (с бешанной скоростью, мда).

Что ж, видимо теперь нужно крутить параметры и смотреть логику декодирования-кодирования.

s3rjke ()
Ответ на: комментарий от s3rjke

Да, в случае MP4 процедура финализации файла прыгает в заголовок и там записывает критичные для проигрывания метаданные. Это специфика формата MP4.

Andrey_Utkin ★★ ()
Ответ на: комментарий от Andrey_Utkin

Заборол сегфолт, оказалось, что надо писать в файл функцией av_interleaved_write_frame(), вместо av_write_frame().

ffprobe теперь нормально показывает информацию о файле. Ну как, нормально. major_brand : isom хотя я пытаюсь получить mp42 (как в исходных файлах).

Вот вывод av_dump_format(ofmt_ctx, 0, filename, 1); в момент вызова из кода функции ОП-поста:

[libx264 @ 0x25d3500] using SAR=1/1
[libx264 @ 0x25d3500] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2
[libx264 @ 0x25d3500] profile High, level 3.2
Output #0, mp4, to 'out.mp4':
    Stream #0:0: Video: h264 (libx264), none, 1280x720, q=10-30, 59.94 tbn, 59.94 tbc

Наверное, дело в profile High, level 3.2?

s3rjke ()
Ответ на: комментарий от Andrey_Utkin

Можете подсказать по еще одному вопросу?

Хочу попробовать взять видео с вебки.

Но не могу понять, почему в данном кусочке (логику «как открыть вебку» взял тут):

ifmt_ctx = avformat_alloc_context();
//ifmt = av_find_input_format("v4l2");
ifmt = av_find_input_format("video4linux2");
if (!ifmt)
{
	printf("%d\tcan't find suitable input format\n", j);
	return -1;
}
else
{
	printf("%d\tinput format created\n", j);
	j++;
}

Функция av_find_input_format() не может корректно отработать. Как мне кажется, судя по исходникам (libavformat/format.c), она просто проходится по структурам типа AVInputFormat и проверяет поле name:

AVInputFormat *av_find_input_format(const char *short_name)
{
	AVInputFormat *fmt = NULL;
	while ((fmt = av_iformat_next(fmt)))
		if (av_match_name(short_name, fmt->name))
			return fmt;
	return NULL;
}

В исходниках libavdevice/v4l2.c в самом конце файла есть вот такое определение:

AVInputFormat ff_v4l2_demuxer = {
	.name           = "video4linux2,v4l2",
	.long_name      = NULL_IF_CONFIG_SMALL("Video4Linux2 device grab"),
	.priv_data_size = sizeof(struct video_data),
	.read_probe     = v4l2_read_probe,
	.read_header    = v4l2_read_header,
	.read_packet    = v4l2_read_packet,
	.read_close     = v4l2_read_close,
	.get_device_list = v4l2_get_device_list,
	.flags          = AVFMT_NOFILE,
	.priv_class     = &v4l2_class,
};

Опять же, как мне кажется, при очередном вызове av_iformat_next(fmt) функции av_find_input_format() она таки должна будет попасть на структуру, определенную выше и получится совпадение либо с «video4linux2», либо с «v4l2». Но, видимо, не происходит.

В программу добавлено #include <libavdevice/avdevice.h>, в опции gcc: -lavdevice.

Скомпиленный файл v4l2.c, вроде как, есть в либе avdevice.a:

$ ar -t libavdevice.a | grep v4l2
v4l2-common.o
v4l2.o
v4l2enc.o

В чем может быть проблема? У меня есть подозрение, что я чего-то в языке не понимаю, но чего именно — непонятно. :) Из командной строки ffmpeg'ом вебка нормально читается, как раз с опцией -f v4l2.

s3rjke ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.