duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

cubeb_audio_stream.cpp (11043B)


      1 // SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "host.h"
      5 #include "imgui_manager.h"
      6 
      7 #include "core/settings.h"
      8 
      9 #include "common/assert.h"
     10 #include "common/error.h"
     11 #include "common/log.h"
     12 #include "common/scoped_guard.h"
     13 #include "common/string_util.h"
     14 
     15 #include "cubeb/cubeb.h"
     16 #include "fmt/format.h"
     17 
     18 Log_SetChannel(CubebAudioStream);
     19 
     20 namespace {
     21 
     22 class CubebAudioStream : public AudioStream
     23 {
     24 public:
     25   CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters);
     26   ~CubebAudioStream();
     27 
     28   void SetPaused(bool paused) override;
     29 
     30   bool Initialize(const char* driver_name, const char* device_name, Error* error);
     31 
     32 private:
     33   static void LogCallback(const char* fmt, ...);
     34   static long DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
     35                            long nframes);
     36   static void StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state);
     37 
     38   void DestroyContextAndStream();
     39 
     40   cubeb* m_context = nullptr;
     41   cubeb_stream* stream = nullptr;
     42 };
     43 } // namespace
     44 
     45 static TinyString GetCubebErrorString(int rv)
     46 {
     47   TinyString ret;
     48   switch (rv)
     49   {
     50     // clang-format off
     51 #define C(e) case e: ret.assign(#e); break
     52     // clang-format on
     53 
     54     C(CUBEB_OK);
     55     C(CUBEB_ERROR);
     56     C(CUBEB_ERROR_INVALID_FORMAT);
     57     C(CUBEB_ERROR_INVALID_PARAMETER);
     58     C(CUBEB_ERROR_NOT_SUPPORTED);
     59     C(CUBEB_ERROR_DEVICE_UNAVAILABLE);
     60 
     61     default:
     62       return "CUBEB_ERROR_UNKNOWN";
     63 
     64 #undef C
     65   }
     66 
     67   ret.append_format(" ({})", rv);
     68   return ret;
     69 }
     70 
     71 CubebAudioStream::CubebAudioStream(u32 sample_rate, const AudioStreamParameters& parameters)
     72   : AudioStream(sample_rate, parameters)
     73 {
     74 }
     75 
     76 CubebAudioStream::~CubebAudioStream()
     77 {
     78   DestroyContextAndStream();
     79 }
     80 
     81 void CubebAudioStream::LogCallback(const char* fmt, ...)
     82 {
     83   LargeString str;
     84   std::va_list ap;
     85   va_start(ap, fmt);
     86   str.vsprintf(fmt, ap);
     87   va_end(ap);
     88   DEV_LOG(str);
     89 }
     90 
     91 void CubebAudioStream::DestroyContextAndStream()
     92 {
     93   if (stream)
     94   {
     95     cubeb_stream_stop(stream);
     96     cubeb_stream_destroy(stream);
     97     stream = nullptr;
     98   }
     99 
    100   if (m_context)
    101   {
    102     cubeb_destroy(m_context);
    103     m_context = nullptr;
    104   }
    105 }
    106 
    107 bool CubebAudioStream::Initialize(const char* driver_name, const char* device_name, Error* error)
    108 {
    109   cubeb_set_log_callback(CUBEB_LOG_NORMAL, LogCallback);
    110 
    111   int rv =
    112     cubeb_init(&m_context, "DuckStation", g_settings.audio_driver.empty() ? nullptr : g_settings.audio_driver.c_str());
    113   if (rv != CUBEB_OK)
    114   {
    115     Error::SetStringFmt(error, "Could not initialize cubeb context: {}", GetCubebErrorString(rv));
    116     return false;
    117   }
    118 
    119   static constexpr const std::array<std::pair<cubeb_channel_layout, SampleReader>,
    120                                     static_cast<size_t>(AudioExpansionMode::Count)>
    121     channel_setups = {{
    122       // Disabled
    123       {CUBEB_LAYOUT_STEREO, StereoSampleReaderImpl},
    124       // StereoLFE
    125       {CUBEB_LAYOUT_STEREO_LFE, &SampleReaderImpl<AudioExpansionMode::StereoLFE, READ_CHANNEL_FRONT_LEFT,
    126                                                   READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_LFE>},
    127       // Quadraphonic
    128       {CUBEB_LAYOUT_QUAD, &SampleReaderImpl<AudioExpansionMode::Quadraphonic, READ_CHANNEL_FRONT_LEFT,
    129                                             READ_CHANNEL_FRONT_RIGHT, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
    130       // QuadraphonicLFE
    131       {CUBEB_LAYOUT_QUAD_LFE,
    132        &SampleReaderImpl<AudioExpansionMode::QuadraphonicLFE, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
    133                          READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
    134       // Surround51
    135       {CUBEB_LAYOUT_3F2_LFE_BACK,
    136        &SampleReaderImpl<AudioExpansionMode::Surround51, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
    137                          READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT>},
    138       // Surround71
    139       {CUBEB_LAYOUT_3F4_LFE,
    140        &SampleReaderImpl<AudioExpansionMode::Surround71, READ_CHANNEL_FRONT_LEFT, READ_CHANNEL_FRONT_RIGHT,
    141                          READ_CHANNEL_FRONT_CENTER, READ_CHANNEL_LFE, READ_CHANNEL_REAR_LEFT, READ_CHANNEL_REAR_RIGHT,
    142                          READ_CHANNEL_SIDE_LEFT, READ_CHANNEL_SIDE_RIGHT>},
    143     }};
    144 
    145   cubeb_stream_params params = {};
    146   params.format = CUBEB_SAMPLE_S16LE;
    147   params.rate = m_sample_rate;
    148   params.channels = m_output_channels;
    149   params.layout = channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].first;
    150   params.prefs = CUBEB_STREAM_PREF_NONE;
    151 
    152   u32 latency_frames = GetBufferSizeForMS(
    153     m_sample_rate, (m_parameters.output_latency_ms == 0) ? m_parameters.buffer_ms : m_parameters.output_latency_ms);
    154   u32 min_latency_frames = 0;
    155   rv = cubeb_get_min_latency(m_context, &params, &min_latency_frames);
    156   if (rv == CUBEB_ERROR_NOT_SUPPORTED)
    157   {
    158     DEV_LOG("Cubeb backend does not support latency queries, using latency of {} ms ({} frames).",
    159             m_parameters.buffer_ms, latency_frames);
    160   }
    161   else
    162   {
    163     if (rv != CUBEB_OK)
    164     {
    165       Error::SetStringFmt(error, "cubeb_get_min_latency() failed: {}", GetCubebErrorString(rv));
    166       DestroyContextAndStream();
    167       return false;
    168     }
    169 
    170     const u32 minimum_latency_ms = GetMSForBufferSize(m_sample_rate, min_latency_frames);
    171     DEV_LOG("Minimum latency: {} ms ({} audio frames)", minimum_latency_ms, min_latency_frames);
    172     if (m_parameters.output_latency_minimal)
    173     {
    174       // use minimum
    175       latency_frames = min_latency_frames;
    176     }
    177     else if (minimum_latency_ms > m_parameters.output_latency_ms)
    178     {
    179       WARNING_LOG("Minimum latency is above requested latency: {} vs {}, adjusting to compensate.", min_latency_frames,
    180                   latency_frames);
    181       latency_frames = min_latency_frames;
    182     }
    183   }
    184 
    185   cubeb_devid selected_device = nullptr;
    186   const std::string& selected_device_name = g_settings.audio_output_device;
    187   cubeb_device_collection devices;
    188   bool devices_valid = false;
    189   if (!selected_device_name.empty())
    190   {
    191     rv = cubeb_enumerate_devices(m_context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
    192     devices_valid = (rv == CUBEB_OK);
    193     if (rv == CUBEB_OK)
    194     {
    195       for (size_t i = 0; i < devices.count; i++)
    196       {
    197         const cubeb_device_info& di = devices.device[i];
    198         if (di.device_id && selected_device_name == di.device_id)
    199         {
    200           INFO_LOG("Using output device '{}' ({}).", di.device_id, di.friendly_name ? di.friendly_name : di.device_id);
    201           selected_device = di.devid;
    202           break;
    203         }
    204       }
    205 
    206       if (!selected_device)
    207       {
    208         Host::AddOSDMessage(
    209           fmt::format("Requested audio output device '{}' not found, using default.", selected_device_name), 10.0f);
    210       }
    211     }
    212     else
    213     {
    214       WARNING_LOG("cubeb_enumerate_devices() returned {}, using default device.", GetCubebErrorString(rv));
    215     }
    216   }
    217 
    218   BaseInitialize(channel_setups[static_cast<size_t>(m_parameters.expansion_mode)].second);
    219 
    220   char stream_name[32];
    221   std::snprintf(stream_name, sizeof(stream_name), "%p", this);
    222 
    223   rv = cubeb_stream_init(m_context, &stream, stream_name, nullptr, nullptr, selected_device, &params, latency_frames,
    224                          &CubebAudioStream::DataCallback, StateCallback, this);
    225 
    226   if (devices_valid)
    227     cubeb_device_collection_destroy(m_context, &devices);
    228 
    229   if (rv != CUBEB_OK)
    230   {
    231     Error::SetStringFmt(error, "cubeb_stream_init() failed: {}", GetCubebErrorString(rv));
    232     DestroyContextAndStream();
    233     return false;
    234   }
    235 
    236   rv = cubeb_stream_start(stream);
    237   if (rv != CUBEB_OK)
    238   {
    239     Error::SetStringFmt(error, "cubeb_stream_start() failed: {}", GetCubebErrorString(rv));
    240     DestroyContextAndStream();
    241     return false;
    242   }
    243 
    244   return true;
    245 }
    246 
    247 void CubebAudioStream::StateCallback(cubeb_stream* stream, void* user_ptr, cubeb_state state)
    248 {
    249   // noop
    250 }
    251 
    252 long CubebAudioStream::DataCallback(cubeb_stream* stm, void* user_ptr, const void* input_buffer, void* output_buffer,
    253                                     long nframes)
    254 {
    255   static_cast<CubebAudioStream*>(user_ptr)->ReadFrames(static_cast<s16*>(output_buffer), static_cast<u32>(nframes));
    256   return nframes;
    257 }
    258 
    259 void CubebAudioStream::SetPaused(bool paused)
    260 {
    261   if (paused == m_paused || !stream)
    262     return;
    263 
    264   const int rv = paused ? cubeb_stream_stop(stream) : cubeb_stream_start(stream);
    265   if (rv != CUBEB_OK)
    266   {
    267     ERROR_LOG("Could not {} stream: {}", paused ? "pause" : "resume", rv);
    268     return;
    269   }
    270 
    271   m_paused = paused;
    272 }
    273 
    274 std::unique_ptr<AudioStream> AudioStream::CreateCubebAudioStream(u32 sample_rate,
    275                                                                  const AudioStreamParameters& parameters,
    276                                                                  const char* driver_name, const char* device_name,
    277                                                                  Error* error)
    278 {
    279   std::unique_ptr<CubebAudioStream> stream = std::make_unique<CubebAudioStream>(sample_rate, parameters);
    280   if (!stream->Initialize(driver_name, device_name, error))
    281     stream.reset();
    282   return stream;
    283 }
    284 
    285 std::vector<std::pair<std::string, std::string>> AudioStream::GetCubebDriverNames()
    286 {
    287   std::vector<std::pair<std::string, std::string>> names;
    288   names.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"));
    289 
    290   const char** cubeb_names = cubeb_get_backend_names();
    291   for (u32 i = 0; cubeb_names[i] != nullptr; i++)
    292     names.emplace_back(cubeb_names[i], cubeb_names[i]);
    293   return names;
    294 }
    295 
    296 std::vector<AudioStream::DeviceInfo> AudioStream::GetCubebOutputDevices(const char* driver, u32 sample_rate)
    297 {
    298   std::vector<AudioStream::DeviceInfo> ret;
    299   ret.emplace_back(std::string(), TRANSLATE_STR("AudioStream", "Default"), 0);
    300 
    301   cubeb* context;
    302   int rv = cubeb_init(&context, "DuckStation", (driver && *driver) ? driver : nullptr);
    303   if (rv != CUBEB_OK)
    304   {
    305     ERROR_LOG("cubeb_init() failed: {}", GetCubebErrorString(rv));
    306     return ret;
    307   }
    308 
    309   ScopedGuard context_cleanup([context]() { cubeb_destroy(context); });
    310 
    311   cubeb_device_collection devices;
    312   rv = cubeb_enumerate_devices(context, CUBEB_DEVICE_TYPE_OUTPUT, &devices);
    313   if (rv != CUBEB_OK)
    314   {
    315     ERROR_LOG("cubeb_enumerate_devices() failed: {}", GetCubebErrorString(rv));
    316     return ret;
    317   }
    318 
    319   ScopedGuard devices_cleanup([context, &devices]() { cubeb_device_collection_destroy(context, &devices); });
    320 
    321   // we need stream parameters to query latency
    322   cubeb_stream_params params = {};
    323   params.format = CUBEB_SAMPLE_S16LE;
    324   params.rate = sample_rate;
    325   params.channels = 2;
    326   params.layout = CUBEB_LAYOUT_UNDEFINED;
    327   params.prefs = CUBEB_STREAM_PREF_NONE;
    328 
    329   u32 min_latency = 0;
    330   cubeb_get_min_latency(context, &params, &min_latency);
    331   ret[0].minimum_latency_frames = min_latency;
    332 
    333   for (size_t i = 0; i < devices.count; i++)
    334   {
    335     const cubeb_device_info& di = devices.device[i];
    336     if (!di.device_id)
    337       continue;
    338 
    339     ret.emplace_back(di.device_id, di.friendly_name ? di.friendly_name : di.device_id, min_latency);
    340   }
    341 
    342   return ret;
    343 }