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.