From 283843f0e7e7506790313e4f228be5fdfd2fb022 Mon Sep 17 00:00:00 2001 From: Jairo Correa Date: Sat, 27 Jun 2026 16:35:59 -0300 Subject: [PATCH] ADDED: Audio recording --- examples/audio/audio_stream_recording.c | 148 ++++++++++++++++ examples/audio/audio_stream_recording.png | Bin 0 -> 15234 bytes src/raudio.c | 199 ++++++++++++++++++++++ src/raylib.h | 18 +- 4 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 examples/audio/audio_stream_recording.c create mode 100644 examples/audio/audio_stream_recording.png diff --git a/examples/audio/audio_stream_recording.c b/examples/audio/audio_stream_recording.c new file mode 100644 index 000000000..636b053d7 --- /dev/null +++ b/examples/audio/audio_stream_recording.c @@ -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 // Required for: NULL +#include // 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; +} diff --git a/examples/audio/audio_stream_recording.png b/examples/audio/audio_stream_recording.png new file mode 100644 index 0000000000000000000000000000000000000000..2fffd76e78809a4c6dc557d05cbdb1c1c91c982d GIT binary patch literal 15234 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYU_8XZ1{4ubUVDOp!P?W)#WAEJF8R-|udnOd z0u7{?7AxW)N;nvoT?~-pZM-NTft6saYCVv_o?^1Lx9va%M-LXlpr_2iP52;oNuY)e z+>I9{CtP%~#pMi#?E(^S8+z2X;dH|;h#NNIbOXoaZp#Vyb zqoFVw3Zvx$xX>Og9YOWlXef+^0<3v6nifXW!f0ARZtIWch|wG|nj=PY1bQEPv|Jc1 z7e>p4(Q;w5T);N|Ia)dnh0;+$vEb*YR5k%Q-hUHCnHI}VxF}+eXRJCwlK1sR0fttF zm5cdz?b@ZR^xy>V^oQ&E1D+iI!DWQZd#Y7fXiejXr3%V?B>A(?oX)g`|sg+VI zUz?niJ*aGM!#;c8!fC!9z4|e)JY`od4&M`adT-04sT2OUS6lRl@lR{})7a^qd0@!kcwDHQbOC47z^mviDz)?thK%UaL6eCp39Zst3czdmVG z-}x)!seMtpU&mGVcN{8u|M{+*ITg^E@AS|uXAkdINu8*ts}~0Sd-%zI?Op$zO21XV zGh*W>M(G9amRVfBM_`pbo0W~*?CitZ;1CS{f)TdRKxeoMiG+;{TN?D{zWc6F`lM?@ zRZ02eRdV4*3rbXG-1(rgr2m%k&RvI}#EED1-*a-1_LXwkpRY7HB~N4|x8g#1c=Ml96^nW#B@a}(^Xz^(f2b4 z1HA1ve7Gd5XnvFb>Koo}`KaU9GpZ6Kt~|Y;@zrFDQ$y6#S01a6yS-jKapq-rVEXX> zfp7W*6!}Tg69g2>a64(Ht?F zBSv$?XpTT1br~%eM$3iKa$&Sw7%dlY4M&fTFO1d^qjkh+9Wh!*jMfpObp)s+9KxZH zuyC92-o1MlUo^)xjc4HFv|z`M8LR9P7rO4)v2$my+74XvsEvwpOuNh8F51QKHa(#? zts9p*#?kF+z|DlfP+;)Qb6S41w{vl3682OCoEhGy_~6+yw^DA&{dIqT)te~coDBu4 zl~Xj>#gc6@7w0xW&{7F&EVIK1+qVFhBd}ph&|tB)c<>^KWv|GO+EFKf7Q+pXP+(yA Yykn7^AL{}E;NDsWPgg&ebxsLQ0I6L>!T 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) diff --git a/src/raylib.h b/src/raylib.h index e49d6b2f7..8efcf4375 100644 --- a/src/raylib.h +++ b/src/raylib.h @@ -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'