ICreateDevEnum devEnum;
IEnumMoniker enumMoniker;
IMoniker[] monikers = new IMoniker[1];
IPropertyBag propBag;
devEnum = (ICreateDevEnum)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_SystemDeviceEnum));
devEnum.CreateClassEnumerator(CLSID_AudioInputDeviceCategory, out enumMoniker, 0);
while (enumMoniker.Next(1, monikers, IntPtr.Zero) == 0) {
object variant;
object propBagObj;
monikers[0].BindToStorage(null, null, ref IPropertyBagGuid, out propBagObj);
propBag = (IPropertyBag)propBagObj;
if (propBag.Read("FriendlyName", out variant, null) == 0)
System.Windows.MessageBox.Show(variant.ToString() + " (" + variant.ToString().Length + ")");
}
Под Windows 10 этот код у меня работает прекрасно и выводит полное имя микрофона (44 символа). На Windows 7 имя выводится не полное, а лишь первые 31 символ.
Подскажите, что может быть причиной такого поведения и как это починить (возможно, аналогичный более универсальный код)?
На устройствах, сразу скажу, стоят разные версии DirectX (12 (где всё работает) и 11).
Ответ
Попробуйте сделать это через Multimedia API. У меня нормально выводит FriendlyName больше 40 символов.
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.IO;
using System.Runtime.CompilerServices;
namespace com_test
{
/*Объявления COM-интерфейсов*/
class Native {
public static PROPERTYKEY PKEY_Device_FriendlyName = new PROPERTYKEY(0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
public static PROPERTYKEY PKEY_AudioEndpoint_FormFactor =
new PROPERTYKEY(0x1da5d803, 0xd492, 0x4edd, 0x8c, 0x23, 0xe0, 0xc0, 0xff, 0xee, 0x7f, 0x0e, 0);
}
//http://www.java2s.com/Code/CSharp/Windows/SoundUtils.htm
[Guid("0BD7A1BE-7A1A-44DB-8397-CC5392387B5E"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDeviceCollection
{
//HRESULT GetCount([out, annotation("__out")] UINT* pcDevices);
int GetCount(ref uint pcDevices);
//HRESULT Item([in, annotation("__in")]UINT nDevice, [out, annotation("__out")] IMMDevice** ppDevice);
int Item(uint nDevice, [Out, MarshalAs(UnmanagedType.Interface)] out object ppDevice);
}
[Guid("D666063F-1587-4E43-81F1-B948E807363F"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDevice
{
//HRESULT Activate([in, annotation("__in")] REFIID iid, [in, annotation("__in")] DWORD dwClsCtx, [in,unique, annotation("__in_opt")] PROPVARIANT* pActivationParams, [out,iid_is(iid), annotation("__out")] void** ppInterface);
int Activate(ref Guid iid, uint dwClsCtx, IntPtr pActivationParams, [Out, MarshalAs(UnmanagedType.Interface)] out object ppInterface);
//HRESULT OpenPropertyStore([in, annotation("__in")] DWORD stgmAccess, [out, annotation("__out")] IPropertyStore** ppProperties);
int OpenPropertyStore(int stgmAccess, [Out, MarshalAs(UnmanagedType.Interface)] out object ppProperties);
//HRESULT GetId([out,annotation("__deref_out")] LPWSTR* ppstrId);
int GetId(ref StringBuilder ppstrId);
//HRESULT GetState([out, annotation("__out")] DWORD* pdwState);
int GetState(ref int pdwState);
}
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
class MMDeviceEnumerator
{
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"),
//[Guid("BCDE0395-E52F-467C-8E3D-C4579291692E"),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IMMDeviceEnumerator
{
//HRESULT EnumAudioEndpoints([in, annotation("__in")] EDataFlow dataFlow, [in, annotation("__in")] DWORD dwStateMask, [out, annotation("__out")] IMMDeviceCollection** ppDevices);
int EnumAudioEndpoints(EDataFlow dataFlow, int dwStateMask, [Out, MarshalAs(UnmanagedType.Interface)] out object ppDevices);
//HRESULT GetDefaultAudioEndpoint([in, annotation("__in")] EDataFlow dataFlow, [in, annotation("__in")] ERole role, [out, annotation("__out")] IMMDevice** ppEndpoint);
int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, [Out, MarshalAs(UnmanagedType.Interface)] out object ppEndpoint);
//HRESULT GetDevice([, annotation("__in")]LPCWSTR pwstrId, [out, annotation("__out")] IMMDevice** ppDevice);
int GetDevice(string pwstrId, ref IntPtr ppDevice);
//HRESULT RegisterEndpointNotificationCallback([in, annotation("__in")] IMMNotificationClient* pClient);
int RegisterEndpointNotificationCallback(IntPtr pClient);
//HRESULT UnregisterEndpointNotificationCallback([in, annotation("__in")] IMMNotificationClient* pClient);
int UnregisterEndpointNotificationCallback(IntPtr pClient);
}
public enum EDataFlow
{
eRender,
eCapture,
eAll,
EDataFlow_enum_count
}
public enum ERole
{
eConsole,
eMultimedia,
eCommunications,
ERole_enum_count
}
//*********** Property store *****************************
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IPropertyStore
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetCount([Out] out uint cProps);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void GetAt([In] uint iProp, out PROPERTYKEY pkey);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
int GetValue([In] ref PROPERTYKEY key, out PropVariant pv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetValue([In] ref PROPERTYKEY key, [In] ref object pv);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void Commit();
}
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct PROPERTYKEY
{
public Guid fmtid;
public uint pid;
public PROPERTYKEY(Guid guid, int propertyId)
{
this.fmtid = guid;
this.pid = (uint)propertyId;
}
public PROPERTYKEY(string formatId, int propertyId)
: this(new Guid(formatId), propertyId)
{
}
public PROPERTYKEY(uint a, uint b, uint c, uint d, uint e, uint f, uint g, uint h, uint i, uint j, uint k, int propertyId)
: this(new Guid((uint)a, (ushort)b, (ushort)c, (byte)d, (byte)e, (byte)f, (byte)g, (byte)h, (byte)i, (byte)j, (byte)k), propertyId)
{
}
}
//https://blogs.msdn.microsoft.com/adamroot/2008/04/11/interop-with-propvariants-in-net/
[StructLayout(LayoutKind.Sequential)]
public struct PropVariant
{
ushort vt;
ushort wReserved1;
ushort wReserved2;
ushort wReserved3;
IntPtr p;
int p2;
private byte[] GetDataBytes()
{
byte[] ret = new byte[IntPtr.Size + sizeof(int)];
if (IntPtr.Size == 4)
BitConverter.GetBytes(p.ToInt32()).CopyTo(ret, 0);
else if (IntPtr.Size == 8)
BitConverter.GetBytes(p.ToInt64()).CopyTo(ret, 0);
BitConverter.GetBytes(p2).CopyTo(ret, IntPtr.Size);
return ret;
}
sbyte cVal // CHAR cVal;
{
get { return (sbyte)GetDataBytes()[0]; }
}
short iVal // SHORT iVal;
{
get { return BitConverter.ToInt16(GetDataBytes(), 0); }
}
int lVal // LONG lVal;
{
get { return BitConverter.ToInt32(GetDataBytes(), 0); }
}
long hVal // LARGE_INTEGER hVal;
{
get { return BitConverter.ToInt64(GetDataBytes(), 0); }
}
float fltVal // FLOAT fltVal;
{
get { return BitConverter.ToSingle(GetDataBytes(), 0); }
}
public object Value
{
get
{
switch ((VarEnum)vt)
{
case VarEnum.VT_I1:
return cVal;
//case VarEnum.VT_UI1:return bVal;
case VarEnum.VT_I2:
return iVal;
//case VarEnum.VT_UI2: return uiVal;
case VarEnum.VT_I4:
case VarEnum.VT_INT:
return lVal;
case VarEnum.VT_UI4:
//case VarEnum.VT_UINT: return ulVal;
case VarEnum.VT_I8:
return hVal;
// case VarEnum.VT_UI8: return uhVal;
case VarEnum.VT_R4:
return fltVal;
/*case VarEnum.VT_R8: return dblVal;
case VarEnum.VT_BOOL: return boolVal;
case VarEnum.VT_ERROR: return scode;
case VarEnum.VT_CY: return cyVal;
case VarEnum.VT_DATE: return date;*/
case VarEnum.VT_FILETIME: return DateTime.FromFileTime(hVal);
case VarEnum.VT_BSTR:
return Marshal.PtrToStringBSTR(p);
case VarEnum.VT_BLOB:
byte[] blobData = new byte[lVal];
IntPtr pBlobData;
if (IntPtr.Size == 4)
{
pBlobData = new IntPtr(p2);
}
else if (IntPtr.Size == 8)
{
// In this case, we need to derive a pointer at offset 12,
// because the size of the blob is represented as a 4-byte int
// but the pointer is immediately after that.
pBlobData = new IntPtr(BitConverter.ToInt64(GetDataBytes(), sizeof(int)));
}
else
throw new NotSupportedException();
Marshal.Copy(pBlobData, blobData, 0, lVal);
return blobData;
case VarEnum.VT_LPSTR:
return Marshal.PtrToStringAnsi(p);
case VarEnum.VT_LPWSTR:
return Marshal.PtrToStringUni(p);
case VarEnum.VT_UNKNOWN:
return Marshal.GetObjectForIUnknown(p);
case VarEnum.VT_DISPATCH:
return p;
default:
throw new NotSupportedException("The type of this variable is not support ('" + vt.ToString() + "')");
}
}
}
}
//*****************************************************
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace com_test
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
MMDeviceEnumerator devenum = new MMDeviceEnumerator();//Create CoClass
IMMDeviceEnumerator deviceEnumerator = (IMMDeviceEnumerator)devenum;
IMMDeviceCollection deviceCollection = null;
IMMDevice dev = null;
IPropertyStore propertyStore=null;
try
{
object o = null;
int hr;
hr = deviceEnumerator.EnumAudioEndpoints(EDataFlow.eCapture, 0x0000000F /*DEVICE_STATEMASK_ALL*/, out o);
deviceCollection = o as IMMDeviceCollection;
uint deviceCount = 0;
hr = deviceCollection.GetCount(ref deviceCount);
textBox1.Text = "";
for (uint i = 0; i < deviceCount; i++)//print all devices
{
hr = deviceCollection.Item(i, out o);
dev = o as IMMDevice;
//get device name
hr = dev.OpenPropertyStore(0/*STGM_READ*/, out o);
propertyStore = o as IPropertyStore;
PropVariant friendlyName=new PropVariant();
hr = propertyStore.GetValue(Native.PKEY_Device_FriendlyName, out friendlyName);
//output data
textBox1.Text += "#" + (i + 1).ToString() + ": " + friendlyName.Value.ToString();
textBox1.Text += Environment.NewLine;
//clean up resources
if (dev != null) { Marshal.ReleaseComObject(dev); dev = null; }
if (propertyStore != null) { Marshal.ReleaseComObject(propertyStore); propertyStore = null; }
}
textBox1.Select(0, 0);
}
finally
{
//clean up resources
if (devenum != null) Marshal.ReleaseComObject(devenum);
if (deviceEnumerator != null) Marshal.ReleaseComObject(deviceEnumerator);
if (deviceCollection != null) Marshal.ReleaseComObject(deviceCollection);
if (dev != null) Marshal.ReleaseComObject(dev);
if (propertyStore != null) Marshal.ReleaseComObject(propertyStore);
}
}
}
}
Дополнение
Для получения WaveInID устройства, нужно получить Endpoint ID всех WaveIn-устройств и сравнить его с результатом device.GetID
class DShow
{
[DllImport("winmm.dll")]
public static extern uint waveInGetNumDevs();
public const uint DRV_RESERVED = 0x0800;
public const uint DRV_QUERYFUNCTIONINSTANCEID = (DRV_RESERVED + 17);
public const uint DRV_QUERYFUNCTIONINSTANCEIDSIZE = (DRV_RESERVED + 18);
public const uint DRV_QUERYDEVICEINTERFACE = (DRV_RESERVED + 12);
public const uint DRV_QUERYDEVICEINTERFACESIZE = (DRV_RESERVED + 13);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "waveInMessage")]
public static extern int waveInMessage1(uint hWaveIn, uint msg,
ref uint dw1, uint dw2);
[DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto, EntryPoint = "waveInMessage")]
public static extern int waveInMessage2(uint hWaveIn, uint msg,
[MarshalAs(UnmanagedType.LPWStr)] StringBuilder dw1, uint dw2);
public static uint GetWaveInID(string endpid)
{
uint num = DShow.waveInGetNumDevs();
StringBuilder sb;
uint size = 0;
for (uint i = 0; i < num; i++)
{
size = 0;
DShow.waveInMessage1(i, DShow.DRV_QUERYFUNCTIONINSTANCEIDSIZE, ref size, 0);
sb = new StringBuilder((int)size + 10);
DShow.waveInMessage2(i, DShow.DRV_QUERYFUNCTIONINSTANCEID, sb, size);
if (sb.ToString() == endpid) return i;
}
throw new EntryPointNotFoundException(endpid+" device was not found!");
}
}
Дополнение 2
Ну и объяснение первоначальной проблемы: обрезка Friendly name в DirectShow вызвана ограничением в Wave Audio API, который он внутренне использует в Windows 7. В структуре WAVEINCAPS поле под имя устройства имеет фиксированный размер в 32 байта. В Windows 10 видимо принцип работы изменили, так что эта структура не задействована. Также следует отметить, что Wave Audio API начиная с висты объявлен устаревшим, и новые приложения должны вместо него использовать соответствующие функции из Multimedia Device API, WASAPI, Windows Media Foundation или DirectShow.
Ссылки по теме:
Windows Multimedia Device API
Описание понятия Endpoint ID
Взаимодействие с Wave Audio API
Комментариев нет:
Отправить комментарий