What’s New in Silverlight 5? – Media Changes

What’s New in Silverlight 5?

شاهد كورس كامل سيلفرلايت 5 مجانا هنا.

This content is based on Beta release, expect many changes in the final release.

Read What’s New in Silverlight 5 series here.

Download Sample Code

 

Introduction

In this article, we’ll have a brief discussion of the new features in media stack in Silverlight 5.

 

Overview

Silverlight 5 has undergone few changes in the media stack:

  • Better Battery Management:
    Less battery when playing media. Plus, no screensaver in the full-screen mode.
  • Remote Control Support:
    Now you can control the media being played in Silverlight 5 using a remote control. This feature is currently not available in the Beta.
  • Hardware Decoding; More GPU Support:
    For better performance, Silverlight 5 depends more on the GPU for media decoding especially for the high definition H.264 formats. In Beta, no DRM or Mac support.
  • 1080p Support:
    Combined with hardware decoding, user can now watch high definition 1080p media on a Netbook or Tablet.
  • IIS Media Services 4:
    Support for media streaming using IIS Media Services 4 has been added.

And two more features require more details:

  • Speed Control; Trick Play:
    Allows you to control playing speed, and to play the media forward or backward.
  • Low-Latency Sounds:
    Silverlight 5 has added native support for raw wave files.

 

Speed Control; Trick Play

This feature allows you to control playing speed, to speed-up or slow-down the media. The ability to change playing speed is called, Trick Play. This feature is available in the MediaElement control through a new property called PlaybackRate. This property takes a decimal value ranges from -8, -4, -2, … to …, 0.5, 1.2, 1.4, 2, 4, 8, and the default is 1. The downside of the Beta version is that it doesn’t have a pitch correction, means that when you change the rate to a value other than the default (1) you wouldn’t have any sound. And because of a bug found in the Beta version, you must set this property in code, if you set it in XAML will reset at application start to the default value.

The following are three examples of changing the playing speed of a media element:

    // Normal Speed
    media.PlaybackRate = 1.0;
    // Fast
    media.PlaybackRate = 4.0;
    // Slow
    media.PlaybackRate = -2.0;

 

Low-Latency Sounds

In the past, you had to create a wave file parser that reads file headers to determine if this is a wave file, and after recognizing a wave file, the parser reads file contents and encode them in order for your application to be able to play it. That changed completely in Silverlight 5. Today, you have native support for raw sounds data (i.e. low-latency sounds.) You can now play wave files and wave data directly in your application without the need to encode them.

This feature is available through a new API based on the XNA Framework. It uses the same classes and functions available in XNA. The main class you have is SoundEffect (Microsoft.Xna.Framework.Audio) that you can use it to play wave files and raw data. You have all the control over the media played; you can set volume, pitch, and stereo space. And you can create a more sophisticated class of SoundEffect called SoundEffectInstance that represents an instance of that sound file. This instance can be played repeatedly, and allows you to have all the control like SoundEffect and more, as we’ll see in the examples.

Finally, keep in mind that Silverlight 5 has the same limitations as XNA; it allows only for 8/16bit PCM, mono/stereo, 22.5/44.1/48khz wave files.

The following code loads the SoundEffect class with a wave file available in application resources:

    var resource = Application.GetResourceStream(
      new Uri("MediaLowLatency;component/tada.wav",
        UriKind.RelativeOrAbsolute));

    SoundEffect effect = SoundEffect.FromStream(resource.Stream);

After you have the SoundEffect at your hands, you can call the Play() method to play it. This method have two overrides, one can accept no parameters in which it plays the media normally with the default volume and no changes in the pitch or the stereo space. The other override accepts three values:

  1. volume:
    Ranges from 0.0f to 1.0f.
  2. pitch:
    Ranges from -1.0f to 1.0f. Default is 0f.
  3. pan:
    Controls the stereo space. Ranges from -1.0f to 1.0f. Default is 0f.

The following are some examples of playing sound files using SoundEffect:

    // Normal
    effect.Play();
    // Normal (default values)
    effect.Play(1.0f, 0f, 0f);
    // Faster
    effect.Play(1.0f, 1.0f, 0f);
    // Slower
    effect.Play(1.0f, -1.0f, 0f);

And finally you can use the SoundEffectInstance to create multiple instances of the same audio, or to play it repeatedly (Volume, Pitch, and Pan values are available through properties):

    SoundEffectInstance instance = effect.CreateInstance();

    instance.IsLooped = true;
    instance.Play();

 

Summary

While Silverlight 5 manages battery better, it also has improved media performance by depending more on the GPU for media decoding. And it allows now for high definition 1080p and H.264 videos. In addition, Silverlight 5 has introduced remote control support. And finally, the greatest features are its support for raw wave files and the ability to control playing speed.

Now, check out other What’s New in Silverlight 5? articles.

What’s New in Silverlight 5?

Silverlight 5

شاهد كورس كامل سيلفرلايت 5 مجانا هنا.

This content is based on Beta release, expect many changes in the final release.

Check out other Silverlight 5 articles here.

If you are new to Silverlight, check out this article.

What’s New – XAML Changes
What’s New – Control and Text Changes
What’s New – Graphics Changes
What’s New – Media Improvements
What’s New – Elevated-Trust Changes
What’s New – Other Improvements

Download Sourcecode

 

Silverlight 5

The first thing to know about Silverlight 5 is that it’s currently in beta, means that it definitely has bugs, and it also not yet feature-complete; some features are going to be shipped later in the final version. In addition, being in beta means that no go-live license available, this is for internal use and testing only so do not install on user’s machine.

Worth mentioning that Silverlight 5 was first announced at PDC 2010 conference (October 2010) and the beta version was shipped later at MIX11 (April 2011,) and the final version will be released soon this year.

 

Goal

Goals of the version 5 include:

  • Improving performance
  • Getting closer to WPF
  • Enabling line-of-business scenarios
  • Better development experience

 

Improvements

Silverlight 5 has come with lots of improvements and changes, and those improvements can be divided into several areas:

 

Getting Started

To get started you need to have first Microsoft Visual Studio 2010 Service Pack 1 installed on you PC. After that, you can go to http://silverlight.net and download the beta SDK and tools for VS2010.

In addition, you can install Microsoft Expression Blend Preview for Silverlight 5 Beta if you like using it.

After you have VS2010 SP1 and Silverlight 5 SDK installed on your PC, you can continue reading and start making your cool stuff!

Now let’s start with XAML changes, but first let’s take a look at our model that’s going to be used through all the samples.

 

What’s next?

Start with…

Building Applications that Can Talk

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Overview

Stephen Hawking is one of the most famous people using speech synthesis to communicate
Stephen Hawking is one of the most famous people using speech synthesis to communicate

In this article we are going to explore the Speech API library that’s part of the TTS SDK that helps you reading text and speaking it. We’re going to see how to do it programmatically using C# and VB.NET and how to make use of LINQ to make it more interesting. The last part of this article talks about…… won’t tell you more, let’s see!

Introduction

The Speech API library that we are going to use today is represented by the file sapi.dll which’s located in %windir%System32SpeechCommon. This library is not part of the .NET BCL and it’s not even a .NET library, so we’ll use Interoperability to communicate with it (don’t worry, using Visual Studio it’s just a matter of adding a reference to the application.)

Implementation

In this example, we are going to create a Console application that reads text from the user and speaks it. To complete this example, follow these steps:

As an example, we’ll create a simple application that reads user inputs and speaks it. Follow these steps:

  1. Create a new Console application.
  2. Add a reference to the Microsoft Speech Object Library (see figure 1.)

    Figure 1 - Adding Reference to SpeechLib Library
    Figure 1 - Adding Reference to SpeechLib Library
  3. Write the following code and run your application:
// C#

using SpeechLib;

static void Main()
{
    Console.WriteLine("Enter the text to read:");
    string txt = Console.ReadLine();
    Speak(txt);
}

static void Speak(string text)
{
    SpVoice voice = new SpVoiceClass();
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault);
}
' VB.NET

Imports SpeechLib

Sub Main()
    Console.WriteLine("Enter the text to read:")
    Dim txt As String = Console.ReadLine()
    Speak(txt)
End Sub

Sub Speak(ByVal text As String)
    Dim voice As New SpVoiceClass()
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault)
End Sub

If you are using Visual Studio 2010 and .NET 4.0 and the application failed to run because of Interop problems, try disabling Interop Type Embedding feature from the properties on the reference SpeechLib.dll.

Building Talking Strings

Next, we’ll make small modifications to the code above to provide an easy way to speak a given System.String. We’ll make use of the Extension Methods feature of LINQ to add the Speak() method created earlier to the System.String. Try the following code:

// C#

using SpeechLib;

static void Main()
{
    Console.WriteLine("Enter the text to read:");
    string txt = Console.ReadLine();
    txt.Speak();
}

static void Speak(this string text)
{
    SpVoice voice = new SpVoiceClass();
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault);
}
' VB.NET

Imports SpeechLib
Imports System.Runtime.CompilerServices

Sub Main()
    Console.WriteLine("Enter the text to read:")
    Dim txt As String = Console.ReadLine()
    txt.Speak()
End Sub

<Extension()> _
Sub Speak(ByVal text As String)
    Dim voice As New SpVoiceClass()
    voice.Speak(text, SpeechVoiceSpeakFlags.SVSFDefault)
End Sub

I Love YOU ♥

Let’s make it more interesting. We are going to code a VBScript file that says “I Love YOU” when you call it. To complete this example, these steps:

  1. Open Notepad.
  2. Write the following code:
    CreateObject("SAPI.SpVoice").Speak "I love YOU!"

    Of course, CreateObject() is used to create a new instance of an object resides in a given library. SAPI is the name of the Speech API library stored in Windows Registry. SpVoice is the class name.

  3. Save the file as ‘love.vbs’ (you can use any name you like, just preserve the vbs extension.)
  4. Now open the file and listen, who is telling that he loves you!

Microsoft Speech API has many voices; two of them are Microsoft Sam (male), the default for Windows XP and Windows 2000, and Microsoft Ann (female), the default for Windows Vista and Windows 7. Read more about Microsoft TTS voices here.

Thanks to our friend, Mohamed Gafar, for providing the VBScript.

Setting Device Information in MCI

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Interested in MCI multimedia processing? First check this article out if you didn’t:

Creating a Sound Recorder in C and C#

After we received your feedbacks and comments about th.e article, we decided to add a small appendix to the end of the article about setting information (volume, channel, sampling rate, etc.) to a MCI device (a Waveform device of course.)

Like anything else in MCI, you can set device information using a MCI command (string/numeric), and this time it’s the MCI_SET command.

This command is used to set information about a specific device. This command requires an input parameter of the MCI_SET_PARMS structure. However, that input parameter might have extended members for specific devices. Because we are concentrating of Waveform devices, so we are going to use the MCI_WAVE_SET_PARMS structure that contains the extended members for our device and is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwTimeFormat;
    DWORD     dwAudio;
    UINT      wInput;
    UINT      wOutput;
    WORD      wFormatTag;
    WORD      wReserved2;
    WORD      nChannels;
    WORD      wReserved3;
    DWORD     nSamplesPerSec;
    DWORD     nAvgBytesPerSec;
    WORD      nBlockAlign;
    WORD      wReserved4;
    WORD      wBitsPerSample;
    WORD      wReserved5;
} MCI_WAVE_SET_PARMS;

This structure contains all and every little piece of information that can be set to a device. I expect that you read the main article and you are already familiar with members like dwCallback (other members are self-explanatory) that we have talked about many times, and you are fine too with function calls and setting up input parameters, so I won’t get into the discussion of the structure or how you are going to use that command. However, if you need more help setting up the input parameters for the structure, you should take a brief look at the MCI_WAVE_SET_PARMS Structure documentation in the MSDN.

As you know, the MCI_WAVE_SET_PARMS unmanaged structure can be marshaled in C# as following:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_WAVE_SET_PARMS
{
    public IntPtr dwCallback;
    public uint     dwTimeFormat;
    public uint     dwAudio;
    public uint      wInput;
    public uint      wOutput;
    public ushort      wFormatTag;
    public ushort      wReserved2;
    public ushort      nChannels;
    public ushort      wReserved3;
    public uint     nSamplesPerSec;
    public uint     nAvgBytesPerSec;
    public ushort      nBlockAlign;
    public ushort      wReserved4;
    public ushort      wBitsPerSample;
    public ushort      wReserved5;
}

Congratulations! You did set the device information! So how to get them back?

This can be done through the MCI_STATUS (discussed earlier) by setting up the MCI_STATUS_ITEM flag and setting the query item to the required information you need to query about (like MCI_DGV_STATUS_VOLUME to query about volume.)

More about the MCI_STATUS command can be found in the MSDN documentation.

Creating a Sound Recorder in C and C#

هذه المقالة متوفرة أيضا باللغة العربية، اقرأها هنا.

Code: SMPREC – C.zip
Code: SimpleRec – C#.zip

Contents

Contents of this article:

  • Contents
  • Overview
  • Introduction
  • Sending MCI Commands
  • The Wait, Notify, and Test Flags
  • Handling MCI Errors
  • Recording from an Input Device
    • Opening the Device
    • Starting Recording
    • Pausing Recording
    • Resuming Recording
    • Stopping Recording
    • Retrieving Buffer Length
    • Saving the Recorded Buffer
    • Closing the Device
  • Playing a Sound File
    • Loading the File
    • Playing the File
    • Pausing
    • Resuming
    • Retrieving Current Position
    • Retrieving File Length
    • Seeking a Specific Position
    • Closing the Device
    • In a Nutshell
  • MCI and .NET
    • Creating the Managed Signature
    • Receiving MCI_MMNOTIFY
  • Appendix A: Setting Device Information [NEW]
  • Sample Code

Overview

This writing will focus on how you can record sound from an input device and how you can play sound files using MCI (Media Control Interface) in C and C#.

This writing does not involve a discussion or even an introduction to MCI. Instead, it provides technical discussion of what we will need to use to record and to play sound files. If you need an introduction to MCI refer to the MSDN documentation.

We will begin by a discussion to types and functions required to accomplish our tasks. Then we will look on how you can utilize those types and functions in your C or C# application.

Our demonstration examples in this writing will be in C. In the last section we will have a look at .NET and C#. Besides this, there are sample applications written by C and C# attached with the article.

Introduction

Icon to represent multimedia
Image via Wikipedia

Actually, there are many ways to record and play sound files. However, in this writing we will focus on MCI (Media Control Interface) as it is one of the high-level easy-to-use interfaces for controlling all media types.

You can control a multimedia hardware using MCI by either sending commands like Windows messages or by sending string messages to the MCI. The second approach is preferable in scripting languages. As we are concentrating on languages C and C#, we will consider the first approach only.

Because .NET Framework does not include classes for handling MCI (or multimedia at all,) we will need to dig into Windows SDK, specifically WinMM.dll (Windows Multimedia Library) that resides in the system directory.

In order to access this library from your C# application, you will need to create your own marshaling types and PInvoke (Platform Invocation) methods to allow your managed code to communicate with the unmanaged server. That is what the last section of this article is devoted for.

To access this library from your C application, just reference the library object file WinMM.lib in your project. Notice that all of the functions and structures related to the MCI are prefixed with mci.

In the next sections we will talk about the functions and structures that we need to be aware of before digging into coding.

Sending MCI Commands

The key function to Windows multimedia programming using MCI is mciSendCommand(). This function sends a command to the MCI. As we have said, you can program MCI using one of two approaches, you can send Windows-messages-like commands, and you can also send string messages to the MCI. The key function that we will use to send MCI commands is mciSendCommand(). To send string messages, you can use the function mciSendMessage() which is not covered here.

The definition of the function mciSendCommand() is as follows:

MCIERROR mciSendCommand(
  MCIDEVICEID IDDevice,
  UINT        uMsg,
  DWORD       fdwCommand,
  DWORD_PTR   dwParam
);

This function receives four input arguments:

  • IDDevice:
    The ID of the device to which to receive the command. For example, the ID of the input device when recording or the output device if playing. As you know, many devices could be connected to the PC. You can forget about this argument and just pass the function 0 to direct the message to the default device (selected in the Sound applet in the Control Panel.)
  • uMsg:
    The message (command) to be sent. Common messages are covered in the next few sections.
  • fdwCommand:
    Flags (options) of the message. Every message has its own options. However, all messages share the flags MCI_WAIT, MCI_NOTIFY, and MCI_TEST (covered soon.)
  • dwParam:
    A structure contains the specific parameter for the command message.

As a result, every command (message) has its name, flags, and structure parameter.

This function returns 0 if succeeded or an error code otherwise.

The Wait, Notify, and Test Flags

Common flags for all MCI messages are, MCI_WAIT, MCI_NOTIFY, and MCI_TEST.

The Wait flag, MCI_WAIT, indicates that the message should be processed synchronously; means that the function would not return until message processing finishes. For example, if you send a play message with the MCI_WAIT, your application would be suspended until the entire file finishes. Therefore, you should not use MCI_WAIT for play or record messages.

The Notify flag, MCI_NOTIFY, is the reverse of the Wait flag. It indicates that the message should be processed asynchronously; means that the function would return immediately and does not wait for the completion of the message. When the processing of the message finishes, it sends a MM_MCINOTIFY message to the windows specified in the dwCallback member of the message parameter. This flag should be used for play and record messages.

The wParam value of the MM_MCINOTIFY message is usually set to either MCI_NOTIFY_SUCCESSFUL to indicate command success or MCI_NOTIFY_FAILURE otherwise.

The Test flag, MCI_TEST, checks if the device can process this message, it does not process the message. The function would return 0 if the device can process the message or non-zero otherwise. You will use this flag in rare cases only.

Keep in mind that you should choose only one of the three flags, you cannot combine them.

If you didn’t specify any of these three flags, the call would be treated asynchronously and you will not be notified when it completes.

Handling MCI Errors

Every MCI command could succeed or fail. If the command succeeded, mciSendCommand() returns 0 (FALSE/false.) Otherwise, it returns the error code.

MCI defines error codes as constants that are prefixed with MCIERR_ like MCIERR_DEVICE_NOT_INSTALLED and MCIERR_FILE_NOT_FOUND (names are self-explanatory.) You can get the friendly error message of the code using the function mciGetErrorString(). The definition of this function is as follows:

BOOL mciGetErrorString(
  DWORD fdwError,
  LPTSTR lpszErrorText,
  UINT cchErrorText
);

This function accepts three arguments:

  • fdwError:
    Error code returned by mciSendCommand() function.
  • lpszErrorText:
    A string buffer that receives the description of the error.
  • cchErrorText:
    Length of the buffer in characters.

The following C example shows how you can display a friendly error message to the user if an error occurred:

	TCHAR szBuffer[256];
	DWORD dwErr;

	dwErr = mciSendCommand(/* arguments */);

	mciGetErrorStringW(dwErr, szBuffer, 256);
	// cchErrorText =
	//     sizeof(szBuffer) / sizeof(szBuffer[0])

Recording from an Input Device

To record from an input device using MCI, follow these steps:

  1. Open the input device to receive the data from.
  2. Order the MCI to start the recording process.
  3. When you finish, stop the recording process.
  4. Save the record buffer if applicable.
  5. Close the opened device.

Keep in mind that you should check whether an error occurred or not after sending each command. The previous approach to retrieve error messages would be very useful.

Opening the Device

To open a device just pass the open command MCI_OPEN to the mciSendCommand() function along with its flags and parameter.

The parameter of MCI_OPEN is the structure MCI_OPEN_PARMS. This structure contains information about the open command. The definition of this structure is as following:

typedef struct {
    DWORD_PTR    dwCallback;
    MCIDEVICEID  wDeviceID;
    LPCSTR       lpstrDeviceType;
    LPCSTR       lpstrElementName;
    LPCSTR       lpstrAlias;
} MCI_OPEN_PARMS;

Actually, you will make use of only the third and fourth members, lpstrDeviceType and lpstrElementName, of the structure when you open a device. lpstrDeviceType determines the type of the device (digital-audio, digital-video, etc.) that will be used. In our example that records and plays sound files, we will set this member to €œwaveaudio€ to indicate that we are going to work with waveform (WAV) data.

lpstrElementName on the other hand, should be set to an empty string (that is €œ€) if you are opening an input device for recording. If you want to play a file, set this member to the full path of that file.

Common flags of the command MCI_OPEN are:

  • The Wait, Notify, and Test flags:
    The Wait command is usually used for MCI_OPEN.
  • MCI_OPEN_ELEMENT:
    Mandatory. The lpstrDeviceType of the MCI_OPEN_PARMS is set. It is set to €œwaveaudio€ for WAV data.
  • MCI_OPEN_TYPE:
    Mandatory. The lpstrElementType of the MCI_OPEN_PARMS is set. It is set to an empty string if recording or a path to a file if you want to play it.

You will always combine the flags MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE for the MCI_OPEN command.

When the function returns, the wDeviceID member of the structure is set to the ID of the device opened. You should keep this ID for future calls on that device until you close it using the close command.

The following C code shows how you can open an input device for recording:

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

Starting Recording

After you have opened the input device, you can order the MCI to start the recording process using the command MCI_RECORD. This command requires an opened input device and a parameter of type MCI_RECORD_PARMS. The definition of this structure is as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_RECORD_PARMS;

Members this structure defines are as following:

  • dwCallback:
    The window handle that should be called after the processing of the command finishes if MCI_NOTIFY is specified in command flags.
  • dwFrom:
    Indicates the position of the buffer (in thousands of a second) to start recording from. In most cases this would be set to zero.
  • dwTo:
    Indicates the position of the buffer to stop recording when it is reached. Unless you want to record for a given period, this member should be zero.

Common flags for MCI_RECORD are:

  • MCI_WAIT, MCI_NOTIFY, and MCI_TEST:
    Usually you will use the MCI_NOTIFY flag. If so, you should handle the MM_MCINOTIFY message.
  • MCI_FROM:
    Set if you used the dwFrom member of the parameter.
  • MCI_TO:
    Set if you used the dwTo member of the parameter.

If you want to record for a specific period, set dwTo member of the structure to that specific period (in thousands of seconds) and combine your flags with MCI_TO. When this period ends, MCI automatically stops the recording process and it sends a MM_MCINOTIFY message if you have set MCI_NOTIFY in the flags.

The following C example shows how you can start recording:

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// We do not need a notification message
	// we will send a Stop command, when
	// we finish.
	mciSendCommand(wDeviceID, MCI_RECORD, 0, (DWORD)&parmRec);

The following code shows how you can record for a specific period:

	MCI_RECORD_PARMS parmRec;

	parmRec.dwCallback = hWnd;	// window handle
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 30000; // 30 seconds

	// Notify me when you finish the 30 seconds
	mciSendCommand(wDeviceID, MCI_RECORD,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

Pausing Recording

To pause the recording process, just pass the MCI_PAUSE command to the MCI. This command accepts a parameter of the structure MCI_GENERIC_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
} MCI_GENERIC_PARMS;

This structure contains only one member, dwCallback. As you know, if you specify MCI_NOTIFY in command flags, MCI will send a MM_MCINOTIFY message to the window specified in this member.

MCI_PAUSE does not have specific flags, just the Wait, Notify, and Test flags.

The following C example shows how you can pause the recording process. Notice that you should already be recording or an error would be returned by the mciSendCommand().

	MCI_GENERIC_PARMS parmGen;

	parmGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_PAUSE, MCI_WAIT, (DWORD)&parmGen);

Resuming Recording

To resume after pausing, you can send a MCI_RESUME command to MCI. This command is very similar to the MCI_PAUSE command. It accepts the same parameter and the same flags. The example is the same, just change command name.

Stopping Recording

After you finish recording you will need to stop the recording process. To accomplish this, pass the MCI_STOP command along with the device ID and its parameter to the MCI.

This command is the same as MCI_PAUSE and MCI_RESUME. It accepts the same flags and the same parameters, and the example is identical too, just change the command name.

Retrieving Buffer Length

How long have you been recording? This could be easily answered with the MCI_STATUS command. This command queries MCI and retrieves information about the current session.

MCI_STATUS accepts a structure parameter of type MCI_STATUS_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwReturn;
    DWORD     dwItem;
    DWORD     dwTrack;
} MCI_STATUS_PARMS;

This structure defines the following members:

  • dwCallback:
    Discussed previously.
  • dwReturn:
    After returning from the call, it should contains the return value of the query.
  • dwItem:
    The item to query about.
  • dwTracks:
    Length or number of tracks (specific to some query items.)

Common flags that MCI_STATUS accepts:

  • The Wait, Notify, and Test flags:
    You will usually use the Wait flag.
  • MCI_STATUS_ITEM:
    Mandatory in most cases. Indicates that the dwItem of the structure is set.

If you want to query MCI about specific information, pass the MCI_STATUS command to the MCI along with MCI_STATUS_ITEM and MCI_WAIT flags, and set the dwItem field of the parameter to one of the following values (some are for output devices):

  • MCI_STATUS_LENGTH:
    Retrieves the total length of the buffer (in thousands of a second.)
  • MCI_STATUS_MODE:
    Retrieves current mode of the device which can be one of the following values (names are self-explanatory):

    • MCI_MODE_NOT_READY
    • MCI_MODE_PAUSE
    • MCI_MODE_PLAY
    • MCI_MODE_STOP
    • MCI_MODE_RECORD
    • MCI_MODE_SEEK
  • MCI_STATUS_NUMBER_OF_TRACKS:
    Retrieves the total number of tracks.
  • MCI_STATUS_POSITION:
    Retrieves current position (in thousands of a second.)
  • MCI_STATUS_READY:
    Returns TRUE (true in C#) if the device is ready or FALSE (false in C#) otherwise.

When the function returns, the dwReturn field of the structure should contain the result of the query item selected.

The following C example retrieves the length of the buffer recorded:

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_LENGTH;
	parmStatus.dwTrack    = 0;

	mciSendCommand(0, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the number of seconds
	// parmStatus.dwReturn / 1000

Saving the Recorded Buffer

Before you close the device, you can save the current recorded buffer in an audio (waveform) file. The command MCI_SAVE orders MCI to save the buffer to the file specified in its structure parameter.

The parameter of MCI_SAVE is a MCI_SAVE_PARMS structure and it is defined as following:

typedef struct {
    DWORD_PTR  dwCallback;
    LPCTSTR    lpfilename;
} MCI_SAVE_PARMS;

This structure contains only two members, dwCallback (discussed before) and lpfilename. lpfilename points to a string buffer contains the name and full path of the file to save.

MCI_SAVE accepts few flags:

  • The Wait, Notify, and Test flags:
    You will always use the Wait (MCI_WAIT) flag.
  • MCI_SAVE_FILE:
    Mandatory. You will always set this flag. It indicates that lpfilename contains the path of the target file.

The following example saves the recorded buffer to the file €œrecording.wav€:

	MCI_SAVE_PARMS parmSave;

	parmSave.dwCallback = 0;
	parmSave.lpfilename = TEXT("recording.wav");
	// save to the current directory

	mciSendCommand(wDeviceID, MCI_SAVE,
		MCI_WAIT | MCI_SAVE_FILE, (DWORD)&parmSave);

Closing the Device

You strictly should always close the device when you finish working with it, and this is done through the MCI_CLOSE command which accepts a parameter of type MCI_GENERIC_PARMS (discussed before.)

MCI_CLOSE accepts only the Wait, Notify, and Test flags.

The following C example closes the opened device:

	MCI_GENERIC_PARMS parmsGen;

	parmsGen.dwCallback = 0;

	mciSendCommand(wDeviceID, MCI_CLOSE, MCI_WAIT, (DWORD)&parmsGen);

Playing a Sound File

Keeping in mind what you have learnt from the previous section would be very helpful in our discussion on how to play sound files using MCI.

Actually, the same rules and commands applied in the previous section, will be applied here.

To play a sound file on MCI, you should follow these steps:

  1. Load the audio file. This automatically opens the output device.
  2. Order the MCI to start the playing process.
  3. Close the opened device when you finish.

Again, you should check whether an error occurred or not after each call to mciSendCommand().

Loading the File

As you know, to open a multimedia device, you pass MCI the MCI_OPEN command. To specify a file to be loaded, specify the full path of the file in the lpstrElementName field of the MCI_OPEN_PARMS structure.

The following C example shows how you can open an output device and load a file for playing:

	MCI_OPEN_PARMS parmOpen;
	WORD wDeviceID;

	parmOpen.dwCallback       = 0;
	parmOpen.wDeviceID        = 0;	// the default device
	parmOpen.lpstrDeviceType  = TEXT("waveaudio");
	parmOpen.lpstrElementName = TEXT("recording.wav");
	parmOpen.lpstrAlias       = 0;

	mciSendCommand(0, // the default device
		MCI_OPEN,
		MCI_WAIT | MCI_OPEN_TYPE | MCI_OPEN_ELEMENT,
		(DWORD)&parmOpen);

	// Keep the device ID for future calls
	wDeviceID = parmOpen.wDeviceID;

Playing the File

To start playing the currently loaded file, you can use the MCI_PLAY command. This command accepts a structure parameter of type MCI_PLAY_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwFrom;
    DWORD     dwTo;
} MCI_PLAY_PARMS;

At first sight, you notice that it is identical to the MCI_RECORD_PARMS. Actually, you are right. This structure defines the same members as the MCI_RECORD_PARMS structure. In addition, the description of the members is the same.

dwFrom specifies the position (in thousands of a second) where to start playing. dwTo on the other hand specifies the position (in thousands of a second too) where to end playing. If you set dwFrom, you will need to set the flag MCI_FROM. Conversely, if you set dwTo, you will need to set the flag MCI_TO. If you need to play the file from the start to the end, leave both members and do not specify either MCI_FROM or MCI_TO.

You will most likely combine MCI_PLAY flags with the Notify flag, MCI_NOTIFY, to allow the code to continue execution while the file plays. If you did so, your application would receive MM_MCINOTIFY message to the window specified in the dwCallback member of the parameter structure.

The following C example shows how to start playing a file:

	MCI_PLAY_PARMS parmPlay;

	// Play the file
	// from the start to the end

	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	parmRec.dwTo	   = 0;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY, (DWORD)&parmRec);

And the following code plays only three minutes from the file:

	MCI_PLAY_PARMS parmPlay;

	// play only 3 minutes
	parmRec.dwCallback = 0;
	parmRec.dwFrom	   = 0;
	// 3 * 60 * 1000
	parmRec.dwTo	   = 180000;

	// notify me when you finish
	mciSendCommand(wDeviceID, MCI_PLAY,
		MCI_NOTIFY | MCI_TO, (DWORD)&parmRec);

Pausing

As you know, you can pause the playback using the MCI_PAUSE command which accepts the MCI_GENERIC_PARMS parameter.

Resuming

To resume the playback after pausing, you can use the MCI_RESUME command discussed before.

Retrieving Current Position

To retrieve the current position of the file, pass the MCI_STATUS command along with its flags (MCI_WAIT and MCI_STATUS_ITEM) and its parameter (MCI_STATUS_PARMS) to the MCI. Do not forget to set dwItem member of the structure to MCI_STATUS_POSITION to retrieve the current position (in thousands of a second) in the dwReturn member of the structure. The following C example demonstrates this:

	MCI_STATUS_PARMS parmStatus;

	parmStatus.dwCallback = 0;
	parmStatus.dwReturn   = 0;
	parmStatus.dwItem     = MCI_STATUS_POSITION;
	parmStatus.dwTrack    = 0;

	mciSendCommand(wDeviceID, MCI_STATUS,
		MCI_WAIT | MCI_STATUS_ITEM, (DWORD)&parmStatus);

	// Display the current position
	// parmStatus.dwReturn / 1000

Retrieving File Length

Like retrieving current position, you can retrieve full file length the same way. However, you will need to specify the item MCI_STATUS_LENGTH instead.

Seeking a Specific Position

To change the current position, pass MCI the command MCI_SEEK. This command accepts the structure parameter MCI_SEEK_PARMS which is defined as following:

typedef struct {
    DWORD_PTR dwCallback;
    DWORD     dwTo;
} MCI_SEEK_PARMS;

This parameter defines the following members:

  • dwCallback:
    The window to be notified with the MCI_MMNOTIFY message if the call was carried out asynchronously using the MCI_NOTIFY flag.
  • dwTo:
    The position (in thousands of a second) to jump to.

The command MCI_SEEK accepts the following flags:

  • The Wait, Notify, and Test flags:
    You will always use the Wait flag.
  • MCI_SEEK_TO_END:
    If specified, it would jump to the end of the file.
  • MCI_SEEK_TO_START:
    If specified, it would jump to the start of the file.
  • MCI_TO:
    If specified, MCI would use the value in the dwTo member of the parameter structure.

The following C example jumps to the start of the file:

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 0;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_SEEK_TO_START, (DWORD)&parmSeek);

And the following example jumps to the third minute of the file:

	MCI_SEEK_PARMS parmSeek;

	parmStatus.dwCallback = 0;
	parmStatus.dwTo	      = 180000;

	mciSendCommand(wDeviceID, MCI_SEEK,
		MCI_WAIT | MCI_TO, (DWORD)&parmSeek);

Closing the Device

You should close the device as soon as you finish working with it, and this is done (as you know) through the MCI_CLOSE command.

In a Nutshell

The following table summarizes the commands that you will usually use with audio files along with their parameter structures and flags.

Command Input/Output Parameter Structure Commonly Used Flags
MCI_OPEN In/Out MCI_OPEN_PARMS MCI_WAIT, MCI_OPEN_ELEMENT, and MCI_OPEN_TYPE
MCI_RECORD In MCI_RECORD_PARMS (none) or MCI_NOTIFY
MCI_PLAY Out MCI_PLAY_PARMS MCI_NOTIFY
MCI_PAUSE In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_RESUME In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_STOP In/Out MCI_GENERIC_PARMS MCI_WAIT
MCI_SEEK Out MCI_SEEK_PARMS MCI_WAIT and MCI_TO / MCI_SEEK_TO_START / MCI_SEEK_TO_END
MCI_SAVE In MCI_SAVE_PARMS MCI_WAIT and MCI_SAVE_FILE
MCI_STATUS In/Out MCI_STATUS_PARMS MCI_WAIT and MCI_STATUS_ITEM
MCI_CLOSE In/Out MCI_GENERIC_PARMS MCI_WAIT

MCI and .NET

Creating the Managed Signature

Because .NET does not support MCI and it does not allow you to call unmanaged code directly, you will need to create your own marshaling types and PInvoke methods.

Keep in mind that you can get the handle of a window by using Control.Handle property.

The following class is the managed signature of our unmanaged structures and functions along with the required constants:

internal static class SafeNativeMethods
{
    // Constants

    public const string WaveAudio = "waveaudio";

    public const uint MM_MCINOTIFY = 0x3B9;

    public const uint MCI_NOTIFY_SUCCESSFUL = 0x0001;
    public const uint MCI_NOTIFY_SUPERSEDED = 0x0002;
    public const uint MCI_NOTIFY_ABORTED = 0x0004;
    public const uint MCI_NOTIFY_FAILURE = 0x0008;

    public const uint MCI_OPEN = 0x0803;
    public const uint MCI_CLOSE = 0x0804;
    public const uint MCI_PLAY = 0x0806;
    public const uint MCI_SEEK = 0x0807;
    public const uint MCI_STOP = 0x0808;
    public const uint MCI_PAUSE = 0x0809;
    public const uint MCI_RECORD = 0x080F;
    public const uint MCI_RESUME = 0x0855;
    public const uint MCI_SAVE = 0x0813;
    public const uint MCI_LOAD = 0x0850;
    public const uint MCI_STATUS = 0x0814;

    public const uint MCI_SAVE_FILE = 0x00000100;
    public const uint MCI_OPEN_ELEMENT = 0x00000200;
    public const uint MCI_OPEN_TYPE = 0x00002000;
    public const uint MCI_LOAD_FILE = 0x00000100;
    public const uint MCI_STATUS_POSITION = 0x00000002;
    public const uint MCI_STATUS_LENGTH = 0x00000001;
    public const uint MCI_STATUS_ITEM = 0x00000100;

    public const uint MCI_NOTIFY = 0x00000001;
    public const uint MCI_WAIT = 0x00000002;
    public const uint MCI_FROM = 0x00000004;
    public const uint MCI_TO = 0x00000008;

    // Structures

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_OPEN_PARMS
    {
        public IntPtr dwCallback;
        public uint wDeviceID;
        public IntPtr lpstrDeviceType;
        public IntPtr lpstrElementName;
        public IntPtr lpstrAlias;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_RECORD_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_PLAY_PARMS
    {
        public IntPtr dwCallback;
        public uint dwFrom;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_GENERIC_PARMS
    {
        public IntPtr dwCallback;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SEEK_PARMS
    {
        public IntPtr dwCallback;
        public uint dwTo;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_SAVE_PARMS
    {
        public IntPtr dwCallback;
        public IntPtr lpfilename;
    }

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct MCI_STATUS_PARMS
    {
        public IntPtr dwCallback;
        public uint dwReturn;
        public uint dwItem;
        public uint dwTrack;
    } ;

    // Functions

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.U4)]
    public static extern uint mciSendCommand(
        uint mciId,
        uint uMsg,
        uint dwParam1,
        IntPtr dwParam2);

    [DllImport("winmm.dll", CharSet = CharSet.Ansi,
        BestFitMapping = true, ThrowOnUnmappableChar = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool mciGetErrorString(
        uint mcierr,
        [MarshalAs(UnmanagedType.LPStr)]
        System.Text.StringBuilder pszText,
        uint cchText);
}

Receiving MCI_MMNOTIFY

In C, you can handle the MCI_MMNOTIFY message in the way you handle any other message. Just add the handler to the window procedure (WndProc) function.

In .NET, you do not have a WndProc function. However, .NET emulates this function using the protected Control.WndProc() function. You can override this function in your form and do the required processing. The following example demonstrates this:

public partial class MainForm : Form
{
    . . .

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == SafeNativeMethods.MM_MCINOTIFY)
        {
            // Handle the message
        }

        // DO NOT REMOVE the following line
        base.WndProc(ref m);
    }
}

Appendix A: Setting Device Information

After we received your feedbacks and comments about the article, we decided to add a small appendix to the end of the article about setting information (volume, channel, sampling rate, etc.) to a MCI device (a Waveform device of course.)

Like anything else in MCI, you can set device information using a MCI command (string/numeric), and this time it’s the MCI_SET command.

This command is used to set information about a specific device. This command requires an input parameter of the MCI_SET_PARMS structure. However, that input parameter might have extended members for specific devices. Because we are concentrating of Waveform devices, so we are going to use the MCI_WAVE_SET_PARMS structure that contains the extended members for our device and is defined as following:

typedef struct {
  DWORD_PTR dwCallback;
  DWORD     dwTimeFormat;
  DWORD     dwAudio;
  UINT      wInput;
  UINT      wOutput;
  WORD      wFormatTag;
  WORD      wReserved2;
  WORD      nChannels;
  WORD      wReserved3;
  DWORD     nSamplesPerSec;
  DWORD     nAvgBytesPerSec;
  WORD      nBlockAlign;
  WORD      wReserved4;
  WORD      wBitsPerSample;
  WORD      wReserved5;
} MCI_WAVE_SET_PARMS;

This structure contains all and every little piece of information that can be set to a device. I expect that you read the main article and you are already familiar with members like dwCallback (other members are self-explanatory) that we have talked about many times, and you are fine too with function calls and setting up input parameters, so I won’t get into the discussion of the structure or how you are going to use that command. However, if you need more help setting up the input parameters for the structure, you should take a brief look at the MCI_WAVE_SET_PARMS Structure documentation in the MSDN.

As you know, the MCI_WAVE_SET_PARMS unmanaged structure can be marshaled in C# as following:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct MCI_WAVE_SET_PARMS
{
  public IntPtr dwCallback;
  public uint     dwTimeFormat;
  public uint     dwAudio;
  public uint      wInput;
  public uint      wOutput;
  public ushort      wFormatTag;
  public ushort      wReserved2;
  public ushort      nChannels;
  public ushort      wReserved3;
  public uint     nSamplesPerSec;
  public uint     nAvgBytesPerSec;
  public ushort      nBlockAlign;
  public ushort      wReserved4;
  public ushort      wBitsPerSample;
  public ushort      wReserved5;
}

Congratulations! You did set the device information! So how to get them back?

This can be done through the MCI_STATUS (discussed earlier) by setting up the MCI_STATUS_ITEM flag and setting the query item to the required information you need to query about (like MCI_DGV_STATUS_VOLUME to query about volume.)

More about the MCI_STATUS command can be found in the MSDN documentation.

Sample Code

Attached with this writing the sample applications, SampleRec (C#) and SMPREC (C).

Download “SMPREC – C.zip”

Download “SampleRec – C#.zip”