본문 바로가기
개발언어/C#

C#을 사용하여 오디오 파형 플롯

by 창용이랑 2023. 2. 21.
728x90
NAudio 및 ScottPlot을 사용하여 마이크 오디오를 실시간으로 표시

오디오 분석 및 시각화 | 2022년 5월 8일 | 퍼머링크 | 뷰 소스

이 페이지에서는 Windows Forms 응용 프로그램을 만들어 마이크 오디오를 실시간으로 플롯하는 방법에 대해 설명합니다.오디오를 캡처하기 위해 NAudio를 사용했고 오디오 입력 장치에서 새로운 데이터가 도착하면 값을 로 변환했습니다.double저장해 두었습니다.readonly double[]ScottPlot을 사용하여 플롯을 작성하는 데 사용됩니다.그 결과, 오디오 데이터를 높은 프레임률로 실시간으로 표시하는 단순한 애플리케이션이 실현됩니다.

💡 이 페이지에서는 Windows Forms 응용 프로그램에 대해 설명하지만 ScottPlot에는 유사한 결과를 얻기 위해 사용할 수 있는 WPF 컨트롤이 있습니다.시작하려면 ScottPlot WPF QuickStart 가이드를 참조하십시오.

오디오 데이터를 저장할 어레이 준비

처음에 플롯할 값을 보유하는 클래스 레벨의 배열을 작성했습니다.이 배열은 단일 녹음 버퍼에서 도달하는 정확한 오디오 값 수를 유지하도록 설계되었습니다.

readonly double[] AudioValues;

어레이의 길이는 선택한 기록 파라미터에서 계산할 수 있습니다.

AudioValues = new double[SampleRate * BufferMilliseconds / 1000];

플롯 설정

오디오 값 배열을 플롯에 추가합니다.다만, 나중에 어레이에 실제 데이터를 입력하고 플롯을 리프레시하도록 의뢰합니다.ScottPlot의 Signal Plot 유형을 사용하고 있습니다. ScottPlot의 Signal Plot 유형은 일정한 간격으로 데이터를 표시하도록 성능이 최적화되어 있습니다.자세한 내용은 ScottPlot 요리책을 참조하십시오.

formsPlot1.Plot.AddSignal(AudioValues);

녹음 디바이스 목록 및 활성화

프로그램이 시작되면 ComboBox에 기록 장치가 채워집니다.

for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
{
    var caps = NAudio.Wave.WaveIn.GetCapabilities(i);
    comboBox1.Items.Add(caps.ProductName);
}
 

💡 Naudio는 고려사항-1유효한 디바이스 ID.일반적으로 Microsoft Sound Mapper에 매핑되지만 단순함을 위해 생략하는 경우가 많습니다.

기록 장치를 선택하면 활성화됩니다.이전에 디바이스를 선택한 경우 해당 디바이스가 먼저 언로드됩니다.

int SampleRate = 44100;
int BitDepth = 16;
int ChannelCount = 1;
int BufferMilliseconds = 20;

NAudio.Wave.WaveInEvent? Wave;

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (Wave is not null)
    {
        Wave.StopRecording();
        Wave.Dispose();
    }

    Wave = new NAudio.Wave.WaveInEvent()
    {
        DeviceNumber = comboBox1.SelectedIndex,
        WaveFormat = new NAudio.Wave.WaveFormat(SampleRate, BitDepth, ChannelCount),
        BufferMilliseconds = BufferMilliseconds
    };

    Wave.DataAvailable += WaveIn_DataAvailable;
    Wave.StartRecording();
}

새 데이터를 사용할 수 있는 경우 값 업데이트

에서 기능하기 때문에DataAvailable이벤트 핸들러는 UI 스레드 이외의 스레드에서 호출될 수 있으므로 이러한 기능에서 UI를 업데이트하지 않는 것이 중요합니다.대신 이 함수는 버퍼의 바이트를 폼 작성 시 작성한 배열에 저장된 값으로 변환합니다.

void WaveIn_DataAvailable(object? sender, NAudio.Wave.WaveInEventArgs e)
{
    for (int i = 0; i < e.Buffer.Length / 2; i++)
        AudioValues[i] = BitConverter.ToInt16(e.Buffer, i * 2);
}

타이머를 사용하여 UI 업데이트

를 설정했습니다.Timer20밀리초마다 실행되도록 합니다.AudioValues배열하고 레벨 미터와 플롯을 업데이트합니다.

private void timer1_Tick(object sender, EventArgs e)
{
    int level = (int)AudioValues.Max();
    pbVolume.Value = (int)Math.Max(level, pbVolume.Maximum);
    formsPlot1.RefreshRequest();
}
 

💡 ScottPlot'sRefreshRequest()에 대한 비독점적인 대체 수단이다.Refresh()다시 그리는 동안 UI 창이 대화식으로 유지되도록 설계되었습니다.

전체 소스 코드

여기에 표시된 확장 소스 코드에는 위의 코드 샘플에서 설명되지 않은 몇 가지 추가 기능(예: 플롯의 자동 스케일링)이 있습니다.이 소스코드는 페이지 상단에 애니메이션 스크린샷을 만드는 데 사용된 코드입니다.GitHub에서 전체 프로젝트 소스 코드를 다운로드할 수 있습니다.오디오 모니터

public partial class Form1 : Form
{
    NAudio.Wave.WaveInEvent? Wave;

    readonly double[] AudioValues;

    int SampleRate = 44100;
    int BitDepth = 16;
    int ChannelCount = 1;
    int BufferMilliseconds = 20;

    public Form1()
    {
        InitializeComponent();

        AudioValues = new double[SampleRate * BufferMilliseconds / 1000];

        formsPlot1.Plot.AddSignal(AudioValues, SampleRate / 1000);
        formsPlot1.Plot.YLabel("Level");
        formsPlot1.Plot.XLabel("Time (milliseconds)");
        formsPlot1.Refresh();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        for (int i = 0; i < NAudio.Wave.WaveIn.DeviceCount; i++)
        {
            var caps = NAudio.Wave.WaveIn.GetCapabilities(i);
            comboBox1.Items.Add(caps.ProductName);
        }
    }

    private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
    {
        if (Wave is not null)
        {
            Wave.StopRecording();
            Wave.Dispose();

            for (int i = 0; i < AudioValues.Length; i++)
                AudioValues[i] = 0;
            formsPlot1.Plot.AxisAuto();
        }

        if (comboBox1.SelectedIndex == -1)
            return;

        Wave = new NAudio.Wave.WaveInEvent()
        {
            DeviceNumber = comboBox1.SelectedIndex,
            WaveFormat = new NAudio.Wave.WaveFormat(SampleRate, BitDepth, ChannelCount),
            BufferMilliseconds = BufferMilliseconds
        };

        Wave.DataAvailable += WaveIn_DataAvailable;
        Wave.StartRecording();

        formsPlot1.Plot.Title(comboBox1.SelectedItem.ToString());
    }

    void WaveIn_DataAvailable(object? sender, NAudio.Wave.WaveInEventArgs e)
    {
        for (int i = 0; i < e.Buffer.Length / 2; i++)
            AudioValues[i] = BitConverter.ToInt16(e.Buffer, i * 2);
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
        int level = (int)AudioValues.Max();

        // auto-scale the maximum progressbar level
        if (level > pbVolume.Maximum)
            pbVolume.Maximum = level;
        pbVolume.Value = level;

        // auto-scale the plot Y axis limits
        var currentLimits = formsPlot1.Plot.GetAxisLimits();
        formsPlot1.Plot.SetAxisLimits(
            yMin: Math.Min(currentLimits.YMin, -level),
            yMax: Math.Max(currentLimits.YMax, level));

        // request a redraw using a non-blocking render queue
        formsPlot1.RefreshRequest();
    }
}

Resources

 

 

 

출처 : Plot Audio Waveform with C# (swharden.com)