Search code examples
c++audiovoipportaudioopus

Portaudio + Opus: Horrible sound quality


I'm currently developing a VOIP application. To achieve this, I use the PortAudio library to retrieve and play sound, and the Opus library to encode and decode sound packets.

For now, I successfully managed to use PortAudio. My program simply do:

  1. Get Sound from the microphone
  2. Play back the sound

The sound quality is absolutely good.

I'm now trying to encode and decode the sound packet. I've coded an EncodeManagerClass to do it and my program now do:

  1. Get sound from the microphone
  2. Encode the sound
  3. Decode it
  4. Play it back

But now, the sound quality is absolutely horrible (and it's obviouly problematic in a VOIP application).

Here is my EncodeManager class:

class EncodeManager {

    // ctor - dtor
    public:
        EncodeManager(void);
        ~EncodeManager(void);

    // coplien form
    private:
        EncodeManager(const EncodeManager &) {}
        const EncodeManager &operator=(const EncodeManager &) { return *this; }

    // encode - decode
    public:
        Sound::Encoded  encode(const Sound::Decoded &sound);
        Sound::Decoded  decode(const Sound::Encoded &sound);

    // attributes
    private:
        OpusEncoder *mEncoder;
        OpusDecoder *mDecoder;

};

Here is the source file:

EncodeManager::EncodeManager(void) {
    int error;

    mEncoder = opus_encoder_create(Sound::SAMPLE_RATE, Sound::NB_CHANNELS, OPUS_APPLICATION_VOIP, &error);
    if (error != OPUS_OK)
        throw new SoundException("fail opus_encoder_create");

    mDecoder = opus_decoder_create(Sound::SAMPLE_RATE, Sound::NB_CHANNELS, &error);
    if (error != OPUS_OK)
        throw new SoundException("fail opus_decoder_create");
}

EncodeManager::~EncodeManager(void) {
    if (mEncoder)
        opus_encoder_destroy(mEncoder);

    if (mDecoder)
        opus_decoder_destroy(mDecoder);
}

Sound::Encoded  EncodeManager::encode(const Sound::Decoded &sound) {
    Sound::Encoded encoded;

    encoded.buffer = new unsigned char[sound.size];
    encoded.size = opus_encode_float(mEncoder, sound.buffer, Sound::FRAMES_PER_BUFFER, encoded.buffer, sound.size);

    if (encoded.size < 0)
        throw new SoundException("fail opus_encode_float");

    return encoded;
}

Sound::Decoded  EncodeManager::decode(const Sound::Encoded &sound) {
    Sound::Decoded decoded;

    decoded.buffer = new float[Sound::FRAMES_PER_BUFFER * Sound::NB_CHANNELS];
    decoded.size = opus_decode_float(mDecoder, sound.buffer, sound.size, decoded.buffer, Sound::FRAMES_PER_BUFFER, 0);

    if (decoded.size < 0)
        throw new SoundException("fail opus_decode_float");

    return decoded;
}

Here is my main:

int main(void) {
    SoundInputDevice input;
    SoundOutputDevice output;
    EncodeManager encodeManager;

    input.startStream();
    output.startStream();

    while (true) {
        Sound::Decoded *sound;

        input >> sound;
        if (sound) {
            Sound::Encoded encodedSound = encodeManager.encode(*sound);
            Sound::Decoded decodedSound = encodeManager.decode(encodedSound);
            output << &decodedSound;
        }
    }

    return 0;
}

Additional informations:

const int   SAMPLE_RATE = 48000;
const int   NB_CHANNELS = 2;
const int   FRAMES_PER_BUFFER = 480;

I've tried to configure the opus encoder with opus_encode_ctl (bandwidth, bitrate, VBR), but it doesn't work at all: sound quality is still shitty.

Even if I change the SAMPLE_RATE or the FRAME_PER_BUFFER, sound quality doesn't improve...

Have I missed something concerning PortAudio/Opus?


Solution

  • I finally find a solution.

    The issue does not come from Opus but from the main...

    if (sound) {
      Sound::Encoded encodedSound = encodeManager.encode(*sound);
      Sound::Decoded decodedSound = encodeManager.decode(encodedSound);
      output << &decodedSound;
     }
    

    Here, I pass a local variable to my output stream. But the output stream works asynchronously: so my variable was destroyed before the sound packed was played.

    Using pointers is the simplest way to solve the problem. I personnaly decided to refactore a little bit my code.