123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250 |
- #include "config.h"
- #include "oboe.h"
- #include <cassert>
- #include <cstring>
- #include "alu.h"
- #include "core/logging.h"
- #include "oboe/Oboe.h"
- namespace {
- constexpr char device_name[] = "Oboe Default";
- struct OboePlayback final : public BackendBase, public oboe::AudioStreamCallback {
- OboePlayback(ALCdevice *device) : BackendBase{device} { }
- oboe::ManagedStream mStream;
- oboe::DataCallbackResult onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
- int32_t numFrames) override;
- void open(const char *name) override;
- bool reset() override;
- void start() override;
- void stop() override;
- };
- oboe::DataCallbackResult OboePlayback::onAudioReady(oboe::AudioStream *oboeStream, void *audioData,
- int32_t numFrames)
- {
- assert(numFrames > 0);
- const int32_t numChannels{oboeStream->getChannelCount()};
- if UNLIKELY(numChannels > 2 && mDevice->FmtChans == DevFmtStereo)
- {
- /* If the device is only mixing stereo but there's more than two
- * output channels, there are unused channels that need to be silenced.
- */
- if(mStream->getFormat() == oboe::AudioFormat::Float)
- memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(float));
- else
- memset(audioData, 0, static_cast<uint32_t>(numFrames*numChannels)*sizeof(int16_t));
- }
- mDevice->renderSamples(audioData, static_cast<uint32_t>(numFrames),
- static_cast<uint32_t>(numChannels));
- return oboe::DataCallbackResult::Continue;
- }
- void OboePlayback::open(const char *name)
- {
- if(!name)
- name = device_name;
- else if(std::strcmp(name, device_name) != 0)
- throw al::backend_exception{al::backend_error::NoDevice, "Device name \"%s\" not found",
- name};
- /* Open a basic output stream, just to ensure it can work. */
- oboe::Result result{oboe::AudioStreamBuilder{}.setDirection(oboe::Direction::Output)
- ->setPerformanceMode(oboe::PerformanceMode::LowLatency)
- ->openManagedStream(mStream)};
- if(result != oboe::Result::OK)
- throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
- oboe::convertToText(result)};
- mDevice->DeviceName = name;
- }
- bool OboePlayback::reset()
- {
- oboe::AudioStreamBuilder builder;
- builder.setDirection(oboe::Direction::Output);
- builder.setPerformanceMode(oboe::PerformanceMode::LowLatency);
- /* Don't let Oboe convert. We should be able to handle anything it gives
- * back.
- */
- builder.setSampleRateConversionQuality(oboe::SampleRateConversionQuality::None);
- builder.setChannelConversionAllowed(false);
- builder.setFormatConversionAllowed(false);
- builder.setCallback(this);
- if(mDevice->Flags.test(FrequencyRequest))
- builder.setSampleRate(static_cast<int32_t>(mDevice->Frequency));
- if(mDevice->Flags.test(ChannelsRequest))
- {
- /* Only use mono or stereo at user request. There's no telling what
- * other counts may be inferred as.
- */
- builder.setChannelCount((mDevice->FmtChans==DevFmtMono) ? oboe::ChannelCount::Mono
- : (mDevice->FmtChans==DevFmtStereo) ? oboe::ChannelCount::Stereo
- : oboe::ChannelCount::Unspecified);
- }
- if(mDevice->Flags.test(SampleTypeRequest))
- {
- oboe::AudioFormat format{oboe::AudioFormat::Unspecified};
- switch(mDevice->FmtType)
- {
- case DevFmtByte:
- case DevFmtUByte:
- case DevFmtShort:
- case DevFmtUShort:
- format = oboe::AudioFormat::I16;
- break;
- case DevFmtInt:
- case DevFmtUInt:
- case DevFmtFloat:
- format = oboe::AudioFormat::Float;
- break;
- }
- builder.setFormat(format);
- }
- oboe::Result result{builder.openManagedStream(mStream)};
- /* If the format failed, try asking for the defaults. */
- while(result == oboe::Result::ErrorInvalidFormat)
- {
- if(builder.getFormat() != oboe::AudioFormat::Unspecified)
- builder.setFormat(oboe::AudioFormat::Unspecified);
- else if(builder.getSampleRate() != oboe::kUnspecified)
- builder.setSampleRate(oboe::kUnspecified);
- else if(builder.getChannelCount() != oboe::ChannelCount::Unspecified)
- builder.setChannelCount(oboe::ChannelCount::Unspecified);
- else
- break;
- result = builder.openManagedStream(mStream);
- }
- if(result != oboe::Result::OK)
- throw al::backend_exception{al::backend_error::DeviceError, "Failed to create stream: %s",
- oboe::convertToText(result)};
- TRACE("Got stream with properties:\n%s", oboe::convertToText(mStream.get()));
- switch(mStream->getChannelCount())
- {
- case oboe::ChannelCount::Mono:
- mDevice->FmtChans = DevFmtMono;
- break;
- case oboe::ChannelCount::Stereo:
- mDevice->FmtChans = DevFmtStereo;
- break;
- /* Other potential configurations. Could be wrong, but better than failing.
- * Assume WFX channel order.
- */
- case 4:
- mDevice->FmtChans = DevFmtQuad;
- break;
- case 6:
- mDevice->FmtChans = DevFmtX51Rear;
- break;
- case 7:
- mDevice->FmtChans = DevFmtX61;
- break;
- case 8:
- mDevice->FmtChans = DevFmtX71;
- break;
- default:
- if(mStream->getChannelCount() < 1)
- throw al::backend_exception{al::backend_error::DeviceError,
- "Got unhandled channel count: %d", mStream->getChannelCount()};
- /* Assume first two channels are front left/right. We can do a stereo
- * mix and keep the other channels silent.
- */
- mDevice->FmtChans = DevFmtStereo;
- break;
- }
- setDefaultWFXChannelOrder();
- switch(mStream->getFormat())
- {
- case oboe::AudioFormat::I16:
- mDevice->FmtType = DevFmtShort;
- break;
- case oboe::AudioFormat::Float:
- mDevice->FmtType = DevFmtFloat;
- break;
- case oboe::AudioFormat::Unspecified:
- case oboe::AudioFormat::Invalid:
- throw al::backend_exception{al::backend_error::DeviceError,
- "Got unhandled sample type: %s", oboe::convertToText(mStream->getFormat())};
- }
- mDevice->Frequency = static_cast<uint32_t>(mStream->getSampleRate());
- /* Ensure the period size is no less than 10ms. It's possible for FramesPerCallback to be 0
- * indicating variable updates, but OpenAL should have a reasonable minimum update size set.
- * FramesPerBurst may not necessarily be correct, but hopefully it can act as a minimum
- * update size.
- */
- mDevice->UpdateSize = maxu(mDevice->Frequency / 100,
- static_cast<uint32_t>(mStream->getFramesPerBurst()));
- mDevice->BufferSize = maxu(mDevice->UpdateSize * 2,
- static_cast<uint32_t>(mStream->getBufferSizeInFrames()));
- return true;
- }
- void OboePlayback::start()
- {
- const oboe::Result result{mStream->start()};
- if(result != oboe::Result::OK)
- throw al::backend_exception{al::backend_error::DeviceError, "Failed to start stream: %s",
- oboe::convertToText(result)};
- }
- void OboePlayback::stop()
- {
- oboe::Result result{mStream->stop()};
- if(result != oboe::Result::OK)
- throw al::backend_exception{al::backend_error::DeviceError, "Failed to stop stream: %s",
- oboe::convertToText(result)};
- }
- } // namespace
- bool OboeBackendFactory::init() { return true; }
- bool OboeBackendFactory::querySupport(BackendType type)
- { return type == BackendType::Playback; }
- std::string OboeBackendFactory::probe(BackendType type)
- {
- switch(type)
- {
- case BackendType::Playback:
- /* Includes null char. */
- return std::string{device_name, sizeof(device_name)};
- case BackendType::Capture:
- break;
- }
- return std::string{};
- }
- BackendPtr OboeBackendFactory::createBackend(ALCdevice *device, BackendType type)
- {
- if(type == BackendType::Playback)
- return BackendPtr{new OboePlayback{device}};
- return nullptr;
- }
- BackendFactory &OboeBackendFactory::getFactory()
- {
- static OboeBackendFactory factory{};
- return factory;
- }
|