cubeb_sink.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
  2. // SPDX-License-Identifier: GPL-2.0-or-later
  3. #include <span>
  4. #include <vector>
  5. #include "audio_core/common/common.h"
  6. #include "audio_core/sink/cubeb_sink.h"
  7. #include "audio_core/sink/sink_stream.h"
  8. #include "common/logging/log.h"
  9. #include "common/scope_exit.h"
  10. #include "core/core.h"
  11. #ifdef _WIN32
  12. #include <objbase.h>
  13. #undef CreateEvent
  14. #endif
  15. namespace AudioCore::Sink {
  16. /**
  17. * Cubeb sink stream, responsible for sinking samples to hardware.
  18. */
  19. class CubebSinkStream final : public SinkStream {
  20. public:
  21. /**
  22. * Create a new sink stream.
  23. *
  24. * @param ctx_ - Cubeb context to create this stream with.
  25. * @param device_channels_ - Number of channels supported by the hardware.
  26. * @param system_channels_ - Number of channels the audio systems expect.
  27. * @param output_device - Cubeb output device id.
  28. * @param input_device - Cubeb input device id.
  29. * @param name_ - Name of this stream.
  30. * @param type_ - Type of this stream.
  31. * @param system_ - Core system.
  32. * @param event - Event used only for audio renderer, signalled on buffer consume.
  33. */
  34. CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_,
  35. cubeb_devid output_device, cubeb_devid input_device, const std::string& name_,
  36. StreamType type_, Core::System& system_)
  37. : SinkStream(system_, type_), ctx{ctx_} {
  38. #ifdef _WIN32
  39. CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  40. #endif
  41. name = name_;
  42. device_channels = device_channels_;
  43. system_channels = system_channels_;
  44. cubeb_stream_params params{};
  45. params.rate = TargetSampleRate;
  46. params.channels = device_channels;
  47. params.format = CUBEB_SAMPLE_S16LE;
  48. params.prefs = CUBEB_STREAM_PREF_NONE;
  49. switch (params.channels) {
  50. case 1:
  51. params.layout = CUBEB_LAYOUT_MONO;
  52. break;
  53. case 2:
  54. params.layout = CUBEB_LAYOUT_STEREO;
  55. break;
  56. case 6:
  57. params.layout = CUBEB_LAYOUT_3F2_LFE;
  58. break;
  59. }
  60. u32 minimum_latency{0};
  61. const auto latency_error = cubeb_get_min_latency(ctx, &params, &minimum_latency);
  62. if (latency_error != CUBEB_OK) {
  63. LOG_CRITICAL(Audio_Sink, "Error getting minimum latency, error: {}", latency_error);
  64. minimum_latency = TargetSampleCount * 2;
  65. }
  66. minimum_latency = std::max(minimum_latency, TargetSampleCount * 2);
  67. LOG_INFO(Service_Audio,
  68. "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) "
  69. "latency {}",
  70. name, type, params.rate, params.channels, system_channels, minimum_latency);
  71. auto init_error{0};
  72. if (type == StreamType::In) {
  73. init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
  74. &params, output_device, nullptr, minimum_latency,
  75. &CubebSinkStream::DataCallback,
  76. &CubebSinkStream::StateCallback, this);
  77. } else {
  78. init_error = cubeb_stream_init(ctx, &stream_backend, name.c_str(), input_device,
  79. nullptr, output_device, &params, minimum_latency,
  80. &CubebSinkStream::DataCallback,
  81. &CubebSinkStream::StateCallback, this);
  82. }
  83. if (init_error != CUBEB_OK) {
  84. LOG_CRITICAL(Audio_Sink, "Error initializing cubeb stream, error: {}", init_error);
  85. return;
  86. }
  87. }
  88. /**
  89. * Destroy the sink stream.
  90. */
  91. ~CubebSinkStream() override {
  92. LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name);
  93. if (!ctx) {
  94. return;
  95. }
  96. Finalize();
  97. #ifdef _WIN32
  98. CoUninitialize();
  99. #endif
  100. }
  101. /**
  102. * Finalize the sink stream.
  103. */
  104. void Finalize() override {
  105. Stop();
  106. cubeb_stream_destroy(stream_backend);
  107. }
  108. /**
  109. * Start the sink stream.
  110. *
  111. * @param resume - Set to true if this is resuming the stream a previously-active stream.
  112. * Default false.
  113. */
  114. void Start(bool resume = false) override {
  115. if (!ctx || !paused) {
  116. return;
  117. }
  118. paused = false;
  119. if (cubeb_stream_start(stream_backend) != CUBEB_OK) {
  120. LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream");
  121. }
  122. }
  123. /**
  124. * Stop the sink stream.
  125. */
  126. void Stop() override {
  127. if (!ctx || paused) {
  128. return;
  129. }
  130. paused = true;
  131. if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
  132. LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
  133. }
  134. }
  135. private:
  136. /**
  137. * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
  138. * provide samples to be copied (audio in).
  139. *
  140. * @param stream - Cubeb-specific data about the stream.
  141. * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
  142. * @param in_buff - Input buffer to be used if the stream is an input type.
  143. * @param out_buff - Output buffer to be used if the stream is an output type.
  144. * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
  145. */
  146. static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
  147. [[maybe_unused]] const void* in_buff, void* out_buff,
  148. long num_frames_) {
  149. auto* impl = static_cast<CubebSinkStream*>(user_data);
  150. if (!impl) {
  151. return -1;
  152. }
  153. const std::size_t num_channels = impl->GetDeviceChannels();
  154. const std::size_t frame_size = num_channels;
  155. const std::size_t num_frames{static_cast<size_t>(num_frames_)};
  156. if (impl->type == StreamType::In) {
  157. std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
  158. num_frames * frame_size};
  159. impl->ProcessAudioIn(input_buffer, num_frames);
  160. } else {
  161. std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
  162. impl->ProcessAudioOutAndRender(output_buffer, num_frames);
  163. }
  164. return num_frames_;
  165. }
  166. /**
  167. * Cubeb callback for if a device state changes. Unused currently.
  168. *
  169. * @param stream - Cubeb-specific data about the stream.
  170. * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
  171. * @param state - New state of the device.
  172. */
  173. static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
  174. /// Main Cubeb context
  175. cubeb* ctx{};
  176. /// Cubeb stream backend
  177. cubeb_stream* stream_backend{};
  178. };
  179. CubebSink::CubebSink(std::string_view target_device_name) {
  180. // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
  181. #ifdef _WIN32
  182. com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  183. #endif
  184. if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
  185. LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
  186. return;
  187. }
  188. if (target_device_name != auto_device_name && !target_device_name.empty()) {
  189. cubeb_device_collection collection;
  190. if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
  191. LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
  192. } else {
  193. const auto collection_end{collection.device + collection.count};
  194. const auto device{
  195. std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
  196. return info.friendly_name != nullptr &&
  197. target_device_name == std::string(info.friendly_name);
  198. })};
  199. if (device != collection_end) {
  200. output_device = device->devid;
  201. }
  202. cubeb_device_collection_destroy(ctx, &collection);
  203. }
  204. }
  205. cubeb_get_max_channel_count(ctx, &device_channels);
  206. device_channels = device_channels >= 6U ? 6U : 2U;
  207. }
  208. CubebSink::~CubebSink() {
  209. if (!ctx) {
  210. return;
  211. }
  212. for (auto& sink_stream : sink_streams) {
  213. sink_stream.reset();
  214. }
  215. cubeb_destroy(ctx);
  216. #ifdef _WIN32
  217. if (SUCCEEDED(com_init_result)) {
  218. CoUninitialize();
  219. }
  220. #endif
  221. }
  222. SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
  223. const std::string& name, StreamType type) {
  224. SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
  225. ctx, device_channels, system_channels, output_device, input_device, name, type, system));
  226. return stream.get();
  227. }
  228. void CubebSink::CloseStream(SinkStream* stream) {
  229. for (size_t i = 0; i < sink_streams.size(); i++) {
  230. if (sink_streams[i].get() == stream) {
  231. sink_streams[i].reset();
  232. sink_streams.erase(sink_streams.begin() + i);
  233. break;
  234. }
  235. }
  236. }
  237. void CubebSink::CloseStreams() {
  238. sink_streams.clear();
  239. }
  240. f32 CubebSink::GetDeviceVolume() const {
  241. if (sink_streams.empty()) {
  242. return 1.0f;
  243. }
  244. return sink_streams[0]->GetDeviceVolume();
  245. }
  246. void CubebSink::SetDeviceVolume(f32 volume) {
  247. for (auto& stream : sink_streams) {
  248. stream->SetDeviceVolume(volume);
  249. }
  250. }
  251. void CubebSink::SetSystemVolume(f32 volume) {
  252. for (auto& stream : sink_streams) {
  253. stream->SetSystemVolume(volume);
  254. }
  255. }
  256. std::vector<std::string> ListCubebSinkDevices(bool capture) {
  257. std::vector<std::string> device_list;
  258. cubeb* ctx;
  259. #ifdef _WIN32
  260. auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  261. #endif
  262. if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
  263. LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
  264. return {};
  265. }
  266. #ifdef _WIN32
  267. if (SUCCEEDED(com_init_result)) {
  268. CoUninitialize();
  269. }
  270. #endif
  271. auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
  272. cubeb_device_collection collection;
  273. if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
  274. LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
  275. } else {
  276. for (std::size_t i = 0; i < collection.count; i++) {
  277. const cubeb_device_info& device = collection.device[i];
  278. if (device.friendly_name && device.friendly_name[0] != '\0' &&
  279. device.state == CUBEB_DEVICE_STATE_ENABLED) {
  280. device_list.emplace_back(device.friendly_name);
  281. }
  282. }
  283. cubeb_device_collection_destroy(ctx, &collection);
  284. }
  285. cubeb_destroy(ctx);
  286. return device_list;
  287. }
  288. namespace {
  289. static long TmpDataCallback(cubeb_stream*, void*, const void*, void*, long) {
  290. return TargetSampleCount;
  291. }
  292. static void TmpStateCallback(cubeb_stream*, void*, cubeb_state) {}
  293. } // namespace
  294. bool IsCubebSuitable() {
  295. #if !defined(HAVE_CUBEB)
  296. return false;
  297. #else
  298. cubeb* ctx{nullptr};
  299. #ifdef _WIN32
  300. auto com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  301. #endif
  302. // Init cubeb
  303. if (cubeb_init(&ctx, "yuzu Latency Getter", nullptr) != CUBEB_OK) {
  304. LOG_ERROR(Audio_Sink, "Cubeb failed to init, it is not suitable.");
  305. return false;
  306. }
  307. SCOPE_EXIT({ cubeb_destroy(ctx); });
  308. #ifdef _WIN32
  309. if (SUCCEEDED(com_init_result)) {
  310. CoUninitialize();
  311. }
  312. #endif
  313. // Test min latency
  314. cubeb_stream_params params{};
  315. params.rate = TargetSampleRate;
  316. params.channels = 2;
  317. params.format = CUBEB_SAMPLE_S16LE;
  318. params.prefs = CUBEB_STREAM_PREF_NONE;
  319. params.layout = CUBEB_LAYOUT_STEREO;
  320. u32 latency{0};
  321. const auto latency_error = cubeb_get_min_latency(ctx, &params, &latency);
  322. if (latency_error != CUBEB_OK) {
  323. LOG_ERROR(Audio_Sink, "Cubeb could not get min latency, it is not suitable.");
  324. return false;
  325. }
  326. latency = std::max(latency, TargetSampleCount * 2);
  327. if (latency > TargetSampleCount * 3) {
  328. LOG_ERROR(Audio_Sink, "Cubeb latency is too high, it is not suitable.");
  329. return false;
  330. }
  331. // Test opening a device with standard parameters
  332. cubeb_devid output_device{0};
  333. cubeb_devid input_device{0};
  334. std::string name{"Yuzu test"};
  335. cubeb_stream* stream{nullptr};
  336. if (cubeb_stream_init(ctx, &stream, name.c_str(), input_device, nullptr, output_device, &params,
  337. latency, &TmpDataCallback, &TmpStateCallback, nullptr) != CUBEB_OK) {
  338. LOG_CRITICAL(Audio_Sink, "Cubeb could not open a device, it is not suitable.");
  339. return false;
  340. }
  341. cubeb_stream_stop(stream);
  342. cubeb_stream_destroy(stream);
  343. return true;
  344. #endif
  345. }
  346. } // namespace AudioCore::Sink