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!