Condividi tramite


Rendering di un flusso

Il client chiama i metodi nell'interfaccia IAudioRenderClient per scrivere i dati di rendering in un buffer dell'endpoint. Per un flusso in modalità condivisa, il client condivide il buffer dell'endpoint con il motore audio. Per un flusso in modalità esclusiva, il client condivide il buffer dell'endpoint con il dispositivo audio. Per richiedere un buffer di endpoint di una determinata dimensione, il client chiama il metodo IAudioClient::Initialize . Per ottenere le dimensioni del buffer allocato, che potrebbe essere diverso dalle dimensioni richieste, il client chiama il metodo IAudioClient::GetBufferSize.

Per spostare un flusso di dati di rendering attraverso il buffer dell'endpoint, il client chiama in alternativa il metodo IAudioRenderClient::GetBuffer e il metodo IAudioRenderClient::ReleaseBuffer. Il client accede ai dati nel buffer dell'endpoint come una serie di pacchetti di dati. La chiamata GetBuffer recupera il pacchetto successivo in modo che il client possa riempirlo con i dati di rendering. Dopo aver scritto i dati nel pacchetto, il client chiama ReleaseBuffer per aggiungere il pacchetto completato alla coda di rendering.

Per un buffer di rendering, il valore di riempimento riportato dal metodo IAudioClient::GetCurrentPadding rappresenta la quantità di dati di rendering accodati per la riproduzione nel buffer. Un'applicazione di rendering può usare il valore di spaziatura interna per determinare la quantità di nuovi dati che può scrivere in modo sicuro nel buffer senza il rischio di sovrascrivere dati scritti in precedenza che il motore audio non ha ancora letto dal buffer. Lo spazio disponibile è semplicemente la dimensione del buffer meno le dimensioni della spaziatura interna. Il client può richiedere una dimensione del pacchetto che rappresenta uno o tutto questo spazio disponibile nella successiva chiamata GetBuffer.

Le dimensioni di un pacchetto sono espresse in fotogrammi audio. Un frame audio in un flusso PCM è un set di campioni (il set contiene un esempio per ogni canale nel flusso) che vengono riprodotti o registrati contemporaneamente (tick clock). Di conseguenza, le dimensioni di un frame audio sono le dimensioni del campione moltiplicate per il numero di canali nel flusso. Ad esempio, le dimensioni dei fotogrammi per un flusso stereo (a 2 canali) con campioni a 16 bit sono quattro byte.

L'esempio di codice seguente illustra come riprodurre un flusso audio nel dispositivo di rendering predefinito:

//-----------------------------------------------------------
// Play an audio stream on the default audio rendering
// device. The PlayAudioStream function allocates a shared
// buffer big enough to hold one second of PCM audio data.
// The function uses this buffer to stream data to the
// rendering device. The inner 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_IAudioRenderClient = __uuidof(IAudioRenderClient);

HRESULT PlayAudioStream(MyAudioSource *pMySource)
{
    HRESULT hr;
    REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
    REFERENCE_TIME hnsActualDuration;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    IAudioClient *pAudioClient = NULL;
    IAudioRenderClient *pRenderClient = NULL;
    WAVEFORMATEX *pwfx = NULL;
    UINT32 bufferFrameCount;
    UINT32 numFramesAvailable;
    UINT32 numFramesPadding;
    BYTE *pData;
    DWORD flags = 0;

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

    hr = pEnumerator->GetDefaultAudioEndpoint(
                        eRender, 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)

    // Tell the audio source which format to use.
    hr = pMySource->SetFormat(pwfx);
    EXIT_ON_ERROR(hr)

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

    hr = pAudioClient->GetService(
                         IID_IAudioRenderClient,
                         (void**)&pRenderClient);
    EXIT_ON_ERROR(hr)

    // Grab the entire buffer for the initial fill operation.
    hr = pRenderClient->GetBuffer(bufferFrameCount, &pData);
    EXIT_ON_ERROR(hr)

    // Load the initial data into the shared buffer.
    hr = pMySource->LoadData(bufferFrameCount, pData, &flags);
    EXIT_ON_ERROR(hr)

    hr = pRenderClient->ReleaseBuffer(bufferFrameCount, flags);
    EXIT_ON_ERROR(hr)

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

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

    // Each loop fills about half of the shared buffer.
    while (flags != AUDCLNT_BUFFERFLAGS_SILENT)
    {
        // Sleep for half the buffer duration.
        Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

        // See how much buffer space is available.
        hr = pAudioClient->GetCurrentPadding(&numFramesPadding);
        EXIT_ON_ERROR(hr)

        numFramesAvailable = bufferFrameCount - numFramesPadding;

        // Grab all the available space in the shared buffer.
        hr = pRenderClient->GetBuffer(numFramesAvailable, &pData);
        EXIT_ON_ERROR(hr)

        // Get next 1/2-second of data from the audio source.
        hr = pMySource->LoadData(numFramesAvailable, pData, &flags);
        EXIT_ON_ERROR(hr)

        hr = pRenderClient->ReleaseBuffer(numFramesAvailable, flags);
        EXIT_ON_ERROR(hr)
    }

    // Wait for last data in buffer to play before stopping.
    Sleep((DWORD)(hnsActualDuration/REFTIMES_PER_MILLISEC/2));

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

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

    return hr;
}

Nell'esempio precedente, la funzione PlayAudioStream accetta un singolo parametro, , pMySourceche è un puntatore a un oggetto che appartiene a una classe definita dal client, MyAudioSource, con due funzioni membro, LoadData e SetFormat. Il codice di esempio non include l'implementazione di MyAudioSource perché:

  • Nessuno dei membri della classe comunica direttamente con uno dei metodi nelle interfacce in WASAPI.
  • La classe può essere implementata in diversi modi, a seconda dei requisiti del client. Ad esempio, potrebbe leggere i dati di rendering da un file WAV ed eseguire la conversione on-the-fly nel formato di flusso.

Tuttavia, alcune informazioni sul funzionamento delle due funzioni sono utili per comprendere l'esempio.

La funzione LoadData scrive un numero specificato di fotogrammi audio (primo parametro) in una posizione del buffer specificata (secondo parametro). Le dimensioni di un frame audio sono il numero di canali nel flusso moltiplicato per le dimensioni del campione. La funzione PlayAudioStream usa LoadData per riempire parti del buffer condiviso con dati audio. La funzione SetFormat specifica il formato per la funzione LoadData da utilizzare per i dati. Se la funzione LoadData è in grado di scrivere almeno un frame nella posizione del buffer specificata, ma esaurisce i dati prima che abbia scritto il numero specificato di fotogrammi, scrive il silenzio nei fotogrammi rimanenti.

Se LoadData riesce a scrivere almeno un frame di dati reali (non silenzio) nella posizione del buffer specificata, restituisce 0 tramite il terzo parametro, che, nell'esempio di codice precedente, è un puntatore di output alla flags variabile. Quando LoadData è fuori dai dati e non può scrivere anche un singolo frame nella posizione del buffer specificata, non scrive nulla nel buffer (nemmeno silenzio) e scrive il valore AUDCLNT_BUFFERFLAGS_SILENT nella flags variabile. La flags variabile trasmette questo valore al metodo IAudioRenderClient::ReleaseBuffer , che risponde riempiendo il numero specificato di fotogrammi nel buffer con silenzio.

Nella chiamata al metodo IAudioClient::Initialize , la funzione PlayAudioStream nell'esempio precedente richiede un buffer condiviso con durata di un secondo. Il buffer allocato potrebbe avere una durata leggermente più lunga. Nelle chiamate iniziali ai metodi IAudioRenderClient::GetBuffer e IAudioRenderClient::ReleaseBuffer, la funzione riempie l'intero buffer prima di chiamare il metodo IAudioClient::Start per iniziare a riprodurre il buffer.

All'interno del ciclo principale, la funzione riempie in modo iterativo la metà del buffer a intervalli di metà secondo. Subito prima di ogni chiamata alla funzione Windows Sleep nel ciclo main, il buffer è pieno o quasi pieno. Quando viene restituita la chiamata sospensione , il buffer è circa metà pieno. Il ciclo termina dopo la chiamata finale alla funzione LoadData imposta la flags variabile sul valore AUDCLNT_BUFFERFLAGS_SILENT. A questo punto, il buffer contiene almeno un frame di dati reali e può contenere fino a mezzo secondo di dati reali. Il resto del buffer contiene il silenzio. La chiamata Sospensione che segue il ciclo fornisce tempo sufficiente (mezzo secondo) per riprodurre tutti i dati rimanenti. Il silenzio che segue i dati impedisce suoni indesiderati prima della chiamata al metodo IAudioClient::Stop arresta il flusso audio. Per altre informazioni sulla sospensione, vedere la documentazione di Windows SDK.

Dopo la chiamata al metodo IAudioClient::Initialize, il flusso rimane aperto fino a quando il client rilascia tutti i riferimenti all'interfaccia IAudioClient e a tutti i riferimenti alle interfacce del servizio ottenute dal client tramite il metodo IAudioClient::GetService. La chiamata finale release chiude il flusso.

La funzione PlayAudioStream nell'esempio di codice precedente chiama la funzione CoCreateInstance per creare un enumeratore per i dispositivi endpoint audio nel sistema. A meno che il programma chiamante chiamato in precedenza la funzione CoCreateInstance o CoInitializeEx per inizializzare la libreria COM, la chiamata CoCreateInstance avrà esito negativo. Per altre informazioni su CoCreateInstance, CoCreateInstance e CoInitializeEx, vedere la documentazione di Windows SDK.

Gestione dei flussi