I am trying to use Windows Media .Net library to copy an audio/video file that's an asf file. I'm relatively new to the Windows Media Format SDK, so I'm not sure if I'm doing this right. I have looked through some of the sample projects that are included with the download and looked through the documentation for the C++ version, but I can't seem to figure out why it's crashing when I call EndWriting. So if someone could help point out what I'm doing wrong here I would gratefully appreciate it.
I have a WinForm that will have a button on it to start the recording, and another button to end the recording. My class implements the IWMReaderCallback and uses a private class called SampleProp to be used to hold the values for the pointers and such that will be returned in the OnSample method. So in the OnSample method I'm filling a byte[] member of the SampleProp instance with a copy of the sample being returned. I then add the instance of SampleProp to a collection that will be used in another method called ProcessReaderSample. In this other method I am creating the IWMReader and IWMWriter objects, and has a while loop that will call BeginWriting, AllocateSample, WriteSample, then EndWriting. And that's where it crashes. Here is my code that I'm using...
public class MyClass : IWMReaderCallback
{
static readonly string _streamingFileName = "C:\\tmpStream.asf";
static readonly string _streamingURL = #"http://localhost:8080";
static readonly string _recordingFileName = "C:\\tmpRecording.asf";
IWMReader _reader = null;
IWMReaderAdvanced _readerAdvanced = null;
IWMHeaderInfo _readerHeaderInfo = null;
IWMProfile _readerProfile = null;
IWMWriter _writer = null;
IWMWriterAdvanced _writerAdvanced = null;
IWMHeaderInfo _writerHeaderInfo = null;
int _streamCount = 0;
Guid[] _guidStreamType = null;
short[] _streamNumber = null;
void GetReader()
{
WMUtils.WMCreateReader(IntPtr.Zero, Rights.Playback, out _reader);
_readerAdvanced = _reader as IWMReaderAdvanced;
_readerHeaderInfo = _reader as IWMHeaderInfo;
_reader.Open(_streamingFileName, this, IntPtr.Zero);
_readerAdvanced.SetUserProvidedClock(true);
_readerAdvanced.SetManualStreamSelection(true);
}
void GetWritter()
{
WMUtils.WMCreateWriter(IntPtr.Zero, out _writer);
_writerAdvanced = _writer as IWMWriterAdvanced;
_writerHeaderInfo = _writer as IWMHeaderInfo;
_writer.SetProfile(_readerProfile);
_writer.SetOutputFilename(_recordingFileName);
int inputCount = 0;
_writer.GetInputCount(out inputCount);
for (int i = 0; i < inputCount; i++)
{
_writer.SetInputProps(i, null);
}
}
class SampleProp
{
public int OutputNum { get; private set; }
public long SampleTime { get; private set; }
public SampleFlag Flag { get; private set; }
public byte[] Sample;
public int Size { get; private set; }
public SampleProp(int size, int outputNum, long sampleTime, SampleFlag flag)
{
Size = size;
OutputNum = outputNum;
SampleTime = sampleTime;
Flag = flag;
Sample = new byte[size];
}
}
List<SampleProp> _writableSamples = null;
public void OnSample(int dwOutputNum, long cnsSampleTime, long cnsSampleDuration, SampleFlag dwFlags, INSSBuffer pSample, IntPtr pvContext)
{
int size = 0;
pSample.GetLength(out size);
var prop = new SampleProp(size, dwOutputNum, cnsSampleTime, dwFlags);
IntPtr ptr = IntPtr.Zero;
pSample.GetBuffer(out ptr);
Marshal.Copy(ptr, prop.Sample, 0, size);
_writableSamples.Add(prop);
}
void ProcessReaderSample()
{
_event.Reset();
GetReader();
GetProfileInfo();
GetWritter();
_reader.Start(0, 0, 1.0f, IntPtr.Zero);
_isRecording = true;
var hasStarted = false;
while (_isRecording || _writableSamples.Count > 0)
{
if (_writableSamples.Count > 0)
{
_writer.BeginWriting();
INSSBuffer buffer;
_writer.AllocateSample(_writableSamples[0].Size, out buffer);
IntPtr ptr = IntPtr.Zero;
buffer.GetBuffer(out ptr);
Marshal.Copy(_writableSamples[0].Sample, 0, ptr, _writableSamples[0].Size);
buffer.SetLength(_writableSamples[0].Size);
_writer.WriteSample(_writableSamples[0].OutputNum, _writableSamples[0].SampleTime, _writableSamples[0].Flag, buffer);
Marshal.ReleaseComObject(buffer);
_writableSamples.RemoveAt(0);
_writer.EndWriting();
}
}
}
bool _isRecording = false;
public void StartRecording()
{
if (_isRecording) return;
_writableSamples = new List<SampleProp>();
Thread writingThread = new Thread(new ThreadStart(ProcessReaderSample));
writingThread.Start();
}
public void StopRecording()
{
if (!_isRecording) return;
_isRecording = false;
}
So if someone could please help me with this. Thank you in advance.
I went a totally different direction for the solution to this issue. I decided to not use solely the WM .Net Framework and instead used the DirectShow.Net Framework implementing the WM Asf Reader and Writer. Here is a link to my question and answer.
Is it possible to use an http url as your source location of a Source Filter in DirectShow .Net?
Related
I'm playing the same sound files (randomly choosing between them) 5 times a second, and I'm always loading into memory, so the program uses a lot of memory. How could I load the sound file into memory, and start it from there? I'm using NAudio. Current code:
var sound = "sounds/test.mp3";
using (var audioFile = new AudioFileReader(sound))
using (var outputDevice = new WaveOutEvent())
{
outputDevice.Init(audioFile);
outputDevice.Play();
while (outputDevice.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(1000);
}
threadStop();
}
If you remove the using blocks then audioFile and outputDevice will not be disposed. You can then retain them in memory, and the same references will be used each time you play the audio.
With using blocks, you are repeatedly instantiating NAudio objects whose memory may not be deallocated immediately.
var sound = "sounds/test.mp3";
var audioFile = new AudioFileReader(sound);
var outputDevice = new WaveOutEvent();
outputDevice.Init(audioFile);
outputDevice.Play();
while (outputDevice.PlaybackState == PlaybackState.Playing)
{
Thread.Sleep(1000);
}
threadStop();
I fixed the whole issue by using the code found in this article.
It uses the MixingSampleProvider. I load the sounds into a custom class called: CachedSound. And then I play them using another class called: AudioPlaybackEngine. Which handles the mixer, and I use the CachedSoundSampleProvider class to read the cached sound.
The code looks like this:
class AudioPlaybackEngine : IDisposable
{
private readonly IWavePlayer outputDevice;
private readonly MixingSampleProvider mixer;
public AudioPlaybackEngine(int sampleRate = 44100, int channelCount = 2)
{
outputDevice = new WaveOutEvent();
mixer = new MixingSampleProvider(WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channelCount));
mixer.ReadFully = true;
outputDevice.Init(mixer);
outputDevice.Play();
}
public void PlaySound(string fileName)
{
var input = new AudioFileReader(fileName);
AddMixerInput(new AutoDisposeFileReader(input));
}
private ISampleProvider ConvertToRightChannelCount(ISampleProvider input)
{
if (input.WaveFormat.Channels == mixer.WaveFormat.Channels)
{
return input;
}
if (input.WaveFormat.Channels == 1 && mixer.WaveFormat.Channels == 2)
{
return new MonoToStereoSampleProvider(input);
}
throw new NotImplementedException("Not yet implemented this channel count conversion");
}
public void PlaySound(CachedSound sound)
{
AddMixerInput(new CachedSoundSampleProvider(sound));
}
private void AddMixerInput(ISampleProvider input)
{
mixer.AddMixerInput(ConvertToRightChannelCount(input));
}
public void Dispose()
{
outputDevice.Dispose();
}
public static readonly AudioPlaybackEngine Instance = new AudioPlaybackEngine(44100, 2);
}
class CachedSound
{
public float[] AudioData { get; private set; }
public WaveFormat WaveFormat { get; private set; }
public CachedSound(string audioFileName)
{
using (var audioFileReader = new AudioFileReader(audioFileName))
{
// TODO: could add resampling in here if required
WaveFormat = audioFileReader.WaveFormat;
var wholeFile = new List<float>((int)(audioFileReader.Length / 4));
var readBuffer= new float[audioFileReader.WaveFormat.SampleRate * audioFileReader.WaveFormat.Channels];
int samplesRead;
while((samplesRead = audioFileReader.Read(readBuffer,0,readBuffer.Length)) > 0)
{
wholeFile.AddRange(readBuffer.Take(samplesRead));
}
AudioData = wholeFile.ToArray();
}
}
}
class CachedSoundSampleProvider : ISampleProvider
{
private readonly CachedSound cachedSound;
private long position;
public CachedSoundSampleProvider(CachedSound cachedSound)
{
this.cachedSound = cachedSound;
}
public int Read(float[] buffer, int offset, int count)
{
var availableSamples = cachedSound.AudioData.Length - position;
var samplesToCopy = Math.Min(availableSamples, count);
Array.Copy(cachedSound.AudioData, position, buffer, offset, samplesToCopy);
position += samplesToCopy;
return (int)samplesToCopy;
}
public WaveFormat WaveFormat { get { return cachedSound.WaveFormat; } }
}
// This class automatically disposes the file reader that it contains.
class AutoDisposeFileReader : ISampleProvider
{
private readonly AudioFileReader reader;
private bool isDisposed;
public AutoDisposeFileReader(AudioFileReader reader)
{
this.reader = reader;
this.WaveFormat = reader.WaveFormat;
}
public int Read(float[] buffer, int offset, int count)
{
if (isDisposed)
return 0;
int read = reader.Read(buffer, offset, count);
if (read == 0)
{
reader.Dispose();
isDisposed = true;
}
return read;
}
public WaveFormat WaveFormat { get; private set; }
}
I'm building an application where DMX output is generated. The application than gives this data via an USB interface to the hardware. Since DMX uses the RS485 protocol, almost any RS485 USB-to-Serial converter can be used. I want my application to be extendable by user written plugins, which inherit from my base class which than handles the communication between the application and the interface.
So here is my problem:
I can't communicate with the interface when the interface is plugged in after the app was started. Im using WqlEventQuery's and the ManagementEventWatcher classes to detect usb event's (plugin & out) and load & unload my classes accordingly. While debugging I noticed that the driver class indeed see's the device, but can't read from it (eg the serial number). Do I need to reset something before the port get's usable?
status get's set to FT_INVALID_HANDLE after FT_Open which means that a communication is never accomplished.
The FTD2xx class is working as it should be except when the interface is plugged in after starting the app.
EDIT
I got it working. However to make the serial usable in my application I have to open, close and dispose it once from the C# SerialPort class once. Why is that?
var ports = SerialPort.GetPortNames();
SerialPort port = new SerialPort(ports[0]);
port.Open();
port.Close();
port.Dispose();
-> Following code works
Code:
Bases driver class
public abstract class Driver
{
public abstract string FullName { get; set; }
public virtual string Description { get; set; }
public abstract string[] HardwareIDs { get; set; }
public virtual int FrameRate { get; set; }
public Driver(int framerate)
{
this.FrameRate = framerate;
}
public abstract void Start();
public abstract void Stop();
public abstract void SetDMXValue(int channel, byte value);
}
FTD2xx driver class
public class FTD2XX : Driver
{
public override string FullName
{
get { return "FTD2XX Usb-Interface"; }
set {}
}
public override string Description
{
get { return "Driver for the FTD2xx usb-to-dmx interface"; }
set {}
}
public override string[] HardwareIDs
{
get { return new string[] { #"USB\VID_0403&PID_6001" }; }
set {}
}
public byte[] buffer { get; private set; }
public uint handle;
public bool done = false;
public int bytesWritten = 0;
public FT_STATUS status;
public const byte BITS_8 = 8;
public const byte STOP_BITS_2 = 2;
public const byte PARITY_NONE = 0;
public const UInt16 FLOW_NONE = 0;
public const byte PURGE_RX = 1;
public const byte PURGE_TX = 2;
Thread workThread;
// DLL IMPORT'S LEFT OUT FOR SIZE //
public FTD2XX(int frameRate) : base(frameRate)
{
this.buffer = new byte[255];
}
public override void Start()
{
handle = 0;
status = FT_Open(0, ref handle);
workThread = new Thread(new ThreadStart(writeData));
workThread.Start();
}
public override void SetDMXValue(int channel, byte value)
{
if (buffer != null)
{
buffer[channel] = value;
}
}
public void writeData()
{
while (!done)
{
InitOpenDMX();
FT_SetBreakOn(handle);
FT_SetBreakOff(handle);
bytesWritten = write(handle, buffer, buffer.Length);
Thread.Sleep(FrameRate);
}
}
public int write(uint handle, byte[] data, int length)
{
IntPtr ptr = Marshal.AllocHGlobal((int)length);
Marshal.Copy(data, 0, ptr, (int)length);
uint bytesWritten = 0;
status = FT_Write(handle, ptr, (uint)length, ref bytesWritten);
return (int)bytesWritten;
}
public void InitOpenDMX()
{
status = FT_ResetDevice(handle);
status = FT_SetDivisor(handle, (char)12); // set baud rate
status = FT_SetDataCharacteristics(handle, BITS_8, STOP_BITS_2, PARITY_NONE);
status = FT_SetFlowControl(handle, (char)FLOW_NONE, 0, 0);
status = FT_ClrRts(handle);
status = FT_Purge(handle, PURGE_TX);
status = FT_Purge(handle, PURGE_RX);
}
public override void Stop()
{
this.done = true;
this.workThread.Abort();
this.workThread = null;
status = FT_ResetDevice(handle);
status = FT_Close(handle);
}
/// <summary>
/// Enumaration containing the varios return status for the DLL functions.
/// </summary>
public enum FT_STATUS
{
FT_OK = 0,
FT_INVALID_HANDLE,
FT_DEVICE_NOT_FOUND,
FT_DEVICE_NOT_OPENED,
FT_IO_ERROR,
FT_INSUFFICIENT_RESOURCES,
FT_INVALID_PARAMETER,
FT_INVALID_BAUD_RATE,
FT_DEVICE_NOT_OPENED_FOR_ERASE,
FT_DEVICE_NOT_OPENED_FOR_WRITE,
FT_FAILED_TO_WRITE_DEVICE,
FT_EEPROM_READ_FAILED,
FT_EEPROM_WRITE_FAILED,
FT_EEPROM_ERASE_FAILED,
FT_EEPROM_NOT_PRESENT,
FT_EEPROM_NOT_PROGRAMMED,
FT_INVALID_ARGS,
FT_OTHER_ERROR
};
}
}
USB event handler
private void DeviceInsertedEvent(object sender, EventArrivedEventArgs e)
{
ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];
foreach(Driver driver in this.drivers)
{
if (driver.HardwareIDs.Any(hID => (instance.Properties["DeviceID"].Value as string).StartsWith(hID)))
{
driver.Start();
if (this.OnDriverLoaded != null)
this.OnDriverLoaded(this, new PluginLoadUnloadEventArgs(driver));
break;
}
}
}
Unplug is basiclly the same, but .Stop() and OnDriverUnload() used
Detecting any compatible usb devices after startup (Code works as desired if this is the case)
ManagementObjectCollection collection;
using (var searcher = new ManagementObjectSearcher(#"Select * From Win32_USBHub"))
collection = searcher.Get();
foreach (var device in collection)
{
foreach (Driver driver in this.drivers)
{
if (driver.HardwareIDs.Any(hID => (device.GetPropertyValue("DeviceID") as string).StartsWith(hID)))
{
driver.Start();
if (this.OnDriverLoaded != null)
this.OnDriverLoaded(this, new PluginLoadUnloadEventArgs(driver));
goto End;
}
}
}
End:
return;
I am working on a small project (just for fun) that allows me to display the frequencies played back in my current audio buffer.
The plan is:
The thread that is executing the WaveOutPlayer.ThreadProc() method keeps playing and refilling two audio buffers. While the first buffer is playing, the second buffer gets refilled and vice versa. To be sure that one buffer does not start playing before the other one stopped playback, I use the WaitOne() method. After the buffer's audio data has beed played back by the WaveOut device, the buffer's OnCompleted() method is called. This method cancels the waiting.
The playback itself starts, but it is very choppy.
Could anyone tell me why this happens?
The Visual Studio project is located at http://www.pour-toujours.net/lowlevelaudio.zip
(currently, only 16-bit, Stereo, 44,1kHz uncompressed wav is supported.)
Here goes some of my code that seems to be relevant:
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Collections;
using System.Linq;
namespace LowLevelAudio
{
internal class WaveOutHelper
{
public static void Try(int err)
{
if (err != WaveNative.MMSYSERR_NOERROR)
throw new Exception(err.ToString());
}
}
public delegate void BufferFillEventHandler(IntPtr data, int size);
public class WaveOutBuffer : IDisposable
{
private AutoResetEvent m_PlayEvent = new AutoResetEvent(false);
private IntPtr m_WaveOut;
private int m_buffersize;
private static byte[] m_samples;
private static double[] m_fftsamples;
private WaveNative.WaveHdr m_Header;
private byte[] m_HeaderData;
private GCHandle m_HeaderHandle;
private GCHandle m_HeaderDataHandle;
private WaveFormat m_waveformat;
private double[] m_fftOccurances;
private double[] m_fftHertzlist;
private bool m_Playing;
public int ID
{
get; set;
}
internal static void WaveOutProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
if (uMsg == WaveNative.MM_WOM_DONE)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveOutBuffer buf = (WaveOutBuffer)h.Target;
buf.OnPlayCompleted();
}
catch(Exception ex)
{
Console.WriteLine("Exception: " + ex.Message);
}
}
}
public WaveOutBuffer(IntPtr waveOutHandle, int size, WaveFormat format)
{
m_WaveOut = waveOutHandle;
m_waveformat = format;
m_HeaderHandle = GCHandle.Alloc(m_Header, GCHandleType.Pinned);
m_Header.dwUser = (IntPtr)GCHandle.Alloc(this);
m_HeaderData = new byte[size];
m_HeaderDataHandle = GCHandle.Alloc(m_HeaderData, GCHandleType.Pinned);
m_Header.lpData = m_HeaderDataHandle.AddrOfPinnedObject();
m_Header.dwBufferLength = size;
m_buffersize = size;
m_samples = new byte[m_buffersize];
WaveOutHelper.Try(WaveNative.waveOutPrepareHeader(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header)));
}
~WaveOutBuffer()
{
Dispose();
}
public int Size
{
get { return m_Header.dwBufferLength; }
}
public IntPtr Data
{
get { return m_Header.lpData; }
}
public void Dispose()
{
if (m_Header.lpData != IntPtr.Zero)
{
WaveNative.waveOutUnprepareHeader(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header));
m_HeaderHandle.Free();
m_Header.lpData = IntPtr.Zero;
}
m_PlayEvent.Close();
if (m_HeaderDataHandle.IsAllocated)
m_HeaderDataHandle.Free();
}
public bool Play()
{
lock(this) // works, but has to be fine tuned... (to do)
{
m_PlayEvent.Reset();
m_Playing = WaveNative.waveOutWrite(m_WaveOut, ref m_Header, Marshal.SizeOf(m_Header)) == WaveNative.MMSYSERR_NOERROR;
if (!m_Playing)
throw new Exception("test exception");
return m_Playing;
}
}
public void WaitForMe()
{
Console.WriteLine(this.ID + " WaitFor()");
if (m_Playing)
{
m_Playing = m_PlayEvent.WaitOne();
}
else
{
m_Playing = false;
}
}
public void OnPlayCompleted()
{
Console.WriteLine(this.ID + " OnCompleted()");
m_PlayEvent.Set();
m_Playing = false;
}
}
public class WaveOutPlayer : IDisposable
{
private IntPtr m_WaveOut;
private WaveOutBuffer[] m_bufferlist;
private Thread m_Thread;
private BufferFillEventHandler m_FillProc;
private bool m_Finished;
private byte m_zero;
private int m_buffercount = 0;
private int m_buffersize = 0;
private WaveFormat m_waveformat;
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveOutBuffer.WaveOutProc);
public static int DeviceCount
{
get { return WaveNative.waveOutGetNumDevs(); }
}
public WaveOutPlayer(int device, WaveFormat format, int bufferSize, BufferFillEventHandler fillProc)
{
m_zero = format.wBitsPerSample == 8 ? (byte)128 : (byte)0;
m_FillProc = fillProc;
m_buffercount = 2;
m_buffersize = bufferSize;
m_waveformat = format;
WaveOutHelper.Try(WaveNative.waveOutOpen(out m_WaveOut, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION));
AllocateBuffers(bufferSize, m_buffercount, format);
m_Thread = new Thread(new ThreadStart(ThreadProc));
m_Thread.Start();
}
~WaveOutPlayer()
{
Dispose();
}
public void Dispose()
{
if (m_Thread != null)
{
try
{
m_Finished = true;
if (m_WaveOut != IntPtr.Zero)
WaveNative.waveOutReset(m_WaveOut);
m_Thread.Join();
m_FillProc = null;
FreeBuffers();
if (m_WaveOut != IntPtr.Zero)
WaveNative.waveOutClose(m_WaveOut);
}
finally
{
m_Thread = null;
m_WaveOut = IntPtr.Zero;
}
}
}
private void ThreadProc()
{
WaveOutBuffer b0 = m_bufferlist[0];
WaveOutBuffer b1 = m_bufferlist[1];
MainForm form = Program.getUI();
bool s = true;
m_FillProc(b0.Data, b0.Size);
while (!m_Finished)
{
if (s)
{
Console.WriteLine("-------------------------");
Console.WriteLine("Playing b0, filling b1");
b0.Play();
m_FillProc(b1.Data, b1.Size);
form.paintEqualizer(b0);
Console.WriteLine("callng waitFor on b0");
b0.WaitForMe();
}
else
{
Console.WriteLine("-------------------------");
Console.WriteLine("Playing b1, filling b0");
b1.Play();
m_FillProc(b0.Data, b0.Size);
form.paintEqualizer(b1);
Console.WriteLine("callng waitFor on b1");
b1.WaitForMe();
}
s = !s;
}
}
private void AllocateBuffers(int bufferSize, int bufferCount, WaveFormat format)
{
FreeBuffers();
m_bufferlist = new WaveOutBuffer[m_buffercount];
if (bufferCount > 0)
{
for (int i = 0; i < m_buffercount; i++)
{
m_bufferlist[i] = new WaveOutBuffer(m_WaveOut, bufferSize, format);
m_bufferlist[i].ID = i;
}
}
}
private void FreeBuffers()
{
if (m_bufferlist != null)
{
foreach (WaveOutBuffer currentBuffer in m_bufferlist)
{
if (currentBuffer != null)
{
currentBuffer.Dispose();
}
}
}
}
}
}
I am uploading a cab file from a web form, and want to unpack it in memory. I've tried tackling the issue with a CabInfo file, but without success. I do know how to unpack a cab file to my local disk, but do not know how to apply this in memory.
Any assistance would be appreciated.
If using another library is possible, take a look at this. The description clearly states the library will allow you to extract into memoty.
WIX is an open source project. You could always ask for this feature, ask for a better solution on their forum or simply modify the code for your need.
Vadim
Your initial question seems to indicate that you are allowed to use the Microsoft.Deployment.Compression DLL. If true, the code below should get you close to what you want:
CabEngine engine = new CabEngine();
foreach (ArchiveFileInfo archiveFileInfo in engine.GetFileInfo(fileStream))
{
Stream stream = engine.Unpack(fileStream, archiveFileInfo.Name);
byte[] buffer = new byte[stream.Length];
//The (int) below is a dirty trick for demonstration purposes only;
//re-work for production code
stream.Read(buffer, 0, (int)stream.Length);
}
Later answers from you seem to indicate that you are not allowed to use 3rd party DLLs in which case you will want to use the FDI* API. This link has some code that you will be able to modify from using hard-coded paths to (memory) streams: Extract .cab file in C#
There is a ready to use project that you can download: Cabinet File (*.CAB) Compression and Extraction
According to version history since Sep 2008 "..you can extract directly to memory."
Here is a utility class that should work in memory (it supports x86 or x64 compilation). Here is out you would use it, if we suppose a .CAB file has been uploaded into ASP.NET using the standard upload protocol:
using (CabFile file = new CabFile(HttpContext.Current.Request.Files[0].InputStream))
{
file.EntryExtract += CabEntryExtract;
file.ExtractEntries();
}
static void CabEntryExtract(object sender, CabEntryExtractEventArgs e)
{
// e.Entry.Name contains the entry name
// e.Entry.Data contains a byte[] with the entry data
// e.Entry.LastWriteTime contains the entry last write time
// e.Entry.Size contains the entry uncompressed size
}
And here is the utility and associated classes:
public sealed class CabFile : IDisposable
{
private IntPtr _hfdi;
private ERF _erf;
private GCHandle _erfHandle;
private byte[] _data;
private Dictionary<IntPtr, object> _handles = new Dictionary<IntPtr, object>();
private MemoryStream _currentEntryData;
private FNALLOC _alloc;
private FNCLOSE _close;
private FNFREE _free;
private FNOPEN _open;
private FNREAD _read;
private FNWRITE _write;
private FNSEEK _seek;
private FNFDINOTIFY _extract;
public event EventHandler<CabEntryExtractEventArgs> EntryExtract;
public CabFile(string filePath)
: this(GetStream(filePath))
{
}
private static Stream GetStream(string filePath)
{
if (filePath == null)
throw new ArgumentNullException("filePath");
return new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
public CabFile(Stream stream)
{
if (stream == null)
throw new ArgumentNullException("stream");
using (MemoryStream data = new MemoryStream())
{
stream.CopyTo(data);
_data = data.ToArray();
}
_erf = new ERF();
_alloc = new FNALLOC(FnAlloc);
_free = new FNFREE(FnFree);
_close = new FNCLOSE(FnClose);
_open = new FNOPEN(FnOpen);
_read = new FNREAD(FnRead);
_write = new FNWRITE(FnWrite);
_seek = new FNSEEK(FnSeek);
_extract = new FNFDINOTIFY(FnNotifyExtract);
_erfHandle = GCHandle.Alloc(_erf, GCHandleType.Pinned);
_hfdi = FDICreate(
Marshal.GetFunctionPointerForDelegate(_alloc),
Marshal.GetFunctionPointerForDelegate(_free),
Marshal.GetFunctionPointerForDelegate(_open),
Marshal.GetFunctionPointerForDelegate(_read),
Marshal.GetFunctionPointerForDelegate(_write),
Marshal.GetFunctionPointerForDelegate(_close),
Marshal.GetFunctionPointerForDelegate(_seek)
, -1, _erfHandle.AddrOfPinnedObject());
}
public void ExtractEntries()
{
FDICopy(_hfdi, string.Empty, string.Empty, 0, Marshal.GetFunctionPointerForDelegate(_extract), IntPtr.Zero, IntPtr.Zero);
}
public void Dispose()
{
if (_hfdi != IntPtr.Zero)
{
FDIDestroy(_hfdi);
_hfdi = IntPtr.Zero;
}
_erfHandle.Free();
}
private void OnEntryExtract(CabEntry entry)
{
EventHandler<CabEntryExtractEventArgs> handler = EntryExtract;
if (handler != null)
{
handler(this, new CabEntryExtractEventArgs(entry));
}
}
private IntPtr FnAlloc(int cb)
{
return Marshal.AllocHGlobal(cb);
}
private void FnFree(IntPtr pv)
{
Marshal.FreeHGlobal(pv);
}
private IntPtr FnOpen(string pszFile, int oflag, int pmode)
{
// only used for reading archive
IntPtr h = new IntPtr(_handles.Count + 1);
_handles.Add(h, 0);
return h;
}
private int FnRead(IntPtr hf, byte[] pv, int cb)
{
// only used for reading archive
int pos = (int)_handles[hf];
int left = _data.Length - pos;
int read = Math.Min(left, cb);
if (read > 0)
{
Array.Copy(_data, pos, pv, 0, read);
_handles[hf] = pos + read;
}
return read;
}
private int FnWrite(IntPtr hf, byte[] pv, int cb)
{
// only used for writing entries
_currentEntryData.Write(pv, 0, cb);
return cb;
}
private int FnClose(IntPtr hf)
{
object o = _handles[hf];
CabEntry entry = o as CabEntry;
if (entry != null)
{
entry.Data = _currentEntryData.ToArray();
_currentEntryData.Dispose();
}
_handles.Remove(hf);
return 0;
}
private int FnSeek(IntPtr hf, int dist, SeekOrigin seektype)
{
// only used for seeking archive
int pos;
switch (seektype)
{
case SeekOrigin.Begin:
pos = dist;
break;
case SeekOrigin.Current:
pos = (int)_handles[hf] + dist;
break;
//case SeekOrigin.End:
default:
pos = _data.Length + dist;
break;
}
_handles[hf] = pos;
return pos;
}
private IntPtr FnNotifyExtract(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION fdin)
{
CabEntry entry;
switch (fdint)
{
case FDINOTIFICATIONTYPE.COPY_FILE:
entry = new CabEntry(fdin);
entry._handle = new IntPtr(_handles.Count + 1);
_handles.Add(entry._handle, entry);
_currentEntryData = new MemoryStream();
return entry._handle;
case FDINOTIFICATIONTYPE.CLOSE_FILE_INFO:
entry = (CabEntry)_handles[fdin.hf];
FnClose(fdin.hf);
OnEntryExtract(entry);
return new IntPtr(1);
default:
return IntPtr.Zero;
}
}
private enum FDINOTIFICATIONTYPE
{
CABINET_INFO = 0,
PARTIAL_FILE = 1,
COPY_FILE = 2,
CLOSE_FILE_INFO = 3,
NEXT_CABINET = 4,
ENUMERATE = 5,
}
[StructLayout(LayoutKind.Sequential)]
private struct ERF
{
public int erfOper;
public int erfType;
public int fError;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
internal class FDINOTIFICATION
{
public int cb;
public IntPtr psz1;
public IntPtr psz2;
public IntPtr psz3;
public IntPtr pv;
public IntPtr hf;
public ushort date;
public ushort time;
public ushort attribs;
public ushort setID;
public ushort iCabinet;
public ushort iFolder;
public int fdie;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr FNALLOC(int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void FNFREE(IntPtr pv);
[UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private delegate IntPtr FNOPEN(string pszFile, int oflag, int pmode);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNREAD(IntPtr hf, [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNWRITE(IntPtr hf, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] byte[] pv, int cb);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNCLOSE(IntPtr hf);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int FNSEEK(IntPtr hf, int dist, SeekOrigin seektype);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate IntPtr FNFDINOTIFY(FDINOTIFICATIONTYPE fdint, FDINOTIFICATION pfdin);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern IntPtr FDICreate(IntPtr pfnalloc, IntPtr pfnfree, IntPtr pfnopen, IntPtr pfnread, IntPtr pfnwriter, IntPtr pfnclose, IntPtr pfnseek, int cpuType, IntPtr perf);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr FDIDestroy(IntPtr hdfi);
[DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern IntPtr FDICopy(IntPtr hdfi, string pszCabinet, string pszCabPath, int flags, IntPtr fnNotify, IntPtr fnDecrypt, IntPtr userData);
}
public sealed class CabEntry
{
internal IntPtr _handle;
internal CabEntry(CabFile.FDINOTIFICATION fdin)
{
Name = Marshal.PtrToStringAnsi(fdin.psz1);
Size = fdin.cb;
LastWriteTime = new DateTime(1980 + GetMask(fdin.date, 9, 15), GetMask(fdin.date, 5, 8), GetMask(fdin.date, 0, 4),
GetMask(fdin.time, 11, 15), GetMask(fdin.time, 5, 10), 2 * GetMask(fdin.time, 0, 4));
}
private static int GetMask(int value, byte startByte, byte endByte)
{
int final = 0;
int v = 1;
for (byte b = startByte; b <= endByte; b++)
{
if ((value & (1 << b)) != 0)
{
final += v;
}
v = v * 2;
}
return final;
}
public string Name { get; private set; }
public int Size { get; private set; }
public DateTime LastWriteTime { get; private set; }
public byte[] Data { get; internal set; }
}
public sealed class CabEntryExtractEventArgs : EventArgs
{
public CabEntryExtractEventArgs(CabEntry entry)
{
if (entry == null)
throw new ArgumentNullException("entry");
Entry = entry;
}
public CabEntry Entry { get; private set; }
}
NOTE: this code allocates big byte[] chunks so it could be optimized to use things like ChunkedMemoryStream (available for example in this library: CodeFluent Runtime Client) instead of byte[] to avoid impacting the LOH (Large Object Heap) too much.
I found a opensource code that was a much simpler version of NAudio for my C# application. Which for me is better because all I am looking to do is play a brief sound out of the speakers to test that they are plugged in and working, and I also want to listen to the microphone. The project it self looked ok, but i felt that it could be refactored a bit into smaller chucks. Another thing i found is that the WaveOutBuffer.cs and WaveInBuffer.cs files were remarkably similar. So i started to make a abstract class WaveBuffer. This would have all the similar functions and variables in it that would be passed. One that i am having troubles with is as follows. Another file WaveNative.cs has delegate in it. I am very new to delegates and Events so i know it has the potential to be very important. but i dont like the way that it is setup to be used. So i'll post what Is in each file and how it is used, then show what i have been trying to get to work. Maybe between all of our minds we can consolidate and refactor this to make it more efficient :)
so First.
WaveNative.cs
//callbacks
public delegate void WaveDelegate(IntPtr hdrvr, int uMsg, int dwUser, ref WaveHdr wavhdr, int dwParam2);
WaveInBuffer.cs / WaveOutBuffer.cs
internal static void WaveInProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
if (uMsg == WaveNative.MM_WIM_DATA)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveInBuffer buf = (WaveInBuffer)h.Target;
buf.OnCompleted();
}
catch
{
}
}
}
//WaveOutBuffer.cs version
internal static void WaveOutProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
if (uMsg == WaveNative.MM_WOM_DONE)
{
try
{
GCHandle h = (GCHandle)wavhdr.dwUser;
WaveOutBuffer buf = (WaveOutBuffer)h.Target;
buf.OnCompleted();
}
catch
{
}
}
}
WaveInRecorder.cs / WaveOutRecorder
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc);
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveOutBuffer.WaveOutProc);
In the end they are used for a PInvoke call as follows
[DllImport(mmdll)]
public static extern int waveOutOpen(out IntPtr hWaveOut, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
[DllImport(mmdll)]
public static extern int waveInOpen(out IntPtr phwi, int uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, int dwInstance, int dwFlags);
I've consolidated most the other things in the abstract version of WaveBuffer such as the HeaderData, the size of the header, the IntPtr data, a GCHandle HeaderHandle, and HeaderDataHandle. A WaitFor command a OnCompleteCommand, a bool Busy, and a AutoResetEvent. Not sure what it is used for but it's used for, but it is used the same in each file so i just moved it over. Thank you for your patience and reading through this thread.
EDIT
sorry for the confusion, i got so wrapped up in finding all this stuff I forgot to ask what i was meaning to ask. Basically teh question is How can I combine these 2 functions that do nearly EXACTLY the same thing? How does this Delegate work from WaveNative such that i can make a new instance in WaveInBuffer/WaveOutBuffer and it means the same thing. I just assumed that I had to always either call the one from the other or just call the base class one. As for m_BufferedProc I'll post the entire code since it is hard for me to understand. Mind you this code I'm posting is not my own. Here it is
public class WaveInRecorder : IDisposable
{
private IntPtr m_WaveIn;
private WaveInBuffer m_Buffers; // linked list
private WaveInBuffer m_CurrentBuffer;
private Thread m_Thread;
private BufferDoneEventHandler m_DoneProc;
private bool m_Finished;
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveInProc);
public static int DeviceCount
{
get { return WaveNative.waveInGetNumDevs(); }
}
public WaveInRecorder(int device, WaveFormat format, int bufferSize, int bufferCount, BufferDoneEventHandler doneProc)
{
m_DoneProc = doneProc;
WaveInHelper.Try(WaveNative.waveInOpen(out m_WaveIn, device, format, m_BufferProc, 0, WaveNative.CALLBACK_FUNCTION));
AllocateBuffers(bufferSize, bufferCount);
for (int i = 0; i < bufferCount; i++)
{
SelectNextBuffer();
m_CurrentBuffer.Record();
}
WaveInHelper.Try(WaveNative.waveInStart(m_WaveIn));
m_Thread = new Thread(new ThreadStart(ThreadProc));
m_Thread.Start();
}
~WaveInRecorder()
{
Dispose();
}
public void Dispose()
{
if (m_Thread != null)
try
{
m_Finished = true;
if (m_WaveIn != IntPtr.Zero)
WaveNative.waveInReset(m_WaveIn);
WaitForAllBuffers();
m_Thread.Join();
m_DoneProc = null;
FreeBuffers();
if (m_WaveIn != IntPtr.Zero)
WaveNative.waveInClose(m_WaveIn);
}
finally
{
m_Thread = null;
m_WaveIn = IntPtr.Zero;
}
GC.SuppressFinalize(this);
}
private void ThreadProc()
{
while (!m_Finished)
{
Advance();
if (m_DoneProc != null && !m_Finished)
m_DoneProc(m_CurrentBuffer.Data, m_CurrentBuffer.Size);
m_CurrentBuffer.Record();
}
}
private void AllocateBuffers(int bufferSize, int bufferCount)
{
FreeBuffers();
if (bufferCount > 0)
{
m_Buffers = new WaveInBuffer(m_WaveIn, bufferSize);
WaveInBuffer Prev = m_Buffers;
try
{
for (int i = 1; i < bufferCount; i++)
{
WaveInBuffer Buf = new WaveInBuffer(m_WaveIn, bufferSize);
Prev.NextBuffer = Buf;
Prev = Buf;
}
}
finally
{
Prev.NextBuffer = m_Buffers;
}
}
}
private void FreeBuffers()
{
m_CurrentBuffer = null;
if (m_Buffers != null)
{
WaveInBuffer First = m_Buffers;
m_Buffers = null;
WaveInBuffer Current = First;
do
{
WaveInBuffer Next = Current.NextBuffer;
Current.Dispose();
Current = Next;
} while(Current != First);
}
}
private void Advance()
{
SelectNextBuffer();
m_CurrentBuffer.WaitFor();
}
private void SelectNextBuffer()
{
m_CurrentBuffer = m_CurrentBuffer == null ? m_Buffers : m_CurrentBuffer.NextBuffer;
}
private void WaitForAllBuffers()
{
WaveInBuffer Buf = m_Buffers;
while (Buf.NextBuffer != m_Buffers)
{
Buf.WaitFor();
Buf = Buf.NextBuffer;
}
}
Mind you that that code is not mine but rather it is Ianier Munoz's. I have a similar version I'm working on, you can browse the code that i extracted from it at http://code.google.com/p/adli/source/browse/#svn%2Ftrunk%2FAspects%2FCustom%2FAudio
how I use it (not fully implemented is at here)
http://code.google.com/p/adli/source/browse/trunk/Aspects/Panels/Main%20Panels/AudioBrightnessPanel.cs
look at the action listener for my microphone test.
again sorry for the confusion i will try not to post a question without a question again.
well you've got mutually exclusive if blocks which means its pretty easy to merge the 2 together... (i removed the empty catch block because its evil)
internal static void WaveProc(IntPtr hdrvr, int uMsg, int dwUser, ref WaveNative.WaveHdr wavhdr, int dwParam2)
{
GCHandle h = (GCHandle)wavhdr.dwUser;
if (uMsg == WaveNative.MM_WIM_DATA)
{
WaveInBuffer buf = (WaveInBuffer)h.Target;
buf.OnCompleted();
}
if (uMsg == WaveNative.MM_WOM_DONE)
{
WaveOutBuffer buf = (WaveOutBuffer)h.Target;
buf.OnCompleted();
}
}
private WaveNative.WaveDelegate m_BufferProc = new WaveNative.WaveDelegate(WaveInBuffer.WaveProc);