From 527685684567d9877263876a910dfeeb62ff283d Mon Sep 17 00:00:00 2001 From: thexai <58434170+thexai@users.noreply.github.com> Date: Fri, 12 Apr 2024 17:07:57 +0200 Subject: [Audio] TrueHD rework - totally new MAT packer implementation --- xbmc/cores/AudioEngine/CMakeLists.txt | 6 +- .../AudioEngine/Engines/ActiveAE/ActiveAESink.cpp | 2 +- xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.cpp | 195 +-------- xbmc/cores/AudioEngine/Utils/AEBitstreamPacker.h | 21 +- xbmc/cores/AudioEngine/Utils/AEPackIEC61937.cpp | 6 +- xbmc/cores/AudioEngine/Utils/AEPackIEC61937.h | 4 +- xbmc/cores/AudioEngine/Utils/PackerMAT.cpp | 443 +++++++++++++++++++++ xbmc/cores/AudioEngine/Utils/PackerMAT.h | 119 ++++++ 8 files changed, 599 insertions(+), 197 deletions(-) create mode 100644 xbmc/cores/AudioEngine/Utils/PackerMAT.cpp create mode 100644 xbmc/cores/AudioEngine/Utils/PackerMAT.h 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(); + m_packer = std::make_unique(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 @@ -17,51 +18,18 @@ #include #include -extern "C" -{ -#include -} - 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 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 mat_middle_code = { - 0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0, -}; - -constexpr std::array 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 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(); } 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(MatCodes.size()); nextCodeIdx++) - if (m_thd.bufferFilled <= MatCodes[nextCodeIdx].pos) - break; - - if (nextCodeIdx >= static_cast(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(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 +#include #include #include 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 m_trueHD[2]; + std::unique_ptr 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 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 +#include +#include + +extern "C" +{ +#include +#include +} + +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 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 mat_middle_code = {0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA, + 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0}; + +constexpr std::array 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 CPackerMAT::GetOutputFrame() +{ + std::vector 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 +#include +#include + +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 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 m_buffer; + std::deque> 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}; +}; -- cgit v1.2.3