jeudi 27 septembre 2018

No sound output in WASAPI shared event driven mode

I have been hammering away at this for the last 24 hours, but no matter what I do, I can't get any sound output through WASAPI (Tried all 3 IAudioClient interfaces, and in the end, I decided to stick with the most recent one). I'm not getting any errors, and everything looks normal on the debugger. Heh, I guess that's what happens when you are just reading docs left and right and hacking code together without having the slightest clue what you are doing.

For the most part, I think my decoder code is correct, unless I misunderstood something. According to the libFLAC docs the "buffer parameter" of the write_callback is an array of signed samples of length frame->header.blocksize. Which matches up with what the Microsoft docs had to say about it

Anyhow, here's my (extremely hacky) code:

This is what my header file looks like:

#pragma once
#include <iostream>
#define NOMINMAX
#include <Mmdeviceapi.h>
#include <Audioclient.h>
#include <limits>

#include <FLAC++/decoder.h>

class WASAPIBackend
{
public:
    WASAPIBackend();
    ~WASAPIBackend();
private:
    HRESULT hr;
    IMMDeviceEnumerator* pEnumerator;
    IMMDevice* pDevice;
    IAudioClient3* pAudioClient;
    IAudioRenderClient* pAudioRenderClient;
    WAVEFORMATEX* pMixFormat;
    uint32_t defaultPeriodInFrames, fundamentalPeriodInFrames, minPeriodInFrames, maxPeriodInFrames;
};

And this is what my cpp file looks like:

#include "WASAPIBackend.h"

// TODO: Fix this mess.
BYTE* m_audioBuf = nullptr;
int32_t* m_audioFrame = nullptr;
size_t currentFrame = 0;

class FLACDecoder : public FLAC::Decoder::File {
    virtual ::FLAC__StreamDecoderWriteStatus write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]);
    virtual void metadata_callback(const ::FLAC__StreamMetadata *metadata);
    virtual void error_callback(::FLAC__StreamDecoderErrorStatus status);

};

::FLAC__StreamDecoderWriteStatus FLACDecoder::write_callback(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) {
    // Audio frame: size of an audio frame is the sample size multiplied by the number of channels in the stream.

    //memmove(m_audioFrame, buffer[0], frame->header.blocksize);
    // printf("Uniplemented - %llu\n", frame->header.channels * frame->header.blocksize);
    m_audioFrame = new int32_t[frame->header.blocksize * frame->header.channels * sizeof(int16_t*)];
    /*memcpy(m_audioFrame, buffer, frame->header.blocksize * frame->header.channels);

    return FLAC__StreamDecoderWriteStatus::FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;*/ 

    // Code below inspired by SDL_Mixer. (Remember to give proper credit later if I decide to stick with it!!!)

    int16_t *data; // TODO:: Switch to smart pointers once everything works.
    unsigned int i, j, channels;
    int shift_amount = 0;

    if (!is_valid()) {
        return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
    }

    switch (get_bits_per_sample()) {
    case 16:
        shift_amount = 0;
        break;
    case 20:
        shift_amount = 4;
        break;
    case 24:
        shift_amount = 8;
        break;
    default:
        fprintf(stderr, "FLAC decoder doesn't support %d bits_per_sample", get_bits_per_sample());
        return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
    }

    if (get_channels() == 3) {
        /* We'll just drop the center channel for now */
        channels = 2;
    }
    else {
        channels = get_channels();
    }

    data = new int16_t[frame->header.blocksize * channels];
    if (!data) {
        fprintf(stderr, "Couldn't allocate %d bytes stack memory", (int)(frame->header.blocksize * channels * sizeof(*data)));
        return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
    }

    if (get_channels() == 3) {
        int16_t *dst = data;
        for (i = 0; i < frame->header.blocksize; ++i) {
            int16_t FL = (buffer[0][i] >> shift_amount);
            int16_t FR = (buffer[1][i] >> shift_amount);
            int16_t FCmix = (int16_t)((buffer[2][i] >> shift_amount) * 0.5f);
            int sample;

            sample = (FL + FCmix);
            if (sample > std::numeric_limits<int16_t>::max()) {
                *dst = std::numeric_limits<int16_t>::max();
            }
            else if (sample < std::numeric_limits<int16_t>::min()) {
                *dst = std::numeric_limits<int16_t>::min();
            }
            else {
                *dst = sample;
            }
            ++dst;

            sample = (FR + FCmix);
            if (sample > std::numeric_limits<int16_t>::max()) {
                *dst = std::numeric_limits<int16_t>::max();
            }
            else if (sample < std::numeric_limits<int16_t>::min()) {
                *dst = std::numeric_limits<int16_t>::min();
            }
            else {
                *dst = sample;
            }
            ++dst;
        }
    }
    else {
        for (i = 0; i < channels; ++i) {
            int16_t *dst = data + i;
            for (j = 0; j < frame->header.blocksize; ++j) {
                *dst = (buffer[i][j] >> shift_amount);
                dst += channels;
            }
        }
    }

    // Supposedly, the audio *should* be interleaved
    memcpy(m_audioFrame, data, (frame->header.blocksize * channels * sizeof(*data)));

    delete[] data;

    return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
}

void FLACDecoder::metadata_callback(const ::FLAC__StreamMetadata *metadata)
{
    /* print some stats */
    if (metadata->type == FLAC__METADATA_TYPE_STREAMINFO) {
        /* save for later */
        uint64_t total_samples = metadata->data.stream_info.total_samples;
        unsigned int sample_rate = metadata->data.stream_info.sample_rate;
        unsigned int channels = metadata->data.stream_info.channels;
        unsigned int bps = metadata->data.stream_info.bits_per_sample;


        fprintf(stderr, "blocksize    : %u bytes\n", metadata->data.stream_info.max_blocksize);
        fprintf(stderr, "sample rate    : %u Hz\n", sample_rate);
        fprintf(stderr, "channels       : %u\n", channels);
        fprintf(stderr, "bits per sample: %u\n", bps);
        fprintf(stderr, "total samples  : %llu\n", total_samples);
    }
}

void FLACDecoder::error_callback(::FLAC__StreamDecoderErrorStatus status)
{
    fprintf(stderr, "Got error callback: %d\n", status/*FLAC__StreamDecoderErrorStatusString[status]*/);
}

/* Helper function to fill up the audio buffer up to n frames. As the FLAC decoder can only do
    a whole file at once, or frame by frame */ 
void fillBuffer(FLACDecoder &d, uint32_t frames) {
    for (size_t i = 0; i < frames; ++i) {
        d.process_single();
        memcpy(&m_audioBuf[i], &m_audioFrame, d.get_blocksize() * d.get_channels());
    }
}

constexpr void SafeRelease(IUnknown** p) {
    if (p) {
        (*p)->Release();
    }
}

// TODO: Move everything out of the constructor once playback works.
// TODO: Have the class create a thread, instead of the burden of doing so being on the user.
WASAPIBackend::WASAPIBackend() : hr(0), pEnumerator(nullptr), pDevice(nullptr), pAudioClient(nullptr), pAudioRenderClient(nullptr), pMixFormat(nullptr)
{
    CoInitializeEx(nullptr, COINIT_MULTITHREADED);

    hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), reinterpret_cast<void**>(&pEnumerator));
    if (FAILED(hr)) {
        printf("Got error: %x, while creating MMDeviceEnumerator\n", hr);
    }

    hr = pEnumerator->GetDefaultAudioEndpoint(EDataFlow::eRender, ERole::eConsole, &pDevice);

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting to fetch default audio endpoint (render)\n", hr);
    }

    hr = pDevice->Activate(__uuidof(IAudioClient3), CLSCTX_ALL, NULL, reinterpret_cast<void**>(&pAudioClient));

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting activate IAudioClient3 (render)\n", hr);
    }

    // The sample file is a 2 channel, 16bit, 41000hz FLAC. Since this fetches the audio engine format, it should be fine for now.
    // Not that it would matter according to the docs. In shared mode, Windows takes care of converting to and from the desired audio format.
    hr = pAudioClient->GetMixFormat(&pMixFormat);


    if (FAILED(hr)) {
        printf("Got error: %x, while attempting get mix format (render)\n", hr);
    }

    hr = pAudioClient->GetSharedModeEnginePeriod(pMixFormat, &defaultPeriodInFrames, &fundamentalPeriodInFrames, &minPeriodInFrames, &maxPeriodInFrames);

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting get shared mode periodicities (render)\n", hr);
    }

    hr = pAudioClient->InitializeSharedAudioStream(AUDCLNT_STREAMFLAGS_EVENTCALLBACK, minPeriodInFrames, pMixFormat, nullptr);

    if (FAILED(hr)) {
        printf("Got error: %x, while initializing shared audio stream (render)\n", hr);
    }

    HANDLE audioSamplesReadyEvent = CreateEventEx(NULL, NULL, 0, SYNCHRONIZE | EVENT_MODIFY_STATE);

    if (!audioSamplesReadyEvent) {
        printf("Got error: %x, attempting to create event\n", GetLastError());
    }

    hr = pAudioClient->SetEventHandle(audioSamplesReadyEvent);

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting to set event handle (render)\n", hr);
    }

    hr = pAudioClient->GetService(__uuidof(IAudioRenderClient), reinterpret_cast<void**>(&pAudioRenderClient));

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting to GetService(IAudioRenderClient) (render)\n", hr);
    }

    uint32_t bufferSize = 0;
    uint32_t safeToWrite = 0;
    uint32_t padding = 0;
    pAudioClient->GetCurrentPadding(&padding);
    pAudioClient->GetBufferSize(&bufferSize);

    // TODO: This is garbage, figure out how to organize the code at a later time.
    FLACDecoder d;
    // I got this file from OCRemix, remember to thank them later.
    d.init("audio/07 DaMonz - Choose Your Destiny (Super Smash Bros. Melee).flac");

    d.process_until_end_of_metadata();

    // Grab the entire buffer for the initial fill operation.
    hr = pAudioRenderClient->GetBuffer(bufferSize, &m_audioBuf);

    if (FAILED(hr)) {
        printf("Got error: %x, while retrieving audio buffer (render)\n", hr);
    }

    fillBuffer(d, bufferSize);

    hr = pAudioRenderClient->ReleaseBuffer(bufferSize, 0);

    if (FAILED(hr)) {
        printf("Got error: %x, while attempting to release audio buffer (render)\n", hr);
    }

    pAudioClient->Start();

    // TODO: Infinite loops without ending conditions are bad, so fix it up once everything works.
    while (true) {
        // In theory, this will only hang if the audio client hasn't started playback.
        WaitForSingleObject(audioSamplesReadyEvent, INFINITE);

        pAudioClient->GetCurrentPadding(&padding);

        // printf("Current padding: %d\n", padding);

        safeToWrite = bufferSize - padding;

        hr = pAudioRenderClient->GetBuffer(safeToWrite, &m_audioBuf);

        if (FAILED(hr)) {
            printf("Got error: %x, while retrieving audio buffer (rendering)\n", hr);
        }

        if (FAILED(hr)) {
            printf("Got error: %x, while retrieving current audio buffer padding (rendering)\n", hr);
        }

        // Fill all the "safe space" in the buffer with new data.
        fillBuffer(d, safeToWrite);

        hr = pAudioRenderClient->ReleaseBuffer(safeToWrite, 0);

        if (FAILED(hr)) {
            printf("Got error: %x, while attempting to release audio buffer (render)\n", hr);
        }
    }

    pAudioClient->Stop();

}

WASAPIBackend::~WASAPIBackend()
{
    CoTaskMemFree(pMixFormat);
    SafeRelease(reinterpret_cast<IUnknown**>(&pAudioRenderClient));
    SafeRelease(reinterpret_cast<IUnknown**>(&pAudioClient));
    SafeRelease(reinterpret_cast<IUnknown**>(&pDevice));
    SafeRelease(reinterpret_cast<IUnknown**>(&pEnumerator));
    CoUninitialize();
}

Sorry for the massive blocks of code, If I knew what was wrong, I would have sent the suspect code only. I'm getting mixed signals all over the place. According to my console output, it should be working, as I get no errors, and I get this: relevant console output which is the output from the FLAC decoder metadata_callback.

Aucun commentaire:

Enregistrer un commentaire