【发布时间】:2017-08-02 14:27:28
【问题描述】:
我正在尝试为 VOIP 场景增加麦克风的增益。 我正在使用 PortAudio 获取输入流(带有 paFloat32 类型的样本),我将这些值乘以浮点数,然后将结果流传递到输出设备。 注意:我将它传递给一个虚拟输出设备,该设备会自动重定向到一个虚拟输入设备(程序:VB-Cable),VOIP 应用程序可以将其用作麦克风输入并应用增益。
我想知道是否有更好的方法来增加信号增益以更好地保持质量。
我了解到,执行此类增益计算最好先将输入转换为更高精度的格式,以这种格式执行增益乘法,应用削波,然后再还原为原始格式。 我不确定如何使用 PortAudio 的 paFloat32 类型执行此操作,我已将我的尝试包含在源代码中注释掉。当我启用它时,即使增益设置为 1,也会出现明显的噪音问题。
依赖:tinycon, PortAudio
编译:g++ main.cpp tinycon.cpp -o main -L./ -lcygportaudio-2 -lrt -lm -pthread -std=c++11
代码:
#include "portaudio.h"
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include "tinycon.h"
#define SAMPLE_RATE (44100)
#define FRAMES_PER_BUFFER (441)
#define DITHER_FLAG (1)
#define PA_SAMPLE_TYPE paFloat32
#define SAMPLE_SIZE (4)
#define SAMPLE_SILENCE (0)
#define PRINTF_S_FORMAT "%f"
/*******************************************************************/
double multiplier = 1.0;
double multiplierStep = 0.1;
int main(int argc, char **argv);
int xrun(PaStream *stream, int err, char* sampleBlock);
void error1(PaStream *stream, char* sampleBlock);
void error2(PaStream *stream, int err);
void listDevices();
// Use tinycon and a second thread for non blocking input
class tcon : public tinyConsole
{
public:
tcon (std::string s): tinyConsole(s) {;}
int hotkeys(char c)
{
if (c == 's') {
if (multiplier >= (0+multiplierStep)) {
multiplier -= multiplierStep;
}
printf( "Multiplier: %f\n", multiplier );
return 1;
}
if (c == 'w') {
multiplier += multiplierStep;
printf( "Multiplier: %f\n", multiplier );
return 1;
}
return 0;
}
};
int inputThread() {
tcon tc (std::string(""));
tc.run();
}
void listDevices() {
int i, numDevices, defaultDisplayed;
const PaDeviceInfo *deviceInfo;
Pa_Initialize();
numDevices = Pa_GetDeviceCount();
printf( "Number of devices = %d\n", numDevices );
int isInputDevice = 0;
for( i=0; i<numDevices; i++ )
{
deviceInfo = Pa_GetDeviceInfo( i );
int isInputDevice = (deviceInfo->maxInputChannels > 0);
printf( "%sDeviceID: %d, Name: %s\n", (isInputDevice ? "Input" : "Output"), i, deviceInfo->name);
}
fprintf (stderr, "Press any key to close\n");
getch();
}
int main (int argc, char **argv)
{
int c;
int inputDeviceId = -1;
int outputDeviceId = -1;
opterr = 0;
const char* helpMessage =
"-h : show this help message\n"
"-i <int> : select the INPUT DEVICE by id\n"
"-o <int> : select the OUPUT DEVICE by id\n"
"-m <double> : SIGNAL MULTIPLIER\n"
"-s <double> : SIGNAL MULTIPLIER STEP (press w or s while console focused to go up and down by this ammount.\n"
"-d : list devices\n";
while ((c = getopt (argc, argv, "i:o:l:m:s:hd")) != -1) {
switch (c) {
case 'i':
inputDeviceId = atoi(optarg);
break;
case 'o':
outputDeviceId = atoi(optarg);
break;
case 'm':
multiplier = atof(optarg);
break;
case 's':
multiplierStep = atof(optarg);
break;
case 'd':
listDevices();
return 0;
case '?':
if (isprint (optopt))
fprintf (stderr, "Unknown option `-%c'.\n", optopt);
else
fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt);
case 'h':
fprintf (stderr, helpMessage);
fprintf (stderr, "Press any key to close\n");
getch();
return 1;
default:
abort ();
}
}
// Start non blocking input thread
std::thread nonBlockingInputThread(inputThread);
PaStreamParameters inputParameters, outputParameters;
PaStream *stream = NULL;
PaError err;
const PaDeviceInfo* inputInfo;
const PaDeviceInfo* outputInfo;
char *sampleBlock = NULL;
int i;
int numBytes;
int numChannels;
err = Pa_Initialize();
if( err != paNoError ) error2(stream, err);
inputParameters.device = (inputDeviceId == -1) ? Pa_GetDefaultInputDevice() : inputDeviceId; /* default input device */
inputInfo = Pa_GetDeviceInfo( inputParameters.device );
outputParameters.device = (outputDeviceId == -1) ? Pa_GetDefaultOutputDevice() : outputDeviceId; /* default output device */
outputInfo = Pa_GetDeviceInfo( outputParameters.device );
numChannels = inputInfo->maxInputChannels < outputInfo->maxOutputChannels
? inputInfo->maxInputChannels : outputInfo->maxOutputChannels;
inputParameters.channelCount = numChannels;
inputParameters.sampleFormat = PA_SAMPLE_TYPE;
inputParameters.suggestedLatency = inputInfo->defaultHighInputLatency ;
inputParameters.hostApiSpecificStreamInfo = NULL;
printf( "Input device # %d.\n", inputParameters.device );
printf( " Name: %s\n", inputInfo->name );
outputParameters.channelCount = numChannels;
outputParameters.sampleFormat = PA_SAMPLE_TYPE;
outputParameters.suggestedLatency = outputInfo->defaultHighOutputLatency;
outputParameters.hostApiSpecificStreamInfo = NULL;
printf( "Output device # %d.\n", outputParameters.device );
printf( " Name: %s\n", outputInfo->name );
/* -- setup -- */
err = Pa_OpenStream(
&stream,
&inputParameters,
&outputParameters,
SAMPLE_RATE,
FRAMES_PER_BUFFER,
paClipOff, /* we won't output out of range samples so don't bother clipping them */
NULL, /* no callback, use blocking API */
NULL ); /* no callback, so no callback userData */
if( err != paNoError ) error2(stream, err);
numBytes = FRAMES_PER_BUFFER * numChannels * SAMPLE_SIZE ;
sampleBlock = (char *) malloc( numBytes );
if( sampleBlock == NULL )
{
printf("Could not allocate record array.\n");
error1(stream, sampleBlock);
}
err = Pa_StartStream( stream );
if( err != paNoError ) error1(stream, sampleBlock);
while (1) {
// You may get underruns or overruns if the output is not primed by PortAudio.
err = Pa_ReadStream( stream, sampleBlock, FRAMES_PER_BUFFER );
if( err ) xrun(stream, err, sampleBlock);
int blockIndex;
float* sampleBlockShort = (float*)sampleBlock;
for (blockIndex = 0; blockIndex < FRAMES_PER_BUFFER; blockIndex++) {
/*
double dSample = (double)sampleBlockShort[blockIndex];
dSample *= multiplier;
if (dSample > 32767.0) dSample = 32767.0;
if (dSample < -32768.0) dSample = -32768.0;
sampleBlockShort[blockIndex] = (short)dSample;
*/
sampleBlockShort[blockIndex] *= multiplier;
}
err = Pa_WriteStream( stream, sampleBlock, FRAMES_PER_BUFFER );
if( err ) xrun(stream, err, sampleBlock);
}
printf("Wire off.\n"); fflush(stdout);
err = Pa_StopStream( stream );
if( err != paNoError ) error1(stream, sampleBlock);
free( sampleBlock );
Pa_Terminate();
return 0;
}
int xrun(PaStream *stream, int err, char* sampleBlock) {
printf("err = %d\n", err); fflush(stdout);
if( stream ) {
Pa_AbortStream( stream );
Pa_CloseStream( stream );
}
free( sampleBlock );
Pa_Terminate();
if( err & paInputOverflow )
fprintf( stderr, "Input Overflow.\n" );
if( err & paOutputUnderflow )
fprintf( stderr, "Output Underflow.\n" );
return -2;
}
void error1(PaStream *stream, char* sampleBlock) {
free( sampleBlock );
exit(-1);
}
void error2(PaStream *stream, int err) {
if( stream ) {
Pa_AbortStream( stream );
Pa_CloseStream( stream );
}
Pa_Terminate();
fprintf( stderr, "An error occured while using the portaudio stream\n" );
fprintf( stderr, "Error number: %d\n", err );
fprintf( stderr, "Error message: %s\n", Pa_GetErrorText( err ) );
exit(-1);
}
【问题讨论】:
-
猜猜剪裁浮点数是将乘法限制为 -1.0 如果小于 -1.0 则限制为 -1.0 如果大于 1.0 则限制为 1.0。与您简称的方式相同。您是否尝试在不进行任何乘法的情况下将输入数据复制到输出?你应该听到没有问题的好声音。你猜你知道,以同样的方式增加音量,你也会增加输入信号的噪音。
-
@alexander 直接将输入复制到输出不会产生质量问题。我确实知道增加增益会增加噪声,但我并没有从放大已经存在的噪声的角度考虑它,这更有意义。
-
Portaudio 在您的配置中使用交错数据格式。因此,如果 'numChannels > 1' 您迭代部分缓冲区并且一些样本保持不变。所以你可能会听到噪音问题。将循环条件更改为“blockIndex
-
糟糕!我想这意味着我只对缓冲区的两个通道的前半部分应用了增益,很惊讶我让那个滑倒了。
-
And current attempt looks fine except lost clipping (-1.0 .. 1.0).所以我应该这样做:float sampleWithGain = sampleBlockShort[blockIndex]; sampleWithGain *= multiplier; sampleWithGain = (sampleWithGain > 1) ? 1 : (sampleWithGain < 1) ? -1 : sampleWithGain; sampleBlokcShort[blockIndex] = sampleWithGain;
标签: c++ signal-processing microphone portaudio