Last active
April 26, 2018 02:27
-
-
Save auzanmuh/63f4f2283a76d32823c6767374ff693f to your computer and use it in GitHub Desktop.
Portaudio callback
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <chrono> | |
#include <csignal> | |
#include <portaudio.h> | |
#include <thread> | |
#include <opus/opus.h> | |
#include <log4cpp/Category.hh> | |
#include <log4cpp/FileAppender.hh> | |
#include <log4cpp/OstreamAppender.hh> | |
#include <codec2/codec2.h> | |
#define NUM_CHANNELS 2 | |
#define SAMPLE_RATE 8000 | |
#define FRAMES_PER_BUFFER 960 | |
#define BITRATE 8000 | |
static log4cpp::Appender *appender = new log4cpp::OstreamAppender("console", &std::cout); | |
static log4cpp::Category& logger = log4cpp::Category::getRoot(); | |
static volatile sig_atomic_t sig_caught = 0; | |
OpusDecoder *opusDecoder; | |
OpusEncoder *opusEncoder; | |
struct CODEC2 *codec2; | |
short *buf; | |
unsigned char *bits; | |
int nsam, nbit; | |
// encoded buffer | |
std::vector<unsigned char> encoded(FRAMES_PER_BUFFER * NUM_CHANNELS * 0.5); | |
/** | |
* Signal interrupt handler | |
* | |
* @param signal the signal | |
*/ | |
static void sigHandler(int signal) { | |
logger.warn("caught signal %d", signal); | |
sig_caught = signal; | |
} | |
/** | |
* Portaudio callback | |
*/ | |
static int streamCallback( const void *inputBuffer, | |
void *outputBuffer, | |
unsigned long framesPerBuffer, | |
const PaStreamCallbackTimeInfo *timeInfo, | |
PaStreamCallbackFlags statusFlags, | |
void *userData) { | |
(void) timeInfo; // Prevent unused variable warnings. | |
(void) statusFlags; | |
(void) userData; | |
// Cast data to floats: | |
auto *in_buff = (int16_t*)inputBuffer; | |
auto *out_buff = (int16_t*)outputBuffer; | |
opus_int16 enc_bytes; | |
opus_int16 dec_bytes; | |
// Clean playback | |
// unsigned long i; | |
// for( i = 0; i < framesPerBuffer * NUM_CHANNELS; i++ ) { | |
// out_buff[i] = in_buff[i]; | |
// } | |
// OPUS ENCODE & DECODE | |
// if ((enc_bytes = opus_encode(opusEncoder, reinterpret_cast<opus_int16 const*>(in_buff), | |
// FRAMES_PER_BUFFER, | |
// encoded.data(), | |
// encoded.size())) < 0) { | |
// std::cout << "opus_encode failed: " << enc_bytes << "\n"; | |
// sig_caught = 2; | |
// } | |
// if ((dec_bytes = opus_decode(opusDecoder, | |
// encoded.data(), | |
// enc_bytes, | |
// out_buff, FRAMES_PER_BUFFER, 0)) < 0) { | |
// std::cout << "opus_decode failed: " << dec_bytes << "\n"; | |
// sig_caught = 2; | |
// } | |
// CODEC2 ENCODE & DECODE | |
codec2_encode(codec2, bits, in_buff); | |
codec2_decode(codec2, out_buff, bits); | |
return paContinue; | |
} | |
/** | |
* main function | |
* | |
* Program flow: | |
* - Parse file configuration | |
* - Init PortAudio engine and open default input and output audio devices | |
* - Loop PortAudio until Ctrl + C | |
* - Clean up PortAudio engine | |
*/ | |
int main(int argc, char *argv[]){ | |
/////////////////////// | |
// init logger | |
/////////////////////// | |
appender->setLayout(new log4cpp::BasicLayout()); | |
logger.setPriority(log4cpp::Priority::INFO); | |
logger.addAppender(appender); | |
/////////////////////// | |
// init opus library | |
/////////////////////// | |
int opusErr; | |
opusEncoder = opus_encoder_create(SAMPLE_RATE, NUM_CHANNELS, OPUS_APPLICATION_AUDIO, &opusErr); | |
if(opusErr != OPUS_OK) { | |
std::cout <<"opus_encoder_create failed :" << opusErr << std::endl; | |
return 0; | |
} | |
opusDecoder = opus_decoder_create(SAMPLE_RATE, NUM_CHANNELS, &opusErr); | |
if(opusErr != OPUS_OK) { | |
std::cout <<"opus_decoder_create failed :" << opusErr << std::endl; | |
return 0; | |
} | |
int opus_bandwidth; | |
opus_encoder_ctl(opusEncoder, OPUS_SET_VBR(0)); | |
opus_encoder_ctl(opusEncoder, OPUS_SET_SIGNAL(OPUS_SIGNAL_VOICE)); | |
opus_encoder_ctl(opusEncoder, OPUS_SET_COMPLEXITY(5)); | |
opus_encoder_ctl(opusEncoder, OPUS_SET_DTX(1)); | |
opus_encoder_ctl(opusEncoder, OPUS_GET_BANDWIDTH(&opus_bandwidth)); | |
opus_encoder_ctl(opusEncoder, OPUS_SET_INBAND_FEC(1)); | |
opusErr = opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(BITRATE)); | |
if(opusErr != OPUS_OK) { | |
std::cout <<"opus_encoder_ctl failed to set bitrate :" << opusErr << std::endl; | |
return 0; | |
} | |
/////////////////////// | |
// init codec2 library | |
/////////////////////// | |
codec2 = codec2_create(CODEC2_MODE_3200); | |
nsam = codec2_samples_per_frame(codec2); | |
buf = (short*)malloc(nsam*sizeof(short)); | |
nbit = codec2_bits_per_frame(codec2); | |
bits = (unsigned char*)malloc(nbit*sizeof(char)); | |
/////////////////////// | |
// init audio library | |
/////////////////////// | |
PaError err; | |
err = Pa_Initialize(); | |
if(err != paNoError) { | |
logger.error("PortAudio error: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
// list all devices (include hostApi): | |
auto deviceCount = (int) Pa_GetDeviceCount(); | |
logger.info("There are %d audio devices", deviceCount); | |
for( int i = 0; i < deviceCount; i++ ){ | |
const PaDeviceInfo *devInfo; | |
devInfo = Pa_GetDeviceInfo( (PaDeviceIndex)i ); | |
const PaHostApiInfo *host; | |
host = Pa_GetHostApiInfo( devInfo->hostApi ); | |
logger.info("Device Id Number %d: %s : %s", i, host->name, devInfo->name); | |
} | |
PaStream *stream = nullptr; | |
PaStreamParameters inputParameters{}; | |
PaStreamParameters outputParameters{}; | |
// input parameter | |
inputParameters.device = Pa_GetDefaultInputDevice(); | |
if (inputParameters.device == paNoDevice) { | |
logger.error("No default input device."); | |
exit(-1); | |
} | |
inputParameters.channelCount = NUM_CHANNELS; | |
inputParameters.sampleFormat = paInt16; | |
inputParameters.suggestedLatency = Pa_GetDeviceInfo(inputParameters.device)->defaultLowInputLatency; | |
inputParameters.hostApiSpecificStreamInfo = nullptr; | |
logger.info("inputParameters.suggestedLatency: %.4f", inputParameters.suggestedLatency); | |
// output parameter | |
outputParameters.device = Pa_GetDefaultOutputDevice(); | |
if(outputParameters.device == paNoDevice) { | |
logger.error("No default output device."); | |
exit(-1); | |
} | |
outputParameters.channelCount = NUM_CHANNELS; | |
outputParameters.sampleFormat = paInt16; | |
outputParameters.suggestedLatency = Pa_GetDeviceInfo(outputParameters.device)->defaultHighOutputLatency; | |
outputParameters.hostApiSpecificStreamInfo = nullptr; | |
logger.info("outputParameters.suggestedLatency: %.4f", outputParameters.suggestedLatency); | |
err = Pa_OpenStream(&stream, // the input stream | |
&inputParameters, // input params | |
&outputParameters, // output params | |
SAMPLE_RATE, // sample rate | |
nsam, // frames per buffer | |
paClipOff, // we won't output out of range samples so don't bother clipping them | |
streamCallback, // PortAudio callback function | |
nullptr); // data pointer | |
if(err != paNoError) { | |
logger.error("Failed to open input stream: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
// start the streams | |
err = Pa_StartStream(stream); | |
if(err != paNoError) { | |
logger.error("Failed to start stream: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
// init signal handler | |
struct sigaction action; | |
action.sa_handler = sigHandler; | |
action.sa_flags = 0; | |
sigemptyset(&action.sa_mask); | |
sigaction(SIGINT, &action, nullptr); | |
sigaction(SIGTERM, &action, nullptr); | |
while(!sig_caught) { | |
std::this_thread::sleep_for(std::chrono::milliseconds(250)); | |
} | |
/////////////////////////// | |
// clean up audio library | |
/////////////////////////// | |
logger.warn("Cleaning up PortAudio..."); | |
// close streams | |
logger.warn("Stop stream"); | |
err = Pa_StopStream(stream); | |
if(err != paNoError) { | |
logger.error("Failed to close stop: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
logger.warn("Closing stream"); | |
err = Pa_CloseStream(stream); | |
if(err != paNoError) { | |
logger.error("Failed to close stream: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
// terminate PortAudio engine | |
logger.warn("Terminating PortAudio engine"); | |
err = Pa_Terminate(); | |
if(err != paNoError) { | |
logger.error("PortAudio error: %s", Pa_GetErrorText(err)); | |
exit(-1); | |
} | |
// destroy opus | |
logger.warn("Destroy opus"); | |
opus_decoder_destroy(opusDecoder); | |
opus_encoder_destroy(opusEncoder); | |
// destroy codec2 | |
logger.warn("Destroy codec2"); | |
codec2_destroy(codec2); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment