title: FFmpeg Autogen and Unity C# to generate video from screenshots (FFmpeg.Autogen)
body: <p>I've taken the <code>FFmpegHelper</code>, <code>VideoFrameConverter</code>, <code>H264VideoStreamEncoder</code> classes straight from the <a href="https://github.com/Ruslan-B/FFmpeg.AutoGen/tree/master/FFmpeg.AutoGen.Example" rel="nofollow noreferrer">FFmpeg.AutoGen.Example</a>, rolled my own <code>FFmpegBinariesHelper class</code> and <code>Size struct</code> and mangled the <code>EncodeImagesToH264</code> from Program.cs to look like the below code. I capture a bunch of frames into textures and feed them into <code>Encoder.EncodeImagesToH264</code>. It produces a file I'm calling <code>outputFileName.h264</code> just fine, no errors. I've changed <code>H264VideoStreamEncoder</code> a little based on ffmpeg's own c++ examples because they had a few things it seemed the C# example was missing but that hasn't made any difference.</p>
  <p>The video is weird:</p>
  <ol>
  <li>it only plays in VLC, is there another <code>AVPixelFormat</code> I should be using for the <code>destinationPixelFormat</code> so that anything can play?</li>
  <li>VLC is unable to detect the video length or show current time</li>
  <li>it plays back weird as if the first few seconds are all the same frame then starts playing what appears to be some of the frames I'd expect</li>
  </ol>
  <pre><code>    public static class Encoder
      {
          public static unsafe void EncodeImagesToH264(Texture2D[] images, int fps, string outputFileName)
          {
              FFmpegBinariesHelper.RegisterFFmpegBinaries();
  
              var fistFrameImage = images[0];
              outputFileName = Path.ChangeExtension(outputFileName, &quot;.h264&quot;);
              var sourceSize = new Size(fistFrameImage.width, fistFrameImage.height);
              var sourcePixelFormat = AVPixelFormat.AV_PIX_FMT_RGB24;
              var destinationSize = sourceSize;
              var destinationPixelFormat = AVPixelFormat.AV_PIX_FMT_YUV420P;
  
              try
              {
                  using (var vfc = new VideoFrameConverter(
                      sourceSize,
                      sourcePixelFormat,
                      destinationSize,
                      destinationPixelFormat))
                  {
                      using var fs = File.Open(outputFileName, FileMode.Create);
                      using var vse = new H264VideoStreamEncoder(fs, fps, destinationSize);
                      var frameNumber = 0;
                      foreach (var frameFile in images)
                      {
                          var bitmapData = GetBitmapData(frameFile);
  
                          //var pBitmapData = (byte*)NativeArrayUnsafeUtility
                          //    .GetUnsafeBufferPointerWithoutChecks(bitmapData);
  
                          fixed (byte* pBitmapData = bitmapData)
                          {
                              var data = new byte_ptrArray8 { [0] = pBitmapData };
                              var linesize = new int_array8 { [0] = bitmapData.Length / sourceSize.Height };
                              var frame = new AVFrame
                              {
                                  data = data,
                                  linesize = linesize,
                                  height = sourceSize.Height
                              };
  
                              var convertedFrame = vfc.Convert(frame);
                              convertedFrame.pts = frameNumber;
  
                              vse.Encode(convertedFrame);
  
                              Debug.Log($&quot;frame: {frameNumber}&quot;);
                              frameNumber++;
                          }
                      }
                      byte[] endcode = { 0, 0, 1, 0xb7 };
                      fs.Write(endcode, 0, endcode.Length);
                  }
                  Debug.Log(outputFileName);
              }
              catch (Exception ex)
              {
                  Debug.LogException(ex);
              }
          }
  
          private static byte[] GetBitmapData(Texture2D frameBitmap)
          {
              return frameBitmap.GetRawTextureData();
          }
      }
  
      public sealed unsafe class H264VideoStreamEncoder : IDisposable
      {
          private readonly Size _frameSize;
          private readonly int _linesizeU;
          private readonly int _linesizeV;
          private readonly int _linesizeY;
          private readonly AVCodec* _pCodec;
          private readonly AVCodecContext* _pCodecContext;
          private readonly Stream _stream;
          private readonly int _uSize;
          private readonly int _ySize;
  
          public H264VideoStreamEncoder(Stream stream, int fps, Size frameSize)
          {
              _stream = stream;
              _frameSize = frameSize;
  
              var codecId = AVCodecID.AV_CODEC_ID_H264;
              _pCodec = ffmpeg.avcodec_find_encoder(codecId);
              if (_pCodec == null)
                  throw new InvalidOperationException(&quot;Codec not found.&quot;);
  
              _pCodecContext = ffmpeg.avcodec_alloc_context3(_pCodec);
              _pCodecContext-&gt;bit_rate = 400000;
              _pCodecContext-&gt;width = frameSize.Width;
              _pCodecContext-&gt;height = frameSize.Height;
              _pCodecContext-&gt;time_base = new AVRational { num = 1, den = fps };
              _pCodecContext-&gt;gop_size = 10;
              _pCodecContext-&gt;max_b_frames = 1;
              _pCodecContext-&gt;pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;
  
              if (codecId == AVCodecID.AV_CODEC_ID_H264)
                  ffmpeg.av_opt_set(_pCodecContext-&gt;priv_data, &quot;preset&quot;, &quot;veryslow&quot;, 0);
  
              ffmpeg.avcodec_open2(_pCodecContext, _pCodec, null).ThrowExceptionIfError();
  
              _linesizeY = frameSize.Width;
              _linesizeU = frameSize.Width / 2;
              _linesizeV = frameSize.Width / 2;
  
              _ySize = _linesizeY * frameSize.Height;
              _uSize = _linesizeU * frameSize.Height / 2;
          }
  
          public void Dispose()
          {
              ffmpeg.avcodec_close(_pCodecContext);
              ffmpeg.av_free(_pCodecContext);
          }
  
          public void Encode(AVFrame frame)
          {
              if (frame.format != (int)_pCodecContext-&gt;pix_fmt)
                  throw new ArgumentException(&quot;Invalid pixel format.&quot;, nameof(frame));
              if (frame.width != _frameSize.Width)
                  throw new ArgumentException(&quot;Invalid width.&quot;, nameof(frame));
              if (frame.height != _frameSize.Height)
                  throw new ArgumentException(&quot;Invalid height.&quot;, nameof(frame));
              if (frame.linesize[0] &lt; _linesizeY)
                  throw new ArgumentException(&quot;Invalid Y linesize.&quot;, nameof(frame));
              if (frame.linesize[1] &lt; _linesizeU)
                  throw new ArgumentException(&quot;Invalid U linesize.&quot;, nameof(frame));
              if (frame.linesize[2] &lt; _linesizeV)
                  throw new ArgumentException(&quot;Invalid V linesize.&quot;, nameof(frame));
              if (frame.data[1] - frame.data[0] &lt; _ySize)
                  throw new ArgumentException(&quot;Invalid Y data size.&quot;, nameof(frame));
              if (frame.data[2] - frame.data[1] &lt; _uSize)
                  throw new ArgumentException(&quot;Invalid U data size.&quot;, nameof(frame));
  
              var pPacket = ffmpeg.av_packet_alloc();
              try
              {
                  int error;
                  do
                  {
                      ffmpeg.avcodec_send_frame(_pCodecContext, &amp;frame).ThrowExceptionIfError();
                      ffmpeg.av_packet_unref(pPacket);
                      error = ffmpeg.avcodec_receive_packet(_pCodecContext, pPacket);
                  } while (error == ffmpeg.AVERROR(ffmpeg.EAGAIN));
  
                  error.ThrowExceptionIfError();
  
                  using var packetStream = new UnmanagedMemoryStream(pPacket-&gt;data, pPacket-&gt;size);
                  packetStream.CopyTo(_stream);
              }
              finally
              {
                  ffmpeg.av_packet_free(&amp;pPacket);
              }
          }
      }
  </code></pre>