SNDCAP -- Sound Capture Library for C programs

Copyright (C) 2007 Laszlo Menczel (menczel at mailbox dot hu)

This is free software distributed under the GNU Lesser General Public Licence (LGPL) version 2.1.

SNDCAP is distributed in the hope that it will be useful, but with NO WARRANTY expressed or implied.

Special licence clause

The copyright holder (Laszlo Menczel) hereby grants you the additional right to statically link the SNDCAP library to your application and distribute the resulting binary executable under a licence of your own choice.

The above clause does not free you from the obligation of providing (upon request) to the recipients of your binary executable the full source code of the SNDCAP library used for building said executable. In the documentation accompanying your binary executable you must inform the end user that the source code of the library is available upon request.

If you distribute modified versions of the SNDCAP library you must also grant end users the additional right of static linking as described above.

Summary

SNDCAP is a simple sound capture library written in C for Linux and Win9x/ME/2000/XP. It can open the default sound capture device and save the captured samples to a ring buffer from which applications can retrive the sound data. The library also contains functions for ring buffer management and for saving and loading WAV files.

Content

1. Introduction

2. The SNDCAP API

2.1. Ring buffer management
     rbuf_create
     rbuf_destroy
     rbuf_reset
     rbuf_incr
     rbuf_empty
     rbuf_full
     rbuf_insert
     rbuf_remove

2.2. Sound capture
     scap_init
     scap_start
     scap_stop
     scap_exit

2.3. WAV data manipulation
     wav_load_format
     wav_load
     wav_save
     wav_compare_format
     wav_convert
     wav_discard
     wav_info_str

2.4. Error handling
2.4.1. Error codes
2.4.2. Error functions
     scap_last_error
     scap_errmsg

3. Using the library
3.1. Supported platforms
3.2. Compiling the library
3.3. Application coding

4. Acknowledgements

1. Introduction

This library is the result of my frustration. During the development of another library (Voice Chat System for networked games, VCS) I needed a sound capture library (written in C) which could work under Windows and Linux. After searching the net I decided to use OpenAL (it seemed to be just the thing I wanted). However, the sound capture module of OpenAL did not work under Windows as advertised (at least for me, I tried to use it in several ways but always had problems).

I realized that the waveOut-waveIn API in Windows is fairly simple, and under Linux the ALSA system can be used for sound management. So it seemed to me that writing a sound capture library should not be a very difficult job. I started to code and this library is the result.

The functionality implemented by SNDCAP is fairly simple. Don't expect professional multi-channel audio recording. SNDCAP just opens the default capture device and keeps on saving sound samples in a ring buffer. It does contain a few utility functions for the manipulation of sound in WAV format, as well as functions for the creation and management of ring buffers needed for capture.

2. The SNDCAP API

The functions in SNDCAP always return a positive integer or a valid pointer if the operation was successful. In case of error the return value is zero or NULL. The type of error can be obtained by using the error reporting functions described in Section 2.4.

2.1. Ring buffer management

The following data structures and constants (defined in the header 'sndcap.h') are relevant to ring buffers:

typedef struct ringbuf_s
{
  byte  *data;		// buffer containing data
  int   dynamic;	// 1 = dynamically allocated
  int   incr;		// size of data items
  int   size;		// number of bytes in buffer
  int   count;		// current number of data items in buffer
  int   tail;		// pointer to first data item
  int   head;		// pointer to first empty location
  int   overflow;	// overflow flag
} ringbuf_t;

enum { RBUF_TAIL, RBUF_HEAD };
enum { RBUF_BYTE, RBUF_WORD, RBUF_DWORD };

ringbuf_t *rbuf_create(int count, int size)

Creates a ring buffer which can store 'count' data chunks each 'size' bytes long. Returns a pointer to the new ring buffer structure, or NULL in case of error. The field 'dynamic' is set to non-zero to indicate that not only the data buffer, but the structure itself is also dynamically allocated. 'size' is set to the total number of bytes in the data buffer, 'incr' is set to the value of 'size', the other fields are initialized to zero.

int rbuf_destroy(ringbuf_t *buf)

Discards the data structures used by the dynamically allocated ringbuf 'buf'. If 'buf->dynamic' is zero, the function returns zero (error) and nothing is done. Otherwise if 'buf->data' is not NULL, the data buffer is discarded, and finally the structure itself is also discarded by calling 'free()'.

int rbuf_reset(ringbuf_t *buf, int incr)

Resets the ringbuf 'buf'. This means that 'buf->data' is filled with zeros and all structure fields (except 'incr' and 'size') are set to zero.

int rbuf_incr(ringbuf_t *buf, int which)

Increments the tail or head pointer of the ring buffer 'buf' depending on the value of 'which' (may be RBUF_TAIL or RBUF_HEAD). The pointer wraps around to zero if it reaches the end of the buffer. 'buf->count' is incremented when the head pointer is incremented, and is decremented if the tail pointer is incremented. So its value is the number of data items in the buffer. Returns failure (zero) if 'buf' is invalid.

The buffer may overflow (i.e. a new item is added to a buffer already full). In this case the oldest item is overwritten and 'buf->overflow' is set to 1. Otherwise the value of 'buf->overflow' is zero. The consumer of buffer data should check this flag, and perform the necessary adjustment if it is non-zero. When the item at 'buf->tail' has been processed and removed from the buffer, the flag is set back to zero. Please note that a non-zero flag value means at least one (but possibly several) items overwritten and lost.

int rbuf_empty(ringbuf_t *buf)

Returns non-zero if 'buf' is empty (no consumable data chunk), zero otherwise. Also returns non-zero if 'buf' is invalid, so that a non-existent buffer is always treated as empty and (hopefully) no attempt is made to read data from it.

int rbuf_full(ringbuf_t *buf)

Returns non-zero if 'buf' is full (no data chunk can be added w/o overwriting an old one), zero otherwise. Also returns non-zero if 'buf' is invalid, so that a non-existent buffer is always treated as full and (hopefully) no attempt is made to add data to it.

int rbuf_insert(ringbuf_t *buf, void *data, int data_type)

Adds the data block at address 'data' to the first available empty location (at offset 'buf->head') of ringbuffer 'buf'. The number of bytes in 'data' is assumed to be the same as the value of 'buf->incr', its type is specified in the argument 'data_type' (RBUF_BYTE, RBUF_WORD or RBUF_DWORD). The appropriate fields of the structure 'buf' (i.e. 'count', 'head' and possibly 'tail' and 'overflow') are updated

int rbuf_remove(ringbuf_t *buf, void *data, int data_type)

Copies the oldest available data chunk (at offset 'buf->tail') of the ringbuffer 'buf' to the address 'data'. The number of bytes copied is 'buf->incr', the type of copied data is specified in the argument 'data_type' (RBUF_BYTE, RBUF_WORD or RBUF_DWORD). The appropriate fields of 'buf' ('tail' and 'count') are updated. Returns zero (error) if 'buf' is empty.

2.2. Sound capture

The following constants (enumerated in the header 'sndcap.h') are relevant to sound capture:

enum
{
  SCAP_8K = 8000,
  SCAP_11K = 11025,
  SCAP_22K = 22050,
  SCAP_44K = 44100
};

enum { SCAP_BYTE = 8, SCAP_WORD = 16 };
enum { SCAP_MONO = 1, SCAP_STEREO = 2 };

int scap_init(int chan, int freq, int bits, ringbuf_t *buf)

Initializes the sound system for capture. 'chan' is the number of sound channels (SCAP_MONO or SCAP_STEREO), 'freq' is the sampling rate (SCAP_8K, SCAP_11K, SCAP_22K or SCAP_44K), and 'bits' is the number of bits in a single sound sample (SCAP_BYTE or SCAP_WORD). 'buf' must be a properly initialized ring buffer. The minimum chunk size of this ring buffer is 800 bytes (corresponding to 0.1 seconds of sound assuming 8 kHz 8-bit mono capture). After calling this function the sound capture device is opened and initialized, but capture is not started yet.

int scap_start(void)

Starts the capture of sound.

int scap_stop(void)

Stops the capture of sound.

void scap_exit(void)

This function should be called before the application exits. Stops capture (if running) and properly closes the sound capture device.

2.3. WAV data manipulation

The following constants and data structures (defined in the header 'sndcap.h') are relevant to WAV data manipulation:

#define WAV_MAX_PATHLEN		255

typedef struct
{
  void *buf;				// buffer for sound data
  char name[WAV_MAX_PATHLEN + 1];	// name of WAV file
  int  dynamic;				// 1 = 'data' dynamically allocated
  int  freq;				// sampling rate
  int  numbits;				// size of sound samples
  int  numchan;				// number of sound channels
  int  frames;				// number of sound frames
} wavdata_t;

enum { SCAP_STATIC, SCAP_DYNAMIC };

int wav_load_format(char *name, wavdata_t *data)

Attempts to retrieve format information from the sound file 'name'. If successful, the values are stored in 'data' and 'name' is copied to 'data->name'. In case of error returns zero and the fields of 'data' are reset.

int wav_load(char *name, wavdata_t *data, int count, void *buf, int buftype)

Attempts to load sound data from the file 'name'. If 'count' is non-zero, it specifies the number of bytes to load from the file, otherwise the whole file is loaded. If 'buf' is not NULL, data is copied to this address. If 'buf' is NULL, the function tries to allocate a buffer large enough to store the requested data. The address of the buffer is stored in 'data->buf'. 'buftype' specifies the type of buffer passed, it should be SCAP_DYNAMIC (1, for dynamically allocated buffers) or SCAP_STATIC (zero, for static arrays). 'data->dynamic' is set to this value. The other fields of 'data' are initialized using values obtained from the file header. Returns the number of bytes loaded, or zero in case of error.

int wav_save(char *name, wavdata_t *data, int count)

Saves the sound data from the buffer of 'data' to a file. If 'name' is NULL, 'data->name' is used as filename, otherwise the name of the file will be 'name' (in this case 'data->name' is updated to contain the new name supplied). Sound is saved as standard uncompressed WAV in the format described by 'data'. The number of sound frames to save is specified in the argument 'count'. If 'count' is zero, 'data->frames' is used as count.

int wav_compare_format(wavdata_t *a, wavdata_t *b)

Compares the format information (number of channels, frequency and sample size) of 'a' and 'b'. Returns non-zero if they are identical, zero if not or an error occured.

int wav_convert(wavdata_t *src, wavdata_t *dst)

Not implemented in the present version!

Converts the sound data block described by 'src' to the format specified by 'dst'. A new buffer is allocated to store the converted data and its pointer is stored in 'dst->buf'. Any previous data in the buffer of 'dst' is lost.

int wav_discard(wavdata_t *data)

Discards the data buffer of 'data' (i.e. calls 'free()' to deallocate it). The operation is carried out only if 'data->dynamic' is non-zero meaning that the buffer was allocated dynamically using 'malloc()'. If the data buffer is deleted, all structure fields are reset to zero.

char *wav_info_str(wavdata_t *data)

Returns a pointer to a static string containing information on the format of sound described by 'data'.

2.4. Error handling

2.4.1. Error codes

The following error codes are enumerated in the header 'sndcap.h':


  CAPERR_NONE		no error (value is zero)

  // capture
  CAPERR_INIT		capture module not initialized
  CAPERR_BAD_ARG	bad argument
  CAPERR_ALLOC		memory allocation error
  CAPERR_NULL_PTR	NULL pointer argument
  CAPERR_BAD_BUF	bad ring buffer (data block NULL or increment out of range)
  CAPERR_DEV_OPEN	cannot open capture device
  CAPERR_IN_RECORD	currently recording sound
  CAPERR_NOT_RECORD	not recording sound
  CAPERR_START		could not start sound capture
  CAPERR_BUF_ADD	could not add capture buffer to queue
  CAPERR_THREAD		could not create the capture thread

  // ring buffer
  RBERR_BAD_ARG		bad argument
  RBERR_BAD_BUF		buffer data block is NULL
  RBERR_BUF_STATIC	buffer structure is static (not allocated by the library)
  RBERR_EMPTY		buffer is empty
  
  // WAV
  WAVERR_FILE_NAME	bad file name (empty)
  WAVERR_FILE_OPEN	could not open/create the specified file
  WAVERR_FILE_READ	failed reading data from the file
  WAVERR_FILE_WRITE	failed writing data to the file
  WAVERR_RIFF		not a RIFF file
  WAVERR_WAVE		not a WAVE file
  WAVERR_FMT_CHUNK	'fmt' chunk not found
  WAVERR_TAG		cannot load compressed WAV
  WAVERR_NUM_CHAN	bad channel number (should be 1 or 2)
  WAVERR_DATA_CHUNK	'data' chunk not found
  WAVERR_BITS		bad data size (should be 8 or 16 bits)
  WAVERR_FREQ		bad sampling rate (should be 8000, 11025, 22050, or 44100)
  WAVERR_SAMPLES	missing sound samples
  WAVERR_SAME_FORMAT	same WAV format (conversion is pointless)

The following aliases are also defined:

#define RBERR_NONE	CAPERR_NONE
#define RBERR_ALLOC	CAPERR_ALLOC
#define RBERR_NULL_PTR	CAPERR_NULL_PTR
#define WAVERR_NONE	CAPERR_NONE
#define WAVERR_NULL_PTR	CAPERR_NULL_PTR

2.4.2. Error functions

int scap_last_error(void)

Returns the code of the last error (CAPERR_NONE if the last operation succeeded).

char *scap_errmsg(int code)

Returns a pointer to a static string describing the last error that occured.

3. Using the library

3.1. Supported platforms

The present implementation can be compiled and used under Windows (Win9x, WinME, Win2000, WinXP) and Linux. Since the library contains platform specific code (for handling the sound device) it is not trivial to port to other systems. I have no intention to do this, but feel free to carry out the porting if you wish to use the library under a different OS :-) Platform specific code is restricted to the modules 'capture_win32.c' and 'capture_alsa.c', so this is the only source module which you have to re-implement. The rest of the code is standard ANSI C usable under any OS.

3.2. Compiling the library

The source tree of the library contains subdirectories for building under Windows and Linux. For the Win32 build you must have MinGW installed (it is a port of the GCC compiler and development system to Windows). To compile the library just change to the appropriate build directory and run the make script ('mk.bat' or 'mk'). If you want to compile the library using MS Visual C++, you should create the appropriate Makefile or project file yourself. I don't use MS compilers unless absolutely necessary (it happens very rarely).

Precompiled versions of the library are included in the directories 'bin\linux' and 'bin\win32'. After compiling the library you can save a copy of the new version by running the script 'save(.bat)' from the build directory. The library can be installed by running the 'inst(.bat)' script from 'bin\linux' or 'bin\win32'.

3.3. Application coding

Applications using the library should include the main header 'sndcap.h'. When you link the executable, the appropriate support libraries (winmm.dll and kernel32.dll for Win32, libasound.so and libm.so for Linux) must be specified. I have included a simple test program ('captest.c') which can give you an idea about how to use the library. This program just opens the capture device, records 2 seconds of sound and saves it into a WAV file.

4. Acknowledgements

The code in the module 'resample.c' for converting sound to different formats have been extracted from the libresample library created by Dominic Mazzoni (based on resample-1.7).