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.
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"
18 using namespace std::chrono_literals
;
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"},
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
))
57 void CAESinkStarfish::EnumerateDevicesEx(AEDeviceInfoList
& list
, bool force
)
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
)
98 if (m_format
.m_dataFormat
!= AE_FMT_RAW
)
100 CLog::LogF(LOGERROR
, "CAESinkStarfish: Unsupported format PCM");
103 m_format
.m_frameSize
= 1;
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
;
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
;
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
;
160 CLog::LogF(LOGDEBUG
, "CAESinkStarfish: Unsupported format {}", m_format
.m_streamInfo
.m_type
);
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"] =
173 payload
["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["srcBufferLevelAudio"]["minimum"] =
175 payload
["option"]["externalStreamingInfo"]["bufferingCtrInfo"]["srcBufferLevelAudio"]["maximum"] =
178 CVariant payloadArgs
;
179 payloadArgs
["args"] = CVariant(CVariant::VariantTypeArray
);
180 payloadArgs
["args"].push_back(std::move(payload
));
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");
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;
215 double CAESinkStarfish::GetLatency()
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
;
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;
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
)
253 CLog::LogF(LOGWARNING
, "CAESinkStarfish: Buffer submit returned error: {}", result
);
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
)
281 case PF_EVENT_TYPE_STR_STATE_UPDATE__LOADCOMPLETED
:
282 m_starfishMediaAPI
->Play();
285 std::string logstr
= strValue
!= nullptr ? strValue
: "";
286 CLog::LogF(LOGDEBUG
, "CAESinkStarfish: type: {}, numValue: {}, strValue: {}", type
, numValue
,
291 void CAESinkStarfish::PlayerCallback(const int32_t type
,
292 const int64_t numValue
,
293 const char* strValue
,
296 static_cast<CAESinkStarfish
*>(data
)->PlayerCallback(type
, numValue
, strValue
);