You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 21:14:56