Video in C#
Up until now, the tutorials on this blog have been about a few multimedia concepts including: streaming audio, recording from the microphone, playing internet radio, loading playlists and even networking. In this tutorial you will learn how to accomplish the following:
- Load a video from the file system and play it
- Visual C# Express
- Bass.NET 2.4 (click on forums under Bass, should be a topic on Bass.NET 2.4)
- Un4seen Bass api (version 2.4 beta) (click on forums, find bass 2.4 beta.)
- BassVideo (unofficial release of the bassvideo delphi api created by KSoft)
Once you have downloaded the above requirements you will notice that the BassVideo zip file contains a delphi library called BassVideo.dll as well as the delphi header. If you create a new C# Windows Form application and try to add a reference to this it won't work. Because the bassvideo library is not a managed library we can't simply add it directly. For this, we will need to create a wrapper according to the given delphi header. Open up the .pas file in notepad.
unit BassVideo;
interface
uses Windows;
const // video display
BassVideoDLL = 'bassvideo.dll';
BassVideo_Renderer = $100; // for BassVideo_GetConfig/SetConfigBassVideo_Default = 0;
BassVideo_VMR9 = 1;
BassVideo_VMR9_Windowless = 2;
BassVideo_EVR = 3;
BassVideo_Overlay = 4;
BassVideo_NOVIdeo = 5;
// action
BassVideo_OpenDone = 1;
BassVideo_Buffering = 2;
BassVideo_FoundVideo = 3;// BassVideo
type TVideoSync = procedure(Action, param1, param2, user : DWORD);
VIDEOPROC = procedure(Handle : DWORD; Action, param1, param2, user : DWORD); stdcall;
HSTREAM = DWORD;
TAudioInfo = record
Channels : integer;
Frequency : integer;
Bit : integer;
Float : BOOL;
SPDIF : BOOL;// reserverd, currently not supported!
DTS : BOOL;// reserverd, currently not supported!
end;TVideoType = (RGB8, RGB32, RGB24, RGB16, YUY2, YUV,
// reserved type, not supported at this time :(
YV12, YUV12, YVYU, UYVY, YUY9, Y800);//, 444P, 422P 411P );PCodecName = ^TCodecName;
TCodecName = array[0..100] of char;TVideoInfo = record
Height : integer;
Width : integer;
VideoType : TVideoType;
Codec : PCodecName;
end;QWORD = int64;
function BassVideo_ErrorGetCode : integer; stdcall; external BassVideoDLL;
function BassVideo_StreamCreate(FileName : Pointer; Flags, BassFlags : DWORD; CallBackProc : VIDEOPROC) : DWORD; stdcall; external BassVideoDLL;
procedure BassVideo_SetConfig(Option : DWORD; Value : DWORD); stdcall; external BassVideoDLL;
function BassVideo_GetConfig(Option : DWORD) : DWORD; stdcall; external BassVideoDLL;function BassVideo_Play(handle: DWORD; restart : BOOL) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_Stop(handle: DWORD) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_Pause(handle: DWORD) : BOOL; stdcall; external BassVideoDLL;
function BassVideo_SetPosition(handle: DWORD; pos: QWORD): BOOL; stdcall; external BassVideoDLL;
function BASSVideo_GetPosition(handle: DWORD): QWORD; stdcall; external BassVideoDLL;
function BASSVideo_GetLength(handle: DWORD): QWORD; stdcall; external BassVideoDLL;
procedure BassVideo_StreamFree(handle: DWORD); stdcall; external BassVideoDLL;
function BassVideo_Init : BOOL; stdcall; external BassVideoDLL;
procedure BassVideo_Free; stdcall; external BassVideoDLL;// for video windows function
procedure BASSVideo_SetVideoWindow(handle : HSTREAM; VideoWindowHandle : THandle; VideoRect : TRect); stdcall; external BassVideoDLL;
procedure BASSVideo_Repaint(handle : HSTREAM; WinHandle : HWND; DC : HDC); stdcall; external BassVideoDLL;
procedure BassVideo_WindowResize(handle : HSTREAM; Rect : TRect); stdcall; external BassVideoDLL;
procedure BassVideo_WindowMove(handle : HSTREAM; Msg : DWORD; wParam, lParam : LongInt); stdcall; external BassVideoDLL;
function BassVideo_SetFullScreen(handle : HSTREAM; FullScreen : BOOL) : BOOL; stdcall; external BassVideoDLL;
implementationend.
Before I move into explaining what the above code is doing, I would first like to give a brief overview on the history of delphi and it's architect Anders Hejlsberg.
Delphi, officially Borland Delphi and chief architected by Anders Hejlsberg, is known as one of the first RAD tools when it was released in 1995 for 16-bit Windows 3.1. Delphi 2, released a year later, supported 32-bit Windows environments, and a C++ variant, C++ Builder, followed a few years after. Hejlsberg would later move to Microsoft in 1996, where he worked on Visual J++, and was a key participant in the creation of the Microsoft .NET Framework, becoming the chief designer of C#.
In 2001 a Linux version known as Kylix became available. However, due to low quality and subsequent lack of interest, Kylix was abandoned after version 3.
Support for Linux and Windows cross platform development (through Kylix and the CLX component library) was added in 2002 with the release of Delphi 6.
Delphi 8, released December 2003, was a .NET-only release that allowed developers to compile Delphi Object Pascal code into .NET CIL. It was also significant in that it changed its IDE for the first time, from the multiple-floating-window-on-desktop style IDE to a look and feel similar to Microsoft's Visual Studio.NET. But still, the new IDEs have the "Classic Undocked" windows layout available, for a classical look and feel.
Okay, now that that's underway, we can see that we can compare a few object-oriented concepts from C# to that of Delphi. The first on the list is constants.
All of the variables BassVideoDLL, BassVideo_Renderer, BassVideo_Default are considered constants in delphi. As such, we can create those constants in our own class, BassVideo.
In the delphi code, we see that it specifies a procedure with the parameters: Handle, Action, param1, param2 and user; followed by assigning it to the variable name VIDEOPROC. The equivalent is the delegate shown above. You'll notice that the HSTREAM is being assigned to DWORD (also known as our int in C#). This is simply allowing types to translate or cast implicitly. After that, there is a record being specified.
TAudioInfo = record
Channels : integer;
Frequency : integer;
Bit : integer;
Float : BOOL;
SPDIF : BOOL;// reserverd, currently not supported!
DTS : BOOL;// reserverd, currently not supported!
end;
A record, in the plainest since is the equivalent of a struct in C#. We can replicate this in the following code snippet.
Next, the delphi header specifies the equivalent of an enumeration with the following snippet:
TVideoType = (RGB8, RGB32, RGB24, RGB16, YUY2, YUV,
YV12, YUV12, YVYU, UYVY, YUY9, Y800);//, 444P, 422P 411P );
We can replicate this like we would create any enum as in the following code snippet.
Now that that's done we can move onto functions and procedures. A procedure in delphi is a method that does not return anything in C#. (Or it returns void). The only catch here is that in order to use each of these functions as a reference from C#, we need to use the DllImport attribute. We can import any unmanaged dll into C# by using this attribute in the following manner.
[DllImport("nameofdll.dll")]
Awesome! Now all we have to do is use the api in our media player. I'm going to use the previous MyRadio application and add a few things to get it working. Open up your previous project and add the bass (version 2.4) and bassvideo library to your solution. Make sure to set the Copy to Output Directory to Output if newer. Then, replace the Bass.NET library reference to the new 2.4 version. Your solution explorer should look something like the following.public class BassVideo
{
// Video display
public const string BassVideoDLL = "bassvideo.dll";
public const int BassVideo_Renderer = 100;public const int BassVideo_Default = 0;
public const int BassVideo_VMR9 = 1;
public const int BassVideo_VMR9_Windowless = 2;
public const int BassVideo_EVR = 3;
public const int BassVideo_Overlay = 4;
public const int BassVideo_NOVIdeo = 5;// Action
public const int BassVideo_OpenDone = 1;
public const int BassVideo_Buffering = 2;
public const int BassVideo_FoundVideo = 3;public delegate void VIDEOPROC(int Handle, int Action, int param1, int param2, int user);
public struct TAudioInfo
{
public int Channels;
public int Frequency;
public int Bit;
public bool Float;
public bool SPDIF;
public bool DTS;
}public enum TVideoType
{
RGB8, RGB32, RGB24, RGB16, YUY2, YUV, YV12, YUV12, YVYU, UYVY, YUY9, Y800
}public struct TVideoInfo
{
public int Height;
public int Width;
public TVideoType VideoType;
public IntPtr Codec;
}[DllImport("bassvideo.dll")]
public static extern int BassVideo_GetErrorCode();[DllImport("bassvideo.dll")]
public static extern int BassVideo_StreamCreate(string FileName, int Flags, int BassFlags,
VIDEOPROC CallBackProc);[DllImport("bassvideo.dll")]
public static extern void BassVideo_SetConfig(int Option, int Value);[DllImport("bassvideo.dll")]
public static extern int BassVideo_GetConfig(int Option);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Play(int handle, bool restart);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Stop(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Pause(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_SetPosition(int handle, long pos);[DllImport("bassvideo.dll")]
public static extern long BassVideo_GetPosition(int handle);[DllImport("bassvideo.dll")]
public static extern long BassVideo_GetLength(int handle);[DllImport("bassvideo.dll")]
public static extern void BassVideo_StreamFree(int handle);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_Init();[DllImport("bassvideo.dll")]
public static extern void BassVideo_Free();[DllImport("bassvideo.dll")]
public static extern void BASSVideo_SetVideoWindow(int handle, IntPtr VideoWindowHandle, Rectangle rect);[DllImport("bassvideo.dll")]
public static extern void BASSVideo_Repaint(int handle, IntPtr WinHandle, IntPtr DC);[DllImport("bassvideo.dll")]
public static extern void BassVideo_WindowResize(int handle, Rectangle Rect);[DllImport("bassvideo.dll")]
public static extern void BassVideo_WindowMove(int handle, int Msg, long wParam, long lParam);[DllImport("bassvideo.dll")]
public static extern bool BassVideo_SetFullScreen(int handle, bool FullScreen);
}
After that, open up the Form that you will use to display the video. Add a picture box to the form. Here is mine:
Now that we have something the display the video on, we need to modify our load code to be able to take in a video. Since there are plenty of types, we need to know whether a file is a video. Luckily, we can determine the supported types from the Bass api using the variable SupportedStreamName. This property shows the possible music extensions supported by the Bass api. I have created a function in the Player class below.
Now, in order to support this case when a file gets loaded we need to modify our original Load function. I have changed the original Load function to simply load in the playlist or add to it and the code that actually loads the file is in the LoadIndex function (called by the form when an index is selected on the playlist).
Since streaming video from the internet is currently unavailable to the api, I use the IsLocal to determine whether or not we need to use the LoadVideo, LoadSong, or LoadURL functions. Both the bassvideo and bass api use our stream declaration in the Player class. Hence, we need to declare a state when a video is loaded. Once we have that boolean set we can create the LoadVideo function below.
Notice the callback variable being used in the BassVideo.BassVideo_StreamCreate method call. The callback is a delegate reference whenever the video is processed on certain actions. If you remember, one of the parameters in the delegate is Action which is an integer. We can compare this to processing states also declared as constants in the api like BassVideo.BassVideo_FoundVideo. When we have that we can call the BassVideo.BASSVideo_SetVideoWindow to our picture box that we declare before. Since the picture box is something in the form, we need to create this function in our form code.
Since this code is in our form code, we need to let the Player class know about it. I have modified the constructor to include the BassVideo.VIDEOPROC as a parameter. I have also added a field variable called callback.
NOTE: Bass.NET version 2.4 updated the Bass_Init to use IntPtr when referring to the user. I have specified IntPtr.Zero.
In the constructor here, I am also initializing the BassVideo api by calling the BassVideo.BassVideo_Init method. The last thing we need to do is modify our Player to use either Bass or BassVideo depending on the videoLoaded boolean field. This is modified in the FreeStream, PlaySong and StopSong methods.
The very last thing we need to do is update our destructor to free the bassvideo api by calling the BassVideo.BassVideo_Free method.
And that's it! Hit F5, load up a video and see your new beauty shine. In this tutorial you learned about a new bass video api developed by a member of the Bass api community. You learned about delphi and even converted a delphi library header into C# not to mention you completed another aspect of multimedia within your media player.
[Source Code]
Comments
Hai ,