В этой статье я покажу простой способ генерации видео программами на Python и C/C++ без использования стороннего API. Вам так же потребуется ffmpeg, без него вы не сможете конвертировать файлы в читаемые форматы!
Зачем это нужно?
Можно экспериментировать, например вы можете создать видео максимального качества и проверять как оно будет эффективно сжиматься тем или иным видео кодеком. Можете даже создать картинку с градиентом в 64-битном цвете и с дизерингом, мало ли какие ещё извращения можно придумать. Можно ещё делать видео с быстро движущимися объектами и сохранять его в 1000 кадров в секунду и потом тестировать всякие интерполяторы движения и моушн блюры.
Способ 1: на Python
С помощью скрипта на Python можно создать видео. Просто сохраните этот код в какой-нибудь "main.py"
import os
import sys
state = 0; # переменная нужная для анимации смещения узора
w = 320 # ширина кадра
h = 240 # высота кадра
fps = 25 # кадров в секунду
duration = 2 * fps # сколько длится видео (2 сек)
buffer = bytearray(w * h) # для хранения данных кадра
# генерация кадров
while state < duration:
for y in range(0, h):
for x in range(0, w):
buffer[y * w + x] = ((x + state) ^ y) % 256 # генерация узора
os.write(sys.stdout.fileno(), buffer) # кадр записывается в вывод консоли
state += 1 # немного сдвинуть узор в следующем кадре
Далее исполняете команду в консоли: python main.py | ffmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -framerate 25 -i pipe: out.mkv
В результате у вас получится двухсекундное видео с узором out.mkv.
Как это работает?
В командную строку в Windows и Linux можно выводить не только текст, но и бинарные данные файлов, а так же эти данные можно перенаправлять в другую программу, в данном случае это ffmpeg который принимает RAW кадры и конвертирует их в видео. И в коде и в команде вызова должны совпадать fps/framerate и video_size/w/h иначе всё разъедется. Нельзя просто взять и написать данные пикселей в консоль через print, нужно записывать их в stdout как в файл через os.write.
Если в коде изменить duration на 1, то создастся только один кадр с узором и его можно сохранить как картинку так:python main.py | ffmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -i pipe: out.png
Способ 2: на C/C++
Конечно Питон это медленно и я покажу как сделать это на C и C++, в этих языках стандартный поток вывода stdout тоже считается файлом и в него можно записывать бинарные данные.
С++
#ifdef WIN32
#include <fcntl.h>
#endif
#include <cstdio>
#include <iostream>
#include <cstdint>
#include <vector>
int main() {
constexpr size_t fps = 25;
constexpr size_t w = 320;
constexpr size_t h = 240;
constexpr size_t duration = fps * 5;
constexpr size_t size = w * h;
auto buffer = std::vector<uint8_t>(size);
size_t state = 0;
#ifdef WIN32
setmode(fileno(stdout), O_BINARY);
#endif
while (state < duration) {
for (size_t y = 0; y < h; ++y)
for (size_t x = 0; x < w; ++x)
buffer[y * w + x] = (((x + state) ^ y) + state) % 256u;
++state;
std::cout.write(reinterpret_cast<char*>(buffer.data()), size);
}
}
Сборка и запуск:g++ -Wall -O2 main.cpp -o prog
prog | ffmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -framerate 25 -i pipe: out.mkv
С для Windows
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <malloc.h>
typedef uint8_t byte;
int main() {
const int fps = 25;
const int w = 320;
const int h = 240;
const int duration = fps * 5;
const int size = w * h * sizeof(byte);
byte *buffer = (byte*)malloc(size);
int state = 0;
setmode(fileno(stdout), O_BINARY);
FILE *const out = fdopen(dup(fileno(stdout)), "wb");
while (state < duration) {
for (int y = 0; y < h; ++y)
for (int x = 0; x < w; ++x)
buffer[y * w + x] = (((x + state) ^ y) + state) % 256;
fwrite(buffer, 1, size, out);
++state;
}
fclose(out);
free(buffer);
}
Сборка и запуск::gcc -Wall -O2 main.c -o prog
prog | ffmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -framerate 25 -i pipe: out.mkv
С для Linux
Упрощённый вариант от @staticmain:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
const int fps = 25;
const int w = 320;
const int h = 240;
const int duratuion = fps * 5;
const int size = w * h;
unsigned char * buffer = malloc(size);
int state = 0;
while (state < duratuion) {
for (int y = 0; y < h; ++y)
for (int x = 0; x < w; ++x)
buffer[y * w + x] = (((x + state) ^ y) + state) % 256;
fwrite(buffer, size, 1, stdout);
++state;
}
free(buffer);
return EXIT_SUCCESS;
}
Cборка и запуск:gcc -Wall -O2 main.c -o prog
prog | ffmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -framerate 25 -i pipe: out.mkv
Мой старый вариант
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <malloc.h>
typedef uint8_t byte;
int main() {
const int fps = 25;
const int w = 320;
const int h = 240;
const int duratuion = fps * 5;
const int size = w * h * sizeof(byte);
byte *buffer = (byte*)malloc(size);
int state = 0;
freopen(NULL, "wb", stdout);
while (state < duratuion) {
for (int y = 0; y < h; ++y)
for (int x = 0; x < w; ++x)
buffer[y * w + x] = (((x + state) ^ y) + state) % 256;
fwrite(buffer, 1, size, stdout);
++state;
}
free(buffer);
}
Как сохранить в FFmpeg видео в полном качестве
Я специально не указывал выходной видео кодек для упрощения команд, но вы можете добавить в ffmpeg опции -vcodec libx264rgb -crf 0
для сохранения видео в lossless качестве. Если вы модернизируете программу и добавите в неё поддержку RGBA цвета, то помните что h264 не умеет сохранять прозрачность в кадрах и вам лучше использовать кодек FFV1.
Что ещё можно сделать
Можно сгенерировать видео на любом языке программирования, если на нём можно переключить стандартный вывод в бинарный режим.
По такой же логике можно и перенастроить поток ввода stdin в бинарный режим и передать в программу бинарные данные из ffmpeg, таким образом можно будет смастерить видео-фильтр. В общем надо сделать что-то типа того:
ffmpeg | фильтр | ffmpeg
. Вообще можно просто написать Frei0r фильтр на Си и использовать его в ffmpeg, но мой способ просто не требует никакого стороннего API.Поток можно перенаправлять и в файл и потом этот файл скармливать ffmpeg'у, но учтите что видео будет совсем без сжатия и несколько секунд видео 1280x720 будут весить гигабайты. Сделать это можно так:
prog > video.dat
fmpeg -y -f rawvideo -pixel_format gray -video_size 320x240 -framerate 25 -i video.dat out.mkv
Раз можно сгенерировать сырое видео, то можно и создать сырой PCM звук и конвертировать его в аудио форматы. Можно например генерировать мелодии и сохранять их в pcm_s16le поток. Опять же переключив stdin в pipe режим вы можете получать аудио поток извне, обрабатывать его своей программой и передавать далее, таким образом у вас получится аудио фильтр и не надо никакого VST/LADSPA API.
Заключение
Это очень простой способ создания видео (для программиста). Если что, в ffmpeg уже встроены некоторые генераторы тестовых видео. Сохраняются ли гигабайты сырых кадров в оперативной памяти при использовании такого способа передачи или же на диске - мне это неизвестно, возможно что у такого способа есть какие-то ограничения на размер передаваемых данных. Помните что в передаваемом потоке данных нет никаких меток синхронизации и если что-то где-то потеряется в пути, то видео всё станет кашей, так что не пытайтесь передавать такой поток через net cat (я не пробовал).
Комментарии (3)
staticmain
17.03.2022 01:32+4Простите, но то что у вас — это не код на C для Linux. Вот код на C для Linux:
#include <stdio.h> #include <stdlib.h> // Не нужен typedef. char гарантирован быть 1 байтом int main(void) { const int fps = 25; const int w = 320; const int h = 240; const int duratuion = fps * 5; const int size = w * h; // sizeof(char) всегда 1 байт unsigned char * buffer = malloc(size); // Не нужно кастовать в тип и мешать компилятору проверять типы int state = 0; // Не нужно переоткрывать stdout, в Linux он всегда binary while (state < duratuion) { for (int y = 0; y < h; ++y) for (int x = 0; x < w; ++x) buffer[y * w + x] = (((x + state) ^ y) + state) % 256; fwrite(buffer, size, 1, stdout); // просто пишите в stdout ++state; } free(buffer); return EXIT_SUCCESS; // Верните код выхода }
И да, у вас обрезана команда:./gifout.elf | \
ffmpeg -y \
-f rawvideo \
-pixel_format gray \
-video_size 320x240 \
-framerate 25 \
-i pipe: out.gifAtariSMN82 Автор
17.03.2022 14:59+1Вот код на C для Linux
Я писал такой код с fwrite(buffer, size, 1, stdout) когда делал пример под винду, но он не работал, а на линуксе я и не подумал про такой же. Чем проще код, тем лучше. Вставлю его в статью
И да, у вас обрезана команда
не стал их переносить по строкам, потому что в винде за место \ символ ^
amarao
Не сохраняются в память данные. pipe имеет лимит 64k, который можно подстаивать.