修改AudioDeviceCmdlets.dll设置录音设备DeviceFormat后自动恢复默认
问题:设置录音设备DeviceFormat后无法持久化生效
我正在修改一个开源的AudioDeviceCmdlets项目,目标是实现默认录音设备的DeviceFormat设置功能。目前用PowerShell可以正常获取当前DeviceFormat,但调用Set方法设置后,只有立即调用Get时会显示修改生效;打开系统声音设置的「高级」选项卡查看,DeviceFormat已经恢复默认(就像点了「还原默认值」按钮),再调用Get也会返回默认值。
已经用SoundVolumeView工具测试过,它能成功设置并保持DeviceFormat生效,说明不是权限或本地环境问题,功能是可以实现的。
相关代码
结构体定义
[StructLayout(LayoutKind.Sequential, Pack = 1)] public struct WAVEFORMATEX { public ushort wFormatTag; public ushort nChannels; public uint nSamplesPerSec; public uint nAvgBytesPerSec; public ushort nBlockAlign; public ushort wBitsPerSample; public ushort cbSize; }; [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct WAVEFORMATEXTENSIBLE { public WAVEFORMATEX Format; public ushort wValidBitsPerSample; public ushort wSamplesPerBlock; public ushort wReserved; public uint dwChannelMask; public Guid SubFormat; };
属性存储与DeviceFormat属性
private void GetPropertyInformation(EStgmAccess mode = EStgmAccess.STGM_READ) { IPropertyStore propstore; Marshal.ThrowExceptionForHR(_RealDevice.OpenPropertyStore(mode, out propstore)); _PropertyStore = new PropertyStore(propstore); } public object DeviceFormat { get { if (_PropertyStore == null) GetPropertyInformation(); if (_PropertyStore.Contains(PKEY.PKEY_AudioEngine_DeviceFormat)) { GCHandle pinnedArray = GCHandle.Alloc(_PropertyStore[PKEY.PKEY_AudioEngine_DeviceFormat].Value, GCHandleType.Pinned); IntPtr pointer = pinnedArray.AddrOfPinnedObject(); WAVEFORMATEXTENSIBLE format = (WAVEFORMATEXTENSIBLE)Marshal.PtrToStructure(pointer, typeof(WAVEFORMATEXTENSIBLE)); pinnedArray.Free(); return string.Format("Channels={0}\r\n" + "SampleRate={1}\r\n" + "BitDepth={2}\r\n" + "Tag={3}\r\n" + "AverageBytesPerSec={4}\r\n" + "BlockAlign={5}\r\n" + "Size={6}\r\n" + "wValidBitsPerSample={7}\r\n" + "wSamplesPerBlock={8}\r\n" + "wReserved={9}\r\n" + "dwChannelMask={10}\r\n" + "SubFormat={11}", format.Format.nChannels, format.Format.nSamplesPerSec, format.Format.wBitsPerSample, format.Format.wFormatTag, format.Format.nAvgBytesPerSec, format.Format.nBlockAlign, format.Format.cbSize, format.wValidBitsPerSample, format.wSamplesPerBlock, format.wReserved, format.dwChannelMask, format.SubFormat); } else return "Unknown"; } set { if (_PropertyStore == null) GetPropertyInformation(EStgmAccess.STGM_READWRITE); if (_PropertyStore.Contains(PKEY.PKEY_AudioEngine_DeviceFormat)) { GCHandle pinnedArray = GCHandle.Alloc(_PropertyStore[PKEY.PKEY_AudioEngine_DeviceFormat].Value, GCHandleType.Pinned); IntPtr pointer = pinnedArray.AddrOfPinnedObject(); WAVEFORMATEXTENSIBLE format = (WAVEFORMATEXTENSIBLE)Marshal.PtrToStructure(pointer, typeof(WAVEFORMATEXTENSIBLE)); pinnedArray.Free(); string properties = value.ToString(); string[] property = properties.Split((char)0x20); format.Format.nChannels = Convert.ToUInt16(property[0]); format.Format.wBitsPerSample = Convert.ToUInt16(property[1]); format.Format.nSamplesPerSec = Convert.ToUInt32(property[2]); format.wValidBitsPerSample = format.Format.wBitsPerSample; format.Format.nBlockAlign = (ushort)((format.Format.nChannels * format.Format.wBitsPerSample) / 8); format.Format.nAvgBytesPerSec = (format.Format.nSamplesPerSec * format.Format.nBlockAlign); PropVariant prop = new PropVariant(); prop.Value = format; _PropertyStore.SetValue(PKEY.PKEY_AudioEngine_DeviceFormat, prop); _PropertyStore.Commit(); } } }
PropVariant的Value属性Set方法
public object Value { get { VarEnum ve = (VarEnum)vt; switch (ve) { case VarEnum.VT_I1: return bVal; case VarEnum.VT_I2: return iVal; case VarEnum.VT_I4: return lVal; case VarEnum.VT_I8: return hVal; case VarEnum.VT_INT: return iVal; case VarEnum.VT_UI4: return ulVal; case VarEnum.VT_LPWSTR: return Marshal.PtrToStringUni(everything_else); case VarEnum.VT_BLOB: return GetBlob(); } return "FIXME Type = " + ve.ToString(); } set { vt = (short)VarEnum.VT_BLOB; int length = Marshal.SizeOf(value); IntPtr formatPointer = Marshal.AllocHGlobal(length); Marshal.StructureToPtr(value, formatPointer, false); blobVal.Length = length; blobVal.Data = formatPointer; } }
可能的解决方向
- 补全WAVEFORMATEXTENSIBLE参数:当前只修改了部分字段,
dwChannelMask、SubFormat、wSamplesPerBlock等仍使用旧值,系统需要完整匹配的格式参数才能持久化。比如SubFormat要对应音频格式(PCM对应标准GUID),dwChannelMask要和通道数匹配(单声道用SPEAKER_FRONT_LEFT,立体声用SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)。 - 修复PropVariant内存管理:设置
Value时用Marshal.AllocHGlobal分配的内存未释放,可能导致数据异常。需确保PropVariant的blob数据正确复制,而非直接传递指针,避免内存泄漏或属性存储读取失败。 - 调整属性存储打开模式:打开属性存储时,除
STGM_READWRITE外,可尝试添加STGM_SHARE_DENY_WRITE,防止其他进程同时修改属性导致设置被覆盖。 - 补充音频引擎通知逻辑:修改DeviceFormat后,可能需要调用
IAudioClient接口重启音频引擎,或发送设备状态变更通知,确保系统识别到格式修改。 - 验证参数计算正确性:检查
nBlockAlign、nAvgBytesPerSec的计算逻辑,同时确认WAVEFORMATEX.cbSize字段设置正确(对于WAVEFORMATEXTENSIBLE需设为sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX))。
内容的提问来源于stack exchange,提问作者Nolemonpledge
相关产品推荐
相关产品推荐

