cubeb_sink.cpp 11 KB

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