Share via


스트림 캡처

클라이언트는 IAudioCaptureClient 인터페이스의 메서드를 호출하여 엔드포인트 버퍼에서 캡처된 데이터를 읽습니다. 클라이언트는 공유 모드의 오디오 엔진과 단독 모드의 오디오 디바이스와 엔드포인트 버퍼를 공유합니다. 특정 크기의 엔드포인트 버퍼를 요청하기 위해 클라이언트는 IAudioClient::Initialize 메서드를 호출합니다 . 요청된 크기와 다를 수 있는 할당된 버퍼의 크기를 가져오기 위해 클라이언트는 IAudioClient::GetBufferSize 메서드를 호출합니다.

엔드포인트 버퍼를 통해 캡처된 데이터 스트림을 이동하려면 클라이언트가 IAudioCaptureClient::GetBuffer 메서드 및 IAudioCaptureClient::ReleaseBuffer 메서드를 호출합니다. 클라이언트는 일련의 데이터 패킷으로 엔드포인트 버퍼의 데이터에 액세스합니다. GetBuffer 호출은 버퍼에서 캡처된 데이터의 다음 패킷을 검색합니다. 패킷에서 데이터를 읽은 후 클라이언트는 ReleaseBuffer 를 호출하여 패킷을 해제하고 더 많은 캡처된 데이터에 사용할 수 있도록 합니다.

패킷 크기는 GetBuffer 호출마다 다를 수 있습니다. GetBuffer를 호출하기 전에 클라이언트는 IAudioCaptureClient::GetNextPacketSize 메서드를 호출하여 다음 패킷의 크기를 미리 가져올 수 있습니다. 또한 클라이언트는 IAudioClient::GetCurrentPadding 메서드를 호출하여 버퍼에서 사용할 수 있는 캡처된 데이터의 총 양을 가져올 수 있습니다. 언제든지 패킷 크기는 버퍼에서 캡처된 데이터의 총 양보다 작거나 같습니다.

각 처리 단계에서 클라이언트는 다음 방법 중 하나로 캡처된 데이터를 처리할 수 있습니다.

  • 클라이언트는 GetBuffer와 ReleaseBuffer를 번갈아 호출하여 GetBuffer가 AUDCNT_S_BUFFEREMPTY 반환하여 버퍼가 비어 있음을 나타낼 때까지 각 호출 쌍으로 한 패킷을 읽습니다.
  • 클라이언트는 GetNextPacketSize 가 패킷 크기를 0으로 보고할 때까지 GetBufferReleaseBuffer 에 대한 각 호출 쌍 전에 GetNextPacketSize 를 호출하여 버퍼가 비어 있음을 나타냅니다.

두 기술은 동등한 결과를 생성합니다.

다음 코드 예제에서는 기본 캡처 디바이스에서 오디오 스트림을 기록하는 방법을 보여줍니다.

//-----------------------------------------------------------
// Record an audio stream from the default audio capture
// device. The RecordAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data from the
// capture device. The main loop runs every 1/2 second.
//-----------------------------------------------------------

// REFERENCE_TIME time units per second and per millisecond
#define REFTIMES_PER_SEC  10000000
#define REFTIMES_PER_MILLISEC  10000

#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
const IID IID_IAudioClient = __uuidof(IAudioClient);
const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);

HRESULT RecordAudioStream(MyAudioSink *pMySink)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    REFERENCE_TIME hnsActualDuration;
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioCaptureClient *pCaptureClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    UINT32 packetLength = 0;
    BOOL bDone = FALSE;
    BYTE *pData;
    DWORD flags;

    hr = CoCreateInstance(
           CLSID_MMDeviceEnumerator, NULL,
           CLSCTX_ALL, IID_IMMDeviceEnumerator,
           (void**)&pEnumerator);
    EXIT_ON_ERROR(hr)

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eCapture, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(
                    IID_IAudioClient, CLSCTX_ALL,
                    NULL, (void**)&pAudioClient);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetMixFormat(&pwfx);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->Initialize(
                         AUDCLNT_SHAREMODE_SHARED,
                         0,
                         hnsRequestedDuration,
                         0,
                         pwfx,
                         NULL);
    EXIT_ON_ERROR(hr)

    // Get the size of the allocated buffer.
    hr = pAudioClient->GetBufferSize(&bufferFrameCount);
    EXIT_ON_ERROR(hr)

    hr = pAudioClient->GetService(
                         IID_IAudioCaptureClient,
                         (void**)&pCaptureClient);
    EXIT_ON_ERROR(hr)

    // Notify the audio sink which format to use.
    hr = pMySink->SetFormat(pwfx);
    EXIT_ON_ERROR(hr)

    // Calculate the actual duration of the allocated buffer.
    hnsActualDuration = (double)REFTIMES_PER_SEC *
                     bufferFrameCount / pwfx->nSamplesPerSec;

    hr = pAudioClient->Start();  // Start recording.
    EXIT_ON_ERROR(hr)

    // Each loop fills about half of the shared buffer.
    while (bDone == FALSE)
    {
        // Sleep for half the buffer duration.
        Sleep(hnsActualDuration/REFTIMES_PER_MILLISEC/2);

        hr = pCaptureClient->GetNextPacketSize(&packetLength);
        EXIT_ON_ERROR(hr)

        while (packetLength != 0)
        {
            // Get the available data in the shared buffer.
            hr = pCaptureClient->GetBuffer(
                                   &pData,
                                   &numFramesAvailable,
                                   &flags, NULL, NULL);
            EXIT_ON_ERROR(hr)

            if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
            {
                pData = NULL;  // Tell CopyData to write silence.
            }

            // Copy the available capture data to the audio sink.
            hr = pMySink->CopyData(
                              pData, numFramesAvailable, &bDone);
            EXIT_ON_ERROR(hr)

            hr = pCaptureClient->ReleaseBuffer(numFramesAvailable);
            EXIT_ON_ERROR(hr)

            hr = pCaptureClient->GetNextPacketSize(&packetLength);
            EXIT_ON_ERROR(hr)
        }
    }

    hr = pAudioClient->Stop();  // Stop recording.
    EXIT_ON_ERROR(hr)

Exit:
    CoTaskMemFree(pwfx);
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(pAudioClient)
    SAFE_RELEASE(pCaptureClient)

    return hr;
}

앞의 예제에서 RecordAudioStream 함수는 두 개의 함수인 pMySinkCopyData 및 SetFormat을 사용하여 클라이언트 정의 클래스 MyAudioSink에 속하는 개체에 대한 포인터인 단일 매개 변수 를 사용합니다. 예제 코드에는 다음과 같은 이유로 MyAudioSink 구현이 포함되지 않습니다.

  • 클래스 멤버 중 어느 것도 WASAPI의 인터페이스에 있는 메서드와 직접 통신하지 않습니다.
  • 클래스는 클라이언트의 요구 사항에 따라 다양한 방법으로 구현할 수 있습니다. (예를 들어 캡처 데이터를 WAV 파일에 쓸 수 있습니다.)

그러나 두 메서드의 연산에 대한 정보는 예제를 이해하는 데 유용합니다.

CopyData 함수는 지정된 버퍼 위치에서 지정된 수의 오디오 프레임을 복사합니다. RecordAudioStream 함수는 CopyData 함수를 사용하여 공유 버퍼에서 오디오 데이터를 읽고 저장합니다. SetFormat 함수는 데이터에 사용할 CopyData 함수의 형식을 지정합니다.

MyAudioSink 개체에 추가 데이터가 필요한 한 CopyData 함수는 세 번째 매개 변수를 통해 FALSE 값을 출력합니다. 위의 코드 예제에서는 변수 bDone에 대한 포인터입니다. MyAudioSink 개체에 필요한 모든 데이터가 있으면 CopyData 함수가 TRUE로 설정 bDone 되므로 프로그램이 RecordAudioStream 함수에서 루프를 종료합니다.

RecordAudioStream 함수는 지속 시간이 1초인 공유 버퍼를 할당합니다. (할당된 버퍼의 기간이 약간 더 길 수 있습니다.) 기본 루프 내에서 Windows Sleep 함수를 호출하면 프로그램이 반초 동안 대기합니다. 각 절전 모드 호출이 시작될 때 공유 버퍼가 비어 있거나 거의 비어 있습니다. 절전 모드 호출이 반환되는 시점까지 공유 버퍼는 캡처 데이터로 약 절반으로 채워집니다.

IAudioClient::Initialize 메서드를 호출한 후 클라이언트가 IAudioClient 인터페이스에 대한 모든 참조와 클라이언트가 IAudioClient::GetService 메서드를 통해 얻은 서비스 인터페이스에 대한 모든 참조를 해제할 때까지 스트림은 열린 상태로 유지됩니다. 최종 릴리스 호출은 스트림을 닫습니다.

스트림 관리