如何减少 FFmpeg 创建的视频的大小

如何减少 FFmpeg 创建的视频的大小

我正在使用 FFmpeg 从图像序列制作视频。我的目的是创建一个 mov/mp4 格式的透明背景视频。

使用“-c:v qtrle”或“-c:v png”都可以达到我的目的,但是“-c:v png”总是给我一个超大尺寸的视频。

正常情况下约为 500kb,使用“-c:v png”将增加到 30~40MB。数据速率异常高,我该如何解决?

这是我的命令,ffmpeg -r 30 -i testImage_%03d.png -vcodec png test.mov
我曾尝试将最大比特率添加到命令中,但没有效果。

顺便说一句“-c:v qtrle”运行良好,但是 quicktime 在 Windows 上存在一些问题,所以我倾向于不使用它。

答案1

Png 是一种用于压缩和存储单个图像的格式,而不是用于视频的格式。因此,您只需将图像序列复制到 test.mov 容器中的图像序列即可。在该容器中,每个图像所需的字节数与直接存储到硬盘中时一样多。因为与大多数常见的视频格式不同,像 png 这样的图像格式不会并且不能利用连续图像之间的相似性来获得更高的压缩比。

现在,为了获得高压缩率,视频格式必须关联来自 2 张或更多图像的图像信息。大多数视频格式还会过滤掉相当多的像素颜色信息(假设您在移动图像中不会注意到这一点)。这两种减小视频尺寸的技术都很难跟踪哪个像素具有哪种透明度。

因此,大多数显示透明度的视频格式都依赖于 png 等单一图像格式。即便如此,可能唯一被广泛支持的格式是 Shockwave。似乎没有一种格式能将高压缩率与透明度结合在一起。

结论是,您必须至少忽略以下三个项目中的其中一个:小文件大小、广泛支持或透明度。

答案2

你可以试试UT 视频。它是一种免费的无损压缩格式,支持 alpha 通道(作为 RGBA),解码和编码由 原生支持ffmpeg,并且易于安装,允许集成到 After Effects、Adobe Media Encoder 等中。

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

我目前无法访问 AE,因此没有测试这一点。如果您在 AE 打开时安装了它,请确保重新启动 AE。

答案3

这是我创建一个不太大的 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(
                "Process is still Running. Please wait for the process to complete.");
        }

        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 = //... some date time;
            DateTime maxEndDateTime = //... some later date time
            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)
            {
                // If stilll running, send 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_qahttps://stackoverflow.com/questions/30249101/windows-how-to-get-the-process-group-of-a-process-that-is-already-running/50311226#50311226https://www.youtube.com/watch?v=JEVlRqajKNI

仅供参考,以前我得到的 mp4 文件大小为 7 或 8 GB,但现在使用上述代码录制超过 2 小时的会话时,文件大小只有约 500 MB。

相关内容