从输入到扬声器的音频硬件传递 - 不是用软件完成的

Mic*_*kus 7 c# hardware audio winforms

几年前,我为我的公司编写了一个自定义应用程序,该应用程序只运行一台特定型号的计算机.应用程序必须能够将麦克风插孔中的音频传递到扬声器.我没有处理进入插孔并通过软件传递给扬声器的字节,而是说明了我知道特定的硬件来编写一个功能,该功能使声卡的内置功能能够将音频从输入循环到扬声器.这是该函数(它是用C语言编写的,仅使用mmsystem.dll):

int setMasterLevelsFromMicrophone (int volume, int mute)
{
    MMRESULT error;

    // Open the mixer
    HMIXER mixerHandle;
    if (error = mixerOpen (&mixerHandle, 0, 0, 0, 0))
        return 1;

    // Get the microphone source information
    MIXERLINE mixerline;
    mixerline.cbStruct = sizeof(MIXERLINE);
    mixerline.dwDestination = 0;
    if ((error = mixerGetLineInfo((HMIXEROBJ)mixerHandle, &mixerline, MIXER_GETLINEINFOF_DESTINATION)))
        return 2;

    // Get the microhone source controls
    MIXERCONTROL mixerControlArray[2];
    MIXERLINECONTROLS mixerLineControls;
    mixerLineControls.cbStruct = sizeof(MIXERLINECONTROLS);
    mixerLineControls.cControls = 2;
    mixerLineControls.dwLineID = mixerline.dwLineID;
    mixerLineControls.pamxctrl = &mixerControlArray[0];
    mixerLineControls.cbmxctrl = sizeof(MIXERCONTROL);

    if ((error = mixerGetLineControls((HMIXEROBJ)mixerHandle, &mixerLineControls, MIXER_GETLINECONTROLSF_ALL)))
        return 3;

    // Set the microphone source volume
    MIXERCONTROLDETAILS_UNSIGNED value;
    MIXERCONTROLDETAILS mixerControlDetails;
    mixerControlDetails.cbStruct = sizeof(MIXERCONTROLDETAILS);
    mixerControlDetails.dwControlID = mixerControlArray[0].dwControlID;
    mixerControlDetails.cChannels = 1;
    mixerControlDetails.cMultipleItems = 0;
    mixerControlDetails.paDetails = &value;
    mixerControlDetails.cbDetails = sizeof(MIXERCONTROLDETAILS_UNSIGNED);
    value.dwValue = volume;
    if ((error = mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
        return 4;

    // Set the microphone source mute
    mixerControlDetails.dwControlID = mixerControlArray[1].dwControlID;
    value.dwValue = mute;
    if ((error = mixerSetControlDetails((HMIXEROBJ)mixerHandle, &mixerControlDetails, MIXER_SETCONTROLDETAILSF_VALUE)))
        return 5;

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,此方法非常特定于我当时使用的硬件,因为我已经硬编码了许多数组索引来访问混音器的特定属性.

现在提出问题.

现在已经有几年了,我需要修改我目前在C#winforms中编写的应用程序,以显示相同的行为.也就是说,我需要从麦克风或lini-in插孔接收的音频直接传递到扬声器.这里的诀窍是硬件不再关闭.并且应用程序需要在运行WinXP或更高版本的任何计算机上运行.

我开始与n音讯库工作在软件模式做这个直通(不利用内置声卡直通).这是我在C#中创建的小工具箱:

using System;
using System.ComponentModel;
using NAudio.Wave;

namespace Media
{
    public partial class AudioToolbox : Component
    {
        private WaveIn waveIn = null;
        private WaveOutEvent waveOut = null;
        public int SampleRate { get; set; }
        public int BitsPerSample { get; set; }
        public int Channels { get; set; }

        public AudioToolbox()
        {
            InitializeComponent();

            SampleRate = 22050;
            BitsPerSample = 16;
            Channels = 1;
        }

        public void BeginReading(int deviceNumber)
        {
            if (waveIn == null)
            {
                waveIn = new WaveIn();
                waveIn.DeviceNumber = deviceNumber;
                waveIn.WaveFormat = new NAudio.Wave.WaveFormat(SampleRate, BitsPerSample, Channels);
                waveIn.StartRecording();
            }
        }

        public void BeginLoopback()
        {
            if (waveIn != null && waveOut == null)
            {
                WaveInProvider waveInProvider = new WaveInProvider(waveIn);
                waveOut = new WaveOutEvent();
                waveOut.DeviceNumber = -1;  // Default output device
                waveOut.DesiredLatency = 300;
                waveOut.Init(waveInProvider);
                waveOut.Play();
            }
        }

        public void EndReading()
        {
            if (waveIn != null)
            {
                waveIn.StopRecording();
                waveIn.Dispose();
                waveIn = null;
            }
        }

        public void EndLoopback()
        {
            if (waveOut != null)
            {
                waveOut.Stop();
                waveOut.Dispose();
                waveOut = null;
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是(我假设)资源.此代码允许我将音频循环到扬声器,但在系统上执行任务会在音频中引入弹出和跳过.例如,如果我打开应用程序或快速最小化并最大化文件夹,则播放会弹出并跳过.

有没有办法以某种方式线程NAudio库,以避免这种弹出和跳过?或者,我是否更好地找到通过硬件传递音频的通用方法,就像我几年前用我的C应用程序做的那样?

编辑:

我测试此音频工具箱的应用程序非常简单.它仅仅是由Visual Studio 2010中创建一个默认的WinForms应用程序我添加一个按钮的形式和以下事件存在的上单击事件:

private void button1_Click(object sender, EventArgs e)
{
    AudioToolbox atr = new AudioToolbox();
    atr.BeginReading(0);
    atr.BeginLoopback();
}
Run Code Online (Sandbox Code Playgroud)

我还将项目设置为在.NET Framework 4中运行,因为这是我需要此工具箱最终集成的应用程序框架.当我编译应用程序并单击按钮时,我可以听到音频从麦克风插孔传递到扬声器.然后我打开Windows文件浏览器并不断最小化/最大化它.此操作会导致音频跳过.失败.

我刚刚在NAudio论坛上发布了这个问题.如果将来有人偶然发现此页面,请点击此链接: 在NAudio论坛上发布的问题

Mic*_*kus 1

这是迄今为止我能够实现的最大程度地减少跳过。我将接受它作为答案,以便任何其他偶然发现此页面的人都会看到我所做的事情,但如果有人提出更好的解决方案,我将很乐意选择他们的答案

我要做的第一件事就是放弃 NAudio 1.5,这是最后一个官方 NAudio 版本。相反,我获取了最新的热门版本,它是 NAudio 1.6 的测试版。我这样做是因为 1.6 的测试版包含一个名为 WaveInEvent 的新 WaveInProvider。WaveInEvent 是有益的,因为它可以防止在从麦克风插孔读取数据时调用 GUI 线程。

我做的第二件事是从 WaveOutEvent 切换到 DirectSoundOut。我这样做是因为在测试中,我发现当从文件播放音频时,WaveOutEvent 会根据我对 CPU 的使用情况而跳过,但 DirectSoundOut 不会。因此,我假设从麦克风端口播放音频时会发生相同的行为。因此,我使用 DirectSoundOut 来播放麦克风中的音频。

这是我的新 AudioInputToolbox:

using System; 
using System.ComponentModel; 
using NAudio.Wave; 

namespace Media 
{ 
    public partial class AudioInputToolbox : Component 
    {
        private WaveInEvent waveIn = null;
        private DirectSoundOut waveOut = null;
        public int SampleRate { get; set; } 
        public int BitsPerSample { get; set; } 
        public int Channels { get; set; }

        public AudioInputToolbox() 
        { 
            InitializeComponent(); 

            SampleRate = 22050; 
            BitsPerSample = 16; 
            Channels = 1; 
        } 

        public void BeginReading(int deviceNumber) 
        {
            if (waveIn == null) 
            {
                waveIn = new WaveInEvent(); 
                waveIn.DeviceNumber = deviceNumber; 
                waveIn.WaveFormat = new NAudio.Wave.WaveFormat(SampleRate, BitsPerSample, Channels);
                waveIn.StartRecording(); 
            } 
        }

        public void BeginLoopback() 
        {
            if (waveIn != null && waveOut == null)
            {
                waveOut = new DirectSoundOut(DirectSoundOut.DSDEVID_DefaultPlayback, 300);
                waveOut.Init(new WaveInProvider(waveIn));
                waveOut.Play();
            }
        }

        public void EndReading() 
        {
            if (waveIn != null) 
            { 
                waveIn.StopRecording(); 
                waveIn.Dispose(); 
                waveIn = null; 
            } 
        } 

        public void EndLoopback() 
        {
            if (waveOut != null)
            {
                waveOut.Stop();
                waveOut.Dispose();
                waveOut = null;
            }
        } 
    } 
} 
Run Code Online (Sandbox Code Playgroud)

这是我的新测试应用程序的代码。它只是一个带有两个按钮的表单。每个按钮都有一个回调。一个是启动按钮。另一个是停止按钮。

using System;
using System.Threading;
using System.Windows.Forms;
using Media;

public partial class AITL : Form
{
    AudioInputToolbox atr = new AudioInputToolbox();

    public AITL()
    {
        InitializeComponent();
    }

    private void startButton_Click(object sender, EventArgs e)
    {
        new Thread(() =>
        {
            atr.BeginReading(0);
            atr.BeginLoopback();
        }).Start();              
    }

    private void stopButton_Click(object sender, EventArgs e)
    {
        atr.EndReading();
        atr.EndLoopback();
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法并不能解决我的问题。它只会稍微减少问题发生的频率稍微减轻问题的严重性

再次,我很乐意接受任何能够完全解决跳过问题的人的不同答案。重申一下,在按下开始按钮并反复最小化和最大化窗口后,我遇到了跳过。任何窗口。我一直在windows资源管理器上这样做。(在我的全功能应用程序中,这个音频组件需要适应,有大量的 GUI 密集映射正在进行,因此窗口的最小化/最大化是对该操作的良好模拟)。