Creating a Screen Recorder with Visual Basic

Introduction

Call me Curious George. Just when I think I have created all types of apps imaginable, another idea pops into my head. I guess I now have my answer as to why I cannot sleep most of the time—it is due to my brain thinking of new apps and new ideas. Ah, the joys of being a programmer. Today, I will show you how easy it is to create a video recorder that records your desktop and mouse on your computer. As a side note, it may need more work from your side afterwards, but the idea and the methodology is there.

Create a new Visual Basic Windows Forms project. Once the form is displayed, add two buttons, named btnRecord and btnStop, respectively. Also, add one Backgroundworker and one Timer and leave them with their default names.

Now, before I continue. Always remember that there are millions of tools at your disposal when attempting to create a program that does something like this. The .NET Framework is excellent and most probably can make a program that can encode video frames one by one, doing that might take a very long time. Enter FFMPEG. This small, little tool already has the functionality built in and we now have to send the correct information to it to record a video of what you are doing on your computer.

I think I have created an article using FFMPEG before, and if I remember correctly, the article is this one: Creating a Video Converter.

The only question that remains is: How do we send continuous screenshots to the FFMPEG encoder? Enter the Windows API.

The Windows API

This article explains the ins-and-outs of the Windows API: Windows 32 API.

Okay, the technicalities are done and dusted. Let’s start playing!

Add the following Namespaces to make our program work:

Imports System.IO
Imports System.Net
Imports System.Text
Imports System.Threading
Imports System.Drawing
Imports System.Drawing.Image
Imports System.Runtime.InteropServices

The IO Namespace enables us to work with files on the system. Text enables us to do some advanced string manipulation. Threading enables us to work with separate threads. The Drawing Namespaces enables us to create in-memory images, and the InteropServices Namespace enables us to work with the Windows API. Add the following API Declarations:

   <DllImport("gdi32.dll")> _
   Private Shared Function BitBlt(ByVal hdcDest As IntPtr, _
   ByVal xDest As Integer, ByVal yDest As Integer, _
   ByVal wDest As Integer, ByVal hDest As Integer, _
   ByVal hdcSource As IntPtr, _ ByVal xSrc As Integer, _
   ByVal ySrc As Integer, ByVal rop As CopyPixelOperation) _
   As Boolean
   End Function

   <DllImport("user32.dll")> _
   Private Shared Function ReleaseDC(ByVal hWnd As IntPtr, _
   ByVal hDc As IntPtr) As Boolean
   End Function

   <DllImport("gdi32.dll")> _
   Private Shared Function DeleteDC(ByVal hDc As IntPtr) As IntPtr
   End Function

   <DllImport("gdi32.dll")> _
   Private Shared Function DeleteObject(ByVal hDc As IntPtr) As IntPtr
   End Function

   <DllImport("gdi32.dll")> _
   Private Shared Function CreateCompatibleBitmap(ByVal hdc As IntPtr, _
   ByVal nWidth As Integer, ByVal nHeight As Integer) As IntPtr
   End Function

   <DllImport("gdi32.dll")> _
   Private Shared Function CreateCompatibleDC(ByVal hdc As IntPtr) As IntPtr
   End Function

   <DllImport("gdi32.dll")> _
   Private Shared Function SelectObject(ByVal hdc As IntPtr, _
   ByVal bmp As IntPtr) As IntPtr
   End Function

   <DllImport("user32.dll")> _
   Public Shared Function GetDesktopWindow() As IntPtr
   End Function

   <DllImport("user32.dll")> _
   Public Shared Function GetWindowDC(ByVal ptr As IntPtr) As IntPtr
   End Function

You can find detailed explanations on each of the above API functions here: Desktop APIs.

Add the following Modular variables that will be used throughout your program:

   Dim intScreenWidth As Integer = Screen.PrimaryScreen.Bounds.Width
   Dim intScreenHeight As Integer = Screen.PrimaryScreen.Bounds.Height

   Dim intTotalPixels As Integer

   Public ScreenSize As Size = New Size(intScreenWidth, intScreenHeight)

   Dim prcFFMPGFrames As New Process

   Dim prcFFMPGMerge As New Process

   Dim bmpPicture As New Bitmap(1, 1)

   Dim intFPS As Integer

   Dim strPath As String = "C:"

The first two variables determine the width and height of the current screen. intTotalPixels is a sum of all the pixels on your screen, so it is the Width multiplied by the height. The two Process variables will be used to launch the FFMPEG application. Let me stop quickly here. After you have downloaded the FFMPEG encoder, make sure that you copy the program file where you want it. A good place would be inside the Debug or Release folder under the Bin folder of your video recorder application. FFMPEG does not have a graphical user interface; it solely command based, so we will need to send parameters to this application and it will run in the background and convert our images into a video format.

Add the following code inside the Form’s Load event:

   Private Sub Form1_Load(sender As Object, e As EventArgs) _
   Handles MyBase.Load

      Dim procFFMPEG() As Process = _
      System.Diagnostics.Process.GetProcessesByName("ffmpeg")

      For Each p As Process In procFFMPEG

         p.Kill()

      Next

      If My.Computer.FileSystem.DirectoryExists(strPath & _
      "Captured") = False Then

         My.Computer.FileSystem.CreateDirectory(strPath & _
         "Captured")

      End If

      intTotalPixels = intScreenWidth * intScreenHeight

      intFPS = 25

   End Sub

Here, I make sure there is no other FFMPEG process running. I further determine if the location I want to save my video to exists, then create a folder accordingly. Add the following code under the btnRecord_Click event:

   Private Sub btnRecord_Click(sender As Object, e As EventArgs) Handles btnRecord.Click

      intFPS = 25

      Me.WindowState = FormWindowState.Minimized

         bmpPicture.Save("C:CapturedTemp.mp4")


      btnStop.Enabled = True

      prcFFMPGFrames.StartInfo.FileName = "C:Capturedffmpeg.exe"
      prcFFMPGFrames.StartInfo.Arguments = String.Format("-f image2pipe -r " _
         & intFPS & " -i pipe:.bmp -pix_fmt yuv420p -c:v libx264 -crf 18 _
         -g 1 -y -r " & intFPS & " C:CapturedTemp.mp4")
      prcFFMPGFrames.StartInfo.UseShellExecute = False
      prcFFMPGFrames.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
      prcFFMPGFrames.StartInfo.RedirectStandardInput = True
      prcFFMPGFrames.StartInfo.RedirectStandardOutput = True
      prcFFMPGFrames.StartInfo.CreateNoWindow = True
      prcFFMPGFrames.Start()

      BackgroundWorker1.RunWorkerAsync()

      btnRecord.Enabled = False

   End Sub

I start the FFMPEG process and send the necessary commands to the application to start the recording process.

Add the following code for the BackgroundWorker:

   Private Sub BackgroundWorker1_DoWork(sender As Object, _
         e As System.ComponentModel.DoWorkEventArgs) Handles _
         BackgroundWorker1.DoWork
      Try

         Dim bwStream As New BinaryWriter _
            (prcFFMPGFrames.StandardInput.BaseStream)

         Dim hDesk As IntPtr = GetDesktopWindow()
         Dim hSrce As IntPtr = GetWindowDC(hDesk)
         Dim hDest As IntPtr = CreateCompatibleDC(hSrce)
         Dim hBmp As IntPtr = CreateCompatibleBitmap(hSrce, _
            intScreenWidth, intScreenHeight)
         Dim hOldBmp As IntPtr = SelectObject(hDest, hBmp)
         Dim b As Boolean = BitBlt(hDest, 0, 0, intScreenWidth, _
            intScreenHeight, hSrce, 0, intScreenHeight, _
            CopyPixelOperation.SourceCopy Or _
            CopyPixelOperation.CaptureBlt)
         Dim bmp As Bitmap = Bitmap.FromHbitmap(hBmp)

         SelectObject(hDest, hOldBmp)
         DeleteObject(hBmp)
         DeleteDC(hDest)
         ReleaseDC(hDesk, hSrce)

         bmp.Save(bwStream.BaseStream, _
            System.Drawing.Imaging.ImageFormat.Bmp)

         bmp.Dispose()

         GC.Collect()

      Catch ex As Exception

         MessageBox.Show(ex.Message)

      End Try

   End Sub

The purpose of the Backgroundworker is to make continuous screenshots of what is happening on the screen and save it as a temporary bitmap file that later will get converted, or merged rather, to create the ultimate video.

You could add the following code to add audio to your video:

      prcFFMPGAudio.StartInfo.FileName = " C:Capturedffmpeg.exe"
      prcFFMPGAudio.StartInfo.Arguments = String.Format("-f dshow _
         -i audio=""" + audiostr + """ _
         -acodec libmp3lame -b:a 128k -y C:CapturedAudio.mp3")
      prcFFMPGAudio.StartInfo.UseShellExecute = False
      prcFFMPGAudio.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
      prcFFMPGAudio.StartInfo.RedirectStandardInput = True
      prcFFMPGAudio.StartInfo.RedirectStandardOutput = True
      prcFFMPGAudio.StartInfo.CreateNoWindow = True
      prcFFMPGAudio.Start()

Add the following code to end the recording process:

   Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click

      Timer1.Enabled = True

   End Sub

   Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

      If Process.GetProcessesByName("ffmpeg").Length < 0 Then

         prcFFMPGMerge.StartInfo.FileName = "C:Capturedffmpeg.exe"
         prcFFMPGMerge.StartInfo.Arguments = String.Format("-r " & intFPS & " _
            -i C:CapturedTemp.mp4 -i C:CapturedAudio.mp3 -r " & intFPS & " _
            -c:v copy -c:a aac -async " & intFPS & " -strict experimental _
            -map 0 -map 1 -y C:CapturedDone.mp4")
         prcFFMPGMerge.StartInfo.UseShellExecute = False
         prcFFMPGMerge.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
         prcFFMPGMerge.StartInfo.RedirectStandardInput = True
         prcFFMPGMerge.StartInfo.RedirectStandardOutput = True
         prcFFMPGMerge.StartInfo.CreateNoWindow = True
         prcFFMPGMerge.Start()

      Else

         If Process.GetProcessesByName("ffmpeg").Length < 0 Then Exit Sub

      End If

   End Sub

Conclusion

This project is by no means perfect and it is by no means complete, but it does show you the basics (actually more) of how to create a desktop recorder with Visual Basic. Now, the onus rests on you to take this project further. I hope you enjoyed this article nonetheless—I did!

Hannes DuPreez
Hannes DuPreez
Ockert J. du Preez is a passionate coder and always willing to learn. He has written hundreds of developer articles over the years detailing his programming quests and adventures. He has written the following books: Visual Studio 2019 In-Depth (BpB Publications) JavaScript for Gurus (BpB Publications) He was the Technical Editor for Professional C++, 5th Edition (Wiley) He was a Microsoft Most Valuable Professional for .NET (2008–2017).

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read