[WASAPI] set stream audio category
[xbmc.git] / xbmc / cores / AudioEngine / Sinks / AESinkStarfish.cpp
blobd9c8859cc4693612655e3e0f40217a431f8b8ee7
1 /*
2 * Copyright (C) 2023 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "AESinkStarfish.h"
11 #include "CompileInfo.h"
12 #include "utils/JSONVariantWriter.h"
13 #include "utils/log.h"
14 #include "xbmc/cores/AudioEngine/AESinkFactory.h"
16 #include <thread>
18 using namespace std::chrono_literals;
20 namespace
22 constexpr unsigned int STARFISH_AUDIO_BUFFERS = 8;
23 constexpr unsigned int AC3_SYNCFRAME_SIZE = 2560;
24 constexpr unsigned int DTDHD_MA_MIN_SYNCFRAME_SIZE = 2012 + 2764;
26 static constexpr auto ms_audioCodecMap = make_map<CAEStreamInfo::DataType, std::string_view>({
27 {CAEStreamInfo::STREAM_TYPE_AC3, "AC3"},
28 {CAEStreamInfo::STREAM_TYPE_EAC3, "AC3 PLUS"},
29 {CAEStreamInfo::STREAM_TYPE_DTS_512, "DTS"},
30 {CAEStreamInfo::STREAM_TYPE_DTS_1024, "DTS"},
31 {CAEStreamInfo::STREAM_TYPE_DTS_2048, "DTS"},
32 {CAEStreamInfo::STREAM_TYPE_DTSHD, "DTS"},
33 {CAEStreamInfo::STREAM_TYPE_DTSHD_CORE, "DTS"},
34 {CAEStreamInfo::STREAM_TYPE_DTSHD_MA, "DTS"},
35 });
37 } // namespace
39 void CAESinkStarfish::Register()
41 AE::AESinkRegEntry entry;
42 entry.sinkName = "Starfish";
43 entry.createFunc = CAESinkStarfish::Create;
44 entry.enumerateFunc = CAESinkStarfish::EnumerateDevicesEx;
45 AE::CAESinkFactory::RegisterSink(entry);
48 std::unique_ptr<IAESink> CAESinkStarfish::Create(std::string& device, AEAudioFormat& desiredFormat)
50 auto sink = std::make_unique<CAESinkStarfish>();
51 if (sink->Initialize(desiredFormat, device))
52 return sink;
54 return {};
57 void CAESinkStarfish::EnumerateDevicesEx(AEDeviceInfoList& list, bool force)
59 CAEDeviceInfo info;
60 info.m_deviceName = "Starfish";
61 info.m_displayName = "Starfish (Passthrough only)";
62 info.m_channels = AE_CH_LAYOUT_2_0;
63 info.m_wantsIECPassthrough = false;
64 info.m_onlyPassthrough = true;
66 // PCM disabled for now as the latency is just too high, needs more research
67 // Thankfully, ALSA or PulseAudio do work as an alternative for PCM content
68 info.m_dataFormats.emplace_back(AE_FMT_RAW);
70 info.m_deviceType = AE_DEVTYPE_IEC958;
71 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_AC3);
72 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_EAC3);
73 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTS_512);
74 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTS_1024);
75 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTS_2048);
76 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTSHD);
77 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTSHD_CORE);
78 info.m_streamTypes.emplace_back(CAEStreamInfo::STREAM_TYPE_DTSHD_MA);
80 info.m_sampleRates.emplace_back(32000);
81 info.m_sampleRates.emplace_back(44100);
82 info.m_sampleRates.emplace_back(48000);
84 list.emplace_back(info);
87 CAESinkStarfish::CAESinkStarfish() : m_starfishMediaAPI(std::make_unique<StarfishMediaAPIs>())
91 CAESinkStarfish::~CAESinkStarfish() = default;
93 bool CAESinkStarfish::Initialize(AEAudioFormat& format, std::string& device)
95 m_format = format;
96 m_pts = 0ns;
98 if (m_format.m_dataFormat != AE_FMT_RAW)
100 CLog::LogF(LOGERROR, "CAESinkStarfish: Unsupported format PCM");
101 return false;
103 m_format.m_frameSize = 1;
105 CVariant payload;
106 payload["isAudioOnly"] = true;
107 payload["mediaTransportType"] = "BUFFERSTREAM";
108 payload["option"]["appId"] = CCompileInfo::GetPackage();
109 payload["option"]["needAudio"] = true;
110 payload["option"]["queryPosition"] = true;
111 payload["option"]["externalStreamingInfo"]["contents"]["esInfo"]["pauseAtDecodeTime"] = true;
112 payload["option"]["externalStreamingInfo"]["contents"]["esInfo"]["seperatedPTS"] = true;
113 payload["option"]["externalStreamingInfo"]["contents"]["esInfo"]["ptsToDecode"] = 0;
114 payload["option"]["externalStreamingInfo"]["contents"]["format"] = "RAW";
115 payload["option"]["transmission"]["contentsType"] = "LIVE"; // "LIVE", "WEBRTC"
117 switch (m_format.m_streamInfo.m_type)
119 case CAEStreamInfo::STREAM_TYPE_AC3:
121 if (!m_format.m_streamInfo.m_frameSize)
122 m_format.m_streamInfo.m_frameSize = AC3_SYNCFRAME_SIZE;
123 m_format.m_frames = m_format.m_streamInfo.m_frameSize;
124 m_bufferSize = m_format.m_frames * STARFISH_AUDIO_BUFFERS;
125 break;
127 case CAEStreamInfo::STREAM_TYPE_EAC3:
129 payload["option"]["externalStreamingInfo"]["contents"]["ac3PlusInfo"]["channels"] = 8;
130 payload["option"]["externalStreamingInfo"]["contents"]["ac3PlusInfo"]["frequency"] =
131 static_cast<double>(m_format.m_streamInfo.m_sampleRate) / 1000;
133 if (!m_format.m_streamInfo.m_frameSize)
134 m_format.m_streamInfo.m_frameSize = AC3_SYNCFRAME_SIZE;
135 m_format.m_frames = m_format.m_streamInfo.m_frameSize;
136 m_bufferSize = m_format.m_frames * STARFISH_AUDIO_BUFFERS;
137 break;
139 case CAEStreamInfo::STREAM_TYPE_DTSHD_CORE:
140 case CAEStreamInfo::STREAM_TYPE_DTS_512:
141 case CAEStreamInfo::STREAM_TYPE_DTS_1024:
142 case CAEStreamInfo::STREAM_TYPE_DTS_2048:
143 case CAEStreamInfo::STREAM_TYPE_DTSHD:
144 case CAEStreamInfo::STREAM_TYPE_DTSHD_MA:
146 payload["option"]["externalStreamingInfo"]["contents"]["dtsInfo"]["channels"] =
147 m_format.m_streamInfo.m_channels;
148 payload["option"]["externalStreamingInfo"]["contents"]["dtsInfo"]["frequency"] =
149 static_cast<double>(m_format.m_streamInfo.m_sampleRate) / 1000;
151 m_format.m_frames = m_format.m_streamInfo.m_frameSize;
153 // DTSHD_MA has dynamic frame sizes but we need to ensure that the buffer is large enough
154 if (m_format.m_streamInfo.m_type == CAEStreamInfo::STREAM_TYPE_DTSHD_MA)
155 m_format.m_frames = DTDHD_MA_MIN_SYNCFRAME_SIZE * 2 - 1;
156 m_bufferSize = m_format.m_frames * STARFISH_AUDIO_BUFFERS;
157 break;
159 default:
160 CLog::LogF(LOGDEBUG, "CAESinkStarfish: Unsupported format {}", m_format.m_streamInfo.m_type);
161 return false;
163 payload["option"]["externalStreamingInfo"]["contents"]["codec"]["audio"] =
164 ms_audioCodecMap.at(m_format.m_streamInfo.m_type).data();
166 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["preBufferByte"] = 0;
167 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["bufferMinLevel"] = 0;
168 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["bufferMaxLevel"] = 100;
169 // This is the size after which the sink starts blocking
170 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["qBufferLevelAudio"] =
171 m_bufferSize;
172 // Internal buffer?
173 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["srcBufferLevelAudio"]["minimum"] =
174 m_format.m_frames;
175 payload["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["srcBufferLevelAudio"]["maximum"] =
176 m_bufferSize;
178 CVariant payloadArgs;
179 payloadArgs["args"] = CVariant(CVariant::VariantTypeArray);
180 payloadArgs["args"].push_back(std::move(payload));
182 std::string json;
183 CJSONVariantWriter::Write(payloadArgs, json, true);
185 m_starfishMediaAPI->notifyForeground();
186 CLog::LogFC(LOGDEBUG, LOGAUDIO, "CAESinkStarfish: Sending Load payload {}", json);
187 if (!m_starfishMediaAPI->Load(json.c_str(), &CAESinkStarfish::PlayerCallback, this))
189 CLog::LogF(LOGERROR, "CAESinkStarfish: Load failed");
190 return false;
193 format = m_format;
195 return true;
198 void CAESinkStarfish::Deinitialize()
200 m_starfishMediaAPI->Unload();
203 double CAESinkStarfish::GetCacheTotal()
205 if (m_format.m_dataFormat == AE_FMT_RAW)
207 auto frameTimeSeconds = std::chrono::duration_cast<std::chrono::duration<double>>(
208 std::chrono::duration<double, std::milli>(m_format.m_streamInfo.GetDuration()));
209 return STARFISH_AUDIO_BUFFERS * frameTimeSeconds.count() * 4;
211 else
212 return 0.0;
215 double CAESinkStarfish::GetLatency()
217 return 0.0;
220 unsigned int CAESinkStarfish::AddPackets(uint8_t** data, unsigned int frames, unsigned int offset)
222 auto frameTime = std::chrono::duration_cast<std::chrono::nanoseconds>(
223 std::chrono::duration<double, std::milli>(m_format.m_streamInfo.GetDuration()));
225 std::chrono::nanoseconds pts = 0ns;
226 if (!m_firstFeed && offset == 0)
227 pts += m_pts + frameTime;
229 CVariant payload;
230 uint8_t* buffer = data[0] + offset * m_format.m_frameSize;
231 payload["bufferAddr"] = fmt::format("{:#x}", reinterpret_cast<std::uintptr_t>(buffer));
232 payload["bufferSize"] = frames * m_format.m_frameSize;
233 payload["pts"] = pts.count();
234 payload["esData"] = 2;
236 std::string json;
237 CJSONVariantWriter::Write(payload, json, true);
239 std::string result = m_starfishMediaAPI->Feed(json.c_str());
240 while (result.find("BufferFull") != std::string::npos)
242 std::this_thread::sleep_for(std::chrono::nanoseconds(frameTime));
243 result = m_starfishMediaAPI->Feed(json.c_str());
246 if (result.find("Ok") != std::string::npos)
248 m_pts = pts;
249 m_firstFeed = false;
250 return frames;
253 CLog::LogF(LOGWARNING, "CAESinkStarfish: Buffer submit returned error: {}", result);
254 return 0;
257 void CAESinkStarfish::AddPause(unsigned int millis)
259 m_starfishMediaAPI->Pause();
260 std::this_thread::sleep_for(std::chrono::milliseconds(millis));
261 m_starfishMediaAPI->Play();
264 void CAESinkStarfish::GetDelay(AEDelayStatus& status)
266 auto delay = m_pts - std::chrono::nanoseconds(m_starfishMediaAPI->getCurrentPlaytime());
267 status.SetDelay(std::chrono::duration_cast<std::chrono::duration<double>>(delay).count());
270 void CAESinkStarfish::Drain()
272 m_starfishMediaAPI->pushEOS();
275 void CAESinkStarfish::PlayerCallback(const int32_t type,
276 const int64_t numValue,
277 const char* strValue)
279 switch (type)
281 case PF_EVENT_TYPE_STR_STATE_UPDATE__LOADCOMPLETED:
282 m_starfishMediaAPI->Play();
283 break;
284 default:
285 std::string logstr = strValue != nullptr ? strValue : "";
286 CLog::LogF(LOGDEBUG, "CAESinkStarfish: type: {}, numValue: {}, strValue: {}", type, numValue,
287 logstr);
291 void CAESinkStarfish::PlayerCallback(const int32_t type,
292 const int64_t numValue,
293 const char* strValue,
294 void* data)
296 static_cast<CAESinkStarfish*>(data)->PlayerCallback(type, numValue, strValue);