Microsoft MVP성태의 닷넷 이야기
닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio) [링크 복사], [링크+제목 복사],
조회: 9936
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 9개 있습니다.)
.NET Framework: 618. C# - NAudio를 이용한 MP3 파일 재생
; https://www.sysnet.pe.kr/2/0/11092

닷넷: 2236. C# - Audio 장치 열람 (Windows Multimedia, NAudio)
; https://www.sysnet.pe.kr/2/0/13594

닷넷: 2237. C# - Audio 장치 열기 (Windows Multimedia, NAudio)
; https://www.sysnet.pe.kr/2/0/13595

닷넷: 2238. C# - WAV 기본 파일 포맷
; https://www.sysnet.pe.kr/2/0/13596

닷넷: 2239. C# - WAV 파일의 PCM 데이터 생성 및 출력
; https://www.sysnet.pe.kr/2/0/13597

닷넷: 2240. C# - WAV 파일 포맷 + LIST 헤더
; https://www.sysnet.pe.kr/2/0/13598

닷넷: 2241. C# - WAV 파일의 PCM 사운드 재생(Windows Multimedia)
; https://www.sysnet.pe.kr/2/0/13599

닷넷: 2243. C# - PCM 사운드 재생(NAudio)
; https://www.sysnet.pe.kr/2/0/13601

닷넷: 2244. C# - PCM 오디오 데이터를 연속(Streaming) 재생 (Windows Multimedia)
; https://www.sysnet.pe.kr/2/0/13602




C# - Audio 장치 열람 (Windows Multimedia, NAudio)

다음과 같은 멋진 강좌가 있군요. ^^

Waveform
; http://www.soen.kr/lecture/library/waveform/wave.htm

"1.사운드 파일" 글에 있는 내용을 C#으로 실습해 볼 텐데요, 핵심은 단지 P/Invoke 메서드 2개 만드는 것뿐입니다.

using System.Runtime.InteropServices;

namespace AudioLibrary;

internal class NativeMethods
{
    [DllImport("winmm.dll", SetLastError = true)]
    public static extern uint waveOutGetNumDevs();

    [DllImport("winmm.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern uint waveOutGetDevCaps(IntPtr hwo, ref WAVEOUTCAPS pwoc, uint cbwoc);
}

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public struct WAVEOUTCAPS
{
    public short wMid;
    public short wPid;
    public int vDriverVersion;

    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szPname;

    public int dwFormats;
    public short wChannels;
    public short wReserved;
    public int dwSupport;
}

이렇게 만들어 놨으면 간단하게 다음과 같이 호출할 수 있습니다.

using AudioLibrary;
using System.Runtime.InteropServices;

namespace ConsoleApp1;
internal class Program
{
    static void Main(string[] args)
    {
        uint countOfDevices = NativeMethods.waveOutGetNumDevs();

        for (int i = 0; i < countOfDevices; i++)
        {
            WAVEOUTCAPS caps = new WAVEOUTCAPS();
            uint result = NativeMethods.waveOutGetDevCaps((IntPtr)i, ref caps, (uint)Marshal.SizeOf(caps));
            if (result == 0)
            {
                System.Console.WriteLine($"[{i}]: {caps.szPname}, {caps.wChannels} channels, {caps.dwFormats:x} formats, {caps.dwSupport:x} support");
            }
        }
    }
}

/* 출력 결과
[0]: Speakers (Realtek(R) Audio), 2 channels, fffff formats, 2e support
*/

출력 결과를 보면 formats가 0xfffff 값을 갖고 있는데요, 이것의 개별 비트 의미는 Visual C++의 "mmeapi.h" 헤더 파일에서 알아낼 수 있습니다.

/* defines for dwFormat field of WAVEINCAPS and WAVEOUTCAPS */
#define WAVE_INVALIDFORMAT     0x00000000       /* invalid format */
#define WAVE_FORMAT_1M08       0x00000001       /* 11.025 kHz, Mono,   8-bit  */
#define WAVE_FORMAT_1S08       0x00000002       /* 11.025 kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_1M16       0x00000004       /* 11.025 kHz, Mono,   16-bit */
#define WAVE_FORMAT_1S16       0x00000008       /* 11.025 kHz, Stereo, 16-bit */
#define WAVE_FORMAT_2M08       0x00000010       /* 22.05  kHz, Mono,   8-bit  */
#define WAVE_FORMAT_2S08       0x00000020       /* 22.05  kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_2M16       0x00000040       /* 22.05  kHz, Mono,   16-bit */
#define WAVE_FORMAT_2S16       0x00000080       /* 22.05  kHz, Stereo, 16-bit */
#define WAVE_FORMAT_4M08       0x00000100       /* 44.1   kHz, Mono,   8-bit  */
#define WAVE_FORMAT_4S08       0x00000200       /* 44.1   kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_4M16       0x00000400       /* 44.1   kHz, Mono,   16-bit */
#define WAVE_FORMAT_4S16       0x00000800       /* 44.1   kHz, Stereo, 16-bit */

#define WAVE_FORMAT_44M08      0x00000100       /* 44.1   kHz, Mono,   8-bit  */
#define WAVE_FORMAT_44S08      0x00000200       /* 44.1   kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_44M16      0x00000400       /* 44.1   kHz, Mono,   16-bit */
#define WAVE_FORMAT_44S16      0x00000800       /* 44.1   kHz, Stereo, 16-bit */
#define WAVE_FORMAT_48M08      0x00001000       /* 48     kHz, Mono,   8-bit  */
#define WAVE_FORMAT_48S08      0x00002000       /* 48     kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_48M16      0x00004000       /* 48     kHz, Mono,   16-bit */
#define WAVE_FORMAT_48S16      0x00008000       /* 48     kHz, Stereo, 16-bit */
#define WAVE_FORMAT_96M08      0x00010000       /* 96     kHz, Mono,   8-bit  */
#define WAVE_FORMAT_96S08      0x00020000       /* 96     kHz, Stereo, 8-bit  */
#define WAVE_FORMAT_96M16      0x00040000       /* 96     kHz, Mono,   16-bit */
#define WAVE_FORMAT_96S16      0x00080000       /* 96     kHz, Stereo, 16-bit */

0xfffff니까... 위의 모든 포맷을 지원하는 것입니다. 그다음 support 값이 0x2e인데요, 이것 역시 헤더 파일에 이렇게 정의돼 있습니다.

#define WAVECAPS_PITCH          0x0001   /* supports pitch control */
#define WAVECAPS_PLAYBACKRATE   0x0002   /* supports playback rate control */
#define WAVECAPS_VOLUME         0x0004   /* supports volume control */
#define WAVECAPS_LRVOLUME       0x0008   /* separate left-right volume control */
#define WAVECAPS_SYNC           0x0010
#define WAVECAPS_SAMPLEACCURATE 0x0020

따라서 0x2e니까, WAVECAPS_SAMPLEACCURATE | WAVECAPS_LRVOLUME | WAVECAPS_VOLUME | WAVECAPS_PLAYBACKRATE 조합이 됩니다. 근래의 시스템에서는 사실상 아주 특별한 시스템이 아닌 한, 대체로 모두 위와 같은 출력을 나타낸다고 봐도 될 듯합니다.




요즘 같은 시대에 ^^ 로우-레벨 API를 이용해 직접 오디오를 제어하는 경우는 거의 없을 것입니다. 게다가 오픈소스로 너무 잘 만들어진 NAudio 패키지가 있기 때문에,

// https://github.com/naudio/NAudio/

Install-Package NAudio 

이전에 Win32 API로 작성했던 출력을 NAudio로도 매칭시켜 알아보겠습니다. 사실 NAudio도 Windows Multimedia에 대한 Interop을 WaveInterop 클래스에 모두 해주고 있습니다.

그래서, 위에서 작성한 소스코드를 그대로 다음과 같이 포팅할 수 있습니다.

// NAdio 사용

int countOfDevices = WaveInterop.waveOutGetNumDevs();

for (int i = 0; i < countOfDevices; i++)
{
    WaveOutCapabilities caps = new WaveOutCapabilities();
    MmResult result = WaveInterop.waveOutGetDevCaps(i, out caps, Marshal.SizeOf(caps));
    if (result == MmResult.NoError)
    {
        System.Console.WriteLine($"[{i}]: {caps.szPname}, {caps.wChannels} channels, {caps.dwFormats:x} formats, {caps.dwSupport:x} support");
    }
}

/* dwFormats, dwSupport 값은 각각 supportedFormats, support 필드로 담고 있지만 private으로 정의 */

private static long GetFormats(WaveOutCapabilities caps)
{
    long formats = 0;

    foreach (var value in Enum.GetValues<SupportedWaveFormat>())
    {
        if (caps.SupportsWaveFormat(value) == true)
        {
            formats |= (long)value;
        }
    }

    return formats;
}

private static int GetSupports(WaveOutCapabilities caps)
{
    FieldInfo? fi = caps.GetType().GetField("support", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    if (fi == null)
    {
        return 0;
    }

    object objValue = fi.GetValue(caps);
    if (objValue == null)
    {
        return 0;
    }

    return (int)objValue;
}


또한 github의 문서를 보면 아래와 같은 글로 다양한 방법을 소개하고 있습니다.

Enumerating Audio Devices
; https://github.com/naudio/NAudio/blob/master/Docs/EnumerateOutputDevices.md

그중에서 WaveOut은 NAudio.WinForms 패키지를 추가로 참조해야 하고,

Install-Package NAudio.WinForms

.NET Core/5+인 경우 TargetFramework을 명시적으로 "-windows" 접미사를 명시해 플랫폼 제한을 걸어야 합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="NAudio" Version="2.2.1" />
    <PackageReference Include="NAudio.WinForms" Version="2.2.1" />
  </ItemGroup>

</Project>

이후에는 문서에 나온 내용대로 코딩하시면 됩니다. ^^

for (int n = -1; n < WaveOut.DeviceCount; n++)
{
    var caps = WaveOut.GetCapabilities(n);
    System.Console.WriteLine($"[{n}]: {caps.ProductName}, {caps.Channels} channels, {GetFormats(caps):x} formats, {GetSupports(caps):x} support");
}

/* 출력 결과
[-1]: Microsoft Sound Mapper, 2 channels, fffff formats, 2e support
[0]: Speakers (Realtek(R) Audio), 2 channels, fffff formats, 2e support
*/

그런데 "1.사운드 파일" 글과는 달리 -1부터 루프를 돌고 있으며 실제로 -1이 "Microsoft Sound Mapper"라는 이름을 가지고 있습니다. 검색해 보면,

Q. What is Microsoft Sound Mapper?
; https://www.soundonsound.com/sound-advice/q-what-microsoft-sound-mapper

이것은 실제 장치는 아니고, 사용자가 playback/recording 용으로 선택한 장치로의 통로 역할을 하는 가상 장치를 의미한다고 합니다. ("WME-WDM" 또는 "Wave Mapper"라고 표기되는 경우도 있다고.) 설명을 보면, 단순히 사용자가 선택한 오디오 장치로의 alias가 아닌, 엄연히 가상 장치로써 나름 하는 역할이 있는 것 같습니다. (저도 이 분야로는 초짜라. ^^)

(첨부 파일은 이 글의 예제 코드를 포함합니다.)

참고로, -1부터 for 루프를 돌았던 것은 Win32 API와 NAudio WaveInterop 예제 코드에도 그대로 적용해 WaveOut과 동일한 결과를 얻을 수 있습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 4/13/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...
NoWriterDateCnt.TitleFile(s)
1756정성태9/23/201427488기타: 48. NVidia 제품의 과다한 디스크 사용 [2]
1755정성태9/22/201434280오류 유형: 241. Unity Web Player를 설치해도 여전히 설치하라는 화면이 나오는 경우 [4]
1754정성태9/22/201424666VC++: 80. 내 컴퓨터에서 C++ AMP 코드가 실행이 될까요? [1]
1753정성태9/22/201420611오류 유형: 240. Lync로 세미나 참여 시 소리만 들리지 않는 경우 [1]
1752정성태9/21/201441071Windows: 100. 윈도우 8 - RDP 연결을 이용해 VNC처럼 사용자 로그온 화면을 공유하는 방법 [5]
1751정성태9/20/201438954.NET Framework: 464. 프로세스 간 통신 시 소켓 필요 없이 간단하게 Pipe를 열어 통신하는 방법 [1]파일 다운로드1
1750정성태9/20/201423833.NET Framework: 463. PInvoke 호출을 이용한 비동기 파일 작업파일 다운로드1
1749정성태9/20/201423733.NET Framework: 462. 커널 객체를 위한 null DACL 생성 방법파일 다운로드1
1748정성태9/19/201425386개발 환경 구성: 238. [Synergy] 여러 컴퓨터에서 키보드, 마우스 공유
1747정성태9/19/201428499오류 유형: 239. psexec 실행 오류 - The system cannot find the file specified.
1746정성태9/18/201426106.NET Framework: 461. .NET EXE 파일을 닷넷 프레임워크 버전에 상관없이 실행할 수 있을까요? - 두 번째 이야기 [6]파일 다운로드1
1745정성태9/17/201423038개발 환경 구성: 237. 리눅스 Integration Services 버전 업그레이드 하는 방법 [1]
1744정성태9/17/201431064.NET Framework: 460. GetTickCount / GetTickCount64와 0x7FFE0000 주솟값 [4]파일 다운로드1
1743정성태9/16/201420985오류 유형: 238. 설치 오류 - Failed to get size of pseudo bundle
1742정성태8/27/201426976개발 환경 구성: 236. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 [2]
1741정성태8/26/201421334.NET Framework: 459. GetModuleHandleEx로 알아보는 .NET 메서드의 DLL 모듈 관계파일 다운로드1
1740정성태8/25/201432523.NET Framework: 458. 닷넷 GC가 순환 참조를 해제할 수 있을까요? [2]파일 다운로드1
1739정성태8/24/201426560.NET Framework: 457. 교착상태(Dead-lock) 해결 방법 - Lock Leveling [2]파일 다운로드1
1738정성태8/23/201422052.NET Framework: 456. C# - CAS를 이용한 Lock 래퍼 클래스파일 다운로드1
1737정성태8/20/201419765VS.NET IDE: 93. Visual Studio 2013 동기화 문제
1736정성태8/19/201425580VC++: 79. [부연] CAS Lock 알고리즘은 과연 빠른가? [2]파일 다운로드1
1735정성태8/19/201418235.NET Framework: 455. 닷넷 사용자 정의 예외 클래스의 최소 구현 코드 - 두 번째 이야기
1734정성태8/13/201419896오류 유형: 237. Windows Media Player cannot access the file. The file might be in use, you might not have access to the computer where the file is stored, or your proxy settings might not be correct.
1733정성태8/13/201426363.NET Framework: 454. EmptyWorkingSet Win32 API를 사용하는 C# 예제파일 다운로드1
1732정성태8/13/201434480Windows: 99. INetCache 폴더가 다르게 보이는 이유
1731정성태8/11/201427086개발 환경 구성: 235. 점(.)으로 시작하는 파일명을 탐색기에서 만드는 방법
... 121  122  123  124  125  126  127  128  129  130  131  [132]  133  134  135  ...