How to use ffmpeg to alter video files from a monogame win dx project.

If the post is long then it is because there is not much to do. Then this is the comprehensive version. You can just look at the two classes and download ffmpeg to skip to making it work.

I threw this little code tutorial together because

  1. its pretty specific i don’t see many examples for it.
  2. because i streamlined it pretty well.
  3. because to get video to work in dx you kinda need to convert most of your videos.
  4. because ffmpeg can do a lot.
  5. If you are using monogame then after this you will have a powerful script-able video audio image converter.

So let me start by saying since the example is the tutorial, and it’s super short and simple code.
Ill talk about what you need to do with ffmpeg instead mostly and that’s not much either.

I set up my class so all you need to do is download ffmpeg for the most part, you’ll need to copy paste what the path is to ffmpeg into game one on your computer and that’s about it for ffmpeg.

download the static binary for windows here
ffmpeg windows download at https://ffmpeg.org
i downloaded the static x64 windows build and just left ffmpeg exe in my downloads folder.

The second thing you will need to do is to be able to see the output in the console window.
That is easier then going thru throwing the text to the screen from ffmpeg to monogame via a drawstring so well do that. Though you can easily rig it thru the controller class callbacks.

Monogame can display a console in visual studio via the following instructions, clicking , Project then properties then (application on the left) then looking for the ( output type ) drop down and changing it to console. This will give you both the monogame cornflowerblue window and a console window for debug output.

So here is the short but sweet game1.

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System;

namespace HowToUseFFmpegWithMgDx
{
    public class Game1 : Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        FFmpegController ffmpeg; // The ffmpeg class to tell ffmpeg to do stuff for us from within monogame.

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }
        protected override void Initialize()
        {
            base.Initialize();
        }

        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);  // Create a new SpriteBatch

            // the path to the ffmpeg binary and to foler were your video is.
            string ffmpegExeDir = @"C:\Users\YOUR\Downloads\ffmpeg-20180603-2bd24d4-win64-static\bin\ffmpeg.exe";
            string vidcontentdirectory = @"C:\Users\YOUR\Documents\Vids";

            // the directorys including were the ffmpeg binary is located.
            ffmpeg = new FFmpegController(ffmpegExeDir, vidcontentdirectory, FFmpegController.Combine(vidcontentdirectory, "Output") );

            ffmpeg.InputFileName1 = "YOURTestVideo.mp4";     // input file name.
            ffmpeg.OutputFileName1 = "YOURTestVideo.wmv";  // output file name.

            // Execute a ffmpeg command string on the input file. note mmpeg only creates not overwrites files in this contex.
            ffmpeg.FFmpegVideoConvertCustom("-b:v 2M -vcodec msmpeg4 -acodec wmav2 -b:v 256k -r 16");

            // We can get the info on a video.
            //ffmpeg.VideoInfo(@"C:\Users\YOUR\Documents\Vids\YOURTestVideo.mp4");
        }

        protected override void UnloadContent(){}

        protected override void Update(GameTime gameTime)
        {
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
                Exit();
            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);
            base.Draw(gameTime);
        }
    }
}

You will have noticed from game1 that the class FFmpegController is not a standard monogame class it is the only other requisite thing. This is just a class i wrote to make using ffmpeg easier this is the meat and potatoes of it and this is all you need for the most things regarding image or video conversions.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Xna.Framework
{

    /// <summary>
    /// ffmpeg controller class.
    /// https://ffmpeg.org/
    /// To use this download the static binary for ffmpeg windows and set the path to it.
    /// After that its pretty straight forward, 
    /// set the input file, the output file and set the folder were files are at to work on.
    /// send in a script to FFmpegCommand(,,,)
    /// </summary>
    public class FFmpegController
    {
        ProcessStartInfo procStartInfo;
        Process p;

        public string UserBasePath = "";
        public string UserBaseInputPath = "Content";
        public string UserBaseOutPutPath = "Output";

        public string InputFileName1 = "";
        public string InputFileName2 = "";
        public string OutputFileName1 = "";

        public static string PathToFFMpeg { get;  set; }

        /// <summary>
        /// Construct me bro.
        /// </summary>
        public FFmpegController(string pathToFFmpeg, string inputContentRootOrAssetsDirectory, string nameOfOutPutDirectory)
        {
            PathToFFMpeg = pathToFFmpeg;
            UserBasePath = Environment.CurrentDirectory;
            UserBaseInputPath = Path.Combine(UserBasePath, inputContentRootOrAssetsDirectory);
            UserBaseOutPutPath = Path.Combine(UserBaseInputPath, nameOfOutPutDirectory);

            Console.WriteLine(" FFmpeg path " + PathToFFMpeg);
            Console.WriteLine(" UserBasePath " + UserBasePath);
            Console.WriteLine(" UserBaseInputPath " + UserBaseInputPath);
            Console.WriteLine(" UserBaseOutPutPath " + UserBaseOutPutPath);
            Console.WriteLine();

            if (File.Exists(PathToFFMpeg))
            {
                ExecuteCommand("");
                //ExecuteCommandSync("-h encoder = libx264");
            }
            else
                Console.WriteLine("\n\n !!! The path to the static binary for FFmpeg is invalid, no ffmpeg.EXE can be found at :\n  " + PathToFFMpeg);
        }

        public void VideoInfo()
        {
            VideoInfo(Path.Combine(UserBaseInputPath, InputFileName1));
        }
        public void VideoInfo(string fullFilePath)
        {
            if (File.Exists(fullFilePath))
                ExecuteCommand("-i " + SurroundPathWithQuotes(fullFilePath));
            else
                Console.WriteLine("\n !!! The input file doesn't exist " + fullFilePath);
        }

        public void FFmpegVideoConvertCustom(string commandScript)
        {
            var InputFullFilePath = Path.Combine(UserBaseInputPath, InputFileName1);
            var OutputFullFilePath = Path.Combine(UserBaseOutPutPath, OutputFileName1);
            if (File.Exists(InputFullFilePath))
            {
                var InCmd = "-i ";
                var OutCmd = commandScript;
                DirectoryExistsCreate(Path.GetFullPath(OutputFullFilePath));
                string fullcmdline = InCmd + " " + SurroundPathWithQuotes(InputFullFilePath) + " " + OutCmd + " " + SurroundPathWithQuotes(OutputFullFilePath);
                ExecuteCommand(fullcmdline);
            }
            else
                Console.WriteLine("\n !!! The input file doesn't exist " + InputFullFilePath);
        }

        /// <summary>
        /// While technically this method is syncronous. 
        /// The process and the callbacks operate asyncronously.
        /// So you can actually close monogame and the process will continue till the task is completed.
        /// </summary>
        public void ExecuteCommand(string command)
        {
            procStartInfo = new ProcessStartInfo();
            // well go thru cmd to execute ffmpeg.
            procStartInfo.FileName = "cmd"; 
            procStartInfo.Arguments = "/c " + PathToFFMpeg + " " + command;
            procStartInfo.UseShellExecute = false;
            procStartInfo.CreateNoWindow = true;
            procStartInfo.RedirectStandardInput = true;
            procStartInfo.RedirectStandardOutput = true;
            procStartInfo.RedirectStandardError = true;
            // process
            p = new System.Diagnostics.Process();
            p.StartInfo = procStartInfo;
            p.EnableRaisingEvents = true;
            p.ErrorDataReceived += ProcessTheErrorData;
            p.OutputDataReceived += ProcessTheOutputData;
            p.Start();
            p.BeginErrorReadLine();
            p.BeginOutputReadLine();
        }

        /// <summary>
        /// registered callback with ffmpeg
        /// </summary>
        private void ProcessTheErrorData(object sender, DataReceivedEventArgs e)
        {
            Console.WriteLine(e.Data);
        }

        /// <summary>
        /// registered callback with ffmpeg
        /// </summary>
        private void ProcessTheOutputData(object sender, DataReceivedEventArgs e)
        {
            Console.WriteLine(e.Data);
        }

        #region static helper methods

        public static string CombineWithCurrentDirectory(params string[] str)
        {
            string s = Path.Combine(str);
            return Path.Combine(Environment.CurrentDirectory, s);
        }
        public static string Combine(params string[] str)
        {
            return Path.Combine(str);
        }
        public static void DirectoryExistsCreate(string path)
        {
            string s = path;
            if (Path.HasExtension(path))
            {
                s = Path.GetFullPath(path);
            }
            if (s != null && s != "" && s != " " && !Directory.Exists(s))
            {
                Console.WriteLine(" Create Directory \n " + s + " ");
                s = Path.GetDirectoryName(s);
                Directory.CreateDirectory(s);
            }
        }
        public static string SurroundPathWithQuotes(string pathtoquote)
        {
            return '"' + pathtoquote + '"';
        }

        #endregion

    }
}

.
.
ok so a quick rundown.

.
.

Here we copy the path to a string were the ffmpeg exe is located on your computer after you downloaded it ffmpeg
This can be anywere on your computer that monogame can access. You don’t have to put it in program files or link it to a env variable or use reg edit to assign it. You can ignore all that (install) stuff you read about if you come across it, just ignore it.

string ffmpegExeDir = @"C:\Users\YOUR\Downloads\ffmpeg-20180603-2bd24d4-win64-static\bin\ffmpeg.exe";
string vidcontentdirectory = @"C:\Users\YOUR\Documents\Vids";

We are just going to pass it to the constructor and let system diagnostics call cmd and let it do the work but you don’t have to worry about that either. You can see how its called if you like in the ExecuteCommand(string command) method.

  ffmpeg = new FFmpegController
  (
     ffmpegExeDir, 
     vidcontentdirectory, 
     FFmpegController.Combine(vidcontentdirectory, "Output") 
  );

The string vidContentDirectory likewise can be any path on your computer to a folder that has a video you want to convert. The last parameter just adds a folder onto that directory for the output.

Next we name the input file and specify the name for the output file we want the resulting converted file to have that will end up in the output directory.

ffmpeg.InputFileName1 = "YOURTestVideo.mp4";     // input file name.
ffmpeg.OutputFileName1 = "YOURTestVideo.wmv";  // output file name.

finally we execute a ffmpeg script. You can google how to do this for just about any video. This one takes the video which is the mp4 and turns it into a wmv using specifically the msmpg4 video codec an wmav2 audio codec (these are known shipped windows media player codecs) additionally i have limited the bit rate here to 256 and the frame rate for the video i have set to 16 via the -r command.
ffmpeg.FFmpegVideoConvertCustom("-b:v 2M -vcodec msmpeg4 -acodec wmav2 -b:v 256k -r 16");

Running this the first time will produce console output asyncronously to monogames console.
This means that if you close your monogame application ffmpeg will continue to run till it completes its task in the background as a separate process. For a large file it might take some while for it to convert over to the new type depending on how you tell ffmpeg to convert it.
This will show as a file in a folder that if you keep closing and opening the directory then hovering over it to see the file size it will continue to grow till it completes.

One more note. Keep in mind ffmpeg will not overwrite files though
so to rerun this script you must move or delete or change the name of the outputed file.

.
.
hope you like it or it helps.

.
.

This is the output when performing a conversion of a mp4 file into a wmv at the specified output directory.
.
.
.

ffmpeg version N-91217-g2bd24d4a37 Copyright (c) 2000-2018 the FFmpeg developers
  built with gcc 7.3.0 (GCC)
  configuration: --enable-gpl --enable-version3 --enable-sdl2 --enable-bzlib --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enab
le-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --en
able-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable
-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-l
ibvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --e
nable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth
  libavutil      56. 18.102 / 56. 18.102
  libavcodec     58. 19.105 / 58. 19.105
  libavformat    58. 17.100 / 58. 17.100
  libavdevice    58.  4.100 / 58.  4.100
  libavfilter     7. 25.100 /  7. 25.100
  libswscale      5.  2.100 /  5.  2.100
  libswresample   3.  2.100 /  3.  2.100
  libpostproc    55.  2.100 / 55.  2.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'C:\Users\my\Documents\Vids\thor.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf58.17.100
  Duration: 00:01:52.20, start: 0.000000, bitrate: 718 kb/s
    Stream #0:0(und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p, 680x480 [SAR 64:51 DAR 16:9], 583 kb/s, 30 fps, 30 tbr, 15360 t
bn, 60 tbc (default)
    Metadata:
      handler_name    : VideoHandler
    Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (native) -> msmpeg4v3 (msmpeg4))
  Stream #0:1 -> #0:1 (aac (native) -> wmav2 (native))
Press [q] to stop, [?] for help
Output #0, asf, to 'C:\Users\my\Documents\Vids\Output\ThorClip.wmv':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    WM/EncodingSettings: Lavf58.17.100
    Stream #0:0(und): Video: msmpeg4v3 (msmpeg4) (MP43 / 0x3334504D), yuv420p, 680x480 [SAR 64:51 DAR 16:9], q=2-31, 256 kb/s, 16 fps, 1k tbn, 16 tbc
(default)
    Metadata:
      handler_name    : VideoHandler
      encoder         : Lavc58.19.105 msmpeg4
    Side data:
      cpb: bitrate max/min/avg: 0/0/256000 buffer size: 0 vbv_delay: -1
    Stream #0:1(und): Audio: wmav2 (a[1][0][0] / 0x0161), 44100 Hz, stereo, fltp, 128 kb/s (default)
    Metadata:
      handler_name    : SoundHandler
      encoder         : Lavc58.19.105 wmav2
Past duration 0.933327 too large
[msmpeg4 @ 00000231d9fe1740] warning, clipping 1 dct coefficients to -127..127
frame=  221 fps=0.0 q=8.7 size=     842kB time=00:00:13.75 bitrate= 501.4kbits/s dup=0 drop=189 speed=27.5x
frame=  482 fps=482 q=14.5 size=    1742kB time=00:00:30.09 bitrate= 474.1kbits/s dup=0 drop=417 speed=30.1x
frame=  751 fps=501 q=31.0 size=    2617kB time=00:00:46.87 bitrate= 457.3kbits/s dup=0 drop=653 speed=31.2x
frame= 1035 fps=517 q=31.0 size=    3482kB time=00:01:04.62 bitrate= 441.4kbits/s dup=0 drop=901 speed=32.3x
frame= 1319 fps=527 q=31.0 size=    4342kB time=00:01:22.38 bitrate= 431.7kbits/s dup=0 drop=1150 speed=32.9x
frame= 1615 fps=538 q=16.0 size=    5210kB time=00:01:40.87 bitrate= 423.1kbits/s dup=0 drop=1409 speed=33.6x
frame= 1796 fps=540 q=2.7 Lsize=    5770kB time=00:01:52.18 bitrate= 421.4kbits/s dup=0 drop=1567 speed=33.7x
video:3684kB audio:1753kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: 6.135947%

ffmpeg is very much developer oriented and it works well under monogame.

Additionally to play the video in monogame if you want a easy way to test it.
see the post here wmv’s at the time are working with a little finese under dx and the script shown here actually was used for that purpose. gl has no player as of yet.

2 Likes

Cool, I wrote a lib for work used by a Unity Client project using FFMPEG to stream RTSP from cameras, might look at porting it to my MG samples :slight_smile: