
src="http://pagead2.googlesyndication.com/pagead/show_ads.js">
C# is a simple, type-safe, object oriented, general-purpose programming language. Visual C# provides code-focused developers with powerful tools and language support to build rich, connected web and client applications on the .NET Framework.
Introduction
This example shows you how to receive data from a microphone and stream it over UDP to another computer. The example application can act like a direct phone, if both endpoints listen for data and send microphone data to each other. One would probably suspect that no source code exists for that, but of course it does. I hate those who will do commercial advertising. There is also a second related project what will contain a UDP server that we need to send/receive audio and compress it with g711 codec.Though only UDP is not the best way to transport audio data, RTP is the right way to go. RTP adds quality of service to transported audio, you can see how many packets are lost and can even arrange disordered packets. I will try to add an RTP example soon, so be calm, it's under way. There are some similar example applications, but most of them aren't well commented and missing some parts, so I will try to fill this part.
The package contains:
LumiSoft.Media - Audio related API (Included in example app)
LumiSoft.Net - UDP server, G711 codec
Example Application
Using the code
WaveIn - class provides a simple way to receive audio from a microphone. Actually all what you need to do is:WavIn.Devices - returns all available input devices from where we can get data.
///
/// Application main class.
///
public class Test
{
private WavIn m_pSoundReceiver = null;
///
/// Default constructor.
///
public Test()
{
// G711 needs 8KHZ 16 bit 1 channel audio,
// 400kb buffer gives us 25ms audio frame.
m_pSoundReceiver = new WavIn(WavIn.Devices[0],8000,16,1,400);
m_pSoundReceiver.BufferFull += new BufferFullHandler
(m_pSoundReceiver_BufferFull);
m_pSoundReceiver.Start();
}
///
/// This method is called when recording buffer is full
/// and we need to process it.
///
/// Recorded data.
private void m_pSoundReceiver_BufferFull(byte[] buffer)
{
// Just store audio data or stream it over the network ...
}
}
class provides methods for playing out streaming data. The only thing you need to do is just call waveoutInstance.Play method.In my opinion, the whole example application has been coded well enough, so dig into the code.
Note: Sound quality depends on network delay jittering, if there will be too big a variance in delays, voice will have holes in it. In addition, UDP packet loss and disordered packets will affect it too.
Links:-
Examples
License
This article has no explicit
Background
Like my earlier articles [^], this one steps you through the code writing process, displaying code snippets for each change. I'm sure that readers will have many valuable comments, criticisms, and suggestions to offer. You are encouraged to do so in the discussion section at the bottom of the article. I will do my best to incorporate your thoughts in the next revision of the article - but even if I'm a lazy bum and don't get around to it, you will be sharing your perceptions and ideas with others in the community.
What is Context?
When you're running an application (as in life), you are always in multiple contexts at any given moment. For example, if the focus is in a particular TextBox control, the context can be all of the following: the TextBox, a TabPage containing the TextBox, the Tab control containing the TabPage, the dialog box containing the TabPage, and the application itself. It is up to the help author to decide which of these contexts should have an associated help topic.
The general approach many applications take when launching Context Sensitive Help is to start with the control with the focus and check to see if it has an associated help topic. If so, the application launches the topic, and if not, it moves outward (i.e., up the parent chain) to the containing control and repeats the process.
Implementing F1 Help
We'll start by implementing a system for displaying an associated topic when the F1 key is pressed, by using a ContextID to topic mapping that will be stored in an XML configuration file. After this is working, we'll get to adding the feature that enables help authors to edit these mappings from inside the application UI. Click on this link to skip to the fun part. :)
The IContextHelp Interface
The first step is to create an Interface (IContextHelp) that you can add to the various controls in your project. This interface identifies controls that are candidates for acting as a context for help, and provides a unique string identifier (ContextID) to identify the context.
public interface IContextHelp
{
string ContextHelpID { get; }
}
Since such controls are typically derived from UserControl, we can simplify things further by creating a UserControlEx that implements the interface. To use this, derive your user controls from UserControlEx instead of UserControl. In the default implementation, the ContextID for the control is the fully qualified class name of the control.
public virtual string ContextHelpID {get{return this.GetType().FullName;}}
Similarly, we create a FormEx that implements IContextHelp in the same way. When you define a form in your application, change it to derive from FormEx instead of Form.
If you want to provide context help for standard Windows or third party controls, you can do so easily by deriving new controls and adding the interface. For example:
// PanelEx is like a Panel, except that it implements IContextHelp
public class PanelEx : Panel, IContextHelp
{
private string m_sContextID;
public string ContextHelpID
{
get
{
if (string.IsNullOrEmpty(m_sContextID))
return this.Name;
return m_sContextID;
}
set { m_sContextID = value; }
}
}
static void Main()
{
//add message filter
MessageFilter oFilter = new MessageFilter();
System.Windows.Forms.Application.AddMessageFilter(
(IMessageFilter)oFilter);
// etc.
}
internal class MessageFilter : IMessageFilter
{
#region IMessageFilter Members
bool IMessageFilter.PreFilterMessage(ref Message m)
{
//Use a switch so we can trap other messages in the future.
switch (m.Msg)
{
case 0x100 : // WM_KEYDOWN
if ((int)m.WParam == (int)Keys.F1)
{
HelpUtility.ProcessHelpRequest(Control.FromHandle(m.HWnd));
return true;
}
break;
}
return false;
}
#endregion
}
public static class HelpUtility
{
public static void ProcessHelpRequest(Control ctrContext)
{
ShowContextHelp(ctrContext);
}
}
The ShowContextHelp() method travels up the parent chain, looking for a control that:
implements IContextHelp,
has a non-empty IContextHelp.ContextHelpID, and
has a corresponding entry in the mapping XML file.
If one is found, it launches the help viewer to display it. If not, it launches the default topic.
// Process a request to display help
// for the context specified by ctrContext.
public static void ShowContextHelp(Control ctrContext)
{
Control ctr = ctrContext;
string sHTMLFileName = null;
while (ctr != null)
{
// Get the first control in the parent chain
// with the IContextHelp interface.
IContextHelp help = GetIContextHelpControl(ctr);
// If there isn't one, display the default help for the application.
if (help == null)
break;
// Check to see if it has a ContextHelpID value.
if (help.ContextHelpID != null)
{
// Check to see if the ID has a mapped HTML file name.
sHTMLFileName = LookupHTMLHelpPathFromID(help.ContextHelpID);
if (sHTMLFileName != null && ShowHelp(ctrContext, sHTMLFileName))
return;
}
// Get the parent control and repeat.
ctr = ((Control)help).Parent;
}
// Show the default topic.
ShowHelp(ctrContext, "");
}
The GetIContextHelpControl() method traverses up the parent chain, looking for an IContextHelp control.
// Get the first control in the parent chain
// (including the control passed in)
// that implements IContextHelp.
private static IContextHelp GetIContextHelpControl(Control ctl)
{
while (ctl != null)
{
IContextHelp help = ctl as IContextHelp;
if (help != null)
{
return help;
}
ctl = ctl.Parent;
}
return null;
}
The ShowHelp() method launches HTML help, displaying the topic specified by sHTMLHelp.
static private StringDictionary ms_sdContextPaths = null;
private const string mc_sMAPPING_FILE_NAME = "HelpContextMapping.Config";
private const string mc_sIDMAP_ELEMENT_NAME = "IDMap";
private const string mc_sCONTEXTID_ELEMENT_NAME = "ContextID";
private const string mc_sID_ATTRIBUTE_NAME = "ID";
private const string mc_sHTMLPATH_ATTRIBUTE_NAME = "HTMLPath";
The MappingFilePath property returns the path to the mapping file, which is assumed to be in the same directory as the application executable.
private static string MappingFilePath
{
get { return Path.Combine(Application.StartupPath, mc_sMAPPING_FILE_NAME); }
}
The ContextPaths property implements the cache by reading the mapping file only on the first call.
private static StringDictionary ContextPaths
{
get
{
if (ms_sdContextPaths == null)
{
ms_sdContextPaths = ReadMappingFile();
}
return ms_sdContextPaths;
}
}
The ReadMappingFile() method creates a StringDictionary and populates it with information read from the XML help mapping configuration file.
// Read the mapping file to create a list of ID to HTML file mappings.
private static StringDictionary ReadMappingFile()
{
StringDictionary sdMapping = new StringDictionary();
XmlDocument docMapping = new XmlDocument();
if (File.Exists(MappingFilePath) == true)
{
try { docMapping.Load(MappingFilePath); }
catch
{
MessageBox.Show(string.Format("Could not read help mapping file '{0}'.",
MappingFilePath), "Context Help Made Easy", MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw;
}
XmlNodeList nlMappings = docMapping.SelectNodes("//" +
mc_sCONTEXTID_ELEMENT_NAME);
foreach (XmlElement el in nlMappings)
{
string sID = el.GetAttribute(mc_sID_ATTRIBUTE_NAME);
string sPath = el.GetAttribute(mc_sHTMLPATH_ATTRIBUTE_NAME);
if (sID != "" && sPath != "")
sdMapping.Add(sID, sPath);
}
}
return sdMapping;
}
A mapping file looks something like this:
With the cache implemented, the LookupHTMLHelpPathFromID() implementation becomes trivial.
// Given an ID, return the associated HTML Help path
private static string LookupHTMLHelpPathFromID(string sContextID)
{
if (ContextPaths.ContainsKey(sContextID))
return ContextPaths[sContextID];
return null;
}
Finally! We're done with the F1 implementation, and we can move on to the really cool part - instrumenting your code to allow help authors implement context sensitive help without your involvement.
public static void ProcessHelpRequest(Control ctrContext)
{
if (Control.ModifierKeys == Keys.Control)
{
ShowHelpMappingDialog(ctrContext);
return;
}
ShowContextHelp(ctrContext);
}
In practice, you will probably want to put in an additional test to prevent end-users from getting this dialog if they accidentally hit Ctrl-F1. In my implementation, the test for the control key is ANDed with a registry check for a key value that enables this feature. You can, of course, check the application configuration file or anything else that could uniquely identify a help author.
// Traverse the parent control chain looking for controls that implement the
// IContextHelp interface. For each one found, add it to the list of available
// contexts. Include the associated HTML path if it's define
// Finally, show the dialog for the help author to edit the mappings.
public static void ShowHelpMappingDialog(Control ctrContext)
{
IContextHelp help = GetIContextHelpControl(ctrContext);
List
// Create a list of contexts starting with the current help context
// and moving up the parent chain.
while (help != null)
{
string sContextID = help.ContextHelpID;
if (sContextID != null)
{
string sHTMLHelpPath = LookupHTMLHelpPathFromID(sContextID);
alContextPaths.Add(new ContextIDHTMLPathMap(sContextID, sHTMLHelpPath));
}
help = GetIContextHelpControl(((Control)help).Parent);
}
// Pop up the mapping dialog. If it returns true, this means a change was made
// so we rewrite the XML mapping file with the new information.
if (FHelpMappingDialog.ShowHelpWriterHelper(alContextPaths) == true)
{
foreach (ContextIDHTMLPathMap pathMap in alContextPaths)
{
if (!string.IsNullOrEmpty(pathMap.ContextID))
{
if (!string.IsNullOrEmpty(pathMap.HTMLPath))
{
ContextPaths[pathMap.ContextID] = pathMap.HTMLPath;
}
else
{
if (ContextPaths.ContainsKey(pathMap.ContextID))
ContextPaths.Remove(pathMap.ContextID);
}
}
}
SaveMappingFile(ContextPaths);
}
}
}
// Utility class for maintaining relationship between context Id and path.
public class ContextIDHTMLPathMap
{
public string ContextID;
public string HTMLPath;
public ContextIDHTMLPathMap(string ID, string Path)
{
ContextID = ID;
HTMLPath = Path;
}
}
// Saves the specified StringDictionary that contains ID to Path mappings to the
// XML mapping file.
private static void SaveMappingFile(StringDictionary sdMappings)
{
// Create a new XML document and initialize it with the XML declaration and the
// outer IDMap element.
XmlDocument docMapping = new XmlDocument();
XmlDeclaration xmlDecl = docMapping.CreateXmlDeclaration("1.0", null, null);
docMapping.InsertBefore(xmlDecl, docMapping.DocumentElement);
XmlElement elIDMap = AddChildElementToNode(docMapping, docMapping,
mc_sIDMAP_ELEMENT_NAME);
// Add the defined mappings between contextID and filename.
foreach (DictionaryEntry de in sdMappings)
{
XmlElement elMapping = AddChildElementToNode(elIDMap, docMapping,
mc_sCONTEXTID_ELEMENT_NAME);
elMapping.SetAttribute(mc_sID_ATTRIBUTE_NAME, de.Key as string);
elMapping.SetAttribute(mc_sHTMLPATH_ATTRIBUTE_NAME, de.Value as string);
}
try
{
docMapping.Save(MappingFilePath);
}
catch
{
MessageBox.Show(string.Format("Could not write help mapping file '{0}'",
MappingFilePath), "Context Help Made Easy", MessageBoxButtons.OK,
MessageBoxIcon.Error);
throw;
}
}
The AddChildElementToNode() utility function makes the code a bit more readable:
// Small utility method to add XML elements to a parent node.
private static XmlElement AddChildElementToNode(XmlNode node,
XmlDocument doc, string elementName)
{
XmlElement el = doc.CreateElement(elementName);
node.AppendChild(el);
return el;
}
The Mapping Dialog
The mapping dialog consists of a ListView control, and buttons for editing the topic file name, OK, and Cancel. It is an editor of the list of ContextIDHTMLPathMap structures that contain the current control's contexts. The dialog populates the ListView control, permits editing of the HTML File names in it, and writes the results back to the List
The entry point for the dialog is the static method ShowHelpWriterHelper() that instantiates the form and initializes its data.
// Static entry point to pop up this form.
public static bool
ShowHelpWriterHelper(List
{
FHelpMappingDialog frmHelper = new FHelpMappingDialog();
frmHelper.IDList = contextIDs; // Populate the treelist.
if( frmHelper.lvMapping.Items.Count > 0 )
frmHelper.lvMapping.SelectedIndices.Add(0);
frmHelper.ShowDialog(); // Popup the form.
if (frmHelper.Changed)
{
// For each item in the ListView,
// change the path map to correspond to the UI.
foreach (ListViewItem lvi in frmHelper.lvMapping.Items)
{
ContextIDHTMLPathMap pathMap = (ContextIDHTMLPathMap)lvi.Tag;
pathMap.HTMLPath = (string)lvi.SubItems[0].Text.Trim();
}
}
return frmHelper.Changed;
}
The ListView is populated in the IDList property set method:
// Gets and sets the list of ids. The setter updates the UI.
public List
{
set
{
lvMapping.Items.Clear();
foreach (ContextIDHTMLPathMap pathMap in value)
{
AddMappingNode(pathMap);
}
}
}
// Utility to add a node to the treelist.
private void AddMappingNode(ContextIDHTMLPathMap pathMap)
{
ListViewItem lvi = new ListViewItem(pathMap.HTMLPath);
lvi.SubItems.Add(pathMap.ContextID);
lvi.Tag = pathMap;
lvMapping.Items.Add(lvi);
}
Other methods in the dialog are what you'd probably expect:
// Begin editing the label of the selected
// item when they click this button
private void btnEditTopicFile_Click(object sender, EventArgs e)
{
if( lvMapping.SelectedItems.Count == 1 )
{
ListViewItem lvi = lvMapping.SelectedItems[0];
lvi.BeginEdit();
}
}
// If the item has changed after editing
// the label, flag the dialog as changed.
private void lvMapping_AfterLabelEdit(object sender,
LabelEditEventArgs e)
{
this.Changed = true;
};
}
The details of implementing the dialog can be found in the source attached to this article, but this should give you the basic idea.
HelpButton = true;
MaximizeBox = false;
MinimizeBox = false;
Now we need to override the OnHelpButtonClicked() method to call our help handler and cancel the default behavior of changing the cursor.
protected override void OnHelpButtonClicked(CancelEventArgs e)
{
HelpUtility.ProcessHelpRequest(this);
base.OnHelpButtonClicked(e);
e.Cancel = true;
}