Как я могу уменьшить размер видео, созданного с помощью FFmpeg?

Вопрос или проблема

Я использую FFmpeg для создания видео из последовательностей изображений. Моя цель — создать видео с прозрачным фоном в формате mov/mp4.

Использование “-c:v qtrle” или “-c:v png” позволяет достичь моей цели, однако “-c:v png” всегда дает мне видео с очень большим размером.

Обычно размер около 500 КБ, а при использовании “-c:v png” он увеличивается до 30~40 МБ. Скорость передачи данных необычно высока, как я могу это исправить?

Вот моя команда ffmpeg -r 30 -i testImage_%03d.png -vcodec png test.mov
Я пытался добавить максимальный битрейт к команде, но это не сработало.

Кстати, “-c:v qtrle” работает нормально, но QuickTime имеет некоторые проблемы в Windows, так что я предпочитаю его не использовать.

Png — это формат для сжатия и хранения отдельных изображений, а не для видео. Вы просто копируете свою последовательность изображений в последовательность изображений в контейнере test.mov. В этом контейнере каждое изображение требует столько же байтов, сколько если бы оно было сохранено прямо на вашем жестком диске. Поскольку, в отличие от большинства распространенных видеоформатов, такие форматы изображений как png не могут использовать сходства между последовательными изображениями для достижения более высоких коэффициентов сжатия.

Теперь, чтобы получить высокий коэффициент сжатия, видеоформаты должны связывать информацию о изображениях с 2 или более изображениями. Большинство из них также отфильтровывает довольно много цветовой информации пикселей, предполагая, что вы этого не заметите на движущихся изображениях. Оба метода уменьшения размера видео делают довольно сложным отслеживание того, какой пиксель имеет какую прозрачность.

Поэтому большинство видеоформатов, которые демонстрируют прозрачность, полагаются на один формат изображений, такой как png. И даже тогда, возможно, единственным форматом, который широко поддерживается, является Shockwave. Похоже, нет формата, который комбинирует высокое сжатие с прозрачностью.

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

Вы можете попробовать UT video. Это бесплатный формат сжатия без потерь, который поддерживает альфа-канал (в формате RGBA), декодирование и кодирование нативно поддерживается ffmpeg, и его легко установить для интеграции в After Effects, Adobe Media Encoder и т. д.

ffmpeg -framerate 30 -i testImage_%03d.png -c:v utvideo test.avi

У меня сейчас нет доступа к AE, поэтому я это не тестировал. Убедитесь, что вы перезапустили AE, если вы его установили, пока он был открыт.

Вот мое решение для создания файла ffmpeg, который не слишком большой.

using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

public class ConsoleAppManager
{
    private readonly string appName;
    private readonly Process process = new Process();
    private readonly object theLock = new object();
    private SynchronizationContext context;
    private string pendingWriteData;

    public ConsoleAppManager(string appName)
    {
        this.appName = appName;

        this.process.StartInfo.FileName = this.appName;
        this.process.StartInfo.RedirectStandardError = true;
        this.process.StartInfo.StandardErrorEncoding = Encoding.UTF8;

        this.process.StartInfo.RedirectStandardInput = true;
        this.process.StartInfo.RedirectStandardOutput = true;
        this.process.EnableRaisingEvents = true;
        this.process.StartInfo.CreateNoWindow = true;

        this.process.StartInfo.UseShellExecute = false;

        this.process.StartInfo.StandardOutputEncoding = Encoding.UTF8;

        this.process.Exited += this.ProcessOnExited;
    }

    public event EventHandler<string> ErrorTextReceived;
    public event EventHandler ProcessExited;
    public event EventHandler<string> StandartTextReceived;

    public int ExitCode
    {
        get { return this.process.ExitCode; }
    }

    public bool Running
    {
        get; private set;
    }

    public void ExecuteAsync(params string[] args)
    {
        if (this.Running)
        {
            throw new InvalidOperationException(
                "Процесс все еще выполняется. Пожалуйста, подождите, пока процесс завершится.");
        }

        string arguments = string.Join(" ", args);

        this.process.StartInfo.Arguments = arguments;

        this.context = SynchronizationContext.Current;

        this.process.Start();
        this.Running = true;

        new Task(this.ReadOutputAsync).Start();
        new Task(this.WriteInputTask).Start();
        new Task(this.ReadOutputErrorAsync).Start();
    }

    public void Write(string data)
    {
        if (data == null)
        {
            return;
        }

        lock (this.theLock)
        {
            this.pendingWriteData = data;
        }
    }

    public void WriteLine(string data)
    {
        this.Write(data + Environment.NewLine);
    }

    protected virtual void OnErrorTextReceived(string e)
    {
        EventHandler<string> handler = this.ErrorTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    protected virtual void OnProcessExited()
    {
        EventHandler handler = this.ProcessExited;
        if (handler != null)
        {
            handler(this, EventArgs.Empty);
        }
    }

    protected virtual void OnStandartTextReceived(string e)
    {
        EventHandler<string> handler = this.StandartTextReceived;

        if (handler != null)
        {
            if (this.context != null)
            {
                this.context.Post(delegate { handler(this, e); }, null);
            }
            else
            {
                handler(this, e);
            }
        }
    }

    private void ProcessOnExited(object sender, EventArgs eventArgs)
    {
        this.OnProcessExited();
    }

    private async void ReadOutputAsync()
    {
        var standart = new StringBuilder();
        var buff = new char[1024];
        int length;

        while (this.process.HasExited == false)
        {
            standart.Clear();

            length = await this.process.StandardOutput.ReadAsync(buff, 0, buff.Length);
            standart.Append(buff.SubArray(0, length));
            this.OnStandartTextReceived(standart.ToString());
            Thread.Sleep(1);
        }

        this.Running = false;
    }

    private async void ReadOutputErrorAsync()
    {
        var sb = new StringBuilder();

        do
        {
            sb.Clear();
            var buff = new char[1024];
            int length = await this.process.StandardError.ReadAsync(buff, 0, buff.Length);
            sb.Append(buff.SubArray(0, length));
            this.OnErrorTextReceived(sb.ToString());
            Thread.Sleep(1);
        }
        while (this.process.HasExited == false);
    }

    private async void WriteInputTask()
    {
        while (this.process.HasExited == false)
        {
            Thread.Sleep(1);

            if (this.pendingWriteData != null)
            {
                await this.process.StandardInput.WriteLineAsync(this.pendingWriteData);
                await this.process.StandardInput.FlushAsync();

                lock (this.theLock)
                {
                    this.pendingWriteData = null;
                }
            }
        }
    }
}

Затем, при фактическом запуске процесса и отправке CTRL-C в моем основном приложении:

            DateTime maxStartDateTime = //... какая-то дата и время;
            DateTime maxEndDateTime = //... какое-то более позднее время
            var duration = maxEndDateTime.Subtract(maxStartDateTime);
            appManager = new ConsoleAppManager("ffmpeg.exe");
            string[] args = new string[] { "-rtbufsize 100M -f dshow -i video=\"screen-capture-recorder\":audio=\"virtual-audio-capturer\" -r 20 -timelimit " +
                Convert.ToString(duration.TotalSeconds) +
                " -vcodec libx264 -qp 0 -x264opts keyint=100:min_keyint=80 -acodec libmp3lame -ab 128k -ac 1 -ar 44100 -async 30 C:\\Users\\Psalm3_3\\GDrive\\Workspace\\TutorApplication\\Videos\\out_vid.mp4" };

            appManager.ExecuteAsync(args);
            await Task.Delay(Convert.ToInt32(duration.TotalSeconds * 1000) + 20000);

            if (appManager.Running)
            {
                // Если все еще работает, отправьте CTRL-C
                appManager.Write("\x3");
            }

Для подробностей см. https://stackoverflow.com/questions/21848271/redirecting-standard-input-of-console-application?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa и https://stackoverflow.com/questions/30249101/windows-how-to-get-the-process-group-of-a-process-that-is-already-running/50311226#50311226 и https://www.youtube.com/watch?v=JEVlRqajKNI

Для справки, ранее я получал mp4 файлы размером 7 или 8 ГБ, но теперь с помощью приведенного выше кода для записи сеанса длиной более 2 часов размер файла составляет всего около 500 МБ.

Ответ или решение

Как уменьшить размер видео, созданного с помощью FFmpeg

Создание видео с прозрачным фоном из последовательности изображений с использованием FFmpeg может быть сложной задачей, особенно когда вы сталкиваетесь с проблемой значительного увеличения размера видео, как в случае с кодеком PNG. Понимание форматов сжатия и правильный выбор параметров кодирования могут значительно помочь в снижении размера файла.

1. Выбор кодека

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

  • Ut Video Codec: Этот бесплатный кодек предлагает сжатие без потерь и поддержку альфа-канала (прозрачности). Например, команда для создания видео с помощью Ut Video может выглядеть так:

    ffmpeg -framerate 30 -i testImage_%03d.png -c:v utvideo test.avi

2. Настройка параметров кодирования

Если вы хотите продолжать использовать формат MP4, рассмотрите использование кодека H.264 с регулировкой параметров качества и битрейта:

ffmpeg -r 30 -i testImage_%03d.png -c:v libx264 -preset slow -crf 18 -pix_fmt yuv420p test.mp4
  • -preset slow: Указывает FFmpeg на использование более медленных настроек кодирования, что позволяет достичь более высокого уровня сжатия.
  • -crf 18: Оптимизирует качество. Значения варьируются от 18 (чем меньше, тем лучше качество) до 28 (меньшее качество, меньший размер файла).
  • -pix_fmt yuv420p: Устанавливает цветовое пространство, поддерживаемое большинством плееров и платформ.

3. Использование других контейнеров и форматов

Если поддержка формата MOV или MP4 является проблемой, вы можете попробовать другие форматы контейнеров, такие как AVI с встраиванием Ut Video. Это позволит сохранить прозрачность альфа-канала, используя кодек с безупречным сжатием.

4. Сжатие и уменьшение разрешения

Если уменьшение размера файла является вашей главной целью, подумайте о снижении разрешения видео. Это можно сделать добавлением параметра -s (размер видео):

ffmpeg -r 30 -i testImage_%03d.png -s 1280x720 -c:v libx264 -crf 23 test.mp4

5. Эксперименты с параметрами битрейта

Ваша попытка изменять максимальный битрейт также может помочь. Например:

ffmpeg -r 30 -i testImage_%03d.png -b:v 1M -c:v libx264 test.mp4

Этот метод ограничивает битрейт, что в свою очередь может повлиять на размер конечного файла.

Заключение

Как показывает практика, для достижения наилучшего результата при создании видео с прозрачным фоном вам придется балансировать между качеством видео, размером файла и поддержкой форматов. Потребуется время и эксперименты, чтобы найти идеальные параметры кодирования для ваших нужд. Используя предложенные методы и команды, вы сможете значительно уменьшить размер создаваемого видео, сохраняя желаемое качество и прозрачный фон.

Оцените материал
Добавить комментарий

Капча загружается...