Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Microphone input
#2
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.

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


Reply


Messages In This Thread
Microphone input - by Unseen Machine - 12-25-2025, 02:27 AM
RE: Microphone input - by Petr - 12-25-2025, 10:31 AM
RE: Microphone input - by Kernelpanic - 12-25-2025, 02:13 PM
RE: Microphone input - by Petr - 12-25-2025, 02:47 PM
RE: Microphone input - by Kernelpanic - 12-25-2025, 04:35 PM
RE: Microphone input - by Unseen Machine - 12-25-2025, 06:54 PM
RE: Microphone input - by Kernelpanic - 12-25-2025, 07:25 PM
RE: Microphone input - by Petr - 12-25-2025, 07:25 PM
RE: Microphone input - by Petr - 12-25-2025, 07:42 PM
RE: Microphone input - by Kernelpanic - 12-25-2025, 09:10 PM
RE: Microphone input - by Unseen Machine - 12-25-2025, 07:49 PM
RE: Microphone input - by Petr - 12-25-2025, 09:34 PM
RE: Microphone input - by Kernelpanic - 12-25-2025, 11:03 PM
RE: Microphone input - by Petr - 12-25-2025, 11:31 PM
RE: Microphone input - by Kernelpanic - 12-25-2025, 11:50 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  Let's take this input for a spin doppler 0 629 05-07-2022, 02:57 PM
Last Post: doppler

Forum Jump:


Users browsing this thread: 1 Guest(s)