Search code examples
c++portaudioopus

Joining Portaudio and Opus


Hi im trying to take sound from an open PortAudio Stream, encode it with opus, decode it and reproduce it again with portaudio.

Im doing this as a prototype just to try and understand the mechanics of this systems so, no real interest on following this concrete flow.

Thing is, portaudio gives buffers where OPUS needs Frames. Mi thought lead me to this in the portaudio side:

err = (Pa_ReadStream(stream, readBuffer, FRAMES_PER_BUFFER));
        if (err = paNoError){
            qDebug()<<"Fail read";
            qDebug()<<Pa_GetErrorText(err);
          // blockingRecord = false;
        }
       while (pos<FRAMES_PER_BUFFER){
            memcpy(frameBuffer,readBuffer+(pos*FRAME_SIZE*NUM_CHANNELS),FRAME_SIZE*CHANNELS);
            compressedSound = om.encodeOpus(frameBuffer);
            unCompressedSound = om.decodeOpus(compressedSound);
            memcpy(readBuffer+(pos*FRAME_SIZE*NUM_CHANNELS),unCompressedSound,FRAME_SIZE*CHANNELS);
            pos++;
        }
        pos = 0;
        err = (Pa_WriteStream(stream, readBuffer, FRAMES_PER_BUFFER));
        if (err != paNoError)
        {
            qDebug() << "FAIL WRITE";
            qDebug()<<Pa_GetErrorText(err);
            //blockingRecord = false;
        }

And this on the OPUS side:

unsigned char * OpusManager::encodeOpus(unsigned char *frame){
    memcpy(encoded, frame, FRAME_SIZE*CHANNELS);
    int ret = opus_encode(enc, encoded, FRAME_SIZE, compressed_buffer, encoded_data_size);
    if (ret<0){
        qDebug()<<"Failure while compressing sound";
        return NULL;
    }
    return (compressed_buffer);
}

unsigned char * OpusManager::decodeOpus(unsigned char *frame){
    int ret= opus_decode(dec, frame, encoded_data_size, decoded, FRAME_SIZE, 0);
    if (ret<0){
        qDebug()<<"Failure while decompressing sound";
        return NULL;
    }
    memcpy(uncompressed_buffer, decoded, FRAME_SIZE*CHANNELS);
    return (uncompressed_buffer);
}

No errors without encocing and perfect soud. With encode i get no errors till the PA_Writestream call, where i get a "Output underflowed" PaError. I suppose the way of taking the frames ive implemmented must be waaay wrong, but cant find info to help me with this.


Solution

  • It seems your interpretation of Opus' frame_size parameters to opus_encode and opus_decode is incorrect. If I understand your code correctly you're recording a packet of size FRAMES_PER_BUFFER frames and then try to turn it into N packets of size FRAME_SIZE. Instead, it seems to me that Opus wants to turn your packet of FRAMES_PER_BUFFER into another packet of equal frame count, and in doing so, only uses it's FRAME_SIZE parameter as some sort of quality control parameter for the encoding process. Below you'll find a complete sample that I believe does what you want. Play around with the '480' magic number in encode()/decode() and hear audio quality change.

    int opusErr;
    PaError paErr;
    std::string s;
    
    int const channels = 2;
    int const bufferSize = 480;
    int const sampleRate = 48000;
    int const durationSeconds = 5;
    
    opus_int32 enc_bytes;
    opus_int32 dec_bytes;
    int framesProcessed = 0;
    
    std::vector<unsigned short> captured(bufferSize * channels);
    std::vector<unsigned short> decoded(bufferSize * channels);
    // * 2: byte count, 16 bit samples
    std::vector<unsigned char> encoded(bufferSize * channels * 2);
    
    // initialize opus
    OpusEncoder* enc = opus_encoder_create(
        sampleRate, channels, OPUS_APPLICATION_AUDIO, &opusErr);
    if (opusErr != OPUS_OK)
    {
        std::cout << "opus_encoder_create failed: " << opusErr << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    OpusDecoder* dec = opus_decoder_create(
        sampleRate, channels, &opusErr);
    if (opusErr != OPUS_OK)
    {
        std::cout << "opus_decoder_create failed: " << opusErr << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    // initialize portaudio
    if ((paErr = Pa_Initialize()) != paNoError)
    {
        std::cout << "Pa_Initialize failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    PaStream* stream = nullptr;
    if ((paErr = Pa_OpenDefaultStream(&stream,
          channels, channels, paInt16, sampleRate,
          bufferSize, nullptr, nullptr)) != paNoError)
    {
        std::cout << "Pa_OpenDefaultStream failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    // start stream
    if ((paErr = Pa_StartStream(stream)) != paNoError) 
    {
        std::cout << "Pa_StartStream failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    // capture, encode, decode & render durationSeconds of audio
    while (framesProcessed < sampleRate * durationSeconds)
    {
        if ((paErr = Pa_ReadStream(stream, 
            captured.data(), bufferSize)) != paNoError)
        {
            std::cout << "Pa_ReadStream failed: " << Pa_GetErrorText(paErr) << "\n";
            std::getline(std::cin, s);
            return 1;
        }
    
        if ((enc_bytes = opus_encode(enc, reinterpret_cast<opus_int16 const*>(
            captured.data()), 480, encoded.data(), encoded.size())) < 0)
        {
            std::cout << "opus_encode failed: " << enc_bytes << "\n";
            std::getline(std::cin, s);
            return 1;
        }
    
        if ((dec_bytes = opus_decode(dec, encoded.data(), enc_bytes,
            reinterpret_cast<opus_int16*>(decoded.data()), 480, 0)) < 0)
        {
            std::cout << "opus_decode failed: " << dec_bytes << "\n";
            std::getline(std::cin, s);
            return 1;
        }
    
        if ((paErr = Pa_WriteStream(stream, decoded.data(), bufferSize)) != paNoError)
        {
            std::cout << "Pa_WriteStream failed: " << Pa_GetErrorText(paErr) << "\n";
            std::getline(std::cin, s);
            return 1;
        }
    
        framesProcessed += bufferSize;
    }
    
    // stop stream
    if ((paErr = Pa_StopStream(stream)) != paNoError)
    {
        std::cout << "Pa_StopStream failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    // cleanup portaudio
    if ((paErr = Pa_CloseStream(stream)) != paNoError) 
    {
        std::cout << "Pa_CloseStream failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    if ((paErr = Pa_Terminate()) != paNoError) 
    {
        std::cout << "Pa_Terminate failed: " << Pa_GetErrorText(paErr) << "\n";
        std::getline(std::cin, s);
        return 1;
    }
    
    // cleanup opus
    opus_decoder_destroy(dec);
    opus_encoder_destroy(enc);