// -----------------------------------------
// SoundScribe (TM) and related software.
//
// Copyright (C) 2007-2011 Vannatech
// http://www.vannatech.com
// All rights reserved.
//
// This source code is subject to the MIT License.
// http://www.opensource.org/licenses/mit-license.php
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// -----------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Vannatech.CoreAudio.Constants;
using Vannatech.CoreAudio.Enumerations;
using Vannatech.CoreAudio.Externals;
using Vannatech.CoreAudio.Interfaces;
namespace CoreAudioTests.Common
{
///
/// Provides common methods and classes for testing CoreAudio API.
///
internal class TestUtilities
{
///
/// Contains various HRESULT codes of interest for testing.
///
public static class HRESULTS
{
///
/// HRESULT returned for element not found error.
///
public const UInt32 E_NOTFOUND = 0x80070490;
///
/// HRESULT returned when an object does not support the specified interface.
///
public const UInt32 E_NOINTERFACE = 0x80004002;
///
/// HRESULT returned when an arguemnt is invalid for the object's current state.
///
public const UInt32 E_INVALIDARG = 0x80070057;
}
///
/// Checks to see if an HRESULT code is a WASAPI error code.
///
/// The HRESULT code.
public static bool IsWasapiError(int hResult)
{
return ((((uint)hResult) > 0x88890000) && (((uint)hResult) < 0x88890018));
}
///
/// Creates a usable IMMDevice interface or the specified direction, using the IMMDeviceEnumerator.GetDefaultAudioEndpoint method.
///
public static IMMDevice CreateIMMDevice(EDataFlow direction)
{
var deviceEnumerator = CreateIMMDeviceEnumerator();
IMMDevice deviceOut;
deviceEnumerator.GetDefaultAudioEndpoint(direction, ERole.eMultimedia, out deviceOut);
return deviceOut;
}
///
/// Creates a usable collection of the IMMDevice interfaces.
///
public static IEnumerable CreateIMMDeviceCollection()
{
return CreateIMMDeviceCollection(EDataFlow.eAll, DEVICE_STATE_XXX.DEVICE_STATEMASK_ALL);
}
///
/// Creates the specified collection of the IMMDevice interfaces.
///
/// The data flow direction.
/// The state masks.
public static IEnumerable CreateIMMDeviceCollection(EDataFlow direction, UInt32 stateMasks)
{
var deviceEnumerator = CreateIMMDeviceEnumerator();
IMMDeviceCollection deviceCollection;
deviceEnumerator.EnumAudioEndpoints(direction, stateMasks, out deviceCollection);
UInt32 deviceCount;
deviceCollection.GetCount(out deviceCount);
var deviceList = new List();
for (uint i = 0; i < deviceCount; i++)
{
IMMDevice device;
deviceCollection.Item(i, out device);
deviceList.Add(device);
}
if (!deviceList.Any()) Assert.Inconclusive("The test cannot be run properly. No devices were found.");
return deviceList;
}
///
/// Creats a usable IMMDeviceEnumerator interface.
///
public static IMMDeviceEnumerator CreateIMMDeviceEnumerator()
{
var deviceEnumeratorType = Type.GetTypeFromCLSID(new Guid(ComCLSIDs.MMDeviceEnumeratorCLSID));
return (IMMDeviceEnumerator)Activator.CreateInstance(deviceEnumeratorType);
}
///
/// Creates a collection of activated MMDevices for the specified interface.
///
/// The type of the interface.
/// The COM interface ID.
/// A collection of DeviceActivation objects using the specified interface.
public static IEnumerable> CreateDeviceActivationCollection(string comIId)
{
object objInstance;
var iid = new Guid(comIId);
var activationList = new List>();
var activeDevices = CreateIMMDeviceCollection(EDataFlow.eAll, DEVICE_STATE_XXX.DEVICE_STATE_ACTIVE);
foreach (var device in activeDevices)
{
var result = device.Activate(iid, (uint)CLSCTX.CLSCTX_INPROC_SERVER, IntPtr.Zero, out objInstance);
AssertCoreAudio.IsHResultOk(result);
if (objInstance != null)
{
activationList.Add(new DeviceActivation
{
MMDevice = device,
ActiveInterface = (T)objInstance
});
}
}
if (!activationList.Any()) Assert.Inconclusive("The test cannot be run properly. No interface instances were found for the specified type.");
return activationList;
}
///
/// Creates a collection of the given interface via the IMMDevice Activate method.
///
/// The type of the interface.
/// The COM interface ID.
/// A collection of objects implementing the specified interface.
public static IEnumerable ActivateFromIMMDevice(string comIId)
{
return CreateDeviceActivationCollection(comIId).Select(mmd => mmd.ActiveInterface);
}
///
/// Recursively finds all outgoing parts.
///
/// The first part.
///
public static List FindParts(IPart part)
{
List allParts = new List();
allParts.Add(part);
PartType pType;
part.GetPartType(out pType);
if (pType == PartType.Connector)
{
var connector = (IConnector)part;
// If the connector is not connected return.
bool isConnected;
connector.IsConnected(out isConnected);
if (!isConnected) return allParts;
// Otherwise, get the other connector.
IConnector connectedTo;
connector.GetConnectedTo(out connectedTo);
IPart connectedPart = (IPart)connectedTo;
// Then enumerate all outgoing parts.
IPartsList partsOutgoing;
connectedPart.EnumPartsOutgoing(out partsOutgoing);
if (partsOutgoing == null) return allParts;
// If there are more outgoing parts, get each one.
UInt32 partCount;
partsOutgoing.GetCount(out partCount);
for (uint i = 0; i < partCount; i++)
{
IPart nextPart;
partsOutgoing.GetPart(i, out nextPart);
allParts.AddRange(FindParts(nextPart));
}
}
else if (pType == PartType.Subunit)
{
// Get sub type, for debugging purposes.
Guid subType;
part.GetSubType(out subType);
// Just enumerate all outgoing parts.
IPartsList partsOutgoing;
part.EnumPartsOutgoing(out partsOutgoing);
if (partsOutgoing == null) return allParts;
// If there are more outgoing parts, get each one.
UInt32 partCount;
partsOutgoing.GetCount(out partCount);
for (uint i = 0; i < partCount; i++)
{
IPart nextPart;
partsOutgoing.GetPart(i, out nextPart);
allParts.AddRange(FindParts(nextPart));
}
}
return allParts;
}
///
/// Creates a collection of IPart instances, obtaining all parts that can be found on the system.
///
///
public static IEnumerable CreateIPartCollection()
{
var partList = new List();
var topologies = ActivateFromIMMDevice(ComIIDs.IDeviceTopologyIID);
foreach (var top in topologies)
{
IConnector connector;
top.GetConnector(0, out connector);
partList.AddRange(FindParts((IPart)connector));
}
if (!partList.Any()) Assert.Inconclusive("The test cannot be run properly. No part interfaces were found.");
return partList.Distinct(new PartComparer());
}
///
/// Creates a collection of activated Parts for the specified interface.
///
/// The type of the interface.
/// The COM interface ID.
/// A collection of PartActivation objects using the specified interface.
public static IEnumerable> CreatePartActivationCollection(string comIId)
{
object objInstance;
var iid = new Guid(comIId);
var activationList = new List>();
var partList = CreateIPartCollection();
foreach (var part in partList)
{
var result = part.Activate((uint)CLSCTX.CLSCTX_INPROC_SERVER, ref iid, out objInstance);
if ((uint)result == HRESULTS.E_NOINTERFACE) continue;
AssertCoreAudio.IsHResultOk(result);
activationList.Add(new PartActivation
{
Part = part,
ActiveInterface = (T)objInstance
});
}
if (!activationList.Any()) Assert.Inconclusive("The test may not have run properly. No interface instances were found for the specified type.");
return activationList;
}
///
/// Creates a collection of the given interface via the IPart Activate method.
///
/// The type of the interface.
/// The COM interface ID.
/// A collection of objects implementing the specified interface.
public static IEnumerable ActivateFromIPart(string comIId)
{
return CreatePartActivationCollection(comIId).Select(p => p.ActiveInterface);
}
///
/// Creates a collection of the given interface via the IAudioClient GetService method, in shared mode.
///
/// The type of the interface.
/// The COM interface ID.
/// A value indicating whether or not to use exclusive mode.
/// A collection of audio clients with services implementing the specified interface.
public static IEnumerable> CreateAudioClientServiceCollection(string comIId)
{
return CreateAudioClientServiceCollection(comIId, false, 0);
}
///
/// Creates a collection of the given interface via the IAudioClient GetService method.
///
/// The type of the interface.
/// The COM interface ID.
/// A value indicating whether or not to use exclusive mode.
/// A collection of audio clients with services implementing the specified interface.
public static IEnumerable> CreateAudioClientServiceCollection(string comIId, bool exclusiveMode)
{
return CreateAudioClientServiceCollection(comIId, exclusiveMode, 0);
}
///
/// Creates a collection of the given interface via the IAudioClient GetService method.
///
/// The type of the interface.
/// The COM interface ID.
/// A value indicating whether or not to use exclusive mode.
/// The stream initialization flags.
/// A collection of audio clients with services implementing the specified interface.
public static IEnumerable> CreateAudioClientServiceCollection(string comIId, bool exclusiveMode, UInt32 streamFlags)
{
object objInstance;
var iid = new Guid(comIId);
var interfaceList = new List>();
var audioClients = ActivateFromIMMDevice(ComIIDs.IAudioClientIID);
foreach (var ac in audioClients)
{
var shareMode = exclusiveMode ? AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED;
var formatPtr = GetFormatPointer(ac, shareMode);
if (formatPtr == IntPtr.Zero) continue;
var context = Guid.NewGuid();
var result = ac.Initialize(shareMode, streamFlags, 5000000, 0, formatPtr, context);
if (IsWasapiError(result)) continue;
AssertCoreAudio.IsHResultOk(result);
result = ac.GetService(iid, out objInstance);
if ((uint)result == HRESULTS.E_NOINTERFACE) continue;
if (IsWasapiError(result)) continue;
AssertCoreAudio.IsHResultOk(result);
interfaceList.Add(new AudioClientService
{
AudioClient = ac,
ServiceInterface = (T)objInstance,
EventContext = context,
ShareMode = shareMode
});
}
if (!interfaceList.Any()) Assert.Inconclusive("The test may not have run properly. No interface instances were found for the specified type.");
return interfaceList;
}
///
/// Tries to resolve a valid format pointer for the audio client.
///
/// The audio client to use.
/// The share mode to use.
/// A pointer to a valid format, or zero if one cannot be found.
public static IntPtr GetFormatPointer(IAudioClient audioClient, AUDCLNT_SHAREMODE shareMode)
{
var formatPtr = IntPtr.Zero;
if (shareMode == AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED)
{
audioClient.GetMixFormat(out formatPtr);
}
else
{
// Otherwise we need to find a supported format
foreach(var format in TestWaveFormats)
{
var formatMatch = IntPtr.Zero;
var supported = audioClient.IsFormatSupported(shareMode, format, out formatMatch);
if (supported == 0) { formatPtr = format; break; }
else if (supported == 1) { formatPtr = formatMatch; break; }
}
if (formatPtr == IntPtr.Zero)
Assert.Inconclusive("Unable to find a valid format pointer.");
}
return formatPtr;
}
///
/// Converts a WAVEFORMATEX pointer to a WAVEFORMATEX structure.
///
/// The pointer to the wave format.
/// A new wave format structure.
public static WAVEFORMATEX PointerToWaveFormat(IntPtr formatPtr)
{
WAVEFORMATEX waveFormat = new WAVEFORMATEX();
if (formatPtr == IntPtr.Zero) return waveFormat;
return (WAVEFORMATEX)Marshal.PtrToStructure(formatPtr, typeof(WAVEFORMATEX));
}
///
/// Gets a collection of wave formats commonly used in pro audio.
///
public static IEnumerable TestWaveFormats
{
get
{
var sampleRates = new int[] { 48000, 96000, 44100, 88200 };
// Pro audio 24 bit formats
foreach (var sampleRate in sampleRates)
{
var format = new WAVEFORMATEX
{
wFormatTag = 0xFFFE,
nChannels = 2,
nSamplesPerSec = (uint)sampleRate,
wBitsPerSample = 24,
nBlockAlign = 2 * (24 / 8),
nAvgBytesPerSec = (uint)((2 * (24 / 8)) * sampleRate)
};
var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
samples.wValidBitsPerSample = 24;
var extensible = new WAVEFORMATEXTENSIBLE
{
Format = format,
SubFormat = new Guid("00000001-0000-0010-8000-00aa00389b71"),
Samples = samples,
dwChannelMask = 0x0003
};
var outPtr = IntPtr.Zero;
Marshal.StructureToPtr(extensible, outPtr, true);
yield return outPtr;
}
// IEEE float formats
foreach (var sampleRate in sampleRates)
{
var format = new WAVEFORMATEX
{
wFormatTag = 0xFFFE,
nChannels = 2,
nSamplesPerSec = (uint)sampleRate,
wBitsPerSample = 32,
nBlockAlign = 2 * (32 / 8),
nAvgBytesPerSec = (uint)((2 * (32 / 8)) * sampleRate)
};
var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
samples.wValidBitsPerSample = 32;
var extensible = new WAVEFORMATEXTENSIBLE
{
Format = format,
SubFormat = new Guid("00000003-0000-0010-8000-00aa00389b71"),
Samples = samples,
dwChannelMask = 0x0003
};
var outPtr = IntPtr.Zero;
Marshal.StructureToPtr(extensible, outPtr, true);
yield return outPtr;
}
// Pro audio 16 bit formats
foreach (var sampleRate in sampleRates)
{
var format = new WAVEFORMATEX
{
wFormatTag = 0xFFFE,
nChannels = 2,
nSamplesPerSec = (uint)sampleRate,
wBitsPerSample = 16,
nBlockAlign = 2 * (16 / 8),
nAvgBytesPerSec = (uint)((2 * (16 / 8)) * sampleRate)
};
var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
samples.wValidBitsPerSample = 16;
var extensible = new WAVEFORMATEXTENSIBLE
{
Format = format,
SubFormat = new Guid("00000001-0000-0010-8000-00aa00389b71"),
Samples = samples,
dwChannelMask = 0x0003
};
var outPtr = IntPtr.Zero;
Marshal.StructureToPtr(extensible, outPtr, true);
yield return outPtr;
}
}
}
}
}