I've been trying to write a simple threaded audio capture/playback application (using OpenAL) as a precursor to attempting a voice chat application.
I currently have the Capture & "Write" (save / playback) functions on separate threads and when saving the data to a wav file everything behaves correctly. The problem occurs when trying to play back the data that has been captured.
I am attempting to do this a byte at a time, so there is minimal data being passed around. However I get no playback. When I am trying to assign the data from the capture buffer vector to the buffer to be played I get an error of AL_INVALID_NAME. I have done some looking around and I believe that this is possibly thread related regarding how the buffer is being written to/read from, but I'm not 100% sure as what I have found is vague.
The code is given below, and I was wondering if anyone could shed some light on the problem I am having. Thanks in advance.
#include <iostream>
#include <conio.h>
#include <Windows.h>
#include <vector>
using std::vector;
#include <al.h>
#include <alc.h>
#include <AL\alut.h>
#define SAMPLE_RATE 22050
#define BUFFER_SIZE 4410
vector<ALbyte> bufferVector;
CRITICAL_SECTION bufferAccess;
ALint iDataSize = 0;
bool isCapturing = true;
ALuint buffer;
ALuint source;
// Each source has several properties, see the code for examples. Here we store position and velocity of
// the sound source above (x, y & z)
ALfloat sourcePos[3] = { 0.0, 0.0, 0.0 };
ALfloat sourceVel[3] = { 0.0, 0.0, 0.0 };
// There is always assumed to be a listener in an OpenAL application. We don't need a specific listener
// variable. However, listeners also have properties (examples in code). Here we store the position and
// velocity of the listener
ALfloat listenerPos[3] = { 0.0, 0.0, 0.0 };
ALfloat listenerVel[3] = { 0.0, 0.0, 0.0 };
// The listener may be at an angle (which may affect the perception of sound). Here we store the
// orientation of the listener. The first three values are the facing direction (x, y, z) of the
// listener - called "at" in the documentation. The next three values are the upward direction
// of the listener, called "up". These vectors can be extracted from a world or view matrix
// NOTE: OpenAL (like OpenGL) uses a right-handed system for 3D coordinates. To convert from the
// left-handed system we have used, we must negate all Z values (facing direction has -ve Z below)
ALfloat listenerOri[6] = { 0.0, 0.0, -1.0,
0.0, 1.0, 0.0 };
typedef struct
{
char szRIFF[4];
long lRIFFSize;
char szWave[4];
char szFmt[4];
long lFmtSize;
WAVEFORMATEX wfex;
char szData[4];
long lDataSize;
} WAVEHEADER;
struct SInfo
{
ALCdevice* mDevice;
};
DWORD WINAPI CaptureThread( LPVOID context )
{
SInfo info = *((SInfo*)context);
ALint samplesAvailable;
ALbyte ALBuffer[BUFFER_SIZE];
WAVEHEADER sWaveHeader;
// Prepare a WAVE file header for the captured data
sprintf(sWaveHeader.szRIFF, "RIFF");
sWaveHeader.lRIFFSize = 0;
sprintf(sWaveHeader.szWave, "WAVE");
sprintf(sWaveHeader.szFmt, "fmt ");
sWaveHeader.lFmtSize = sizeof(WAVEFORMATEX);
sWaveHeader.wfex.nChannels = 1;
sWaveHeader.wfex.wBitsPerSample = 16;
sWaveHeader.wfex.wFormatTag = WAVE_FORMAT_PCM;
sWaveHeader.wfex.nSamplesPerSec = SAMPLE_RATE;
sWaveHeader.wfex.nBlockAlign = sWaveHeader.wfex.nChannels * sWaveHeader.wfex.wBitsPerSample / 8;
sWaveHeader.wfex.nAvgBytesPerSec = sWaveHeader.wfex.nSamplesPerSec * sWaveHeader.wfex.nBlockAlign;
sWaveHeader.wfex.cbSize = 0;
sprintf(sWaveHeader.szData, "data");
sWaveHeader.lDataSize = 0;
alcCaptureStart( info.mDevice );
//** Capture audio til a key is hit.
while( !_kbhit() )
{
//** Find out how many samples have been captured.
alcGetIntegerv( info.mDevice, ALC_CAPTURE_SAMPLES, (ALCsizei)sizeof(ALint), &samplesAvailable );
printf( "Samples available: %d\r", samplesAvailable );
//** When there is enough data to fill the buffer size, grab the data.
if( samplesAvailable > ( BUFFER_SIZE / sWaveHeader.wfex.nBlockAlign ) )
{
//** Consume the samples
alcCaptureSamples( info.mDevice, ALBuffer, BUFFER_SIZE / sWaveHeader.wfex.nBlockAlign );
for( int i = 0; i < BUFFER_SIZE; i++ )
{
EnterCriticalSection( &bufferAccess );
bufferVector.push_back( ALBuffer[i] );
LeaveCriticalSection( &bufferAccess );
}
//** Accumulate the amount of data recorded.
iDataSize += BUFFER_SIZE;
}
}
isCapturing = false;
return S_OK;
}
DWORD WINAPI WriteThread( LPVOID context )
{
//FILE* pFile;
//WAVEHEADER sWaveHeader;
//// Create / open a file for the captured data
//pFile = fopen( "Capture.wav", "wb");
//// Prepare a WAVE file header for the captured data
//sprintf(sWaveHeader.szRIFF, "RIFF");
//sWaveHeader.lRIFFSize = 0;
//sprintf(sWaveHeader.szWave, "WAVE");
//sprintf(sWaveHeader.szFmt, "fmt ");
//sWaveHeader.lFmtSize = sizeof(WAVEFORMATEX);
//sWaveHeader.wfex.nChannels = 1;
//sWaveHeader.wfex.wBitsPerSample = 16;
//sWaveHeader.wfex.wFormatTag = WAVE_FORMAT_PCM;
//sWaveHeader.wfex.nSamplesPerSec = SAMPLE_RATE;
//sWaveHeader.wfex.nBlockAlign = sWaveHeader.wfex.nChannels * sWaveHeader.wfex.wBitsPerSample / 8;
//sWaveHeader.wfex.nAvgBytesPerSec = sWaveHeader.wfex.nSamplesPerSec * sWaveHeader.wfex.nBlockAlign;
//sWaveHeader.wfex.cbSize = 0;
//sprintf(sWaveHeader.szData, "data");
//sWaveHeader.lDataSize = 0;
//fwrite(&sWaveHeader, sizeof(WAVEHEADER), 1, pFile);
// Write the audio data to a file
while( isCapturing || !bufferVector.empty() )
{
if( bufferVector.empty() )
continue;
//fwrite( &bufferVector[0], sizeof( ALbyte ), 1, pFile );
//buffer = bufferVector[0];
alBufferData( buffer, AL_FORMAT_MONO16, (ALvoid*)bufferVector[0], sizeof(ALbyte), SAMPLE_RATE );
ALenum errorEnum = alGetError();
if ( errorEnum == AL_INVALID_NAME )
{
printf( "\nAL_INVALID_NAME\n" );
}
alGenSources( 1, &source );
// Set the properties of the source. The full list of available properties can be found in the documentation
// The last characters of each function name indicate the type of the second parameter (int, float, float vector etc.)
alSourcei ( source, AL_BUFFER, buffer ); // Attach a buffer to the source (identify which sound to play)
alSourcef ( source, AL_PITCH, 1.0f ); // Pitch multiplier, doubling the pitch shifts the sound up 1 octave, halving
// the pitch shifts it down 1 octave. Will also shorten/lengthen the sound
alSourcef ( source, AL_GAIN, 1.0f ); // Effectively the volume of the sound - 0.0 = silent, 1.0 = as recorded. May
// be able to increase volume over 1, but depends on sound
alSourcefv( source, AL_POSITION, sourcePos ); // Position of sound relative to listener affects how it is reproduced through speakers
alSourcefv( source, AL_VELOCITY, sourceVel ); // Velocity of sound relative to listener can cause Doppler effect
alSourcei ( source, AL_LOOPING, AL_FALSE ); // Whether to loop the sound or just stop when it finishes
//****************
// Listener
// Set the properties of the listener. These are all the available listener properties
alListenerfv( AL_POSITION, listenerPos ); // Position, velocity and orientation of listener affect sound...
alListenerfv( AL_VELOCITY, listenerVel ); // ...reproduction as noted above
alListenerfv( AL_ORIENTATION, listenerOri );
alListenerf ( AL_GAIN, 1.0f ); // "Master" gain / volume. Controls overall loudness of all sounds
EnterCriticalSection( &bufferAccess );
bufferVector.erase( bufferVector.begin() );
LeaveCriticalSection( &bufferAccess );
alSourcePlay( source );
}
// Fill in Size information in Wave Header
//fseek(pFile, 4, SEEK_SET);
//ALint iSize = iDataSize + sizeof(WAVEHEADER) - 8;
//fwrite(&iSize, 4, 1, pFile);
//fseek(pFile, 42, SEEK_SET);
//fwrite(&iDataSize, 4, 1, pFile);
//fclose(pFile);
return S_OK;
}
int main()
{
//** Initialise OpenAL
alutInit( 0, 0 );
SInfo info;
info.mDevice = alcCaptureOpenDevice( NULL, SAMPLE_RATE, AL_FORMAT_MONO16, BUFFER_SIZE);
InitializeCriticalSection( &bufferAccess );
HANDLE capThread = CreateThread( NULL, NULL, &CaptureThread, &info, NULL, NULL );
HANDLE writeThread = CreateThread( NULL, NULL, &WriteThread, NULL, NULL, NULL );
WaitForSingleObject( capThread, INFINITE );
WaitForSingleObject( writeThread, INFINITE );
DeleteCriticalSection( &bufferAccess );
system("pause");
alutExit();
return 0;
}