cubeb_sink.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  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 || !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. Unstall();
  128. if (!ctx || paused) {
  129. return;
  130. }
  131. paused = true;
  132. if (cubeb_stream_stop(stream_backend) != CUBEB_OK) {
  133. LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream");
  134. }
  135. }
  136. private:
  137. /**
  138. * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will
  139. * provide samples to be copied (audio in).
  140. *
  141. * @param stream - Cubeb-specific data about the stream.
  142. * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
  143. * @param in_buff - Input buffer to be used if the stream is an input type.
  144. * @param out_buff - Output buffer to be used if the stream is an output type.
  145. * @param num_frames_ - Number of frames of audio in the buffers. Note: Not number of samples.
  146. */
  147. static long DataCallback([[maybe_unused]] cubeb_stream* stream, void* user_data,
  148. [[maybe_unused]] const void* in_buff, void* out_buff,
  149. long num_frames_) {
  150. auto* impl = static_cast<CubebSinkStream*>(user_data);
  151. if (!impl) {
  152. return -1;
  153. }
  154. const std::size_t num_channels = impl->GetDeviceChannels();
  155. const std::size_t frame_size = num_channels;
  156. const std::size_t num_frames{static_cast<size_t>(num_frames_)};
  157. if (impl->type == StreamType::In) {
  158. std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff),
  159. num_frames * frame_size};
  160. impl->ProcessAudioIn(input_buffer, num_frames);
  161. } else {
  162. std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size};
  163. impl->ProcessAudioOutAndRender(output_buffer, num_frames);
  164. }
  165. return num_frames_;
  166. }
  167. /**
  168. * Cubeb callback for if a device state changes. Unused currently.
  169. *
  170. * @param stream - Cubeb-specific data about the stream.
  171. * @param user_data - Custom data pointer passed along, points to a CubebSinkStream.
  172. * @param state - New state of the device.
  173. */
  174. static void StateCallback(cubeb_stream*, void*, cubeb_state) {}
  175. /// Main Cubeb context
  176. cubeb* ctx{};
  177. /// Cubeb stream backend
  178. cubeb_stream* stream_backend{};
  179. };
  180. CubebSink::CubebSink(std::string_view target_device_name) {
  181. // Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
  182. #ifdef _WIN32
  183. com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
  184. #endif
  185. if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
  186. LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
  187. return;
  188. }
  189. if (target_device_name != auto_device_name && !target_device_name.empty()) {
  190. cubeb_device_collection collection;
  191. if (cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &collection) != CUBEB_OK) {
  192. LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
  193. } else {
  194. const auto collection_end{collection.device + collection.count};
  195. const auto device{
  196. std::find_if(collection.device, collection_end, [&](const cubeb_device_info& info) {
  197. return info.friendly_name != nullptr &&
  198. target_device_name == std::string(info.friendly_name);
  199. })};
  200. if (device != collection_end) {
  201. output_device = device->devid;
  202. }
  203. cubeb_device_collection_destroy(ctx, &collection);
  204. }
  205. }
  206. cubeb_get_max_channel_count(ctx, &device_channels);
  207. device_channels = device_channels >= 6U ? 6U : 2U;
  208. }
  209. CubebSink::~CubebSink() {
  210. if (!ctx) {
  211. return;
  212. }
  213. for (auto& sink_stream : sink_streams) {
  214. sink_stream.reset();
  215. }
  216. cubeb_destroy(ctx);
  217. #ifdef _WIN32
  218. if (SUCCEEDED(com_init_result)) {
  219. CoUninitialize();
  220. }
  221. #endif
  222. }
  223. SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels,
  224. const std::string& name, StreamType type) {
  225. SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
  226. ctx, device_channels, system_channels, output_device, input_device, name, type, system));
  227. return stream.get();
  228. }
  229. void CubebSink::CloseStream(SinkStream* stream) {
  230. for (size_t i = 0; i < sink_streams.size(); i++) {
  231. if (sink_streams[i].get() == stream) {
  232. sink_streams[i].reset();
  233. sink_streams.erase(sink_streams.begin() + i);
  234. break;
  235. }
  236. }
  237. }
  238. void CubebSink::CloseStreams() {
  239. sink_streams.clear();
  240. }
  241. f32 CubebSink::GetDeviceVolume() const {
  242. if (sink_streams.empty()) {
  243. return 1.0f;
  244. }
  245. return sink_streams[0]->GetDeviceVolume();
  246. }
  247. void CubebSink::SetDeviceVolume(f32 volume) {
  248. for (auto& stream : sink_streams) {
  249. stream->SetDeviceVolume(volume);
  250. }
  251. }
  252. void CubebSink::SetSystemVolume(f32 volume) {
  253. for (auto& stream : sink_streams) {
  254. stream->SetSystemVolume(volume);
  255. }
  256. }
  257. std::vector<std::string> ListCubebSinkDevices(bool capture) {
  258. std::vector<std::string> device_list;
  259. cubeb* ctx;
  260. if (cubeb_init(&ctx, "yuzu Device Enumerator", nullptr) != CUBEB_OK) {
  261. LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
  262. return {};
  263. }
  264. auto type{capture ? CUBEB_DEVICE_TYPE_INPUT : CUBEB_DEVICE_TYPE_OUTPUT};
  265. cubeb_device_collection collection;
  266. if (cubeb_enumerate_devices(ctx, type, &collection) != CUBEB_OK) {
  267. LOG_WARNING(Audio_Sink, "Audio output device enumeration not supported");
  268. } else {
  269. for (std::size_t i = 0; i < collection.count; i++) {
  270. const cubeb_device_info& device = collection.device[i];
  271. if (device.friendly_name && device.friendly_name[0] != '\0' &&
  272. device.state == CUBEB_DEVICE_STATE_ENABLED) {
  273. device_list.emplace_back(device.friendly_name);
  274. }
  275. }
  276. cubeb_device_collection_destroy(ctx, &collection);
  277. }
  278. cubeb_destroy(ctx);
  279. return device_list;
  280. }
  281. } // namespace AudioCore::Sink