1
votes

I'm trying to make a C++ application to transmit audio via a VoIP protocol between 2 clients (using UDP).

I'm working with Portaudio C library and I have issues to encapsulate this lib. In order to send the recorded audio to another client, I'd like to get sound samples as it is recorded (real time).

For the moment I can only record sound, and then, play what I recorded. I'm not comfortable at all with this library and any help would be appreciated.

Here's what I've done so far.

Audio.cpp -> Callback methods:

static int PaRecordCallback(const void *input, void *output, unsigned long frameCount, \
    const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
    Audio *audio = reinterpret_cast<Audio *>(userData);

    return audio->RecordCallback(input, output, frameCount, timeInfo, statusFlags);
}

static int PaPlayCallback(const void *input, void *output, unsigned long frameCount, \
    const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags, void *userData)
{
    Audio *audio = reinterpret_cast<Audio *>(userData);

    return audio->PlayCallback(input, output, frameCount, timeInfo, statusFlags);
}

int Audio::RecordCallback(const void *input, void *output, unsigned long &frameCount, \
    const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags &statusFlags)
{
    std::cout << "Frame index:\t\t" << _recordedFrameIndex << std::endl << "Max frame index:\t" << _maxFrameIndex << std::endl << "--------------" << std::endl;
    const SAMPLE *rptr = static_cast<const SAMPLE *>(input);
    SAMPLE *wptr = &_recordedSamples[_recordedFrameIndex * NUM_CHANNELS];
    unsigned long framesLeft = _maxFrameIndex - _recordedFrameIndex;
    unsigned long framesToCalc;
    int finished;

    if (framesLeft < frameCount) {
        framesToCalc = framesLeft;
        finished = paComplete;
    } else {
        framesToCalc = frameCount;
        finished = paContinue;
    }
    for (unsigned long i = 0; i < framesToCalc; i++) {
        *wptr++ = *rptr++;
        if (NUM_CHANNELS == 2)
            *wptr++ = *rptr++;
    }
    _recordedFrameIndex += framesToCalc;
    return finished;
}

int Audio::PlayCallback(const void *input, void *output, unsigned long &frameCount, \
    const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags &statusFlags)
{
    SAMPLE *rptr = &_recordedSamples[_playedFrameIndex * NUM_CHANNELS];
    SAMPLE *wptr = static_cast<SAMPLE *>(output);
    unsigned long framesLeft = _maxFrameIndex - _playedFrameIndex;
    unsigned int i;
    int finished;

    if (framesLeft < frameCount) {
        for (i = 0; i < framesLeft; i++) {
            *wptr++ = *rptr++;
            if (NUM_CHANNELS == 2)
                *wptr++ = *rptr++;
        }
        for (; i < frameCount; i++) {
            *wptr++ = 0;
            if (NUM_CHANNELS == 2)
                *wptr++ = 0;
        }
        _playedFrameIndex += framesLeft;
        finished = paComplete;
    } else {
        for (i = 0; i < frameCount; i++) {
            *wptr++ = *rptr++;
            if (NUM_CHANNELS == 2)
                *wptr++ = *rptr++;
        }
        _playedFrameIndex += frameCount;
        finished = paContinue;
    }
    return finished;
}

Audio.cpp -> Record and Play methods:

void Audio::Record()
{
    if (!_recordStream) {
        OpenRecordStream();
        _recordedFrameIndex = 0;
        _err = Pa_StartStream(_recordStream);
        if (_err != paNoError)
            AudioError("Audio::Record -> Pa_StartStream()");
        std::cout << "Audio record stream started." << std::endl;
        std::cout << "Recording ..." << std::endl;
        _recording = true;
        fflush(stdout);
    } else if (_recording)
        Pa_Sleep(1000);
}

void Audio::Play()
{
    if (!_playStream) {
        OpenPlayStream();
        _playedFrameIndex = 0;
        _err = Pa_StartStream(_playStream);
        if (_err != paNoError)
            AudioError("Audio::Play -> Pa_StartStream()");
        std::cout << "Audio play stream started." << std::endl;
        std::cout << "Playing ..." << std::endl;
        _playing = true;
        fflush(stdout);
    } else if (_playing)
        Pa_Sleep(500);
}

Audio.hpp -> Class audio:

#include <portaudio.h>

typedef int16_t SAMPLE;

#define PA_SAMPLE_TYPE      paInt16
#define PRINTF_S_FORMAT     "%.8f"
#define SAMPLE_RATE         44100
#define SAMPLE_SILENCE      0.0f
#define FRAMES_PER_BUFFER   1
#define NUM_SECONDS         5
#define NUM_CHANNELS        2
#define DITHER_FLAG         0
#define WRITE_TO_FILE       0
#define SAMPLE_SIZE         NUM_SECONDS * SAMPLE_RATE * NUM_CHANNELS

class Audio
{
    public:
        Audio();
        ~Audio();

        void Record();
        void Play();

        void OpenRecordStream();
        void OpenPlayStream();

        void CloseRecordStream();
        void ClosePlayStream();

        const bool &isRecording() const;
        const bool &isPlaying() const;

        const PaStream *GetRecordStream() const;
        const PaStream *GetPlayStream() const;

        void GetSamples(SAMPLE *);
        void SetSamples(SAMPLE *);

        int RecordCallback(const void *, void *, unsigned long &, \
            const PaStreamCallbackTimeInfo *, PaStreamCallbackFlags &);
        int PlayCallback(const void *, void *, unsigned long &, \
            const PaStreamCallbackTimeInfo *, PaStreamCallbackFlags &);
        bool _recording;
        bool _playing;

    protected:
    private:
        // Functions:
        void AudioError(const std::string &);

        // Variables:
        PaError _err;
        PaStream *_playStream;
        PaStream *_recordStream;
        SAMPLE *_samplesToPlay;
        SAMPLE *_recordedSamples;
        unsigned long _recordedFrameIndex;
        unsigned long _playedFrameIndex;
        unsigned long _maxFrameIndex;
        PaStreamParameters _inputParameters;
        PaStreamParameters _outputParameters;
};

I apologize if it's very long, but I want you to have all the necessary informations to understand my problem. I don't ask questions often, so I really need some help here.

Thank you.

1
Could you look at reducing the amount of code in this question, please see the help on providing a minimal reproducible example stackoverflow.com/help/minimal-reproducible-exampleOliver Lorton
Thank you, I just suppressed some code to make it clearerThomas Péan
In a nutshell, you need: 1. Write callback method portaudio.com/docs/v19-doxydocs/writing_a_callback.html 2. Process the audio samples which you got in the callback. Most likely you need PaUtilRingBuffer for that - portaudio.com/docs/v19-doxydocs-dev/…. See also stackoverflow.com/questions/44645466/…Vladimir Bershov
Please note that you cannot use blocking or IO operation in callbacks (including std::cout)Vladimir Bershov

1 Answers

1
votes

You need to use a full duplex callback to record and play in real time so you catch the input buffer in chunks for your recordings (when you want to record) and send the recordings (or even the incoming sound) to the output buffer, also in chunks. The chunksize normally would be from 64 to 4096 frames, and each frame normally would contain 2 samples (channel L and channel R)

The full duplex callback is a kind of circular buffer that brings you x frames when the input buffer is filled by the ADC and where you fill the output buffer with x frames for being ready for when the DAC asks for it.