Hi, your program works here as expected. Is microphone recording enabled in system settings?
But, try this (save it as microphone2.h), BAS source remains the same.
But, try this (save it as microphone2.h), BAS source remains the same.
Code: (Select All)
#ifndef QBLIVE_AUDIO_H
#define QBLIVE_AUDIO_H
// IMPORTANT: This is C++ (uses std::vector). QB64PE compiles with a C++ toolchain.
#include <windows.h>
#include <mmsystem.h>
#include <shellapi.h>
#include <vector>
#ifndef SAMPLE_RATE
#define SAMPLE_RATE 44100
#endif
#ifndef CHUNK_SAMPLES
#define CHUNK_SAMPLES 4096 // samples per buffer (mono, 16-bit)
#endif
#ifndef NUM_INPUT_BUFFERS
#define NUM_INPUT_BUFFERS 4 // >=2 recommended; 4..8 typical
#endif
// -----------------------------------------------------------------------------
// Internal state
// -----------------------------------------------------------------------------
static HWAVEIN g_hWaveIn = NULL;
static HWAVEOUT g_hWaveOut = NULL;
static WAVEFORMATEX g_wfx = { 0 };
static WAVEHDR g_inHdr[NUM_INPUT_BUFFERS];
static short g_inBuf[NUM_INPUT_BUFFERS][CHUNK_SAMPLES];
static std::vector<short> g_recorded; // shared between threads -> must lock
static std::vector<short> g_playback; // stable snapshot for async waveOut
static CRITICAL_SECTION g_cs;
static LONG g_csInit = 0;
static volatile LONG g_isInitialized = 0;
static volatile LONG g_isRecording = 0;
static volatile LONG g_isShuttingDown = 0;
static HANDLE g_worker = NULL;
static DWORD g_workerTid = 0;
static HANDLE g_workerReady = NULL;
static const UINT WM_QBLIVE_REQUEUE = (WM_APP + 0x334);
static WAVEHDR g_playHdr;
static LONG g_playHdrPrepared = 0;
static MMRESULT g_lastMM = MMSYSERR_NOERROR;
// -----------------------------------------------------------------------------
// Tiny helpers
// -----------------------------------------------------------------------------
static void qbl_lock() { if (InterlockedCompareExchange(&g_csInit, 0, 0)) EnterCriticalSection(&g_cs); }
static void qbl_unlock() { if (InterlockedCompareExchange(&g_csInit, 0, 0)) LeaveCriticalSection(&g_cs); }
static void qbl_set_last(MMRESULT mm) { g_lastMM = mm; }
// -----------------------------------------------------------------------------
// waveIn callback
// -----------------------------------------------------------------------------
/*
IMPORTANT FIX #1:
The original code called waveInAddBuffer() inside the waveInProc callback.
That is a known deadlock/reentrancy risk on some drivers.
Correct pattern:
- Do *minimal* work in the callback.
- Hand off the WAVEHDR pointer to a worker thread.
- The worker thread copies audio + calls waveInAddBuffer().
*/
static void CALLBACK qbl_waveInProc(HWAVEIN, UINT uMsg, DWORD_PTR, DWORD_PTR dwParam1, DWORD_PTR)
{
if (uMsg != WIM_DATA) return;
if (g_workerTid) PostThreadMessage(g_workerTid, WM_QBLIVE_REQUEUE, (WPARAM)dwParam1, 0);
}
// -----------------------------------------------------------------------------
// Worker thread proc
// -----------------------------------------------------------------------------
/*
IMPORTANT FIX #2:
std::vector is not thread-safe. The original code appended to recordedData in
the driver callback thread while QB code could read it concurrently.
That can cause reallocations + invalid pointers + crashes / silence.
Here we serialize access with a CRITICAL_SECTION and do the copy outside the callback.
*/
static DWORD WINAPI qbl_workerProc(void*)
{
// Ensure message queue exists
MSG msg;
PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE);
if (g_workerReady) SetEvent(g_workerReady);
while (GetMessage(&msg, NULL, 0, 0))
{
if (msg.message != WM_QBLIVE_REQUEUE) continue;
if (InterlockedCompareExchange(&g_isShuttingDown, 0, 0)) continue;
WAVEHDR* pHdr = (WAVEHDR*)msg.wParam;
if (!pHdr) continue;
if (InterlockedCompareExchange(&g_isRecording, 0, 0))
{
const int sampleCount = (int)(pHdr->dwBytesRecorded / sizeof(short));
if (sampleCount > 0)
{
qbl_lock();
g_recorded.insert(g_recorded.end(),
(short*)pHdr->lpData,
(short*)pHdr->lpData + sampleCount);
qbl_unlock();
}
// Re-queue buffer here (NOT in callback)
waveInAddBuffer(g_hWaveIn, pHdr, sizeof(WAVEHDR));
}
}
return 0;
}
// =============================================================================
// Public API for QB64 (must be C linkage, no name mangling)
// =============================================================================
extern "C" {
// Optional debug helpers (not required by your BAS file)
int GetLastMMResult()
{
return (int)g_lastMM;
}
void GetLastMMErrorTextA(char* outText, int outTextBytes)
{
if (!outText || outTextBytes <= 0) return;
outText[0] = 0;
char tmp[256] = { 0 };
if (waveInGetErrorTextA(g_lastMM, tmp, (UINT)sizeof(tmp)) == MMSYSERR_NOERROR ||
waveOutGetErrorTextA(g_lastMM, tmp, (UINT)sizeof(tmp)) == MMSYSERR_NOERROR)
{
lstrcpynA(outText, tmp, outTextBytes);
}
else
{
lstrcpynA(outText, "Unknown MMRESULT", outTextBytes);
}
}
int InitAudio()
{
if (InterlockedCompareExchange(&g_isInitialized, 0, 0))
return 0;
g_lastMM = MMSYSERR_NOERROR;
if (!InterlockedCompareExchange(&g_csInit, 1, 0))
InitializeCriticalSection(&g_cs);
// Start worker thread
g_workerReady = CreateEvent(NULL, TRUE, FALSE, NULL);
g_worker = CreateThread(NULL, 0, qbl_workerProc, NULL, 0, &g_workerTid);
if (!g_worker)
{
qbl_set_last(MMSYSERR_ERROR);
return (int)MMSYSERR_ERROR;
}
WaitForSingleObject(g_workerReady, INFINITE);
CloseHandle(g_workerReady);
g_workerReady = NULL;
// CD-ish format: Mono, 44100Hz, 16-bit PCM
g_wfx.wFormatTag = WAVE_FORMAT_PCM;
g_wfx.nChannels = 1;
g_wfx.nSamplesPerSec = SAMPLE_RATE;
g_wfx.wBitsPerSample = 16;
g_wfx.nBlockAlign = (g_wfx.nChannels * g_wfx.wBitsPerSample) / 8;
g_wfx.nAvgBytesPerSec = g_wfx.nSamplesPerSec * g_wfx.nBlockAlign;
g_wfx.cbSize = 0;
// Open Microphone
MMRESULT res = waveInOpen(&g_hWaveIn, WAVE_MAPPER, &g_wfx, (DWORD_PTR)qbl_waveInProc, 0, CALLBACK_FUNCTION);
if (res != MMSYSERR_NOERROR) { qbl_set_last(res); return (int)res; }
// Prepare multiple input buffers (IMPORTANT FIX #3: not just one)
ZeroMemory(g_inHdr, sizeof(g_inHdr));
for (int i = 0; i < NUM_INPUT_BUFFERS; i++)
{
g_inHdr[i].lpData = (LPSTR)g_inBuf[i];
g_inHdr[i].dwBufferLength = (DWORD)sizeof(g_inBuf[i]);
g_inHdr[i].dwFlags = 0;
res = waveInPrepareHeader(g_hWaveIn, &g_inHdr[i], sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) { qbl_set_last(res); return (int)res; }
}
// Open Speakers
res = waveOutOpen(&g_hWaveOut, WAVE_MAPPER, &g_wfx, 0, 0, CALLBACK_NULL);
if (res != MMSYSERR_NOERROR) { qbl_set_last(res); return (int)res; }
ZeroMemory(&g_playHdr, sizeof(g_playHdr));
InterlockedExchange(&g_playHdrPrepared, 0);
InterlockedExchange(&g_isShuttingDown, 0);
InterlockedExchange(&g_isInitialized, 1);
return 0;
}
void StartRecord()
{
if (!InterlockedCompareExchange(&g_isInitialized, 0, 0)) return;
qbl_lock();
g_recorded.clear();
qbl_unlock();
// Queue buffers BEFORE starting
for (int i = 0; i < NUM_INPUT_BUFFERS; i++)
waveInAddBuffer(g_hWaveIn, &g_inHdr[i], sizeof(WAVEHDR));
InterlockedExchange(&g_isRecording, 1);
waveInStart(g_hWaveIn);
}
void StopRecord()
{
if (!InterlockedCompareExchange(&g_isInitialized, 0, 0)) return;
InterlockedExchange(&g_isRecording, 0);
waveInStop(g_hWaveIn);
waveInReset(g_hWaveIn);
}
int GetRecordedSize()
{
int n = 0;
qbl_lock();
n = (int)g_recorded.size();
qbl_unlock();
return n;
}
void PlayRecording()
{
if (!InterlockedCompareExchange(&g_isInitialized, 0, 0)) return;
/*
IMPORTANT FIX #4:
waveOutWrite is asynchronous. The original code passed recordedData.data()
directly; but recordedData can reallocate / clear later -> invalid pointer.
We snapshot to g_playback so the memory remains stable during playback.
*/
qbl_lock();
g_playback = g_recorded;
qbl_unlock();
if (g_playback.empty()) return;
// Stop current playback if any
waveOutReset(g_hWaveOut);
if (InterlockedCompareExchange(&g_playHdrPrepared, 0, 0))
{
waveOutUnprepareHeader(g_hWaveOut, &g_playHdr, sizeof(WAVEHDR));
InterlockedExchange(&g_playHdrPrepared, 0);
}
ZeroMemory(&g_playHdr, sizeof(g_playHdr));
g_playHdr.lpData = (LPSTR)g_playback.data();
g_playHdr.dwBufferLength = (DWORD)(g_playback.size() * sizeof(short));
g_playHdr.dwFlags = 0;
MMRESULT res = waveOutPrepareHeader(g_hWaveOut, &g_playHdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) { qbl_set_last(res); return; }
InterlockedExchange(&g_playHdrPrepared, 1);
res = waveOutWrite(g_hWaveOut, &g_playHdr, sizeof(WAVEHDR));
if (res != MMSYSERR_NOERROR) { qbl_set_last(res); return; }
}
void OpenMicSettings()
{
// If Windows privacy blocks the mic, capture APIs will appear to "work" but deliver silence.
ShellExecuteA(NULL, "open", "ms-settings:privacy-microphone", NULL, NULL, SW_SHOWNORMAL);
}
void ShutdownAudio()
{
if (!InterlockedCompareExchange(&g_isInitialized, 0, 0)) return;
InterlockedExchange(&g_isShuttingDown, 1);
InterlockedExchange(&g_isRecording, 0);
if (g_hWaveIn)
{
waveInStop(g_hWaveIn);
waveInReset(g_hWaveIn);
for (int i = 0; i < NUM_INPUT_BUFFERS; i++)
waveInUnprepareHeader(g_hWaveIn, &g_inHdr[i], sizeof(WAVEHDR));
waveInClose(g_hWaveIn);
g_hWaveIn = NULL;
}
if (g_hWaveOut)
{
waveOutReset(g_hWaveOut);
if (InterlockedCompareExchange(&g_playHdrPrepared, 0, 0))
{
waveOutUnprepareHeader(g_hWaveOut, &g_playHdr, sizeof(WAVEHDR));
InterlockedExchange(&g_playHdrPrepared, 0);
}
waveOutClose(g_hWaveOut);
g_hWaveOut = NULL;
}
// Stop worker thread
if (g_workerTid) PostThreadMessage(g_workerTid, WM_QUIT, 0, 0);
if (g_worker)
{
WaitForSingleObject(g_worker, INFINITE);
CloseHandle(g_worker);
g_worker = NULL;
}
g_workerTid = 0;
qbl_lock();
g_recorded.clear();
g_playback.clear();
qbl_unlock();
InterlockedExchange(&g_isInitialized, 0);
if (InterlockedCompareExchange(&g_csInit, 0, 0))
{
DeleteCriticalSection(&g_cs);
InterlockedExchange(&g_csInit, 0);
}
}
// QB64 helper: fills a float buffer (mono) for _SNDRAW
void Audio_Get_SndRaw(float* buffer, int offset, int count)
{
if (!buffer || count <= 0) return;
if (offset < 0) offset = 0;
qbl_lock();
const int n = (int)g_recorded.size();
for (int i = 0; i < count; i++)
{
const int idx = offset + i;
if (idx < n)
buffer[i] = (float)g_recorded[idx] / 32768.0f; // [-1, 1)
else
buffer[i] = 0.0f;
}
qbl_unlock();
}
} // extern "C"
#endif // QBLIVE_AUDIO_H
