aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorthexai <58434170+thexai@users.noreply.github.com>2024-04-12 17:07:57 +0200
committerthexai <58434170+thexai@users.noreply.github.com>2024-04-26 16:26:20 +0200
commit527685684567d9877263876a910dfeeb62ff283d (patch)
treef4ae745fde395ea02dfd00a92f4827998bec388e
parentc52e76babc8fe6977bb3855d9ff40bdc0adcad3a (diff)
downloadxbmc-527685684567d9877263876a910dfeeb62ff283d.tar.xz
[Audio] TrueHD rework - totally new MAT packer implementation
-rw-r--r--xbmc/cores/AudioEngine/CMakeLists.txt6
-rw-r--r--xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp2
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp195
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h21
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp6
-rw-r--r--xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h4
-rw-r--r--xbmc/cores/AudioEngine/Utils/PackerMAT.cpp443
-rw-r--r--xbmc/cores/AudioEngine/Utils/PackerMAT.h119
8 files changed, 599 insertions, 197 deletions
diff --git a/xbmc/cores/AudioEngine/CMakeLists.txt b/xbmc/cores/AudioEngine/CMakeLists.txt
index bc57f3620c..debf3dd203 100644
--- a/xbmc/cores/AudioEngine/CMakeLists.txt
+++ b/xbmc/cores/AudioEngine/CMakeLists.txt
@@ -14,7 +14,8 @@ set(SOURCES AEResampleFactory.cpp
Utils/AELimiter.cpp
Utils/AEPackIEC61937.cpp
Utils/AEStreamInfo.cpp
- Utils/AEUtil.cpp)
+ Utils/AEUtil.cpp
+ Utils/PackerMAT.cpp)
set(HEADERS AEResampleFactory.h
AESinkFactory.h
@@ -44,7 +45,8 @@ set(HEADERS AEResampleFactory.h
Utils/AERingBuffer.h
Utils/AEStreamData.h
Utils/AEStreamInfo.h
- Utils/AEUtil.h)
+ Utils/AEUtil.h
+ Utils/PackerMAT.h)
if(TARGET ALSA::ALSA)
list(APPEND SOURCES Sinks/AESinkALSA.cpp
diff --git a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
index 96d8def6b0..07c3a99f26 100644
--- a/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
+++ b/xbmc/cores/AudioEngine/Engines/ActiveAE/ActiveAESink.cpp
@@ -911,7 +911,7 @@ void CActiveAESink::OpenSink()
m_needIecPack = NeedIECPacking();
if (m_needIecPack)
{
- m_packer = std::make_unique<CAEBitstreamPacker>();
+ m_packer = std::make_unique<CAEBitstreamPacker>(m_requestedFormat.m_streamInfo);
m_requestedFormat.m_sampleRate = CAEBitstreamPacker::GetOutputRate(m_requestedFormat.m_streamInfo);
m_requestedFormat.m_channelLayout = CAEBitstreamPacker::GetOutputChannelMap(m_requestedFormat.m_streamInfo);
}
diff --git a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
index cf1c515def..1d3253e348 100644
--- a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
+++ b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp
@@ -10,6 +10,7 @@
#include "AEPackIEC61937.h"
#include "AEStreamInfo.h"
+#include "PackerMAT.h"
#include "utils/log.h"
#include <array>
@@ -17,51 +18,18 @@
#include <stdint.h>
#include <string.h>
-extern "C"
-{
-#include <libavutil/intreadwrite.h>
-}
-
namespace
{
constexpr auto BURST_HEADER_SIZE = 8;
constexpr auto EAC3_MAX_BURST_PAYLOAD_SIZE = 24576 - BURST_HEADER_SIZE;
+} // namespace
-constexpr auto MAT_PKT_OFFSET = 61440;
-constexpr auto MAT_FRAME_SIZE = 61424;
-
-/* magic MAT format values, meaning is unknown at this point */
-constexpr std::array<uint8_t, 20> mat_start_code = {
- 0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01, 0x01, 0x80, 0x00,
- 0x56, 0xA5, 0x3B, 0xF4, 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0,
-};
-
-constexpr std::array<uint8_t, 12> mat_middle_code = {
- 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0,
-};
-
-constexpr std::array<uint8_t, 16> mat_end_code = {
- 0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11,
-};
-
-struct MatCode
-{
- int pos;
- const uint8_t* code;
- unsigned int len;
-};
-
-std::array<MatCode, 3> MatCodes = {{
- {0, mat_start_code.data(), mat_start_code.size()},
- {30708, mat_middle_code.data(), mat_middle_code.size()},
- {MAT_FRAME_SIZE - mat_end_code.size(), mat_end_code.data(), mat_end_code.size()},
-}};
-
-} // unnamed namespace
-
-CAEBitstreamPacker::CAEBitstreamPacker()
+CAEBitstreamPacker::CAEBitstreamPacker(const CAEStreamInfo& info)
{
Reset();
+
+ if (info.m_type == CAEStreamInfo::STREAM_TYPE_TRUEHD)
+ m_packerMAT = std::make_unique<CPackerMAT>();
}
CAEBitstreamPacker::~CAEBitstreamPacker()
@@ -74,7 +42,8 @@ void CAEBitstreamPacker::Pack(CAEStreamInfo &info, uint8_t* data, int size)
switch (info.m_type)
{
case CAEStreamInfo::STREAM_TYPE_TRUEHD:
- PackTrueHD(info, data, size);
+ m_packerMAT->PackTrueHD(data, size);
+ GetDataTrueHD();
break;
case CAEStreamInfo::STREAM_TYPE_DTSHD:
@@ -162,149 +131,27 @@ void CAEBitstreamPacker::Reset()
m_packedBuffer[0] = 0;
}
-/* we need to pack 24 TrueHD audio units into the unknown MAT format before packing into IEC61937 */
-void CAEBitstreamPacker::PackTrueHD(CAEStreamInfo &info, uint8_t* data, int size)
+void CAEBitstreamPacker::GetDataTrueHD()
{
- /* create the buffer if it doesn't already exist */
- if (m_trueHD[0].empty())
+ // limits a bit MAT frames output speed as this is called every 1/1200 seconds and
+ // anyway only is possible obtain a MAT frame every 12 audio units (TrueHD has 24 units
+ // but are send to packer every 12 to reduce latency). As MAT packer can generate more than
+ // one MAT frame at time (but in average only one every 20 ms) this delays the output
+ // a little when thera are more that one frame at queue but still allows the queue to empty.
+ if (m_dataCountTrueHD > 0)
{
- m_trueHD[0].resize(MAT_FRAME_SIZE);
- m_trueHD[1].resize(MAT_FRAME_SIZE);
- m_thd = {};
- }
-
- if (size < 10)
+ m_dataCountTrueHD--;
return;
-
- uint8_t* pBuf = m_trueHD[m_thd.bufferIndex].data();
- const uint8_t* pData = data;
-
- int totalFrameSize = size;
- int dataRem = size;
- int paddingRem = 0;
- int ratebits = 0;
- int nextCodeIdx = 0;
- uint16_t inputTiming = 0;
- bool havePacket = false;
-
- if (AV_RB24(data + 4) == 0xf8726f)
- {
- /* major sync unit, fetch sample rate */
- if (data[7] == 0xba)
- ratebits = data[8] >> 4;
- else if (data[7] == 0xbb)
- ratebits = data[9] >> 4;
- else
- return;
-
- m_thd.samplesPerFrame = 40 << (ratebits & 3);
- }
-
- if (!m_thd.samplesPerFrame)
- return;
-
- inputTiming = AV_RB16(data + 2);
-
- if (m_thd.prevFrameSize)
- {
- uint16_t deltaSamples = inputTiming - m_thd.prevFrameTime;
- /*
- * One multiple-of-48kHz frame is 1/1200 sec and the IEC 61937 rate
- * is 768kHz = 768000*4 bytes/sec.
- * The nominal space per frame is therefore
- * (768000*4 bytes/sec) * (1/1200 sec) = 2560 bytes.
- * For multiple-of-44.1kHz frames: 1/1102.5 sec, 705.6kHz, 2560 bytes.
- *
- * 2560 is divisible by samplesPerFrame.
- */
- int deltaBytes = deltaSamples * 2560 / m_thd.samplesPerFrame;
-
- /* padding needed before this frame */
- paddingRem = deltaBytes - m_thd.prevFrameSize;
-
- // detects stream discontinuities
- if (paddingRem < 0 || paddingRem >= MAT_FRAME_SIZE * 2)
- {
- m_thd = {}; // recovering after seek
- return;
- }
}
- for (nextCodeIdx = 0; nextCodeIdx < static_cast<int>(MatCodes.size()); nextCodeIdx++)
- if (m_thd.bufferFilled <= MatCodes[nextCodeIdx].pos)
- break;
-
- if (nextCodeIdx >= static_cast<int>(MatCodes.size()))
- return;
-
- while (paddingRem || dataRem || MatCodes[nextCodeIdx].pos == m_thd.bufferFilled)
+ if (m_packerMAT->HaveOutput())
{
- if (MatCodes[nextCodeIdx].pos == m_thd.bufferFilled)
- {
- /* time to insert MAT code */
- int codeLen = MatCodes[nextCodeIdx].len;
- int codeLenRemaining = codeLen;
- memcpy(pBuf + MatCodes[nextCodeIdx].pos, MatCodes[nextCodeIdx].code, codeLen);
- m_thd.bufferFilled += codeLen;
-
- nextCodeIdx++;
- if (nextCodeIdx == static_cast<int>(MatCodes.size()))
- {
- nextCodeIdx = 0;
-
- /* this was the last code, move to the next MAT frame */
- havePacket = true;
- m_thd.outputBuffer = pBuf;
- m_thd.bufferIndex ^= 1;
- pBuf = m_trueHD[m_thd.bufferIndex].data();
- m_thd.bufferFilled = 0;
-
- /* inter-frame gap has to be counted as well, add it */
- codeLenRemaining += MAT_PKT_OFFSET - MAT_FRAME_SIZE;
- }
-
- if (paddingRem)
- {
- /* consider the MAT code as padding */
- const int countedAsPadding = std::min(paddingRem, codeLenRemaining);
- paddingRem -= countedAsPadding;
- codeLenRemaining -= countedAsPadding;
- }
- /* count the remainder of the code as part of frame size */
- if (codeLenRemaining)
- totalFrameSize += codeLenRemaining;
- }
-
- if (paddingRem)
- {
- const int paddingLen = std::min(MatCodes[nextCodeIdx].pos - m_thd.bufferFilled, paddingRem);
-
- memset(pBuf + m_thd.bufferFilled, 0, paddingLen);
- m_thd.bufferFilled += paddingLen;
- paddingRem -= paddingLen;
-
- if (paddingRem)
- continue; /* time to insert MAT code */
- }
-
- if (dataRem)
- {
- const int dataLen = std::min(MatCodes[nextCodeIdx].pos - m_thd.bufferFilled, dataRem);
+ const auto& mat = m_packerMAT->GetOutputFrame();
- memcpy(pBuf + m_thd.bufferFilled, pData, dataLen);
- m_thd.bufferFilled += dataLen;
- pData += dataLen;
- dataRem -= dataLen;
- }
+ m_dataSize = CAEPackIEC61937::PackTrueHD(mat.data() + IEC61937_DATA_OFFSET,
+ mat.size() - IEC61937_DATA_OFFSET, m_packedBuffer);
+ m_dataCountTrueHD = 3;
}
-
- m_thd.prevFrameSize = totalFrameSize;
- m_thd.prevFrameTime = inputTiming;
-
- if (!havePacket)
- return;
-
- m_dataSize = CAEPackIEC61937::PackTrueHD(m_thd.outputBuffer, MAT_FRAME_SIZE, m_packedBuffer);
}
void CAEBitstreamPacker::PackDTSHD(CAEStreamInfo &info, uint8_t* data, int size)
diff --git a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h
index 1497c03d68..b7d695b62e 100644
--- a/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h
+++ b/xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h
@@ -12,15 +12,17 @@
#include "AEPackIEC61937.h"
#include <list>
+#include <memory>
#include <stdint.h>
#include <vector>
class CAEStreamInfo;
+class CPackerMAT;
class CAEBitstreamPacker
{
public:
- CAEBitstreamPacker();
+ CAEBitstreamPacker(const CAEStreamInfo& info);
~CAEBitstreamPacker();
void Pack(CAEStreamInfo &info, uint8_t* data, int size);
@@ -32,24 +34,13 @@ public:
static CAEChannelInfo GetOutputChannelMap(const CAEStreamInfo& info);
private:
- void PackTrueHD(CAEStreamInfo &info, uint8_t* data, int size);
+ void GetDataTrueHD();
void PackDTSHD(CAEStreamInfo &info, uint8_t* data, int size);
void PackEAC3(CAEStreamInfo &info, uint8_t* data, int size);
- /* we keep the trueHD and dtsHD buffers separate so that we can handle a fast stream switch */
- std::vector<uint8_t> m_trueHD[2];
+ std::unique_ptr<CPackerMAT> m_packerMAT;
- struct TrueHD
- {
- int prevFrameSize;
- int samplesPerFrame;
- int bufferFilled;
- int bufferIndex;
- uint16_t prevFrameTime;
- uint8_t* outputBuffer;
- };
-
- TrueHD m_thd{};
+ unsigned int m_dataCountTrueHD{0};
std::vector<uint8_t> m_dtsHD;
unsigned int m_dtsHDSize = 0;
diff --git a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp
index 411d48103e..43bfbf3c73 100644
--- a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp
+++ b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp
@@ -86,7 +86,7 @@ int CAEPackIEC61937::PackDTS_2048(uint8_t *data, unsigned int size, uint8_t *des
return PackDTS(data, size, dest, littleEndian, OUT_FRAMESTOBYTES(DTS3_FRAME_SIZE), IEC61937_TYPE_DTS3);
}
-int CAEPackIEC61937::PackTrueHD(uint8_t *data, unsigned int size, uint8_t *dest)
+int CAEPackIEC61937::PackTrueHD(const uint8_t* data, unsigned int size, uint8_t* dest)
{
if (size == 0)
return OUT_FRAMESTOBYTES(TRUEHD_FRAME_SIZE);
@@ -95,8 +95,8 @@ int CAEPackIEC61937::PackTrueHD(uint8_t *data, unsigned int size, uint8_t *dest)
struct IEC61937Packet *packet = (struct IEC61937Packet*)dest;
packet->m_preamble1 = IEC61937_PREAMBLE1;
packet->m_preamble2 = IEC61937_PREAMBLE2;
- packet->m_type = IEC61937_TYPE_TRUEHD;
- packet->m_length = size;
+ packet->m_type = IEC61937_TYPE_TRUEHD;
+ packet->m_length = 61424;
if (data == NULL)
data = packet->m_data;
diff --git a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h
index 5a83b65556..7a6e72741c 100644
--- a/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h
+++ b/xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h
@@ -36,8 +36,8 @@ public:
static int PackDTS_512 (uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
static int PackDTS_1024(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
static int PackDTS_2048(uint8_t *data, unsigned int size, uint8_t *dest, bool littleEndian);
- static int PackTrueHD (uint8_t *data, unsigned int size, uint8_t *dest);
- static int PackDTSHD (uint8_t *data, unsigned int size, uint8_t *dest, unsigned int period);
+ static int PackTrueHD(const uint8_t* data, unsigned int size, uint8_t* dest);
+ static int PackDTSHD(uint8_t* data, unsigned int size, uint8_t* dest, unsigned int period);
static int PackPause(uint8_t *dest, unsigned int millis, unsigned int framesize, unsigned int samplerate, unsigned int rep_period, unsigned int encodedRate);
private:
diff --git a/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp b/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp
new file mode 100644
index 0000000000..d940902981
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/PackerMAT.cpp
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#include "PackerMAT.h"
+
+#include "utils/log.h"
+
+#include <array>
+#include <assert.h>
+#include <utility>
+
+extern "C"
+{
+#include <libavutil/common.h>
+#include <libavutil/intreadwrite.h>
+}
+
+namespace
+{
+constexpr uint32_t FORMAT_MAJOR_SYNC = 0xf8726fba;
+
+constexpr auto BURST_HEADER_SIZE = 8;
+constexpr auto MAT_BUFFER_SIZE = 61440;
+constexpr auto MAT_BUFFER_LIMIT = MAT_BUFFER_SIZE - 24; // MAT end code size
+constexpr auto MAT_POS_MIDDLE = 30708 + BURST_HEADER_SIZE; // middle point + IEC header in front
+
+// magic MAT format values, meaning is unknown at this point
+constexpr std::array<uint8_t, 20> mat_start_code = {0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01,
+ 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4,
+ 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0};
+
+constexpr std::array<uint8_t, 12> mat_middle_code = {0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA,
+ 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0};
+
+constexpr std::array<uint8_t, 24> mat_end_code = {0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+} // namespace
+
+CPackerMAT::CPackerMAT()
+{
+ m_buffer.reserve(MAT_BUFFER_SIZE);
+}
+
+bool CPackerMAT::PackTrueHD(const uint8_t* data, int size)
+{
+ // On a high level, a MAT frame consists of a sequence of padded TrueHD frames
+ // The size of the padded frame can be determined from the frame time/sequence code in the frame header,
+ // since it varies to accommodate spikes in bitrate.
+ // In average all frames are always padded to 2560 bytes, so that 24 frames fit in one MAT frame, however
+ // due to bitrate spikes single sync frames have been observed to use up to twice that size, in which
+ // case they'll be preceded by smaller frames to keep the average bitrate constant.
+ // A constant padding to 2560 bytes can work (this is how the ffmpeg spdifenc module works), however
+ // high-bitrate streams can overshoot this size and therefor require proper handling of dynamic padding.
+
+ TrueHDMajorSyncInfo info;
+
+ // get the ratebits and output timing from the sync frame
+ if (AV_RB32(data + 4) == FORMAT_MAJOR_SYNC)
+ {
+ info = ParseTrueHDMajorSyncHeaders(data, size);
+
+ if (!info.valid)
+ return false;
+
+ m_state.ratebits = info.ratebits;
+ }
+ else if (m_state.prevFrametimeValid == false)
+ {
+ // only start streaming on a major sync frame
+ m_state.numberOfSamplesOffset = 0;
+ return false;
+ }
+
+ const uint16_t frameTime = AV_RB16(data + 2);
+ uint32_t spaceSize = 0;
+ const uint16_t frameSamples = 40 << (m_state.ratebits & 7);
+ m_state.outputTiming += frameSamples;
+
+ if (info.outputTimingPresent)
+ {
+ if (m_state.outputTimingValid && (info.outputTiming != m_state.outputTiming))
+ {
+ CLog::LogF(LOGWARNING,
+ "detected a stream discontinuity -> output timing expected: {}, found: {}",
+ m_state.outputTiming, info.outputTiming);
+ }
+ m_state.outputTiming = info.outputTiming;
+ m_state.outputTimingValid = true;
+ }
+
+ // compute final padded size for the previous frame, if any
+ if (m_state.prevFrametimeValid)
+ spaceSize = uint16_t(frameTime - m_state.prevFrametime) * (64 >> (m_state.ratebits & 7));
+
+ // compute padding (ie. difference to the size of the previous frame)
+ assert(!m_state.prevFrametimeValid || spaceSize >= m_state.prevMatFramesize);
+
+ // if for some reason the spaceSize fails, align the actual frame size
+ if (spaceSize < m_state.prevMatFramesize)
+ spaceSize = FFALIGN(m_state.prevMatFramesize, (64 >> (m_state.ratebits & 7)));
+
+ m_state.padding += (spaceSize - m_state.prevMatFramesize);
+
+ // detect seeks and re-initialize internal state i.e. skip stream
+ // until the next major sync frame
+ if (m_state.padding > MAT_BUFFER_SIZE * 5)
+ {
+ CLog::LogF(LOGINFO, "seek detected, re-initializing MAT packer state");
+ m_state = {};
+ m_state.init = true;
+ m_buffer.clear();
+ m_bufferCount = 0;
+ return false;
+ }
+
+ // store frame time of the previous frame
+ m_state.prevFrametime = frameTime;
+ m_state.prevFrametimeValid = true;
+
+ // Write the MAT header into the fresh buffer
+ if (GetCount() == 0)
+ {
+ WriteHeader();
+
+ // initial header, don't count it for the frame size
+ if (m_state.init == false)
+ {
+ m_state.init = true;
+ m_state.matFramesize = 0;
+ }
+ }
+
+ // write padding of the previous frame (if any)
+ while (m_state.padding > 0)
+ {
+ WritePadding();
+
+ assert(m_state.padding == 0 || GetCount() == MAT_BUFFER_SIZE);
+
+ // Buffer is full, submit it
+ if (GetCount() == MAT_BUFFER_SIZE)
+ {
+ FlushPacket();
+
+ // and setup a new buffer
+ WriteHeader();
+ }
+ }
+
+ // count the number of samples in this frame
+ m_state.samples += frameSamples;
+
+ // write actual audio data to the buffer
+ int remaining = FillDataBuffer(data, size, Type::DATA);
+
+ // not all data could be written, or the buffer is full
+ if (remaining || GetCount() == MAT_BUFFER_SIZE)
+ {
+ // flush out old data
+ FlushPacket();
+
+ if (remaining)
+ {
+ // setup a new buffer
+ WriteHeader();
+
+ // and write the remaining data
+ remaining = FillDataBuffer(data + (size - remaining), remaining, Type::DATA);
+
+ assert(remaining == 0);
+ }
+ }
+
+ // store the size of the current MAT frame, so we can add padding later
+ m_state.prevMatFramesize = m_state.matFramesize;
+ m_state.matFramesize = 0;
+
+ return true;
+}
+
+std::vector<uint8_t> CPackerMAT::GetOutputFrame()
+{
+ std::vector<uint8_t> buffer;
+
+ if (m_outputQueue.empty())
+ return {};
+
+ buffer = std::move(m_outputQueue.front());
+
+ m_outputQueue.pop_front();
+
+ return buffer;
+}
+
+void CPackerMAT::WriteHeader()
+{
+ m_buffer.resize(MAT_BUFFER_SIZE);
+
+ // reserve size for the IEC header and the MAT start code
+ const size_t size = BURST_HEADER_SIZE + mat_start_code.size();
+
+ // write MAT start code. IEC header written later, skip space only
+ memcpy(m_buffer.data() + BURST_HEADER_SIZE, mat_start_code.data(), mat_start_code.size());
+ m_bufferCount = size;
+
+ // unless the start code falls into the padding, it's considered part of the current MAT frame
+ // Note that audio frames are not always aligned with MAT frames, so we might already have a partial
+ // frame at this point
+ m_state.matFramesize += size;
+
+ // The MAT metadata counts as padding, if we're scheduled to write any, which mean the start bytes
+ // should reduce any further padding.
+ if (m_state.padding > 0)
+ {
+ // if the header fits into the padding of the last frame, just reduce the amount of needed padding
+ if (m_state.padding > size)
+ {
+ m_state.padding -= size;
+ m_state.matFramesize = 0;
+ }
+ else
+ {
+ // otherwise, consume all padding and set the size of the next MAT frame to the remaining data
+ m_state.matFramesize = (size - m_state.padding);
+ m_state.padding = 0;
+ }
+ }
+}
+
+void CPackerMAT::WritePadding()
+{
+ if (m_state.padding == 0)
+ return;
+
+ if (!m_logPadding && m_state.padding > MAT_BUFFER_SIZE / 2)
+ {
+ m_logPadding = true;
+ CLog::LogF(LOGWARNING,
+ "a large padding block of {} bytes is required due to unusual timestamps",
+ m_state.padding);
+ }
+ else if (m_logPadding && m_state.padding < MAT_BUFFER_SIZE / 2)
+ m_logPadding = false;
+
+ // for padding not writes any data (nullptr) as buffer is already zeroed
+ // only counts/skip bytes
+ const int remaining = FillDataBuffer(nullptr, m_state.padding, Type::PADDING);
+
+ // not all padding could be written to the buffer, write it later
+ if (remaining >= 0)
+ {
+ m_state.padding = remaining;
+ m_state.matFramesize = 0;
+ }
+ else
+ {
+ // more padding then requested was written, eg. there was a MAT middle/end marker
+ // that needed to be written
+ m_state.padding = 0;
+ m_state.matFramesize = -remaining;
+ }
+}
+
+void CPackerMAT::AppendData(const uint8_t* data, int size, Type type)
+{
+ // for padding not write anything, only skip bytes
+ if (type == Type::DATA)
+ memcpy(m_buffer.data() + m_bufferCount, data, size);
+
+ m_state.matFramesize += size;
+ m_bufferCount += size;
+}
+
+int CPackerMAT::FillDataBuffer(const uint8_t* data, int size, Type type)
+{
+ if (GetCount() >= MAT_BUFFER_LIMIT)
+ return size;
+
+ int remaining = size;
+
+ // Write MAT middle marker, if needed
+ // The MAT middle marker always needs to be in the exact same spot, any audio data will be split.
+ // If we're currently writing padding, then the marker will be considered as padding data and
+ // reduce the amount of padding still required.
+ if (GetCount() <= MAT_POS_MIDDLE && GetCount() + size > MAT_POS_MIDDLE)
+ {
+ // write as much data before the middle code as we can
+ int nBytesBefore = MAT_POS_MIDDLE - GetCount();
+ AppendData(data, nBytesBefore, type);
+ remaining -= nBytesBefore;
+
+ // write the MAT middle code
+ AppendData(mat_middle_code.data(), mat_middle_code.size(), Type::DATA);
+
+ // if we're writing padding, deduct the size of the code from it
+ if (type == Type::PADDING)
+ remaining -= mat_middle_code.size();
+
+ // write remaining data after the MAT marker
+ if (remaining > 0)
+ remaining = FillDataBuffer(data + nBytesBefore, remaining, type);
+
+ return remaining;
+ }
+
+ // not enough room in the buffer to write all the data,
+ // write as much as we can and add the MAT footer
+ if (GetCount() + size >= MAT_BUFFER_LIMIT)
+ {
+ // write as much data before the middle code as we can
+ int nBytesBefore = MAT_BUFFER_LIMIT - GetCount();
+ AppendData(data, nBytesBefore, type);
+ remaining -= nBytesBefore;
+
+ // write the MAT end code
+ AppendData(mat_end_code.data(), mat_end_code.size(), Type::DATA);
+
+ assert(GetCount() == MAT_BUFFER_SIZE);
+
+ // MAT markers don't displace padding, so reduce the amount of padding
+ if (type == Type::PADDING)
+ remaining -= mat_end_code.size();
+
+ // any remaining data will be written in future calls
+ return remaining;
+ }
+
+ AppendData(data, size, type);
+
+ return 0;
+}
+
+void CPackerMAT::FlushPacket()
+{
+ if (GetCount() == 0)
+ return;
+
+ assert(GetCount() == MAT_BUFFER_SIZE);
+
+ // normal number of samples per frame
+ const uint16_t frameSamples = 40 << (m_state.ratebits & 7);
+ const uint32_t MATSamples = (frameSamples * 24);
+
+ // push MAT packet to output queue
+ m_outputQueue.emplace_back(std::move(m_buffer));
+
+ if (m_outputQueue.size() > 1)
+ CLog::LogF(LOGDEBUG,
+ "several MAT packets generated in a row, the size of the output queue is {}",
+ m_outputQueue.size());
+
+ // we expect 24 frames per MAT frame, so calculate an offset from that
+ // this is done after delivery, because it modifies the duration of the frame,
+ // eg. the start of the next frame
+ if (MATSamples != m_state.samples)
+ m_state.numberOfSamplesOffset += m_state.samples - MATSamples;
+
+ m_state.samples = 0;
+
+ m_buffer.clear();
+ m_bufferCount = 0;
+}
+
+TrueHDMajorSyncInfo CPackerMAT::ParseTrueHDMajorSyncHeaders(const uint8_t* p, int buffsize) const
+{
+ TrueHDMajorSyncInfo info;
+
+ if (buffsize < 32)
+ return {};
+
+ // parse major sync and look for a restart header
+ int majorSyncSize = 28;
+ if (p[29] & 1) // restart header exists
+ {
+ int extensionSize = p[30] >> 4; // calculate headers size
+ majorSyncSize += 2 + extensionSize * 2;
+ }
+
+ CBitStream bs(p + 4, buffsize - 4);
+
+ bs.SkipBits(32); // format_sync
+
+ info.ratebits = bs.ReadBits(4); // ratebits
+ info.valid = true;
+
+ // (1) 6ch_multichannel_type
+ // (1) 8ch_multichannel_type
+ // (2) reserved
+ // (2) 2ch_presentation_channel_modifier
+ // (2) 6ch_presentation_channel_modifier
+ // (5) 6ch_presentation_channel_assignment
+ // (2) 8ch_presentation_channel_modifier
+ // (13) 8ch_presentation_channel_assignment
+ // (16) signature
+ // (16) flags
+ // (16) reserved
+ // (1) variable_rate
+ // (15) peak_data_rate
+ bs.SkipBits(1 + 1 + 2 + 2 + 2 + 5 + 2 + 13 + 16 + 16 + 16 + 1 + 15);
+
+ const int numSubstreams = bs.ReadBits(4);
+
+ bs.SkipBits(4 + (majorSyncSize - 17) * 8);
+
+ // substream directory
+ for (int i = 0; i < numSubstreams; i++)
+ {
+ int extraSubstreamWord = bs.ReadBits(1);
+ // (1) restart_nonexistent
+ // (1) crc_present
+ // (1) reserved
+ // (12) substream_end_ptr
+ bs.SkipBits(15);
+ if (extraSubstreamWord)
+ bs.SkipBits(16); // drc_gain_update, drc_time_update, reserved
+ }
+
+ // substream segments
+ for (int i = 0; i < numSubstreams; i++)
+ {
+ if (bs.ReadBits(1))
+ { // block_header_exists
+ if (bs.ReadBits(1))
+ { // restart_header_exists
+ bs.SkipBits(14); // restart_sync_word
+ info.outputTiming = bs.ReadBits(16);
+ info.outputTimingPresent = true;
+ // XXX: restart header
+ }
+ // XXX: Block header
+ }
+ // XXX: All blocks, all substreams?
+ break;
+ }
+
+ return info;
+}
diff --git a/xbmc/cores/AudioEngine/Utils/PackerMAT.h b/xbmc/cores/AudioEngine/Utils/PackerMAT.h
new file mode 100644
index 0000000000..d2bd787aca
--- /dev/null
+++ b/xbmc/cores/AudioEngine/Utils/PackerMAT.h
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2024 Team Kodi
+ * This file is part of Kodi - https://kodi.tv
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ * See LICENSES/README.md for more information.
+ */
+
+#pragma once
+
+#include <deque>
+#include <stdint.h>
+#include <vector>
+
+struct TrueHDMajorSyncInfo
+{
+ int ratebits{0};
+ uint16_t outputTiming{0};
+ bool outputTimingPresent{false};
+ bool valid{false};
+};
+
+enum class Type
+{
+ PADDING,
+ DATA,
+};
+
+class CPackerMAT
+{
+public:
+ CPackerMAT();
+ ~CPackerMAT() = default;
+
+ bool PackTrueHD(const uint8_t* data, int size);
+ bool HaveOutput() const { return !m_outputQueue.empty(); }
+ std::vector<uint8_t> GetOutputFrame();
+
+private:
+ struct MATState
+ {
+ bool init; // differentiates the first header
+
+ // audio_sampling_frequency:
+ // 0 -> 48 kHz
+ // 1 -> 96 kHz
+ // 2 -> 192 kHz
+ // 8 -> 44.1 kHz
+ // 9 -> 88.2 kHz
+ // 10 -> 176.4 kHz
+ int ratebits;
+
+ // Output timing obtained parsing TrueHD major sync headers (when available) or
+ // inferred increasing a counter the rest of the time.
+ uint16_t outputTiming;
+ bool outputTimingValid;
+
+ // Input timing of audio unit (obtained of each audio unit) and used to calculate padding
+ // bytes. On the contrary of outputTiming, frametime is present in all audio units.
+ uint16_t prevFrametime;
+ bool prevFrametimeValid;
+
+ uint32_t matFramesize; // size in bytes of current MAT frame
+ uint32_t prevMatFramesize; // size in bytes of previous MAT frame
+
+ uint32_t padding; // padding bytes pending to write
+ uint32_t samples; // number of samples accumulated in current MAT frame
+ int numberOfSamplesOffset; // offset respect number of samples in a standard MAT frame (40 * 24)
+ };
+
+ void WriteHeader();
+ void WritePadding();
+ void AppendData(const uint8_t* data, int size, Type type);
+ uint32_t GetCount() const { return m_bufferCount; }
+ int FillDataBuffer(const uint8_t* data, int size, Type type);
+ void FlushPacket();
+ TrueHDMajorSyncInfo ParseTrueHDMajorSyncHeaders(const uint8_t* p, int buffsize) const;
+
+ MATState m_state{};
+
+ bool m_logPadding{false};
+
+ uint32_t m_bufferCount{0};
+ std::vector<uint8_t> m_buffer;
+ std::deque<std::vector<uint8_t>> m_outputQueue;
+};
+
+class CBitStream
+{
+public:
+ // opens an existing byte array as bitstream
+ CBitStream(const uint8_t* bytes, int _size)
+ {
+ data = bytes;
+ size = _size;
+ }
+
+ // reads bits from bitstream
+ int ReadBits(int bits)
+ {
+ int dat = 0;
+ for (int i = index; i < index + bits; i++)
+ {
+ dat = dat * 2 + getbit(data[i / 8], i % 8);
+ }
+ index += bits;
+ return dat;
+ }
+
+ // skip bits from bitstream
+ void SkipBits(int bits) { index += bits; }
+
+private:
+ uint8_t getbit(uint8_t x, int y) { return (x >> (7 - y)) & 1; }
+
+ const uint8_t* data{nullptr};
+ int size{0};
+ int index{0};
+};