Skip to content

Commit

Permalink
Add 24 bit support to Oboe
Browse files Browse the repository at this point in the history
  • Loading branch information
badaix committed Jun 30, 2024
1 parent ad06a38 commit 142afab
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 15 deletions.
51 changes: 44 additions & 7 deletions client/player/oboe_player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
// 3rd party headers

// standard headers
#include <cstring>
#include <iostream>


Expand Down Expand Up @@ -81,13 +82,28 @@ oboe::Result OboePlayer::openStream()
if (settings_.sharing_mode == ClientSettings::SharingMode::exclusive)
sharing_mode = oboe::SharingMode::Exclusive;

oboe::AudioFormat audio_format;
switch (stream_->getFormat().bits())
{
case 32:
audio_format = oboe::AudioFormat::I32;
break;
case 24:
audio_format = oboe::AudioFormat::I24;
break;
case 16:
default:
audio_format = oboe::AudioFormat::I16;
break;
}

// The builder set methods can be chained for convenience.
oboe::AudioStreamBuilder builder;
auto result = builder.setSharingMode(sharing_mode)
->setPerformanceMode(oboe::PerformanceMode::None)
->setChannelCount(stream_->getFormat().channels())
->setSampleRate(stream_->getFormat().rate())
->setFormat(oboe::AudioFormat::I16)
->setFormat(audio_format)
->setDataCallback(this)
->setErrorCallback(this)
->setDirection(oboe::Direction::Output)
Expand All @@ -96,11 +112,12 @@ oboe::Result OboePlayer::openStream()
//->setFramesPerCallback(960) // 2*192)
->openStream(out_stream_);

LOG(INFO, LOG_TAG) << "Hardware sample rate: " << out_stream_->getHardwareSampleRate()
<< ", hardware format: " << oboe::convertToText(out_stream_->getHardwareFormat()) << "\n";
if (out_stream_->getAudioApi() == oboe::AudioApi::AAudio)
{
LOG(INFO, LOG_TAG) << "AudioApi: AAudio\n";
// mLatencyTuner = std::make_unique<oboe::LatencyTuner>(*out_stream_);
mLatencyTuner = nullptr;
latency_tuner_ = nullptr;
}
else
{
Expand Down Expand Up @@ -150,20 +167,40 @@ double OboePlayer::getCurrentOutputLatencyMillis() const

oboe::DataCallbackResult OboePlayer::onAudioReady(oboe::AudioStream* /*oboeStream*/, void* audioData, int32_t numFrames)
{
if (mLatencyTuner)
mLatencyTuner->tune();
if (latency_tuner_)
latency_tuner_->tune();

double output_latency = getCurrentOutputLatencyMillis();
// LOG(INFO, LOG_TAG) << "getCurrentOutputLatencyMillis: " << output_latency << ", frames: " << numFrames << "\n";
chronos::usec delay(static_cast<int>(output_latency * 1000.));

if (!stream_->getPlayerChunkOrSilence(audioData, delay, numFrames))
void* buffer = audioData;
if (stream_->getFormat().bits() == 24)
{
// Oboe expects 24 bit audio in 3 bytes, while Snapcast stores 24 bit in 4 bytes.
// Data must be converted before passing it to Oboe, but first we need to adabt the buffer size
size_t needed = stream_->getFormat().frameSize() * numFrames;
if (audio_data_.size() < needed)
{
LOG(INFO, LOG_TAG) << "Resizing audio buffer to " << numFrames << " frames, (" << needed << ") bytes\n";
audio_data_.resize(needed);
}
buffer = audio_data_.data();
}

if (!stream_->getPlayerChunkOrSilence(buffer, delay, numFrames))
{
// LOG(INFO, LOG_TAG) << "Failed to get chunk. Playing silence.\n";
}
else
{
adjustVolume(static_cast<char*>(audioData), numFrames);
adjustVolume(static_cast<char*>(buffer), numFrames);
if (stream_->getFormat().bits() == 24)
{
// Copy the 24 bit, 4 bytes data into Oboes 24 bit, 3 bytes buffer
for (size_t n = 0; n < static_cast<size_t>(numFrames) * stream_->getFormat().channels(); ++n)
memcpy(static_cast<char*>(audioData) + 3 * n, audio_data_.data() + 4 * n, 3);
}
}

return oboe::DataCallbackResult::Continue;
Expand Down
23 changes: 15 additions & 8 deletions client/player/oboe_player.hpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/***
This file is part of snapcast
Copyright (C) 2014-2022 Johannes Pohl
Copyright (C) 2014-2024 Johannes Pohl
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -16,8 +16,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
***/

#ifndef OBOE_PLAYER_HPP
#define OBOE_PLAYER_HPP
#pragma once

// local headers
#include "player.hpp"
Expand All @@ -27,6 +26,7 @@
#include <oboe/Oboe.h>

// standard headers
#include <vector>


namespace player
Expand All @@ -41,30 +41,37 @@ static constexpr auto OBOE = "oboe";
class OboePlayer : public Player, public oboe::AudioStreamDataCallback, public oboe::AudioStreamErrorCallback
{
public:
/// c'tor
OboePlayer(boost::asio::io_context& io_context, const ClientSettings::Player& settings, std::shared_ptr<Stream> stream);
/// d'tor
virtual ~OboePlayer();

void start() override;
void stop() override;

protected:
// AudioStreamDataCallback overrides
/// AudioStreamDataCallback::onAudioReady override
oboe::DataCallbackResult onAudioReady(oboe::AudioStream* oboeStream, void* audioData, int32_t numFrames) override;

// AudioStreamErrorCallback overrides
/// AudioStreamErrorCallback::onErrorBeforeClose override
void onErrorBeforeClose(oboe::AudioStream* oboeStream, oboe::Result error) override;
/// AudioStreamErrorCallback::onErrorAfterClose override
void onErrorAfterClose(oboe::AudioStream* oboeStream, oboe::Result error) override;

protected:
/// Open the oboe stream
oboe::Result openStream();
/// Get current audio playout delay
double getCurrentOutputLatencyMillis() const;

bool needsThread() const override;
/// The oboe output stream
std::shared_ptr<oboe::AudioStream> out_stream_;
/// Latency tuner to optimize the latency
std::unique_ptr<oboe::LatencyTuner> latency_tuner_;

std::unique_ptr<oboe::LatencyTuner> mLatencyTuner;
/// Audio data buffer used for chaning the bit depth
std::vector<char> audio_data_;
};

} // namespace player

#endif

0 comments on commit 142afab

Please sign in to comment.