Author Topic: Creating a microphone monitor  (Read 279 times)

Ethin

  • Posts: 6
Creating a microphone monitor
« on: 14 Dec '18 - 05:48 »
So, I'm trying to create a voice chat application that will constantly record from the microphone and send the (either encoded or raw) data packets of audio over the network. In doing this, I decided to create a test application to get more experience with BASS. The goal of my application is to record and simultaneously play back what is being recorded in real time, like you might do in Reaper by creating a new track, arming it, and placing it in monitor mode. I have headers in here I haven't used yet, but I'm trying to get the initial framework down (and at least be able to hear myself). I have the following code (I apologize for the lack of indentation or formatting, I'm visually impaired):
Code: [Select]
#include <iostream>
#include <string>
#include <cstdlib>
#include "bass.h"
#include "bass_fx.h"
#include "bassenc_flac.h"
#include "bassflac.h"
#include <conio.h>
using namespace std;
HRECORD record;
HPLUGIN flac, eflac;
HSAMPLE sample;
BOOL CALLBACK recordproc(HRECORD handle, const void *buffer, DWORD length, void *user);
int main() {
flac = BASS_PluginLoad("bassflac.dll", 0);
if (flac==0) {
cout << "Could not load flac decoder: " << endl;
switch (BASS_ErrorGetCode()) {
case BASS_ERROR_FILEOPEN:
cout << "The file could not be opened.";
break;
case BASS_ERROR_FILEFORM:
cout << "The file is not a plugin.";
break;
case BASS_ERROR_VERSION:
cout << "The plugin requires a different BASS version.";
break;
case BASS_ERROR_ALREADY:
cout << "The plugin is already loaded.";
break;
}
cout << endl;
BASS_Free();
exit(0);
}
eflac = BASS_PluginLoad("bassenc_flac.dll", 0);
if (flac==0) {
cout << "Could not load flac decoder: " << endl;
switch (BASS_ErrorGetCode()) {
case BASS_ERROR_FILEOPEN:
cout << "The file could not be opened.";
break;
case BASS_ERROR_FILEFORM:
cout << "The file is not a plugin.";
break;
case BASS_ERROR_VERSION:
cout << "The plugin requires a different BASS version.";
break;
case BASS_ERROR_ALREADY:
cout << "The plugin is already loaded.";
break;
}
cout << endl;
BASS_Free();
exit(0);
}
if (BASS_Init(-1, 192000, BASS_DEVICE_LATENCY|BASS_DEVICE_FREQ, 0, NULL)) {
BASS_DEVICEINFO info;
if (BASS_GetDeviceInfo(BASS_GetDevice(), &info)) {
cout << "Device " << info.name << " initialized with driver " << info.driver << endl;
} else {
cout << "Warning: could not get device information (device invalid)" << endl;
BASS_PluginFree(flac);
BASS_PluginFree(eflac);
BASS_Free();
return 1;
}
if (BASS_RecordInit(-1)) {
record = BASS_RecordStart(48000, 2, 0, recordproc, 0);
if (record==false) {
switch (BASS_ErrorGetCode()) {
case BASS_ERROR_INIT:
cout << "BASS_RecordInit has not been successfully called.";
break;
case BASS_ERROR_BUSY:
cout << "The device is busy. An existing recording may need to be stopped before starting another one.";
break;
case BASS_ERROR_NOTAVAIL:
cout << "The recording device is not available. Another application may already be recording with it, or it could be a half-duplex device that is currently being used for playback.";
break;
case BASS_ERROR_FORMAT:
cout << "The requested format is not supported. If using the BASS_SAMPLE_FLOAT flag, it could be that floating-point recording is not supported.";
break;
case BASS_ERROR_MEM:
cout << "There is insufficient memory.";
break;
default:
cout << "Unknown error.";
break;
}
cout << endl;
BASS_RecordFree();
BASS_PluginFree(flac);
BASS_PluginFree(eflac);
BASS_Free();
return 1;
}
sample = BASS_SampleCreate(0, 192000, 2, 1, 0);
while (true) {
void* data;
BASS_SAMPLE info;
BASS_SampleGetData(sample, &data);
BASS_SampleGetInfo(sample, &info);
HSTREAM stream = BASS_StreamCreateFile(TRUE, data, 0, info.length, 0);
BASS_ChannelPlay(stream, true);
}
} else {
cout << "Can't start recording" << endl;
switch (BASS_ErrorGetCode()) {
case BASS_ERROR_INIT:
cout << "BASS_RecordInit has not been successfully called.";
break;
case BASS_ERROR_BUSY:
cout << "The device is busy. An existing recording may need to be stopped before starting another one.";
break;
case BASS_ERROR_NOTAVAIL:
cout << "The recording device is not available. Another application may already be recording with it, or it could be a half-duplex device that is currently being used for playback.";
break;
case BASS_ERROR_FORMAT:
cout << "The requested format is not supported. If using the BASS_SAMPLE_FLOAT flag, it could be that floating-point recording is not supported.";
break;
case BASS_ERROR_MEM:
cout << "There is insufficient memory.";
break;
default:
cout << "Unknown error.";
break;
}
cout << endl;
BASS_RecordFree();
BASS_PluginFree(flac);
BASS_PluginFree(eflac);
BASS_Free();
return 1;
}
} else {
cout << "Can't initialize BASS." << endl;
switch (BASS_ErrorGetCode()) {
case BASS_ERROR_DX:
cout << "DirectX (or ALSA on Linux or OpenSL ES on Android) is not installed.";
break;
case BASS_ERROR_DEVICE:
cout << "device is invalid.";
break;
case BASS_ERROR_ALREADY:
cout << "The device has already been initialized. BASS_Free must be called before it can be initialized again.";
break;
case BASS_ERROR_DRIVER:
cout << "There is no available device driver. The device may already be in use.";
break;
case BASS_ERROR_FORMAT:
cout << "The specified format is not supported by the device. Try changing the freq and flags parameters.";
break;
case BASS_ERROR_MEM:
cout << "There is insufficient memory.";
break;
case BASS_ERROR_NO3D:
cout << "Could not initialize 3D support.";
break;
default:
cout << "Unknown error.";
break;
}
cout << endl;
BASS_RecordFree();
BASS_PluginFree(flac);
BASS_PluginFree(eflac);
BASS_Free();
return 1;
}
}

BOOL CALLBACK recordproc(HRECORD handle, const void *buffer, DWORD length, void *user) {
if (length>0) {
BASS_SAMPLE info;
info.length = length;
BASS_SampleSetInfo(sample, &info);
BASS_SampleSetData(sample, buffer);
}
return TRUE;
}
When I compile and run this program, I get the device info, but nothing else. I've inserted calls to debug what's being processed, and it appears that the recordproc is functioning properly and is, in fact, processing data. But when I try and play it, nothing happens. (In an earlier prototype which I've since lost, I managed to get it to play, though it gave white noise instead of actual comprehensible sound.) I tried an encoder, but could figure out no way of decoding the audio and playing it. Any useful tips or suggestions? Am I missing something crucial or misusing something?

Ian @ un4seen

  • Administrator
  • Posts: 21539
Re: Creating a microphone monitor
« Reply #1 on: 14 Dec '18 - 15:09 »
To play the recorded sound, you should use a "stream" rather than a "sample", ie. BASS_StreamCreate rather than BASS_SampleCreate. You could make it a "push" stream and have the RECORDPROC pass the recorded data to it via BASS_StreamPutData. For example, something like this:

Code: [Select]
stream = BASS_StreamCreate(freq, chans, 0, STREAMPROC_PUSH, NULL); // create a push stream
record = BASS_RecordStart(freq, chans, MAKELONG(0, 10), RecordProc, NULL); // start recording (with 10ms period)
BASS_ChannelPlay(stream, 0); // start the stream

...

BOOL CALLBACK RecordProc(HRECORD handle, const void *buffer, DWORD length, void *user)
{
BASS_StreamPutData(stream, buffer, length); // pass the data to the stream
return TRUE; // continue recording
}

You may want to wait until the stream has received a certain amount of data before starting playback, to avoid stuttering. The BASS_INFO "minbuf" value can useful to calculate the amount to prebuffer. For example, somethng like this:

Code: [Select]
BASS_INFO info;
BASS_GetInfo(&info);
stream = BASS_StreamCreate(freq, chans, 0, STREAMPROC_PUSH, NULL); // create a push stream
prebuf = BASS_ChannelSeconds2Bytes(stream, info.minbuf / 1000.f); // prebuffer at least "minbuf" worth of data
record = BASS_RecordStart(freq, chans, MAKELONG(0, 10), RecordProc, NULL); // start recording (with 10ms period)

...

BOOL CALLBACK RecordProc(HRECORD handle, const void *buffer, DWORD length, void *user)
{
int buffered = BASS_StreamPutData(stream, buffer, length); // pass the data to the stream
if (prebuf) { // prebuffering
DWORD got = BASS_ChannelGetData(stream, NULL, BASS_DATA_AVAILABLE); // get buffer level
if (got > prebuf) { // got wanted amount
BASS_ChannelPlay(stream, 0); // start the stream
prebuf = 0; // done prebuffering
}
}
return TRUE; // continue recording
}

Please see the documentation for details on the mentioned functions. You could also have a look at the LIVEFX.C example included in the BASS package.

Ethin

  • Posts: 6
Re: Creating a microphone monitor
« Reply #2 on: 14 Dec '18 - 20:25 »
Thank you, and it does work properly now. My question is, how would I encode that into FLAC (or other formats), and then decode that later? I know I'd load the FLAC plugins, for example, and then call BASS_Encode_FLAC_Start() (presumably, I'd also call BASS_Encode_FLAC_GetVersion() and BASS_Encode_GetVersion() to initialize), but when I compress the data, how would I get it from the encoder? It doesn't seem like there's many functions on the HENCODE handle, and while I could specify an ENCODEPROCEX, I don't know exactly what I'd do in there.

Ian @ un4seen

  • Administrator
  • Posts: 21539
Re: Creating a microphone monitor
« Reply #3 on: 18 Dec '18 - 17:12 »
Are you thinking of using FLAC encoding for a voice chat app? If so, I'm not sure that would work well, eg. regarding latency. It would probably be better to use Opus encoding, which has been designed for that purpose. Regarding encoding the recorded data, you can use the recording channel handle (from BASS_RecordStart) in a BASS_Encode_FLAC_Start (or BASS_Encode_OPUS_Start) call. You will then receive the encoded data in a callback function that you also provided in the call. Please see the BASS_Encode_FLAC_Start (or BASS_Encode_OPUS_Start) documentation for details.