diff options
author | sanine <sanine.not@pm.me> | 2022-08-25 14:54:53 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-08-25 14:54:53 -0500 |
commit | 37c97e345d12f95dde44e1d1a4c2f2aadd4615bc (patch) | |
tree | e1bb25bc855883062bdd7847ff2c04290f71c840 /portaudio/examples/paex_record_file.c | |
parent | 5634c7b04da619669f2f29f6798c03982be05180 (diff) |
add initial structure
Diffstat (limited to 'portaudio/examples/paex_record_file.c')
-rw-r--r-- | portaudio/examples/paex_record_file.c | 457 |
1 files changed, 457 insertions, 0 deletions
diff --git a/portaudio/examples/paex_record_file.c b/portaudio/examples/paex_record_file.c new file mode 100644 index 0000000..562a8e9 --- /dev/null +++ b/portaudio/examples/paex_record_file.c @@ -0,0 +1,457 @@ +/** @file paex_record_file.c + @ingroup examples_src + @brief Record input into a file, then playback recorded data from file (Windows only at the moment) + @author Robert Bielik +*/ +/* + * $Id: paex_record_file.c 1752 2011-09-08 03:21:55Z philburk $ + * + * This program uses the PortAudio Portable Audio Library. + * For more information see: http://www.portaudio.com + * Copyright (c) 1999-2000 Ross Bencina and Phil Burk + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR + * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* + * The text above constitutes the entire PortAudio license; however, + * the PortAudio community also makes the following non-binding requests: + * + * Any person wishing to distribute modifications to the Software is + * requested to send the modifications to the original developer so that + * they can be incorporated into the canonical version. It is also + * requested that these non-binding requests be included along with the + * license above. + */ + +#include <stdio.h> +#include <stdlib.h> +#include "portaudio.h" +#include "pa_ringbuffer.h" +#include "pa_util.h" + +#ifdef _WIN32 +#include <windows.h> +#include <process.h> +#endif + +static ring_buffer_size_t rbs_min(ring_buffer_size_t a, ring_buffer_size_t b) +{ + return (a < b) ? a : b; +} + +/* #define SAMPLE_RATE (17932) // Test failure to open with this value. */ +#define FILE_NAME "audio_data.raw" +#define SAMPLE_RATE (44100) +#define FRAMES_PER_BUFFER (512) +#define NUM_SECONDS (10) +#define NUM_CHANNELS (2) +#define NUM_WRITES_PER_BUFFER (4) +/* #define DITHER_FLAG (paDitherOff) */ +#define DITHER_FLAG (0) /**/ + + +/* Select sample format. */ +#if 1 +#define PA_SAMPLE_TYPE paFloat32 +typedef float SAMPLE; +#define SAMPLE_SILENCE (0.0f) +#define PRINTF_S_FORMAT "%.8f" +#elif 1 +#define PA_SAMPLE_TYPE paInt16 +typedef short SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#elif 0 +#define PA_SAMPLE_TYPE paInt8 +typedef char SAMPLE; +#define SAMPLE_SILENCE (0) +#define PRINTF_S_FORMAT "%d" +#else +#define PA_SAMPLE_TYPE paUInt8 +typedef unsigned char SAMPLE; +#define SAMPLE_SILENCE (128) +#define PRINTF_S_FORMAT "%d" +#endif + +typedef struct +{ + unsigned frameIndex; + int threadSyncFlag; + SAMPLE *ringBufferData; + PaUtilRingBuffer ringBuffer; + FILE *file; + void *threadHandle; +} +paTestData; + +/* This routine is run in a separate thread to write data from the ring buffer into a file (during Recording) */ +static int threadFunctionWriteToRawFile(void* ptr) +{ + paTestData* pData = (paTestData*)ptr; + + /* Mark thread started */ + pData->threadSyncFlag = 0; + + while (1) + { + ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferReadAvailable(&pData->ringBuffer); + if ( (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) || + pData->threadSyncFlag ) + { + void* ptr[2] = {0}; + ring_buffer_size_t sizes[2] = {0}; + + /* By using PaUtil_GetRingBufferReadRegions, we can read directly from the ring buffer */ + ring_buffer_size_t elementsRead = PaUtil_GetRingBufferReadRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1); + if (elementsRead > 0) + { + int i; + for (i = 0; i < 2 && ptr[i] != NULL; ++i) + { + fwrite(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file); + } + PaUtil_AdvanceRingBufferReadIndex(&pData->ringBuffer, elementsRead); + } + + if (pData->threadSyncFlag) + { + break; + } + } + + /* Sleep a little while... */ + Pa_Sleep(20); + } + + pData->threadSyncFlag = 0; + + return 0; +} + +/* This routine is run in a separate thread to read data from file into the ring buffer (during Playback). When the file + has reached EOF, a flag is set so that the play PA callback can return paComplete */ +static int threadFunctionReadFromRawFile(void* ptr) +{ + paTestData* pData = (paTestData*)ptr; + + while (1) + { + ring_buffer_size_t elementsInBuffer = PaUtil_GetRingBufferWriteAvailable(&pData->ringBuffer); + + if (elementsInBuffer >= pData->ringBuffer.bufferSize / NUM_WRITES_PER_BUFFER) + { + void* ptr[2] = {0}; + ring_buffer_size_t sizes[2] = {0}; + + /* By using PaUtil_GetRingBufferWriteRegions, we can write directly into the ring buffer */ + PaUtil_GetRingBufferWriteRegions(&pData->ringBuffer, elementsInBuffer, ptr + 0, sizes + 0, ptr + 1, sizes + 1); + + if (!feof(pData->file)) + { + ring_buffer_size_t itemsReadFromFile = 0; + int i; + for (i = 0; i < 2 && ptr[i] != NULL; ++i) + { + itemsReadFromFile += (ring_buffer_size_t)fread(ptr[i], pData->ringBuffer.elementSizeBytes, sizes[i], pData->file); + } + PaUtil_AdvanceRingBufferWriteIndex(&pData->ringBuffer, itemsReadFromFile); + + /* Mark thread started here, that way we "prime" the ring buffer before playback */ + pData->threadSyncFlag = 0; + } + else + { + /* No more data to read */ + pData->threadSyncFlag = 1; + break; + } + } + + /* Sleep a little while... */ + Pa_Sleep(20); + } + + return 0; +} + +typedef int (*ThreadFunctionType)(void*); + +/* Start up a new thread in the given function, at the moment only Windows, but should be very easy to extend + to posix type OSs (Linux/Mac) */ +static PaError startThread( paTestData* pData, ThreadFunctionType fn ) +{ +#ifdef _WIN32 + typedef unsigned (__stdcall* WinThreadFunctionType)(void*); + pData->threadHandle = (void*)_beginthreadex(NULL, 0, (WinThreadFunctionType)fn, pData, CREATE_SUSPENDED, NULL); + if (pData->threadHandle == NULL) return paUnanticipatedHostError; + + /* Set file thread to a little higher prio than normal */ + SetThreadPriority(pData->threadHandle, THREAD_PRIORITY_ABOVE_NORMAL); + + /* Start it up */ + pData->threadSyncFlag = 1; + ResumeThread(pData->threadHandle); + +#endif + + /* Wait for thread to startup */ + while (pData->threadSyncFlag) { + Pa_Sleep(10); + } + + return paNoError; +} + +static int stopThread( paTestData* pData ) +{ + pData->threadSyncFlag = 1; + /* Wait for thread to stop */ + while (pData->threadSyncFlag) { + Pa_Sleep(10); + } +#ifdef _WIN32 + CloseHandle(pData->threadHandle); + pData->threadHandle = 0; +#endif + + return paNoError; +} + + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int recordCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + ring_buffer_size_t elementsWriteable = PaUtil_GetRingBufferWriteAvailable(&data->ringBuffer); + ring_buffer_size_t elementsToWrite = rbs_min(elementsWriteable, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS)); + const SAMPLE *rptr = (const SAMPLE*)inputBuffer; + + (void) outputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + data->frameIndex += PaUtil_WriteRingBuffer(&data->ringBuffer, rptr, elementsToWrite); + + return paContinue; +} + +/* This routine will be called by the PortAudio engine when audio is needed. +** It may be called at interrupt level on some machines so don't do anything +** that could mess up the system like calling malloc() or free(). +*/ +static int playCallback( const void *inputBuffer, void *outputBuffer, + unsigned long framesPerBuffer, + const PaStreamCallbackTimeInfo* timeInfo, + PaStreamCallbackFlags statusFlags, + void *userData ) +{ + paTestData *data = (paTestData*)userData; + ring_buffer_size_t elementsToPlay = PaUtil_GetRingBufferReadAvailable(&data->ringBuffer); + ring_buffer_size_t elementsToRead = rbs_min(elementsToPlay, (ring_buffer_size_t)(framesPerBuffer * NUM_CHANNELS)); + SAMPLE* wptr = (SAMPLE*)outputBuffer; + + (void) inputBuffer; /* Prevent unused variable warnings. */ + (void) timeInfo; + (void) statusFlags; + (void) userData; + + data->frameIndex += PaUtil_ReadRingBuffer(&data->ringBuffer, wptr, elementsToRead); + + return data->threadSyncFlag ? paComplete : paContinue; +} + +static unsigned NextPowerOf2(unsigned val) +{ + val--; + val = (val >> 1) | val; + val = (val >> 2) | val; + val = (val >> 4) | val; + val = (val >> 8) | val; + val = (val >> 16) | val; + return ++val; +} + +/*******************************************************************/ +int main(void); +int main(void) +{ + PaStreamParameters inputParameters, + outputParameters; + PaStream* stream; + PaError err = paNoError; + paTestData data = {0}; + unsigned delayCntr; + unsigned numSamples; + unsigned numBytes; + + printf("patest_record.c\n"); fflush(stdout); + + /* We set the ring buffer size to about 500 ms */ + numSamples = NextPowerOf2((unsigned)(SAMPLE_RATE * 0.5 * NUM_CHANNELS)); + numBytes = numSamples * sizeof(SAMPLE); + data.ringBufferData = (SAMPLE *) PaUtil_AllocateMemory( numBytes ); + if( data.ringBufferData == NULL ) + { + printf("Could not allocate ring buffer data.\n"); + goto done; + } + + if (PaUtil_InitializeRingBuffer(&data.ringBuffer, sizeof(SAMPLE), numSamples, data.ringBufferData) < 0) + { + printf("Failed to initialize ring buffer. Size is not power of 2 ??\n"); + goto done; + } + + err = Pa_Initialize(); + if( err != paNoError ) goto done; + + inputParameters.device = Pa_GetDefaultInputDevice(); /* default input device */ + if (inputParameters.device == paNoDevice) { + fprintf(stderr,"Error: No default input device.\n"); + goto done; + } + inputParameters.channelCount = 2; /* stereo input */ + inputParameters.sampleFormat = PA_SAMPLE_TYPE; + inputParameters.suggestedLatency = Pa_GetDeviceInfo( inputParameters.device )->defaultLowInputLatency; + inputParameters.hostApiSpecificStreamInfo = NULL; + + /* Record some audio. -------------------------------------------- */ + err = Pa_OpenStream( + &stream, + &inputParameters, + NULL, /* &outputParameters, */ + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + recordCallback, + &data ); + if( err != paNoError ) goto done; + + /* Open the raw audio 'cache' file... */ + data.file = fopen(FILE_NAME, "wb"); + if (data.file == 0) goto done; + + /* Start the file writing thread */ + err = startThread(&data, threadFunctionWriteToRawFile); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + printf("\n=== Now recording to '" FILE_NAME "' for %d seconds!! Please speak into the microphone. ===\n", NUM_SECONDS); fflush(stdout); + + /* Note that the RECORDING part is limited with TIME, not size of the file and/or buffer, so you can + increase NUM_SECONDS until you run out of disk */ + delayCntr = 0; + while( delayCntr++ < NUM_SECONDS ) + { + printf("index = %d\n", data.frameIndex ); fflush(stdout); + Pa_Sleep(1000); + } + if( err < 0 ) goto done; + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + /* Stop the thread */ + err = stopThread(&data); + if( err != paNoError ) goto done; + + /* Close file */ + fclose(data.file); + data.file = 0; + + /* Playback recorded data. -------------------------------------------- */ + data.frameIndex = 0; + + outputParameters.device = Pa_GetDefaultOutputDevice(); /* default output device */ + if (outputParameters.device == paNoDevice) { + fprintf(stderr,"Error: No default output device.\n"); + goto done; + } + outputParameters.channelCount = 2; /* stereo output */ + outputParameters.sampleFormat = PA_SAMPLE_TYPE; + outputParameters.suggestedLatency = Pa_GetDeviceInfo( outputParameters.device )->defaultLowOutputLatency; + outputParameters.hostApiSpecificStreamInfo = NULL; + + printf("\n=== Now playing back from file '" FILE_NAME "' until end-of-file is reached ===\n"); fflush(stdout); + err = Pa_OpenStream( + &stream, + NULL, /* no input */ + &outputParameters, + SAMPLE_RATE, + FRAMES_PER_BUFFER, + paClipOff, /* we won't output out of range samples so don't bother clipping them */ + playCallback, + &data ); + if( err != paNoError ) goto done; + + if( stream ) + { + /* Open file again for reading */ + data.file = fopen(FILE_NAME, "rb"); + if (data.file != 0) + { + /* Start the file reading thread */ + err = startThread(&data, threadFunctionReadFromRawFile); + if( err != paNoError ) goto done; + + err = Pa_StartStream( stream ); + if( err != paNoError ) goto done; + + printf("Waiting for playback to finish.\n"); fflush(stdout); + + /* The playback will end when EOF is reached */ + while( ( err = Pa_IsStreamActive( stream ) ) == 1 ) { + printf("index = %d\n", data.frameIndex ); fflush(stdout); + Pa_Sleep(1000); + } + if( err < 0 ) goto done; + } + + err = Pa_CloseStream( stream ); + if( err != paNoError ) goto done; + + fclose(data.file); + + printf("Done.\n"); fflush(stdout); + } + +done: + Pa_Terminate(); + if( data.ringBufferData ) /* Sure it is NULL or valid. */ + PaUtil_FreeMemory( data.ringBufferData ); + if( err != paNoError ) + { + fprintf( stderr, "An error occurred while using the portaudio stream\n" ); + fprintf( stderr, "Error number: %d\n", err ); + fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) ); + err = 1; /* Always return 0 or 1, but no other return codes. */ + } + return err; +} |