6

几年前,我为我的公司编写了一个自定义应用程序,该应用程序只运行一台特定型号的计算机。该应用程序必须能够将来自麦克风插孔的音频传递到扬声器。我没有在软件中处理进入插孔的字节并将它们传递给扬声器,而是利用我知道特定硬件来编写一个函数的事实,该函数使声卡能够将音频从输入循环到扬声器. 这是那个函数(它是用 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;
}

如您所见,此方法高度特定于我当时使用的硬件,因为我已经硬编码了许多数组索引以访问混音器的特定属性。

现在来回答这个问题。

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

我开始使用 NAudio 库在软件模式下执行此直通(不使用内置声卡直通)。这是我在 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;
            }
        }
    }
}

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

有没有办法以某种方式线程化 NAudio 库以避免这种弹出和跳过?还是像我多年前在 C 应用程序中所做的那样,找到一种通过硬件传递音频的通用方法对我来说更好?

编辑:

我测试这个音频工具箱的应用程序非常简单。它只是一个由 Visual Studio 2010 创建的默认 winforms 应用程序。我在表单中添加了一个按钮,并且在 click 事件上发生了以下事件:

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

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

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

4

2 回答 2

1

我认为你只需要将你的处理启动到一个单独的线程中。您正在 UI 线程上完成所有工作,这就是为什么每当您执行任何操作时都会暂停您的处理。我假设音频来自事件的块。事件在调度它们的线程上处理,在这种情况下是您的 ui 线程。

尝试将您的代码包装成这样

AudioToolbox atr = new AudioToolbox();
var audioThread = new Thread(()=> {
    atr.BeginReading(0);
    atr.BeginLoopback();
}).Start();

我看不出执行外部任务会导致中断的任何原因。我已经在一个线程中完成了实时实时音频和视频处理,在许多不同的机器上都没有问题。也许发生的事情是因为它全部在 ui 线程中,当它重绘屏幕时,您的音频处理被暂停。如果是这种情况,那么专用线程将解决这个问题。

于 2012-09-01T23:16:31.037 回答
1

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

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

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

这是我的新音频输入工具箱:

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;
            }
        } 
    } 
} 

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

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();
    }
}

这种方法不能解决我的问题。它只会使问题发生的频率稍低一些,严重程度稍低一些。

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

于 2012-09-05T11:42:43.393 回答