TestUtilities.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. // -----------------------------------------
  2. // SoundScribe (TM) and related software.
  3. //
  4. // Copyright (C) 2007-2011 Vannatech
  5. // http://www.vannatech.com
  6. // All rights reserved.
  7. //
  8. // This source code is subject to the MIT License.
  9. // http://www.opensource.org/licenses/mit-license.php
  10. //
  11. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  12. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  13. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  14. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  15. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  16. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  17. // THE SOFTWARE.
  18. // -----------------------------------------
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Linq;
  22. using System.Runtime.InteropServices;
  23. using System.Text;
  24. using Microsoft.VisualStudio.TestTools.UnitTesting;
  25. using Vannatech.CoreAudio.Constants;
  26. using Vannatech.CoreAudio.Enumerations;
  27. using Vannatech.CoreAudio.Externals;
  28. using Vannatech.CoreAudio.Interfaces;
  29. namespace CoreAudioTests.Common
  30. {
  31. /// <summary>
  32. /// Provides common methods and classes for testing CoreAudio API.
  33. /// </summary>
  34. internal class TestUtilities
  35. {
  36. /// <summary>
  37. /// Contains various HRESULT codes of interest for testing.
  38. /// </summary>
  39. public static class HRESULTS
  40. {
  41. /// <summary>
  42. /// HRESULT returned for element not found error.
  43. /// </summary>
  44. public const UInt32 E_NOTFOUND = 0x80070490;
  45. /// <summary>
  46. /// HRESULT returned when an object does not support the specified interface.
  47. /// </summary>
  48. public const UInt32 E_NOINTERFACE = 0x80004002;
  49. /// <summary>
  50. /// HRESULT returned when an arguemnt is invalid for the object's current state.
  51. /// </summary>
  52. public const UInt32 E_INVALIDARG = 0x80070057;
  53. }
  54. /// <summary>
  55. /// Checks to see if an HRESULT code is a WASAPI error code.
  56. /// </summary>
  57. /// <param name="hResult">The HRESULT code.</param>
  58. public static bool IsWasapiError(int hResult)
  59. {
  60. return ((((uint)hResult) > 0x88890000) && (((uint)hResult) < 0x88890018));
  61. }
  62. /// <summary>
  63. /// Creates a usable IMMDevice interface or the specified direction, using the IMMDeviceEnumerator.GetDefaultAudioEndpoint method.
  64. /// </summary>
  65. public static IMMDevice CreateIMMDevice(EDataFlow direction)
  66. {
  67. var deviceEnumerator = CreateIMMDeviceEnumerator();
  68. IMMDevice deviceOut;
  69. deviceEnumerator.GetDefaultAudioEndpoint(direction, ERole.eMultimedia, out deviceOut);
  70. return deviceOut;
  71. }
  72. /// <summary>
  73. /// Creates a usable collection of the IMMDevice interfaces.
  74. /// </summary>
  75. public static IEnumerable<IMMDevice> CreateIMMDeviceCollection()
  76. {
  77. return CreateIMMDeviceCollection(EDataFlow.eAll, DEVICE_STATE_XXX.DEVICE_STATEMASK_ALL);
  78. }
  79. /// <summary>
  80. /// Creates the specified collection of the IMMDevice interfaces.
  81. /// </summary>
  82. /// <param name="direction">The data flow direction.</param>
  83. /// <param name="stateMasks">The state masks.</param>
  84. public static IEnumerable<IMMDevice> CreateIMMDeviceCollection(EDataFlow direction, UInt32 stateMasks)
  85. {
  86. var deviceEnumerator = CreateIMMDeviceEnumerator();
  87. IMMDeviceCollection deviceCollection;
  88. deviceEnumerator.EnumAudioEndpoints(direction, stateMasks, out deviceCollection);
  89. UInt32 deviceCount;
  90. deviceCollection.GetCount(out deviceCount);
  91. var deviceList = new List<IMMDevice>();
  92. for (uint i = 0; i < deviceCount; i++)
  93. {
  94. IMMDevice device;
  95. deviceCollection.Item(i, out device);
  96. deviceList.Add(device);
  97. }
  98. if (!deviceList.Any()) Assert.Inconclusive("The test cannot be run properly. No devices were found.");
  99. return deviceList;
  100. }
  101. /// <summary>
  102. /// Creats a usable IMMDeviceEnumerator interface.
  103. /// </summary>
  104. public static IMMDeviceEnumerator CreateIMMDeviceEnumerator()
  105. {
  106. var deviceEnumeratorType = Type.GetTypeFromCLSID(new Guid(ComCLSIDs.MMDeviceEnumeratorCLSID));
  107. return (IMMDeviceEnumerator)Activator.CreateInstance(deviceEnumeratorType);
  108. }
  109. /// <summary>
  110. /// Creates a collection of activated MMDevices for the specified interface.
  111. /// </summary>
  112. /// <typeparam name="T">The type of the interface.</typeparam>
  113. /// <param name="comIId">The COM interface ID.</param>
  114. /// <returns>A collection of DeviceActivation objects using the specified interface.</returns>
  115. public static IEnumerable<DeviceActivation<T>> CreateDeviceActivationCollection<T>(string comIId)
  116. {
  117. object objInstance;
  118. var iid = new Guid(comIId);
  119. var activationList = new List<DeviceActivation<T>>();
  120. var activeDevices = CreateIMMDeviceCollection(EDataFlow.eAll, DEVICE_STATE_XXX.DEVICE_STATE_ACTIVE);
  121. foreach (var device in activeDevices)
  122. {
  123. var result = device.Activate(iid, (uint)CLSCTX.CLSCTX_INPROC_SERVER, IntPtr.Zero, out objInstance);
  124. AssertCoreAudio.IsHResultOk(result);
  125. if (objInstance != null)
  126. {
  127. activationList.Add(new DeviceActivation<T>
  128. {
  129. MMDevice = device,
  130. ActiveInterface = (T)objInstance
  131. });
  132. }
  133. }
  134. if (!activationList.Any()) Assert.Inconclusive("The test cannot be run properly. No interface instances were found for the specified type.");
  135. return activationList;
  136. }
  137. /// <summary>
  138. /// Creates a collection of the given interface via the IMMDevice Activate method.
  139. /// </summary>
  140. /// <typeparam name="T">The type of the interface.</typeparam>
  141. /// <param name="comIId">The COM interface ID.</param>
  142. /// <returns>A collection of objects implementing the specified interface.</returns>
  143. public static IEnumerable<T> ActivateFromIMMDevice<T>(string comIId)
  144. {
  145. return CreateDeviceActivationCollection<T>(comIId).Select(mmd => mmd.ActiveInterface);
  146. }
  147. /// <summary>
  148. /// Recursively finds all outgoing parts.
  149. /// </summary>
  150. /// <param name="part">The first part.</param>
  151. /// <returns></returns>
  152. public static List<IPart> FindParts(IPart part)
  153. {
  154. List<IPart> allParts = new List<IPart>();
  155. allParts.Add(part);
  156. PartType pType;
  157. part.GetPartType(out pType);
  158. if (pType == PartType.Connector)
  159. {
  160. var connector = (IConnector)part;
  161. // If the connector is not connected return.
  162. bool isConnected;
  163. connector.IsConnected(out isConnected);
  164. if (!isConnected) return allParts;
  165. // Otherwise, get the other connector.
  166. IConnector connectedTo;
  167. connector.GetConnectedTo(out connectedTo);
  168. IPart connectedPart = (IPart)connectedTo;
  169. // Then enumerate all outgoing parts.
  170. IPartsList partsOutgoing;
  171. connectedPart.EnumPartsOutgoing(out partsOutgoing);
  172. if (partsOutgoing == null) return allParts;
  173. // If there are more outgoing parts, get each one.
  174. UInt32 partCount;
  175. partsOutgoing.GetCount(out partCount);
  176. for (uint i = 0; i < partCount; i++)
  177. {
  178. IPart nextPart;
  179. partsOutgoing.GetPart(i, out nextPart);
  180. allParts.AddRange(FindParts(nextPart));
  181. }
  182. }
  183. else if (pType == PartType.Subunit)
  184. {
  185. // Get sub type, for debugging purposes.
  186. Guid subType;
  187. part.GetSubType(out subType);
  188. // Just enumerate all outgoing parts.
  189. IPartsList partsOutgoing;
  190. part.EnumPartsOutgoing(out partsOutgoing);
  191. if (partsOutgoing == null) return allParts;
  192. // If there are more outgoing parts, get each one.
  193. UInt32 partCount;
  194. partsOutgoing.GetCount(out partCount);
  195. for (uint i = 0; i < partCount; i++)
  196. {
  197. IPart nextPart;
  198. partsOutgoing.GetPart(i, out nextPart);
  199. allParts.AddRange(FindParts(nextPart));
  200. }
  201. }
  202. return allParts;
  203. }
  204. /// <summary>
  205. /// Creates a collection of IPart instances, obtaining all parts that can be found on the system.
  206. /// </summary>
  207. /// <returns></returns>
  208. public static IEnumerable<IPart> CreateIPartCollection()
  209. {
  210. var partList = new List<IPart>();
  211. var topologies = ActivateFromIMMDevice<IDeviceTopology>(ComIIDs.IDeviceTopologyIID);
  212. foreach (var top in topologies)
  213. {
  214. IConnector connector;
  215. top.GetConnector(0, out connector);
  216. partList.AddRange(FindParts((IPart)connector));
  217. }
  218. if (!partList.Any()) Assert.Inconclusive("The test cannot be run properly. No part interfaces were found.");
  219. return partList.Distinct(new PartComparer());
  220. }
  221. /// <summary>
  222. /// Creates a collection of activated Parts for the specified interface.
  223. /// </summary>
  224. /// <typeparam name="T">The type of the interface.</typeparam>
  225. /// <param name="comIId">The COM interface ID.</param>
  226. /// <returns>A collection of PartActivation objects using the specified interface.</returns>
  227. public static IEnumerable<PartActivation<T>> CreatePartActivationCollection<T>(string comIId)
  228. {
  229. object objInstance;
  230. var iid = new Guid(comIId);
  231. var activationList = new List<PartActivation<T>>();
  232. var partList = CreateIPartCollection();
  233. foreach (var part in partList)
  234. {
  235. var result = part.Activate((uint)CLSCTX.CLSCTX_INPROC_SERVER, ref iid, out objInstance);
  236. if ((uint)result == HRESULTS.E_NOINTERFACE) continue;
  237. AssertCoreAudio.IsHResultOk(result);
  238. activationList.Add(new PartActivation<T>
  239. {
  240. Part = part,
  241. ActiveInterface = (T)objInstance
  242. });
  243. }
  244. if (!activationList.Any()) Assert.Inconclusive("The test may not have run properly. No interface instances were found for the specified type.");
  245. return activationList;
  246. }
  247. /// <summary>
  248. /// Creates a collection of the given interface via the IPart Activate method.
  249. /// </summary>
  250. /// <typeparam name="T">The type of the interface.</typeparam>
  251. /// <param name="comIId">The COM interface ID.</param>
  252. /// <returns>A collection of objects implementing the specified interface.</returns>
  253. public static IEnumerable<T> ActivateFromIPart<T>(string comIId)
  254. {
  255. return CreatePartActivationCollection<T>(comIId).Select(p => p.ActiveInterface);
  256. }
  257. /// <summary>
  258. /// Creates a collection of the given interface via the IAudioClient GetService method, in shared mode.
  259. /// </summary>
  260. /// <typeparam name="T">The type of the interface.</typeparam>
  261. /// <param name="comIId">The COM interface ID.</param>
  262. /// <param name="exclusiveMode">A value indicating whether or not to use exclusive mode.</param>
  263. /// <returns>A collection of audio clients with services implementing the specified interface.</returns>
  264. public static IEnumerable<AudioClientService<T>> CreateAudioClientServiceCollection<T>(string comIId)
  265. {
  266. return CreateAudioClientServiceCollection<T>(comIId, false, 0);
  267. }
  268. /// <summary>
  269. /// Creates a collection of the given interface via the IAudioClient GetService method.
  270. /// </summary>
  271. /// <typeparam name="T">The type of the interface.</typeparam>
  272. /// <param name="comIId">The COM interface ID.</param>
  273. /// <param name="exclusiveMode">A value indicating whether or not to use exclusive mode.</param>
  274. /// <returns>A collection of audio clients with services implementing the specified interface.</returns>
  275. public static IEnumerable<AudioClientService<T>> CreateAudioClientServiceCollection<T>(string comIId, bool exclusiveMode)
  276. {
  277. return CreateAudioClientServiceCollection<T>(comIId, exclusiveMode, 0);
  278. }
  279. /// <summary>
  280. /// Creates a collection of the given interface via the IAudioClient GetService method.
  281. /// </summary>
  282. /// <typeparam name="T">The type of the interface.</typeparam>
  283. /// <param name="comIId">The COM interface ID.</param>
  284. /// <param name="exclusiveMode">A value indicating whether or not to use exclusive mode.</param>
  285. /// <param name="streamFlags">The stream initialization flags.</param>
  286. /// <returns>A collection of audio clients with services implementing the specified interface.</returns>
  287. public static IEnumerable<AudioClientService<T>> CreateAudioClientServiceCollection<T>(string comIId, bool exclusiveMode, UInt32 streamFlags)
  288. {
  289. object objInstance;
  290. var iid = new Guid(comIId);
  291. var interfaceList = new List<AudioClientService<T>>();
  292. var audioClients = ActivateFromIMMDevice<IAudioClient>(ComIIDs.IAudioClientIID);
  293. foreach (var ac in audioClients)
  294. {
  295. var shareMode = exclusiveMode ? AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED;
  296. var formatPtr = GetFormatPointer(ac, shareMode);
  297. if (formatPtr == IntPtr.Zero) continue;
  298. var context = Guid.NewGuid();
  299. var result = ac.Initialize(shareMode, streamFlags, 5000000, 0, formatPtr, context);
  300. if (IsWasapiError(result)) continue;
  301. AssertCoreAudio.IsHResultOk(result);
  302. result = ac.GetService(iid, out objInstance);
  303. if ((uint)result == HRESULTS.E_NOINTERFACE) continue;
  304. if (IsWasapiError(result)) continue;
  305. AssertCoreAudio.IsHResultOk(result);
  306. interfaceList.Add(new AudioClientService<T>
  307. {
  308. AudioClient = ac,
  309. ServiceInterface = (T)objInstance,
  310. EventContext = context,
  311. ShareMode = shareMode
  312. });
  313. }
  314. if (!interfaceList.Any()) Assert.Inconclusive("The test may not have run properly. No interface instances were found for the specified type.");
  315. return interfaceList;
  316. }
  317. /// <summary>
  318. /// Tries to resolve a valid format pointer for the audio client.
  319. /// </summary>
  320. /// <param name="audioClient">The audio client to use.</param>
  321. /// <param name="shareMode">The share mode to use.</param>
  322. /// <returns>A pointer to a valid format, or zero if one cannot be found.</returns>
  323. public static IntPtr GetFormatPointer(IAudioClient audioClient, AUDCLNT_SHAREMODE shareMode)
  324. {
  325. var formatPtr = IntPtr.Zero;
  326. if (shareMode == AUDCLNT_SHAREMODE.AUDCLNT_SHAREMODE_SHARED)
  327. {
  328. audioClient.GetMixFormat(out formatPtr);
  329. }
  330. else
  331. {
  332. // Otherwise we need to find a supported format
  333. foreach(var format in TestWaveFormats)
  334. {
  335. var formatMatch = IntPtr.Zero;
  336. var supported = audioClient.IsFormatSupported(shareMode, format, out formatMatch);
  337. if (supported == 0) { formatPtr = format; break; }
  338. else if (supported == 1) { formatPtr = formatMatch; break; }
  339. }
  340. if (formatPtr == IntPtr.Zero)
  341. Assert.Inconclusive("Unable to find a valid format pointer.");
  342. }
  343. return formatPtr;
  344. }
  345. /// <summary>
  346. /// Converts a WAVEFORMATEX pointer to a WAVEFORMATEX structure.
  347. /// </summary>
  348. /// <param name="formatPtr">The pointer to the wave format.</param>
  349. /// <returns>A new wave format structure.</returns>
  350. public static WAVEFORMATEX PointerToWaveFormat(IntPtr formatPtr)
  351. {
  352. WAVEFORMATEX waveFormat = new WAVEFORMATEX();
  353. if (formatPtr == IntPtr.Zero) return waveFormat;
  354. return (WAVEFORMATEX)Marshal.PtrToStructure(formatPtr, typeof(WAVEFORMATEX));
  355. }
  356. /// <summary>
  357. /// Gets a collection of wave formats commonly used in pro audio.
  358. /// </summary>
  359. public static IEnumerable<IntPtr> TestWaveFormats
  360. {
  361. get
  362. {
  363. var sampleRates = new int[] { 48000, 96000, 44100, 88200 };
  364. // Pro audio 24 bit formats
  365. foreach (var sampleRate in sampleRates)
  366. {
  367. var format = new WAVEFORMATEX
  368. {
  369. wFormatTag = 0xFFFE,
  370. nChannels = 2,
  371. nSamplesPerSec = (uint)sampleRate,
  372. wBitsPerSample = 24,
  373. nBlockAlign = 2 * (24 / 8),
  374. nAvgBytesPerSec = (uint)((2 * (24 / 8)) * sampleRate)
  375. };
  376. var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
  377. samples.wValidBitsPerSample = 24;
  378. var extensible = new WAVEFORMATEXTENSIBLE
  379. {
  380. Format = format,
  381. SubFormat = new Guid("00000001-0000-0010-8000-00aa00389b71"),
  382. Samples = samples,
  383. dwChannelMask = 0x0003
  384. };
  385. var outPtr = IntPtr.Zero;
  386. Marshal.StructureToPtr(extensible, outPtr, true);
  387. yield return outPtr;
  388. }
  389. // IEEE float formats
  390. foreach (var sampleRate in sampleRates)
  391. {
  392. var format = new WAVEFORMATEX
  393. {
  394. wFormatTag = 0xFFFE,
  395. nChannels = 2,
  396. nSamplesPerSec = (uint)sampleRate,
  397. wBitsPerSample = 32,
  398. nBlockAlign = 2 * (32 / 8),
  399. nAvgBytesPerSec = (uint)((2 * (32 / 8)) * sampleRate)
  400. };
  401. var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
  402. samples.wValidBitsPerSample = 32;
  403. var extensible = new WAVEFORMATEXTENSIBLE
  404. {
  405. Format = format,
  406. SubFormat = new Guid("00000003-0000-0010-8000-00aa00389b71"),
  407. Samples = samples,
  408. dwChannelMask = 0x0003
  409. };
  410. var outPtr = IntPtr.Zero;
  411. Marshal.StructureToPtr(extensible, outPtr, true);
  412. yield return outPtr;
  413. }
  414. // Pro audio 16 bit formats
  415. foreach (var sampleRate in sampleRates)
  416. {
  417. var format = new WAVEFORMATEX
  418. {
  419. wFormatTag = 0xFFFE,
  420. nChannels = 2,
  421. nSamplesPerSec = (uint)sampleRate,
  422. wBitsPerSample = 16,
  423. nBlockAlign = 2 * (16 / 8),
  424. nAvgBytesPerSec = (uint)((2 * (16 / 8)) * sampleRate)
  425. };
  426. var samples = new WAVEFORMATEXTENSIBLE.SamplesUnion();
  427. samples.wValidBitsPerSample = 16;
  428. var extensible = new WAVEFORMATEXTENSIBLE
  429. {
  430. Format = format,
  431. SubFormat = new Guid("00000001-0000-0010-8000-00aa00389b71"),
  432. Samples = samples,
  433. dwChannelMask = 0x0003
  434. };
  435. var outPtr = IntPtr.Zero;
  436. Marshal.StructureToPtr(extensible, outPtr, true);
  437. yield return outPtr;
  438. }
  439. }
  440. }
  441. }
  442. }