Playing back mp3 files with QJack

Home / Playing back mp3 files with QJack

Yesterday a mail reached me on how to play back mp3 files with QJack. In general, there are two steps involved:

  1. Reading the mp3 file, decoding and filling a ringbuffer for playback
  2. Playing back samples with JACK

Let’s prepare the JACK client with QJack. In order to be able to store a whole mp3, we create a ringbuffer for 1000 seconds of playback. Then we register two output ports and connect them to the system playback.

void MainWindow::setupJackClient() {
    // Connect to JACK server
    _client.connectToServer("mp3_player");

    // Create a 1000 seconds buffer (at 44,1kHz)
    _ringBufferLeft  = QJack::AudioRingBuffer(44100 * 1000);
    _ringBufferRight = QJack::AudioRingBuffer(44100 * 1000);

    // Register two output ports
    _outLeft     = _client.registerAudioOutPort("out_1");
    _outRight    = _client.registerAudioOutPort("out_2");

    _client.setProcessor(this);

    // Activate client.
    _client.activate();

    // Connect automatically to system playback.
    _client.connect(_outLeft, _client.portByName("system:playback_1"));
    _client.connect(_outRight, _client.portByName("system:playback_2"));
}

As for the process method, we just want to take a bunch of sample from the ring buffer and put it into the output buffers when JACK requests it.

void MainWindow::process(int samples) {
    // Just shift samples from the ringbuffers to the output buffers.
    _outLeft.buffer(samples).pop(_ringBufferLeft);
    _outRight.buffer(samples).pop(_ringBufferRight);
}

For decoding, we have to specify the target format, namely sample rate, sample type, channels and codec. The sample rate is taken from the QJack client, the sample type has to be 16bit signed integer (ie the amplitude of a sample may vary between -65536 and +65536) and we want to have stereo. Since we are decompressing, we set “audio/x-raw” as the codec.

void MainWindow::setupMp3Decoder() {
    QAudioFormat targetAudioFormat;
    targetAudioFormat.setSampleRate(_client.sampleRate());
    targetAudioFormat.setSampleType(QAudioFormat::SignedInt);
    targetAudioFormat.setChannelCount(2);
    targetAudioFormat.setCodec("audio/x-raw");
    _audioDecoder.setAudioFormat(targetAudioFormat);
}

Now, when the user selects a file, we pass it to QAudioDecoder and tell it to start decoding.

void MainWindow::on_toolButtonFileChoose_clicked() {
    QString fileName = QFileDialog::getOpenFileName(this, "Open mp3 file", QString(), "*.mp3");
    if(!fileName.isEmpty()) {
        ui->lineEditFileName->setText(fileName);
        _audioDecoder.setSourceFilename(fileName);
        _audioDecoder.start();
    }
}

As soon as QAudioDecoder has a bunch of samples decoded, it will emit the bufferReady()-signal. We hook in with our own method to grab the buffer and pack the samples on top of those in the ringbuffer. We have to take care that we need to convert from signed 16-bit integers to 32 bit floats here.

void MainWindow::transferSamples() {
    QAudioBuffer audioBuffer = _audioDecoder.read();
    if(audioBuffer.isValid()) {
        int frames = audioBuffer.frameCount();
        QJack::AudioSample left[frames];
        QJack::AudioSample right[frames];


        const QAudioBuffer::S16S *stereoBuffer = audioBuffer.constData();
        for (int i = 0; i < frames; i++) {
            left[i]     = (QJack::AudioSample)(stereoBuffer[i].left / 65536.0);
            right[i]    = (QJack::AudioSample)(stereoBuffer[i].right / 65536.0);
        }

        _ringBufferLeft.write(left, frames);
        _ringBufferRight.write(right, frames);

    }
}

That's it. Now you can launch the app, choose an mp3 file and it will start playing back via JACK. You can get the full working source code here:
https://github.com/cybercatalyst/QJack-Examples/tree/master/mp3player

Leave a Reply

Your email address will not be published. Required fields are marked *