6

数年前、私は自分の会社用に、特定の 1 つのモデルのコンピューターのみを実行するカスタム アプリケーションを作成しました。アプリケーションは、マイク ジャックから入ってくるオーディオをスピーカーにパススルーできる必要がありました。ジャックに入るバイトを処理してソフトウェアでスピーカーに渡す代わりに、特定のハードウェアを知っているという事実を利用して、サウンドカードの組み込み機能を有効にして、入力からスピーカーにオーディオをループさせる関数を記述しました。 . これがその関数です (mmsystem.dll のみを使用して C で記述されています)。

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 で作成しているアプリケーションを変更して、同じ動作を示す必要があります。つまり、マイクまたはリニ入力ジャックから受信したオーディオを直接スピーカーにパススルーする必要があります。ここでのトリックは、ハードウェアが閉じられていないことです。また、アプリケーションは、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 アプリケーションです。フォームに 1 つのボタンを追加し、クリック イベントで次のイベントが発生します。

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 の最後の公式リリースである NAudio 1.5 を放棄することでした。代わりに、NAudio 1.6 のベータ版である最新のホット ビルドを入手しました。これを行ったのは、1.6 のベータ版に WaveInEvent という新しい WaveInProvider が含まれているためです。WaveInEvent は、マイク ジャックからの読み取り中に GUI スレッドへの呼び出しを防ぐため、有益です。

次に、WaveOutEvent から DirectSoundOut に切り替えました。これを行ったのは、私のテストでは、ファイルからオーディオを再生するときに、CPU の使用状況に応じて WaveOutEvent がスキップされるが、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;
            }
        } 
    } 
} 

そして、これが私の新しいテスト アプリケーションのコードです。ボタンが 2 つ付いたシンプルなフォームです。各ボタンにはコールバックがあります。1つはスタートボタン。もう一つは停止ボタンです。

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 に答える