65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 FFmpegCore 转换视频

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2021 年 2 月 23 日

CPOL

2分钟阅读

viewsIcon

11423

一个简单的代码片段,展示了如何在 .NET Core 中转换视频

引言

对于大多数开发者来说,处理多媒体是一片未知的领域,因为在常规业务应用中很少会遇到这种情况。因此,当我被要求为我目前正在进行的项目转换视频时,我预计会处理某种古老且维护不良的 C++ 库。所以 FFmpegCore 让我感到惊喜,因为它能够与 .NET Core 协同工作,而 .NET Core 正是我擅长的领域。

本文中的示例将使用 F# 提供 我非常喜欢的一种语言,但它们非常简单明了,因此翻译成 C# 应该不会有任何问题。

安装 FFmpeg

虽然文档中说明这个核心是“一个 .NET Standard FFMpeg/FFProbe 包装器”,但这并没有说明 Ffmpeg/fmprobe 应该安装在运行应用程序的机器上。

此外,我认为值得澄清的是,FFMpeg 是一个跨平台命令行工具,允许处理视频。

在 Linux 上,一旦你执行 apt install 就可以使用了,但在 Windows 上,有一个需要注意的地方。在 Windows 上安装 FFmpeg 涉及下载二进制文件并将其放在一个文件夹中,但一旦你运行该工具,你可能会遇到错误

system.componentmodel.win32exception: the system cannot find the file specified

这个错误可以通过 staticFFMpegOptions 来解决

let options = FFMpegOptions()
options.RootDirectory <- "path to your binaries"
FFMpegOptions.Configure(options)

查询视频信息

为了查询视频信息,我们使用静态的 FFProbe,它具有同步和异步 API 用于视频分析。让我们坚持使用 async 版本并将输出序列化以检查 FFProbe 提供的丰富信息。

async {
    let! videoInfo = FFProbe.AnalyseAsync fileName |> Async.AwaitTask
    return JsonSerializer.Serialize videoInfo
}

输出可能像下面一样丰富

{
   "Path":"D:\\giphy.mp4",
   "Extension":".mp4",
   "Duration":{
      "Ticks":17200000,
      "Days":0,
      "Hours":0,
      "Milliseconds":720,
      "Minutes":0,
      "Seconds":1,
      "TotalDays":1.990740740740741E-05,
      "TotalHours":0.00047777777777777776,
      "TotalMilliseconds":1720,
      "TotalMinutes":0.028666666666666667,
      "TotalSeconds":1.72
   },
   "Format":{
      "Duration":{
         "Ticks":17200000,
         "Days":0,
         "Hours":0,
         "Milliseconds":720,
         "Minutes":0,
         "Seconds":1,
         "TotalDays":1.990740740740741E-05,
         "TotalHours":0.00047777777777777776,
         "TotalMilliseconds":1720,
         "TotalMinutes":0.028666666666666667,
         "TotalSeconds":1.72
      },
      "FormatName":"mov,mp4,m4a,3gp,3g2,mj2",
      "FormatLongName":"QuickTime / MOV",
      "StreamCount":1,
      "ProbeScore":100,
      "BitRate":458339,
      "Tags":{
         "major_brand":"isom",
         "minor_version":"512",
         "compatible_brands":"isomiso2avc1mp41",
         "encoder":"Lavf56.40.101"
      }
   },
   "PrimaryAudioStream":null,
   "PrimaryVideoStream":{
      "AvgFrameRate":25,
      "BitsPerRawSample":8,
      "DisplayAspectRatio":{

      },
      "Profile":"Constrained Baseline",
      "Width":480,
      "Height":264,
      "FrameRate":25,
      "PixelFormat":"yuv420p",
      "Rotation":0,
      "Index":0,
      "CodecName":"h264",
      "CodecLongName":"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
      "BitRate":453744,
      "Duration":{
         "Ticks":17200000,
         "Days":0,
         "Hours":0,
         "Milliseconds":720,
         "Minutes":0,
         "Seconds":1,
         "TotalDays":1.990740740740741E-05,
         "TotalHours":0.00047777777777777776,
         "TotalMilliseconds":1720,
         "TotalMinutes":0.028666666666666667,
         "TotalSeconds":1.72
      },
      "Language":"und",
      "Tags":{
         "language":"und",
         "handler_name":"VideoHandler",
         "vendor_id":"[0][0][0][0]"
      }
   },
   "VideoStreams":[
      {
         "AvgFrameRate":25,
         "BitsPerRawSample":8,
         "DisplayAspectRatio":{

         },
         "Profile":"Constrained Baseline",
         "Width":480,
         "Height":264,
         "FrameRate":25,
         "PixelFormat":"yuv420p",
         "Rotation":0,
         "Index":0,
         "CodecName":"h264",
         "CodecLongName":"H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10",
         "BitRate":453744,
         "Duration":{
            "Ticks":17200000,
            "Days":0,
            "Hours":0,
            "Milliseconds":720,
            "Minutes":0,
            "Seconds":1,
            "TotalDays":1.990740740740741E-05,
            "TotalHours":0.00047777777777777776,
            "TotalMilliseconds":1720,
            "TotalMinutes":0.028666666666666667,
            "TotalSeconds":1.72
         },
         "Language":"und",
         "Tags":{
            "language":"und",
            "handler_name":"VideoHandler",
            "vendor_id":"[0][0][0][0]"
         }
      }
   ],
   "AudioStreams":[

   ]
}

转换视频

为了转换视频,可以使用 static FFMpegArguments 类,它实现了一种静态构建器模式。同样,它也同时提供同步和异步 API,我们将坚持使用后者。

async {
    let! _ =
        FFMpegArguments
            .FromFileInput(fileName)
            .OutputToFile(outputFileName,
                true,
                fun options -> options
                                .WithVideoCodec(VideoCodec.LibX264)
                                .WithAudioCodec(AudioCodec.Aac)
                                .WithVariableBitrate(4)
                                .Resize(newWidth, newHeight)
                                |> ignore)
            .ProcessAsynchronously() |> Async.AwaitTask
    ()
}

在某些情况下,FFMpeg 可能会返回一个错误。

"ffmpeg version 2021-01-24-git-1775688292-full_build-www.gyan.dev Copyright (c) 2000-2021 
the FFmpeg developers\n  built with gcc 10.2.0 (Rev6, Built by MSYS2 project)\n  
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads 
--disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 
--enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-libsrt --enable-libssh 
--enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 
--enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp 
--enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg 
--enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi 
--enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm 
--enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va 
--enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl 
--enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt 
--enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora 
--enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm 
--enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis 
--enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband 
--enable-libsoxr --enable-chromaprint\n  libavutil      
56. 63.101 / 56. 63.101\n  libavcodec     58.117.101 / 58.117.101\n  libavformat    
58. 65.101 / 58. 65.101\n  libavdevice    58. 11.103 / 58. 11.103\n  libavfilter     
7. 96.100 /  7. 96.100\n  libswscale      5.  8.100 /  5.  8.100\n  libswresample   
3.  8.100 /  3.  8.100\n  libpostproc    55.  8.100 / 55.  
8.100\nInput #0, mov,mp4,m4a,3gp,3g2,mj2, from 'D:\\giphy.mp4':\n  Metadata:\n    
major_brand     : isom\n    minor_version   : 512\n    compatible_brands: isomiso2avc1mp41\n
    encoder         : Lavf56.40.101\n  Duration: 00:00:01.72, start: 0.000000, 
bitrate: 458 kb/s\n    Stream #0:0(und): Video: h264 (Constrained Baseline) 
(avc1 / 0x31637661), yuv420p, 480x264 [SAR 1:1 DAR 20:11], 453 kb/s, 25 fps, 25 tbr, 
12800 tbn, 50 tbc (default)\n    Metadata:\n      handler_name    : VideoHandler\n
      vendor_id       : [0][0][0][0]\nCodec AVOption vbr (Variable bit rate mode) 
specified for output file #0 (D:\\kek.mp4) has not been used for any stream. 
The most likely reason is either wrong type (e.g. a video option with no video streams) 
or that it is a private option of some encoder which was not actually used for any stream.
\nStream mapping:\n  Stream #0:0 -> #0:0 (h264 (native) -> h264 (libx264))\nPress [q] to stop, 
[?] for help\n[libx264 @ 000001cd5ac43100] width not divisible by 2 
(101x101)\nError initializing output stream 0:0 -- Error while opening encoder for 
output stream #0:0 - maybe incorrect parameters such as bit_rate, rate, width or 
height\nConversion failed!"

虽然 stacktrace 看起来很吓人,但“宽度不能被 2 整除”表明 FFMpeg 对奇数的 widthheight 有要求。我使用这个简单的技巧来欺骗它并强制它转换我的视频。

let newWidth =
    if videoInfo.PrimaryVideoStream.Height % 2 = 0 then
        videoInfo.PrimaryVideoStream.Height
    else videoInfo.PrimaryVideoStream.Height - 1

videoInfo 这里是几段上面的 FFProbe 的工作结果。

FFmpegCore 能够做更多的事情,例如,截取屏幕截图、更改视频缩略图等,但我将把研究留给好奇的读者。

历史

  • 2021 年 2 月 23 日 - 提交初始版本
© . All rights reserved.