ADDED: Audio recording

This commit is contained in:
Jairo Correa 2026-06-27 16:35:59 -03:00
parent 67359073ca
commit 283843f0e7
4 changed files with 364 additions and 1 deletions

View File

@ -0,0 +1,148 @@
/*******************************************************************************************
*
* raylib [audio] example - stream recording
*
* Example complexity rating: [] 3/4
*
* Example originally created with raylib 6.0, last time updated with raylib 6.0
*
* Example created by Jairo Correa (@jn-jairo) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2026 Jairo Correa (@jn-jairo)
*
********************************************************************************************/
#include "raylib.h"
#include <stdlib.h> // Required for: NULL
#include <string.h> // Required for: memcpy()
//----------------------------------------------------------------------------------
// Global Variables Definition
//----------------------------------------------------------------------------------
static unsigned int maxFrameCount = 0;
static Wave wave = { 0 };
//------------------------------------------------------------------------------------
// Module Functions Declaration
//------------------------------------------------------------------------------------
static void RecordingCallback(const void *framesIn, unsigned int frameCount);
//------------------------------------------------------------------------------------
// Program main entry point
//------------------------------------------------------------------------------------
int main(void)
{
// Initialization
//--------------------------------------------------------------------------------------
const int screenWidth = 800;
const int screenHeight = 450;
InitWindow(screenWidth, screenHeight, "raylib [audio] example - stream recording");
InitAudioDevice();
InitAudioRecordingDevice();
// InitAudioRecordingDeviceEx(24000, 16, 1);
// Configure it so that RecordingCallback is called whenever new samples are read from the microphone
SetAudioRecordingCallback(RecordingCallback);
// Configure the wave to match the recording
wave.sampleRate = GetAudioRecordingSampleRate();
wave.sampleSize = GetAudioRecordingSampleSize();
wave.channels = GetAudioRecordingChannels();
// Allocate buffer for the audio recording
maxFrameCount = wave.sampleRate*5; // 5 seconds
wave.data = RL_CALLOC(maxFrameCount*wave.channels, wave.sampleSize/8);
Sound sound = { 0 };
float timeRecorded = 0.0f;
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
// Update
//----------------------------------------------------------------------------------
// Space key pressed or the recording buffer is full
if (IsKeyPressed(KEY_SPACE) || (IsAudioRecording() && (wave.frameCount == maxFrameCount)))
{
if (IsAudioRecording())
{
StopAudioRecording();
// Reload and play the sound
UnloadSound(sound);
sound = LoadSoundFromWave(wave);
PlaySound(sound);
}
else
{
// Stop the sound and reset the recording frameCount
StopSound(sound);
wave.frameCount = 0;
StartAudioRecording();
}
}
// Get timeRecorded scaled to bar dimensions
timeRecorded = ((float)wave.frameCount/(float)maxFrameCount)*(screenWidth - 40);
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
ClearBackground(RAYWHITE);
// Draw time bar
DrawRectangle(20, screenHeight - 20 - 12, screenWidth - 40, 12, LIGHTGRAY);
DrawRectangle(20, screenHeight - 20 - 12, (int)timeRecorded, 12, MAROON);
DrawRectangleLines(20, screenHeight - 20 - 12, screenWidth - 40, 12, GRAY);
// Draw help instructions
DrawText("Press SPACE to START/STOP recording the sound!", 130, 180, 20, LIGHTGRAY);
EndDrawing();
//----------------------------------------------------------------------------------
}
// De-Initialization
//--------------------------------------------------------------------------------------
CloseAudioRecordingDevice(); // Close audio recording device (recording is automatically stopped)
CloseAudioDevice(); // Close audio device (music streaming is automatically stopped)
UnloadSound(sound);
UnloadWave(wave);
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
//------------------------------------------------------------------------------------
// Module Functions Definition
//------------------------------------------------------------------------------------
static void RecordingCallback(const void *framesIn, unsigned int frameCount)
{
// Check how many frames left in the buffer
unsigned int framesLeft = maxFrameCount - wave.frameCount;
// Do not overflow the buffer
if (framesLeft == 0) return;
unsigned int framesToCopy = (frameCount < framesLeft)? frameCount : framesLeft;
// Append the new data
memcpy((unsigned char *)wave.data + wave.frameCount*wave.channels*wave.sampleSize/8, framesIn, framesToCopy*wave.channels*wave.sampleSize/8);
wave.frameCount += framesToCopy;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -390,6 +390,13 @@ typedef struct AudioData {
size_t pcmBufferSize; // Pre-allocated buffer size
void *pcmBuffer; // Pre-allocated buffer to read audio data from file/memory
} System;
struct {
ma_context context; // miniaudio context data
ma_device device; // miniaudio device
ma_mutex lock; // miniaudio mutex lock
bool isReady; // Check if audio device is ready
AudioRecordingCallback callback; // Recording callback function
} RecordingSystem;
struct {
AudioBuffer *first; // Pointer to first AudioBuffer in the list
AudioBuffer *last; // Pointer to last AudioBuffer in the list
@ -421,6 +428,7 @@ static ma_uint32 ReadAudioBufferFramesInInternalFormat(AudioBuffer *audioBuffer,
static ma_uint32 ReadAudioBufferFramesInMixingFormat(AudioBuffer *audioBuffer, float *framesOut, ma_uint32 frameCount);
static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
static void OnReceiveAudioDataFromDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount);
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer);
static bool IsAudioBufferPlayingInLockedState(AudioBuffer *buffer);
@ -2369,6 +2377,181 @@ void DetachAudioMixedProcessor(AudioCallback process)
ma_mutex_unlock(&AUDIO.System.lock);
}
//----------------------------------------------------------------------------------
// Module Functions Definition - Audio Recording Device initialization and closing
//----------------------------------------------------------------------------------
// Initialize audio recording device with custom sampleRate, sampleSize and channels
void InitAudioRecordingDeviceEx(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels)
{
// Init audio context
ma_context_config ctxConfig = ma_context_config_init();
ma_log_callback_init(OnLog, NULL);
ma_result result = ma_context_init(NULL, 0, &ctxConfig, &AUDIO.RecordingSystem.context);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize recording context");
return;
}
ma_format format = ((sampleSize == 8)? ma_format_u8 : ((sampleSize == 16)? ma_format_s16 : ma_format_f32));
// Init audio device
// NOTE: Using the default device
ma_device_config config = ma_device_config_init(ma_device_type_capture);
config.capture.pDeviceID = NULL; // NULL for the default capture AUDIO.RecordingSystem.device
config.capture.format = format;
config.capture.channels = channels;
config.sampleRate = sampleRate;
config.periodSizeInFrames = AUDIO_DEVICE_PERIOD_SIZE_IN_FRAMES;
config.dataCallback = OnReceiveAudioDataFromDevice;
config.pUserData = NULL;
config.noFixedSizedCallback = true; // raylib does not require fixed sized callback guarantees. This bypasses an internal intermediary buffer
result = ma_device_init(&AUDIO.RecordingSystem.context, &config, &AUDIO.RecordingSystem.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to initialize recording device");
ma_context_uninit(&AUDIO.RecordingSystem.context);
return;
}
// Recording happens on a separate thread which means synchronization is needed
// A mutex is used here to make things simple, but may want to look at something
// a bit smarter later on to keep everything real-time, if that's necessary
if (ma_mutex_init(&AUDIO.RecordingSystem.lock) != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to create mutex for recording");
ma_device_uninit(&AUDIO.RecordingSystem.device);
ma_context_uninit(&AUDIO.RecordingSystem.context);
return;
}
TRACELOG(LOG_INFO, "AUDIO: Recording device initialized successfully");
TRACELOG(LOG_INFO, " > Backend: miniaudio | %s", ma_get_backend_name(AUDIO.RecordingSystem.context.backend));
TRACELOG(LOG_INFO, " > Format: %s -> %s", ma_get_format_name(AUDIO.RecordingSystem.device.capture.format), ma_get_format_name(AUDIO.RecordingSystem.device.capture.internalFormat));
TRACELOG(LOG_INFO, " > Channels: %d -> %d", AUDIO.RecordingSystem.device.capture.channels, AUDIO.RecordingSystem.device.capture.internalChannels);
TRACELOG(LOG_INFO, " > Sample rate: %d -> %d", AUDIO.RecordingSystem.device.sampleRate, AUDIO.RecordingSystem.device.capture.internalSampleRate);
TRACELOG(LOG_INFO, " > Periods size: %d", AUDIO.RecordingSystem.device.capture.internalPeriodSizeInFrames*AUDIO.RecordingSystem.device.capture.internalPeriods);
AUDIO.RecordingSystem.isReady = true;
}
// Initialize audio recording device
void InitAudioRecordingDevice(void)
{
unsigned int sampleSize = ((AUDIO_DEVICE_FORMAT == ma_format_u8)? 8 : ((AUDIO_DEVICE_FORMAT == ma_format_s16)? 16 : 32));
InitAudioRecordingDeviceEx(AUDIO_DEVICE_SAMPLE_RATE, sampleSize, AUDIO_DEVICE_CHANNELS);
}
// Close the audio recording device for all contexts
void CloseAudioRecordingDevice(void)
{
if (AUDIO.RecordingSystem.isReady)
{
ma_mutex_uninit(&AUDIO.RecordingSystem.lock);
ma_device_uninit(&AUDIO.RecordingSystem.device);
ma_context_uninit(&AUDIO.RecordingSystem.context);
AUDIO.RecordingSystem.isReady = false;
TRACELOG(LOG_INFO, "AUDIO: Recording device closed successfully");
}
else TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be closed, not currently initialized");
}
// Check if recording device has been initialized successfully
bool IsAudioRecordingDeviceReady(void)
{
return AUDIO.RecordingSystem.isReady;
}
// Get recording sample rate
unsigned int GetAudioRecordingSampleRate(void)
{
return AUDIO.RecordingSystem.device.sampleRate;
}
// Get recording sample size in bits
unsigned int GetAudioRecordingSampleSize(void)
{
return 8 * ma_get_bytes_per_sample(AUDIO.RecordingSystem.device.capture.format);
}
// Get recording channels
unsigned int GetAudioRecordingChannels(void)
{
return AUDIO.RecordingSystem.device.capture.channels;
}
// Set master volume (recording)
void SetMasterRecordingVolume(float volume)
{
ma_device_set_master_volume(&AUDIO.RecordingSystem.device, volume);
}
// Get master volume (recording)
float GetMasterRecordingVolume(void)
{
float volume = 0.0f;
ma_device_get_master_volume(&AUDIO.RecordingSystem.device, &volume);
return volume;
}
// Audio recording callback to receive new data
void SetAudioRecordingCallback(AudioRecordingCallback callback)
{
ma_mutex_lock(&AUDIO.RecordingSystem.lock);
AUDIO.RecordingSystem.callback = callback;
ma_mutex_unlock(&AUDIO.RecordingSystem.lock);
}
// Start audio recording
void StartAudioRecording(void)
{
if (!AUDIO.RecordingSystem.isReady)
{
TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be started, not currently initialized");
return;
}
ma_result result = ma_device_start(&AUDIO.RecordingSystem.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to start recording device");
return;
}
}
// Stop audio recording
void StopAudioRecording(void)
{
if (!AUDIO.RecordingSystem.isReady)
{
TRACELOG(LOG_WARNING, "AUDIO: Recording device could not be stopped, not currently initialized");
return;
}
ma_result result = ma_device_stop(&AUDIO.RecordingSystem.device);
if (result != MA_SUCCESS)
{
TRACELOG(LOG_WARNING, "AUDIO: Failed to stop recording device");
return;
}
}
// Check if device is recording
bool IsAudioRecording(void)
{
if (!AUDIO.RecordingSystem.isReady)
{
return false;
}
return ma_device_is_started(&AUDIO.RecordingSystem.device);
}
//----------------------------------------------------------------------------------
// Module Internal Functions Definition
//----------------------------------------------------------------------------------
@ -2653,6 +2836,22 @@ static void OnSendAudioDataToDevice(ma_device *pDevice, void *pFramesOut, const
ma_mutex_unlock(&AUDIO.System.lock);
}
// Sending audio data to device callback function
// This function will be called when miniaudio sends more data
static void OnReceiveAudioDataFromDevice(ma_device *pDevice, void *pFramesOut, const void *pFramesInput, ma_uint32 frameCount)
{
AudioRecordingCallback callback = NULL;
ma_mutex_lock(&AUDIO.RecordingSystem.lock);
callback = AUDIO.RecordingSystem.callback;
ma_mutex_unlock(&AUDIO.RecordingSystem.lock);
if (callback)
{
callback(pFramesInput, frameCount);
}
}
// Main mixing function, pretty simple in this project, only an accumulation
// NOTE: framesOut is both an input and an output, it is initially filled with zeros outside of this function
static void MixAudioFrames(float *framesOut, const float *framesIn, ma_uint32 frameCount, AudioBuffer *buffer)

View File

@ -1663,9 +1663,10 @@ RLAPI RayCollision GetRayCollisionTriangle(Ray ray, Vector3 p1, Vector3 p2, Vect
RLAPI RayCollision GetRayCollisionQuad(Ray ray, Vector3 p1, Vector3 p2, Vector3 p3, Vector3 p4); // Get collision info between ray and quad
//------------------------------------------------------------------------------------
// Audio Loading and Playing Functions (Module: audio)
// Audio Loading, Playing and Recording Functions (Module: audio)
//------------------------------------------------------------------------------------
typedef void (*AudioCallback)(void *bufferData, unsigned int frames);
typedef void (*AudioRecordingCallback)(const void *bufferData, unsigned int frames);
// Audio device management functions
RLAPI void InitAudioDevice(void); // Initialize audio device and context
@ -1674,6 +1675,21 @@ RLAPI bool IsAudioDeviceReady(void); // Check i
RLAPI void SetMasterVolume(float volume); // Set master volume (listener)
RLAPI float GetMasterVolume(void); // Get master volume (listener)
// Audio recording device management functions
RLAPI void InitAudioRecordingDevice(void); // Initialize audio recording device
RLAPI void InitAudioRecordingDeviceEx(unsigned int sampleRate, unsigned int sampleSize, unsigned int channels); // Initialize audio recording device with custom sampleRate, sampleSize and channels
RLAPI void CloseAudioRecordingDevice(void); // Close the audio recording device for all contexts
RLAPI bool IsAudioRecordingDeviceReady(void); // Check if recording device has been initialized successfully
RLAPI unsigned int GetAudioRecordingSampleRate(void); // Get recording sample rate
RLAPI unsigned int GetAudioRecordingSampleSize(void); // Get recording sample size in bits
RLAPI unsigned int GetAudioRecordingChannels(void); // Get recording channels
RLAPI void SetMasterRecordingVolume(float volume); // Set master volume (recording)
RLAPI float GetMasterRecordingVolume(void); // Get master volume (recording)
RLAPI void SetAudioRecordingCallback(AudioRecordingCallback callback); // Audio recording callback to receive new data
RLAPI void StartAudioRecording(void); // Start audio recording
RLAPI void StopAudioRecording(void); // Stop audio recording
RLAPI bool IsAudioRecording(void); // Check if device is recording
// Wave/Sound loading/unloading functions
RLAPI Wave LoadWave(const char *fileName); // Load wave data from file
RLAPI Wave LoadWaveFromMemory(const char *fileType, const unsigned char *fileData, int dataSize); // Load wave from memory buffer, fileType refers to extension: i.e. '.wav'