From 36e824a9949b2eb1c5947703f531506d720b31ce Mon Sep 17 00:00:00 2001 From: Kai Sommerfeld Date: Tue, 17 Aug 2021 22:38:52 +0200 Subject: [PATCH 1/2] Add support for RDS data contained in AAC streams. --- CMakeLists.txt | 41 +++ src/aac/BitStream.cpp | 156 +++++++++ src/aac/BitStream.h | 49 +++ src/aac/Decoder.cpp | 171 ++++++++++ src/aac/Decoder.h | 44 +++ src/aac/Profile.h | 31 ++ src/aac/SampleFrequency.h | 28 ++ src/aac/elements/CCE.cpp | 83 +++++ src/aac/elements/CCE.h | 30 ++ src/aac/elements/CPE.cpp | 71 ++++ src/aac/elements/CPE.h | 30 ++ src/aac/elements/DSE.cpp | 85 +++++ src/aac/elements/DSE.h | 33 ++ src/aac/elements/FIL.cpp | 24 ++ src/aac/elements/FIL.h | 30 ++ src/aac/elements/ICS.cpp | 302 +++++++++++++++++ src/aac/elements/ICS.h | 51 +++ src/aac/elements/ICSInfo.cpp | 258 +++++++++++++++ src/aac/elements/ICSInfo.h | 63 ++++ src/aac/elements/LFE.cpp | 24 ++ src/aac/elements/LFE.h | 30 ++ src/aac/elements/PCE.cpp | 101 ++++++ src/aac/elements/PCE.h | 37 +++ src/aac/elements/SCE.cpp | 24 ++ src/aac/elements/SCE.h | 30 ++ src/aac/huffman/Codebooks.h | 405 +++++++++++++++++++++++ src/aac/huffman/Decoder.cpp | 112 +++++++ src/aac/huffman/Decoder.h | 35 ++ src/tvheadend/HTSPDemuxer.cpp | 93 +++--- src/tvheadend/HTSPDemuxer.h | 7 + src/tvheadend/utilities/RDSExtractor.cpp | 53 +++ src/tvheadend/utilities/RDSExtractor.h | 55 +++ 32 files changed, 2549 insertions(+), 37 deletions(-) create mode 100644 src/aac/BitStream.cpp create mode 100644 src/aac/BitStream.h create mode 100644 src/aac/Decoder.cpp create mode 100644 src/aac/Decoder.h create mode 100644 src/aac/Profile.h create mode 100644 src/aac/SampleFrequency.h create mode 100644 src/aac/elements/CCE.cpp create mode 100644 src/aac/elements/CCE.h create mode 100644 src/aac/elements/CPE.cpp create mode 100644 src/aac/elements/CPE.h create mode 100644 src/aac/elements/DSE.cpp create mode 100644 src/aac/elements/DSE.h create mode 100644 src/aac/elements/FIL.cpp create mode 100644 src/aac/elements/FIL.h create mode 100644 src/aac/elements/ICS.cpp create mode 100644 src/aac/elements/ICS.h create mode 100644 src/aac/elements/ICSInfo.cpp create mode 100644 src/aac/elements/ICSInfo.h create mode 100644 src/aac/elements/LFE.cpp create mode 100644 src/aac/elements/LFE.h create mode 100644 src/aac/elements/PCE.cpp create mode 100644 src/aac/elements/PCE.h create mode 100644 src/aac/elements/SCE.cpp create mode 100644 src/aac/elements/SCE.h create mode 100644 src/aac/huffman/Codebooks.h create mode 100644 src/aac/huffman/Decoder.cpp create mode 100644 src/aac/huffman/Decoder.h create mode 100644 src/tvheadend/utilities/RDSExtractor.cpp create mode 100644 src/tvheadend/utilities/RDSExtractor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6aa3bba5..14fa2e47 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,6 +14,39 @@ set(HTS_SOURCES src/addon.h src/Tvheadend.cpp src/Tvheadend.h) +set(HTS_SOURCES_AAC + src/aac/BitStream.cpp + src/aac/BitStream.h + src/aac/Decoder.cpp + src/aac/Decoder.h + src/aac/Profile.h + src/aac/SampleFrequency.h) + +set(HTS_SOURCES_AAC_ELEMENTS + src/aac/elements/CCE.cpp + src/aac/elements/CCE.h + src/aac/elements/CPE.cpp + src/aac/elements/CPE.h + src/aac/elements/DSE.cpp + src/aac/elements/DSE.h + src/aac/elements/FIL.cpp + src/aac/elements/FIL.h + src/aac/elements/ICS.cpp + src/aac/elements/ICS.h + src/aac/elements/ICSInfo.cpp + src/aac/elements/ICSInfo.h + src/aac/elements/LFE.cpp + src/aac/elements/LFE.h + src/aac/elements/PCE.cpp + src/aac/elements/PCE.h + src/aac/elements/SCE.cpp + src/aac/elements/SCE.h) + +set(HTS_SOURCES_AAC_HUFFMAN + src/aac/huffman/Codebooks.h + src/aac/huffman/Decoder.cpp + src/aac/huffman/Decoder.h) + set(HTS_SOURCES_TVHEADEND src/tvheadend/AutoRecordings.cpp src/tvheadend/AutoRecordings.h @@ -69,11 +102,16 @@ set(HTS_SOURCES_TVHEADEND_UTILITIES src/tvheadend/utilities/LifetimeMapper.h src/tvheadend/utilities/AsyncState.cpp src/tvheadend/utilities/AsyncState.h + src/tvheadend/utilities/RDSExtractor.h + src/tvheadend/utilities/RDSExtractor.cpp src/tvheadend/utilities/SyncedBuffer.h src/tvheadend/utilities/TCPSocket.h src/tvheadend/utilities/TCPSocket.cpp) source_group("Source Files" FILES ${HTS_SOURCES}) +source_group("Source Files\\aac" FILES ${HTS_SOURCES_AAC}) +source_group("Source Files\\aac\\elements" FILES ${HTS_SOURCES_AAC_ELEMENTS}) +source_group("Source Files\\aac\\huffman" FILES ${HTS_SOURCES_AAC_HUFFMAN}) source_group("Source Files\\tvheadend" FILES ${HTS_SOURCES_TVHEADEND}) source_group("Source Files\\tvheadend\\entity" FILES ${HTS_SOURCES_TVHEADEND_ENTITY}) source_group("Source Files\\tvheadend\\status" FILES ${HTS_SOURCES_TVHEADEND_STATUS}) @@ -92,6 +130,9 @@ source_group("Resource Files" FILES ${HTS_RESOURCES}) # Combine the file lists list(APPEND HTS_SOURCES + ${HTS_SOURCES_AAC} + ${HTS_SOURCES_AAC_ELEMENTS} + ${HTS_SOURCES_AAC_HUFFMAN} ${HTS_SOURCES_TVHEADEND} ${HTS_SOURCES_TVHEADEND_ENTITY} ${HTS_SOURCES_TVHEADEND_STATUS} diff --git a/src/aac/BitStream.cpp b/src/aac/BitStream.cpp new file mode 100644 index 00000000..c7fc90f6 --- /dev/null +++ b/src/aac/BitStream.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "BitStream.h" + +#include + +using namespace aac; + +BitStream::BitStream(const uint8_t* data, unsigned int dataLen) : m_data(data), m_dataLen(dataLen) +{ +} + +int BitStream::GetBitsLeft() const +{ + return 8 * (m_dataLen - m_pos) + m_bitsCached; +} + +int BitStream::ReadBit() +{ + int result; + + if (m_bitsCached > 0) + { + m_bitsCached--; + } + else + { + m_cache = ReadCache(); + m_bitsCached = 31; + } + + result = (m_cache >> m_bitsCached) & 0x1; + m_bitsRead++; + + return result; +} + +int BitStream::ReadBits(int n) +{ + if (n > 32) + throw std::invalid_argument("aac::BitStream::ReadBits - Attempt to read more than 32 bits"); + + int result; + + if (m_bitsCached >= n) + { + m_bitsCached -= n; + result = (m_cache >> m_bitsCached) & MaskBits(n); + } + else + { + const uint32_t c = m_cache & MaskBits(m_bitsCached); + const int left = n - m_bitsCached; + + m_cache = ReadCache(); + m_bitsCached = 32 - left; + result = ((m_cache >> m_bitsCached) & MaskBits(left)) | (c << left); + } + + m_bitsRead += n; + return result; +} + +void BitStream::SkipBit() +{ + m_bitsRead++; + if (m_bitsCached > 0) + { + m_bitsCached--; + } + else + { + m_cache = ReadCache(); + m_bitsCached = 31; + } +} + +void BitStream::SkipBits(int n) +{ + m_bitsRead += n; + if (n <= m_bitsCached) + { + m_bitsCached -= n; + } + else + { + n -= m_bitsCached; + + while (n >= 32) + { + n -= 32; + ReadCache(); + } + + if (n > 0) + { + m_cache = ReadCache(); + m_bitsCached = 32 - n; + } + else + { + m_cache = 0; + m_bitsCached = 0; + } + } +} + +void BitStream::ByteAlign() +{ + const int toFlush = m_bitsCached & 0x7; + if (toFlush > 0) + SkipBits(toFlush); +} + +uint32_t BitStream::ReadCache() +{ + if (m_pos == m_dataLen) + { + throw std::out_of_range("aac::BitStream::ReadCache - Attempt to read past end of stream"); + } + else if (m_pos > m_dataLen - 4) + { + // read near end of stream; read last 1 to 3 bytes + int toRead = m_dataLen - m_pos; + int i = 0; + if (toRead-- > 0) + i = ((m_data[m_pos] & 0xFF) << 24); + if (toRead-- > 0) + i |= ((m_data[m_pos + 1] & 0xFF) << 16); + if (toRead-- > 0) + i |= ((m_data[m_pos + 2] & 0xFF) << 8); + + m_pos = m_dataLen; + return i; + } + else + { + // read next 4 bytes + const uint32_t i = ((m_data[m_pos] & 0xFF) << 24) | ((m_data[m_pos + 1] & 0xFF) << 16) | + ((m_data[m_pos + 2] & 0xFF) << 8) | (m_data[m_pos + 3] & 0xFF); + + m_pos += 4; + return i; + } +} + +int BitStream::MaskBits(int n) +{ + return (n == 32) ? -1 : (1 << n) - 1; +} diff --git a/src/aac/BitStream.h b/src/aac/BitStream.h new file mode 100644 index 00000000..a9f2bc42 --- /dev/null +++ b/src/aac/BitStream.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include + +namespace aac +{ + +class BitStream +{ +public: + BitStream() = delete; + BitStream(const uint8_t* data, unsigned int dataLen); + + unsigned int GetLength() const { return m_dataLen; } + + int GetBitsLeft() const; + + int ReadBit(); + int ReadBits(int n); + + bool ReadBool() { return (ReadBit() & 0x1) != 0; } + + void SkipBit(); + void SkipBits(int n); + + void ByteAlign(); + +private: + uint32_t ReadCache(); + int MaskBits(int n); + + const uint8_t* m_data = nullptr; + const unsigned int m_dataLen = 0; + + unsigned int m_pos = 0; // offset in the data array + uint32_t m_cache = 0; // current 4 bytes, that are read from data + unsigned int m_bitsCached = 0; // remaining bits in current cache + unsigned int m_bitsRead = 0; // number of total bits read +}; + +} // namespace aac diff --git a/src/aac/Decoder.cpp b/src/aac/Decoder.cpp new file mode 100644 index 00000000..5672ee7f --- /dev/null +++ b/src/aac/Decoder.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "Decoder.h" + +#include "elements/CCE.h" +#include "elements/CPE.h" +#include "elements/DSE.h" +#include "elements/FIL.h" +#include "elements/LFE.h" +#include "elements/PCE.h" +#include "elements/SCE.h" + +#include + +using namespace aac; + +void Decoder::DecodeFrame() +{ + DecodeADTSHeader(); + + for (int i = 0; i < m_rawDataBlockCount; ++i) + DecodeRawDataBlock(); +} + +uint8_t Decoder::DecodeRDS(uint8_t*& data) +{ + m_rdsOnly = true; + + DecodeFrame(); + if (m_rdsDataLen) + data = m_rdsData; + + return m_rdsDataLen; +} + +void Decoder::DecodeADTSHeader() +{ + // Fixed header + + // 12 bits syncword + if (m_stream.ReadBits(12) != 0xFFF) + throw std::logic_error("aac::Decoder::DecodeADTSHeader - Invalid ADTS syncword"); + + // 1 bit ID, 2 bits layer + m_stream.SkipBits(3); + + // 1 bit protection absent + const bool protectionAbsent = m_stream.ReadBool(); + + m_profile = m_stream.ReadBits(2); + + m_sampleFrequencyIndex = m_stream.ReadBits(4); + + // 1 bit private bit, 3 bits channel configuration, 1 bit copy, 1 bit home + m_stream.SkipBits(6); + + // Variable header + + // 1 bit copyrightIDBit, 1 bit copyrightIDStart + m_stream.SkipBits(2); + + // 13 bits frame length + const int aacFrameLength = m_stream.ReadBits(13); + if (aacFrameLength != m_stream.GetLength()) + throw std::logic_error("aac::Decoder::DecodeADTSHeader - Invalid ADTS frame length"); + + // 11 bits adtsBufferFullness + m_stream.SkipBits(11); + + m_rawDataBlockCount = m_stream.ReadBits(2) + 1; + + if (!protectionAbsent) + { + // 16 bits CRC data + m_stream.SkipBits(16); + } +} + +namespace +{ + +constexpr int ELEMENT_SCE = 0; +constexpr int ELEMENT_CPE = 1; +constexpr int ELEMENT_CCE = 2; +constexpr int ELEMENT_LFE = 3; +constexpr int ELEMENT_DSE = 4; +constexpr int ELEMENT_PCE = 5; +constexpr int ELEMENT_FIL = 6; +constexpr int ELEMENT_END = 7; + +} // unnamed namespace + +void Decoder::DecodeRawDataBlock() +{ + int type = ELEMENT_END; + + do + { + // 3 bits elem type + type = m_stream.ReadBits(3); + + switch (type) + { + case ELEMENT_SCE: + { + elements::SCE sce; + sce.Decode(m_stream, m_profile, m_sampleFrequencyIndex); + break; + } + case ELEMENT_LFE: + { + elements::LFE lfe; + lfe.Decode(m_stream, m_profile, m_sampleFrequencyIndex); + break; + } + case ELEMENT_CPE: + { + elements::CPE cpe; + cpe.Decode(m_stream, m_profile, m_sampleFrequencyIndex); + break; + } + case ELEMENT_CCE: + { + elements::CCE cce; + cce.Decode(m_stream, m_profile, m_sampleFrequencyIndex); + break; + } + case ELEMENT_DSE: + { + elements::DSE dse; + if (m_rdsOnly) + { + //! @todo can there be more than one rds data set in one frame? + m_rdsDataLen = dse.DecodeRDS(m_stream, m_rdsData); + } + else + { + dse.Decode(m_stream); + } + break; + } + case ELEMENT_PCE: + { + elements::PCE pce; + pce.Decode(m_stream); + m_profile = pce.GetProfile(); + m_sampleFrequencyIndex = pce.GetSampleFrequencyIndex(); + break; + } + case ELEMENT_FIL: + { + elements::FIL fil; + fil.Decode(m_stream); + break; + } + case ELEMENT_END: + break; + + default: + throw std::logic_error("aac::Decoder::DecodeRawDataBlock - Unexpected element type"); + } + } while (type != ELEMENT_END); + + m_stream.ByteAlign(); +} diff --git a/src/aac/Decoder.h b/src/aac/Decoder.h new file mode 100644 index 00000000..07ca1f2f --- /dev/null +++ b/src/aac/Decoder.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include "BitStream.h" +#include "Profile.h" +#include "SampleFrequency.h" + +#include + +namespace aac +{ + +class Decoder +{ +public: + Decoder() = delete; + Decoder(const uint8_t* const data, unsigned int dataLen) : m_stream(data, dataLen) {} + + // Note: The only purpose of this decoder currently is decoding RDS data. + uint8_t DecodeRDS(uint8_t*& data); + +private: + void DecodeFrame(); + void DecodeADTSHeader(); + void DecodeRawDataBlock(); + + BitStream m_stream; + int m_profile = PROFILE_UNKNOWN; + int m_sampleFrequencyIndex = SAMPLE_FREQUENCY_NONE; + int m_rawDataBlockCount = 0; + + bool m_rdsOnly = false; + uint8_t* m_rdsData = nullptr; + uint8_t m_rdsDataLen = 0; +}; + +} // namespace aac diff --git a/src/aac/Profile.h b/src/aac/Profile.h new file mode 100644 index 00000000..7045ed8e --- /dev/null +++ b/src/aac/Profile.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +static constexpr int PROFILE_UNKNOWN = 0; +static constexpr int PROFILE_AAC_MAIN = 1; +static constexpr int PROFILE_AAC_LC = 2; +static constexpr int PROFILE_AAC_SSR = 3; +static constexpr int PROFILE_AAC_LTP = 4; +static constexpr int PROFILE_AAC_SBR = 5; +static constexpr int PROFILE_AAC_SCALABLE = 6; +static constexpr int PROFILE_TWIN_VQ = 7; +static constexpr int PROFILE_AAC_LD = 11; +static constexpr int PROFILE_ER_AAC_LC = 17; +static constexpr int PROFILE_ER_AAC_SSR = 18; +static constexpr int PROFILE_ER_AAC_LTP = 19; +static constexpr int PROFILE_ER_AAC_SCALABLE = 20; +static constexpr int PROFILE_ER_TWIN_VQ = 21; +static constexpr int PROFILE_ER_BSAC = 22; +static constexpr int PROFILE_ER_AAC_LD = 23; + +} // namespace aac diff --git a/src/aac/SampleFrequency.h b/src/aac/SampleFrequency.h new file mode 100644 index 00000000..535387a7 --- /dev/null +++ b/src/aac/SampleFrequency.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +static constexpr int SAMPLE_FREQUENCY_NONE = -1; +static constexpr int SAMPLE_FREQUENCY_96000 = 0; +static constexpr int SAMPLE_FREQUENCY_88200 = 1; +static constexpr int SAMPLE_FREQUENCY_64000 = 2; +static constexpr int SAMPLE_FREQUENCY_48000 = 3; +static constexpr int SAMPLE_FREQUENCY_44100 = 4; +static constexpr int SAMPLE_FREQUENCY_32000 = 5; +static constexpr int SAMPLE_FREQUENCY_24000 = 6; +static constexpr int SAMPLE_FREQUENCY_22050 = 7; +static constexpr int SAMPLE_FREQUENCY_16000 = 8; +static constexpr int SAMPLE_FREQUENCY_12000 = 9; +static constexpr int SAMPLE_FREQUENCY_11025 = 10; +static constexpr int SAMPLE_FREQUENCY_8000 = 11; + +} // namespace aac diff --git a/src/aac/elements/CCE.cpp b/src/aac/elements/CCE.cpp new file mode 100644 index 00000000..bcd7fe2a --- /dev/null +++ b/src/aac/elements/CCE.cpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "CCE.h" + +#include "../BitStream.h" +#include "../huffman/Decoder.h" +#include "ICS.h" +#include "ICSInfo.h" + +using namespace aac; +using namespace aac::elements; + +void CCE::Decode(BitStream& stream, int profile, int sampleFrequencyIndex) +{ + // 4 bits elem id + stream.SkipBits(4); + + int couplingPoint = 2 * stream.ReadBit(); // ind sw cce flag + const int coupledCount = stream.ReadBits(3); // num coupled elements + + int gainCount = 0; + for (int i = 0; i <= coupledCount; ++i) + { + gainCount++; + const bool channelPair = stream.ReadBool(); + + // 4 bits cc target is cpe + stream.SkipBits(4); + + if (channelPair) + { + const int chSelect = stream.ReadBits(2); + if (chSelect == 3) + gainCount++; + } + } + + couplingPoint += stream.ReadBit(); // cc domain + couplingPoint |= (couplingPoint >> 1); + + // 1 bit gain element sign, 2 bits gain element scale + stream.SkipBits(3); + + ICS ics; + ics.Decode(false, stream, profile, sampleFrequencyIndex); + + const int windowGroupCount = ics.GetInfo().GetWindowGroupCount(); + const int maxSFB = ics.GetInfo().GetMaxSFB(); + const int* sfbCB = ics.GetSfbCB(); + + for (int i = 0; i < gainCount; ++i) + { + int cge = 1; + + if (i > 0) + { + cge = couplingPoint == 2 ? 1 : stream.ReadBit(); + if (cge != 0) + huffman::Decoder::DecodeScaleFactor(stream); + } + + if (couplingPoint != 2) + { + for (int g = 0; g < windowGroupCount; ++g) + { + for (int sfb = 0; sfb < maxSFB; ++sfb) + { + if (sfbCB[sfb] != huffman::ZERO_HCB) + { + if (cge == 0) + huffman::Decoder::DecodeScaleFactor(stream); + } + } + } + } + } +} diff --git a/src/aac/elements/CCE.h b/src/aac/elements/CCE.h new file mode 100644 index 00000000..ef449f10 --- /dev/null +++ b/src/aac/elements/CCE.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Coupling Channel Element +class CCE +{ +public: + CCE() = default; + virtual ~CCE() = default; + + void Decode(aac::BitStream& stream, int profile, int sampleFrequencyIndex); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/CPE.cpp b/src/aac/elements/CPE.cpp new file mode 100644 index 00000000..ffc74dc1 --- /dev/null +++ b/src/aac/elements/CPE.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "CPE.h" + +#include "../BitStream.h" +#include "../SampleFrequency.h" +#include "ICS.h" +#include "ICSInfo.h" + +#include + +using namespace aac; +using namespace aac::elements; + +void CPE::Decode(BitStream& stream, int profile, int sampleFrequencyIndex) +{ + if (sampleFrequencyIndex == SAMPLE_FREQUENCY_NONE) + throw std::invalid_argument("aac::elements::CPE::Decode - Invalid sample frequency"); + + // 4 bits elem id + stream.SkipBits(4); + + ICS icsL; + ICS icsR; + + const bool commonWindow = stream.ReadBool(); + if (commonWindow) + { + ICSInfo& icsInfoL = icsL.GetInfo(); + icsInfoL.Decode(false, stream, profile, sampleFrequencyIndex); + icsR.GetInfo().SetData(icsInfoL); + + // decode mid-side-stereo info + + static constexpr int TYPE_ALL_0 = 0; + static constexpr int TYPE_USED = 1; + static constexpr int TYPE_ALL_1 = 2; + static constexpr int TYPE_RESERVED = 3; + + const int msMaskPresent = stream.ReadBits(2); + if (msMaskPresent == TYPE_USED) + { + // for (int i = 0; i < icsInfoL.GetWindowGroupCount(); ++i) + // { + // for (int sfb = 0; sfb < icsInfoL.GetMaxSFB(); ++sfb) + // { + // // 1 bit ms used + // m_stream.SkipBit(); + // } + // } + stream.SkipBits(icsInfoL.GetWindowGroupCount() * icsInfoL.GetMaxSFB()); + } + else if (msMaskPresent != TYPE_ALL_0 && msMaskPresent != TYPE_ALL_1 && + msMaskPresent != TYPE_RESERVED) + { + throw std::out_of_range("aac::elements::CPE::Decode - Invalid ms mask present value"); + } + } + + // decode left ics + icsL.Decode(commonWindow, stream, profile, sampleFrequencyIndex); + + // decode right ics + icsR.Decode(commonWindow, stream, profile, sampleFrequencyIndex); +} diff --git a/src/aac/elements/CPE.h b/src/aac/elements/CPE.h new file mode 100644 index 00000000..9ca37b0f --- /dev/null +++ b/src/aac/elements/CPE.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Channel Pair Element +class CPE +{ +public: + CPE() = default; + virtual ~CPE() = default; + + void Decode(aac::BitStream& stream, int profile, int sampleFrequencyIndex); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/DSE.cpp b/src/aac/elements/DSE.cpp new file mode 100644 index 00000000..1e9d60a9 --- /dev/null +++ b/src/aac/elements/DSE.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "DSE.h" + +#include "../BitStream.h" + +#include + +using namespace aac; +using namespace aac::elements; + +void DSE::Decode(BitStream& stream) +{ + // 4 bits elem id + stream.SkipBits(4); + + const bool byteAlign = stream.ReadBool(); + int count = stream.ReadBits(8); + if (count == 255) + count += stream.ReadBits(8); + + if (byteAlign) + stream.ByteAlign(); + + stream.SkipBits(8 * count); +} + +uint8_t DSE::DecodeRDS(BitStream& stream, uint8_t*& rdsdata) +{ + // 4 bits elem id + stream.SkipBits(4); + + const bool byteAlign = stream.ReadBool(); + int count = stream.ReadBits(8); + if (count == 255) + count += stream.ReadBits(8); + + if (byteAlign) + stream.ByteAlign(); + + if (count > 2) // we need at least 0xFE + some-other-byte + 0xFF + { + const uint8_t firstElem = static_cast(stream.ReadBits(8)); + if (firstElem == 0xFE) // could be RDS data start + { + rdsdata = new uint8_t[count]; + rdsdata[0] = firstElem; + + try + { + for (int i = 1; i < count; ++i) + rdsdata[i] = static_cast(stream.ReadBits(8)); + + if (rdsdata[count - 1] == 0xFF) // RDS data end + return count; // Note: caller has to delete the data array + } + catch (std::exception&) + { + // cleanup and rethrow + delete[] rdsdata; + rdsdata = nullptr; + throw; + } + + // data start with 0xFE, but do not end with 0xFF, thus no RDS data + delete[] rdsdata; + rdsdata = nullptr; + } + else + { + stream.SkipBits(8 * (count - 1)); + } + } + else + { + stream.SkipBits(8 * count); + } + return 0; +} diff --git a/src/aac/elements/DSE.h b/src/aac/elements/DSE.h new file mode 100644 index 00000000..207e9d9b --- /dev/null +++ b/src/aac/elements/DSE.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Data Stream Element +class DSE +{ +public: + DSE() = default; + virtual ~DSE() = default; + + void Decode(aac::BitStream& stream); + uint8_t DecodeRDS(aac::BitStream& stream, uint8_t*& rdsdata); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/FIL.cpp b/src/aac/elements/FIL.cpp new file mode 100644 index 00000000..64766ebd --- /dev/null +++ b/src/aac/elements/FIL.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "FIL.h" + +#include "../BitStream.h" + +using namespace aac; +using namespace aac::elements; + +void FIL::Decode(BitStream& stream) +{ + int count = stream.ReadBits(4); + if (count == 15) + count += stream.ReadBits(8) - 1; + + if (count > 0) + stream.SkipBits(count * 8); +} diff --git a/src/aac/elements/FIL.h b/src/aac/elements/FIL.h new file mode 100644 index 00000000..ea844cd5 --- /dev/null +++ b/src/aac/elements/FIL.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Fill Element +class FIL +{ +public: + FIL() = default; + virtual ~FIL() = default; + + void Decode(aac::BitStream& stream); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/ICS.cpp b/src/aac/elements/ICS.cpp new file mode 100644 index 00000000..189d1d82 --- /dev/null +++ b/src/aac/elements/ICS.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "ICS.h" + +#include "../BitStream.h" +#include "../huffman/Decoder.h" +#include "ICSInfo.h" + +#include + +using namespace aac; +using namespace aac::elements; + +ICS::ICS() : m_icsInfo(new ICSInfo) +{ +} + +ICS::~ICS() = default; + +void ICS::Decode(bool commonWindow, BitStream& stream, int profile, int sampleFrequencyIndex) +{ + // 8 bits global gain + stream.SkipBits(8); + + if (!commonWindow) + m_icsInfo->Decode(commonWindow, stream, profile, sampleFrequencyIndex); + + DecodeSectionData(stream); + DecodeScaleFactorData(stream); + + const bool pulseDataPresent = stream.ReadBool(); + if (pulseDataPresent) + { + if (m_icsInfo->GetWindowSequence() == EIGHT_SHORT_SEQUENCE) + throw std::logic_error( + "aac::elements::ICS::Decode - Pulse data not allowed for short frames"); + + DecodePulseData(stream); + } + + const bool tnsDataPresent = stream.ReadBool(); + if (tnsDataPresent) + DecodeTNSData(stream); + + const bool gainControlDataPresent = stream.ReadBool(); + if (gainControlDataPresent) + DecodeGainControlData(stream); + + DecodeSpectralData(stream); +} + +void ICS::DecodeSectionData(BitStream& stream) +{ + const int bits = (m_icsInfo->GetWindowSequence() == EIGHT_SHORT_SEQUENCE) ? 3 : 5; + const int escVal = (1 << bits) - 1; + + const int windowGroupCount = m_icsInfo->GetWindowGroupCount(); + const int maxSFB = m_icsInfo->GetMaxSFB(); + + int idx = 0; + for (int g = 0; g < windowGroupCount; ++g) + { + int k = 0; + while (k < maxSFB) + { + int end = k; + const int sectCB = stream.ReadBits(4); + + if (sectCB == 12) + throw std::logic_error( + "aac::elements::ICS::DecodeSectionData - Invalid huffman codebook: 12"); + + int incr; + while ((incr = stream.ReadBits(bits)) == escVal && stream.GetBitsLeft() >= bits) + end += incr; + + end += incr; + + if (stream.GetBitsLeft() < 0 || incr == escVal) + throw std::logic_error("aac::elements::ICS::DecodeSectionData - stream past eof"); + + if (end > m_icsInfo->GetMaxSFB()) + throw std::logic_error("aac::elements::ICS::DecodeSectionData - Too many bands"); + + for (; k < end; ++k) + { + m_sfbCB[idx] = sectCB; + m_sectEnd[idx++] = end; + } + } + } +} + +void ICS::DecodeScaleFactorData(BitStream& stream) +{ + bool noiseFlag = true; + + const int windowGroupCount = m_icsInfo->GetWindowGroupCount(); + const int maxSFB = m_icsInfo->GetMaxSFB(); + + int idx = 0; + for (int g = 0; g < windowGroupCount; ++g) + { + for (int sfb = 0; sfb < maxSFB;) + { + const int end = m_sectEnd[idx]; + switch (m_sfbCB[idx]) + { + case huffman::ZERO_HCB: + for (; sfb < end; ++sfb, ++idx) + ; + break; + case huffman::INTENSITY_HCB: + case huffman::INTENSITY_HCB2: + for (; sfb < end; ++sfb, ++idx) + { + static constexpr int SF_DELTA = 60; + + if (huffman::Decoder::DecodeScaleFactor(stream) - SF_DELTA > 255) + throw std::logic_error( + "aac::elements::ICS::DecodeScaleFactor - Scalefactor out of range"); + } + break; + case huffman::NOISE_HCB: + for (; sfb < end; ++sfb, ++idx) + { + if (noiseFlag) + { + stream.SkipBits(9); + noiseFlag = false; + } + else + { + huffman::Decoder::DecodeScaleFactor(stream); + } + } + break; + default: + for (; sfb < end; ++sfb, ++idx) + { + huffman::Decoder::DecodeScaleFactor(stream); + } + break; + } + } + } +} + +void ICS::DecodePulseData(BitStream& stream) +{ + const int pulseCount = stream.ReadBits(2); + + // 6 bits pulse start sfb + stream.SkipBits(6); + + // for (int i = 0; i < pulseCount + 1; ++i) + // { + // // 5 bits pulse offset, 4 bits pulse amp + // m_stream.SkipBits(9); + // } + stream.SkipBits(9 * (pulseCount + 1)); +} + +void ICS::DecodeTNSData(BitStream& stream) +{ + int bits[3]; + if (m_icsInfo->GetWindowSequence() == EIGHT_SHORT_SEQUENCE) + { + bits[0] = 1; + bits[1] = 4; + bits[2] = 3; + } + else + { + bits[0] = 2; + bits[1] = 6; + bits[2] = 5; + } + + const int windowCount = m_icsInfo->GetWindowCount(); + + for (int w = 0; w < windowCount; ++w) + { + const int nFilt = stream.ReadBits(bits[0]); + if (nFilt != 0) + { + const int coefRes = stream.ReadBit(); + + for (int filt = 0; filt < nFilt; ++filt) + { + stream.SkipBits(bits[1]); + + const int order = stream.ReadBits(bits[2]); + if (order != 0) + { + // 1 bit direction + stream.SkipBit(); + + const int coefCompress = stream.ReadBit(); + const int coefLen = coefRes + 3 - coefCompress; + + // for (int i = 0; i < order; ++i) + // { + // m_stream.SkipBits(coefLen); + // } + stream.SkipBits(order * coefLen); + } + } + } + } +} + +void ICS::DecodeGainControlData(BitStream& stream) +{ + const int maxBand = stream.ReadBits(2) + 1; + + int wdLen, locBits, locBits2 = 0; + switch (m_icsInfo->GetWindowSequence()) + { + case ONLY_LONG_SEQUENCE: + wdLen = 1; + locBits = 5; + locBits2 = 5; + break; + case EIGHT_SHORT_SEQUENCE: + wdLen = 8; + locBits = 2; + locBits2 = 2; + break; + case LONG_START_SEQUENCE: + wdLen = 2; + locBits = 4; + locBits2 = 2; + break; + case LONG_STOP_SEQUENCE: + wdLen = 2; + locBits = 4; + locBits2 = 5; + break; + default: + return; + } + + for (int bd = 1; bd < maxBand; ++bd) + { + for (int wd = 0; wd < wdLen; ++wd) + { + const int len = stream.ReadBits(3); + for (int k = 0; k < len; ++k) + { + stream.SkipBits(4); + const int bits = (wd == 0) ? locBits : locBits2; + stream.SkipBits(bits); + } + } + } +} + +void ICS::DecodeSpectralData(BitStream& stream) +{ + const int windowGroupCount = m_icsInfo->GetWindowGroupCount(); + const int maxSFB = m_icsInfo->GetMaxSFB(); + const uint16_t* swbOffsets = m_icsInfo->GetSWBOffsets(); + + int idx = 0; + for (int g = 0; g < windowGroupCount; ++g) + { + const int groupLen = m_icsInfo->GetWindowGroupLengths()[g]; + + for (int sfb = 0; sfb < maxSFB; ++sfb, ++idx) + { + const int hcb = m_sfbCB[idx]; + const int width = swbOffsets[sfb + 1] - swbOffsets[sfb]; + + if (hcb == huffman::ZERO_HCB || hcb == huffman::INTENSITY_HCB || + hcb == huffman::INTENSITY_HCB2 || hcb == huffman::NOISE_HCB) + { + continue; + } + else + { + for (int w = 0; w < groupLen; ++w) + { + static constexpr int FIRST_PAIR_HCB = 5; + + const int num = (hcb >= FIRST_PAIR_HCB) ? 2 : 4; + for (int k = 0; k < width; k += num) + { + int buf[4] = {}; + huffman::Decoder::DecodeSpectralData(stream, hcb, buf, 0); + } + } + } + } + } +} diff --git a/src/aac/elements/ICS.h b/src/aac/elements/ICS.h new file mode 100644 index 00000000..e7f692e0 --- /dev/null +++ b/src/aac/elements/ICS.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include +#include + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +class ICSInfo; + +// Individual Channel Stream +class ICS +{ +public: + ICS(); + virtual ~ICS(); + + void Decode(bool commonWindow, aac::BitStream& stream, int profile, int sampleFrequencyIndex); + + ICSInfo& GetInfo() const { return *m_icsInfo; } + int const* GetSfbCB() const { return m_sfbCB; } + +private: + void DecodeSectionData(BitStream& stream); + void DecodeScaleFactorData(BitStream& stream); + void DecodePulseData(BitStream& stream); + void DecodeTNSData(BitStream& stream); + void DecodeGainControlData(BitStream& stream); + void DecodeSpectralData(BitStream& stream); + + std::unique_ptr m_icsInfo; + + int m_sfbCB[120] = {}; + int m_sectEnd[120] = {}; +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/ICSInfo.cpp b/src/aac/elements/ICSInfo.cpp new file mode 100644 index 00000000..10f5117b --- /dev/null +++ b/src/aac/elements/ICSInfo.cpp @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "ICSInfo.h" + +#include "../BitStream.h" +#include "../Profile.h" +#include "../SampleFrequency.h" + +#include +#include + +using namespace aac; +using namespace aac::elements; + +namespace +{ + +constexpr int PRED_SFB_MAX[] = {33, 33, 38, 40, 40, 40, 41, 41, 37, 37, 37, 34}; + +constexpr uint16_t SWB_OFFSET_128_96[] = {0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128}; + +constexpr uint16_t SWB_OFFSET_128_64[] = {0, 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 92, 128}; + +constexpr uint16_t SWB_OFFSET_128_48[] = {0, 4, 8, 12, 16, 20, 28, 36, + 44, 56, 68, 80, 96, 112, 128}; + +constexpr uint16_t SWB_OFFSET_128_24[] = {0, 4, 8, 12, 16, 20, 24, 28, + 36, 44, 52, 64, 76, 92, 108, 128}; + +constexpr uint16_t SWB_OFFSET_128_16[] = {0, 4, 8, 12, 16, 20, 24, 28, + 32, 40, 48, 60, 72, 88, 108, 128}; + +constexpr uint16_t SWB_OFFSET_128_8[] = {0, 4, 8, 12, 16, 20, 24, 28, + 36, 44, 52, 60, 72, 88, 108, 128}; + +const uint16_t* const SWB_OFFSET_128[] = {SWB_OFFSET_128_96, SWB_OFFSET_128_96, SWB_OFFSET_128_64, + SWB_OFFSET_128_48, SWB_OFFSET_128_48, SWB_OFFSET_128_48, + SWB_OFFSET_128_24, SWB_OFFSET_128_24, SWB_OFFSET_128_16, + SWB_OFFSET_128_16, SWB_OFFSET_128_16, SWB_OFFSET_128_8}; + +constexpr uint16_t SWB_OFFSET_1024_96[] = {0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, + 44, 48, 52, 56, 64, 72, 80, 88, 96, 108, 120, + 132, 144, 156, 172, 188, 212, 240, 276, 320, 384, 448, + 512, 576, 640, 704, 768, 832, 896, 960, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_64[] = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 64, + 72, 80, 88, 100, 112, 124, 140, 156, 172, 192, 216, 240, 268, 304, 344, 384, + 424, 464, 504, 544, 584, 624, 664, 704, 744, 784, 824, 864, 904, 944, 984, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_48[] = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, + 96, 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, + 480, 512, 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_32[] = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 48, 56, 64, 72, 80, 88, 96, + 108, 120, 132, 144, 160, 176, 196, 216, 240, 264, 292, 320, 352, 384, 416, 448, 480, 512, + 544, 576, 608, 640, 672, 704, 736, 768, 800, 832, 864, 896, 928, 960, 992, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_24[] = { + 0, 4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 52, 60, 68, 76, + 84, 92, 100, 108, 116, 124, 136, 148, 160, 172, 188, 204, 220, 240, 260, 284, + 308, 336, 364, 396, 432, 468, 508, 552, 600, 652, 704, 768, 832, 896, 960, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_16[] = {0, 8, 16, 24, 32, 40, 48, 56, 64, 72, 80, + 88, 100, 112, 124, 136, 148, 160, 172, 184, 196, 212, + 228, 244, 260, 280, 300, 320, 344, 368, 396, 424, 456, + 492, 532, 572, 616, 664, 716, 772, 832, 896, 960, 1024}; + +constexpr uint16_t SWB_OFFSET_1024_8[] = {0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, + 132, 144, 156, 172, 188, 204, 220, 236, 252, 268, 288, + 308, 328, 348, 372, 396, 420, 448, 476, 508, 544, 580, + 620, 664, 712, 764, 820, 880, 944, 1024}; + +const uint16_t* const SWB_OFFSET_1024[] = { + SWB_OFFSET_1024_96, SWB_OFFSET_1024_96, SWB_OFFSET_1024_64, SWB_OFFSET_1024_48, + SWB_OFFSET_1024_48, SWB_OFFSET_1024_32, SWB_OFFSET_1024_24, SWB_OFFSET_1024_24, + SWB_OFFSET_1024_16, SWB_OFFSET_1024_16, SWB_OFFSET_1024_16, SWB_OFFSET_1024_8}; + +} // unnamed namespace + +void ICSInfo::SetData(const ICSInfo& info) +{ + m_windowSequence = info.m_windowSequence; + m_maxSFB = info.m_maxSFB; + m_numWindowGroups = info.m_numWindowGroups; + for (int i = 0; i < 8; ++i) + m_windowGroupLen[i] = info.m_windowGroupLen[i]; + m_swbOffset = info.m_swbOffset; + m_numWindows = info.m_numWindows; +} + +void ICSInfo::Decode(bool commonWindow, BitStream& stream, int profile, int sampleFrequencyIndex) +{ + if (sampleFrequencyIndex == SAMPLE_FREQUENCY_NONE) + throw std::invalid_argument("aac::elements::ICSInfo::Decode - Invalid sample frequency"); + + // 1 bit reserved + stream.SkipBit(); + + const int ws = stream.ReadBits(2); + switch (ws) + { + case 0: + m_windowSequence = ONLY_LONG_SEQUENCE; + break; + case 1: + m_windowSequence = LONG_START_SEQUENCE; + break; + case 2: + m_windowSequence = EIGHT_SHORT_SEQUENCE; + break; + case 3: + m_windowSequence = LONG_STOP_SEQUENCE; + break; + default: + throw std::logic_error("aac::elements::ICSInfo::Decode - Invalid window sequence"); + } + + // 1 bit window shape + stream.SkipBit(); + + m_numWindowGroups = 1; + m_windowGroupLen[0] = 1; + + if (m_windowSequence == EIGHT_SHORT_SEQUENCE) + { + m_maxSFB = stream.ReadBits(4); + + // 7 bits scale factor grouping + for (int i = 0; i < 7; ++i) + { + if (stream.ReadBool()) + { + m_windowGroupLen[m_numWindowGroups - 1]++; + } + else + { + m_numWindowGroups++; + m_windowGroupLen[m_numWindowGroups - 1] = 1; + } + } + + m_numWindows = 8; + m_swbOffset = SWB_OFFSET_128[sampleFrequencyIndex]; + } + else + { + m_maxSFB = stream.ReadBits(6); + + m_numWindows = 1; + m_swbOffset = SWB_OFFSET_1024[sampleFrequencyIndex]; + + const bool predictorDataPresent = stream.ReadBool(); + if (predictorDataPresent) + DecodePredictionData(commonWindow, stream, profile, sampleFrequencyIndex); + } +} + +void ICSInfo::DecodePredictionData(bool commonWindow, + BitStream& stream, + int profile, + int sampleFrequencyIndex) +{ + switch (profile) + { + case PROFILE_AAC_MAIN: + { + const bool predictorReset = stream.ReadBool(); + if (predictorReset) + { + // 5 bits predictor reset group number + stream.SkipBits(5); + } + + const int maxPredSFB = PRED_SFB_MAX[sampleFrequencyIndex]; + const int length = std::min(m_maxSFB, maxPredSFB); + + // for (int sfb = 0; sfb < length; ++sfb) + // { + // // 1 bit prediction used + // m_stream.SkipBit(); + // } + stream.SkipBits(length); + break; + } + + case PROFILE_AAC_LTP: + { + const bool ltpData1Present = stream.ReadBool(); + if (ltpData1Present) + DecodeLTPredictionData(stream); + + if (commonWindow) + { + const bool ltpData2Present = stream.ReadBool(); + if (ltpData2Present) + DecodeLTPredictionData(stream); + } + break; + } + + case PROFILE_ER_AAC_LTP: + { + if (!commonWindow) + { + const bool ltpData1Present = stream.ReadBool(); + if (ltpData1Present) + DecodeLTPredictionData(stream); + } + break; + } + default: + throw std::logic_error( + "aac::elements::ICSInfo::DecodePredictionData - Unexpected profile for LTP"); + } +} + +void ICSInfo::DecodeLTPredictionData(BitStream& stream) +{ + // 11 bits lag, 3 bits coef + stream.SkipBits(14); + + if (m_windowSequence == EIGHT_SHORT_SEQUENCE) + { + for (int w = 0; w < m_numWindows; ++w) + { + const bool shortUsed = stream.ReadBool(); + if (shortUsed) + { + const bool shortLagPresent = stream.ReadBool(); + if (shortLagPresent) + { + // 4 bits short lag + stream.SkipBits(4); + } + } + } + } + else + { + static constexpr int MAX_LTP_SFB = 40; + const int lastBand = std::min(m_maxSFB, MAX_LTP_SFB); + // for (int i = 0; i < lastBand; ++i) + // { + // // 1 bit long used + // m_stream.SkipBit(); + // } + stream.SkipBits(lastBand); + } +} diff --git a/src/aac/elements/ICSInfo.h b/src/aac/elements/ICSInfo.h new file mode 100644 index 00000000..ddeb290d --- /dev/null +++ b/src/aac/elements/ICSInfo.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +enum WindowSequence +{ + ONLY_LONG_SEQUENCE, + LONG_START_SEQUENCE, + EIGHT_SHORT_SEQUENCE, + LONG_STOP_SEQUENCE +}; + +// Individual Channel Stream (ICS) Info +class ICSInfo +{ +public: + ICSInfo() = default; + virtual ~ICSInfo() = default; + + void Decode(bool commonWindow, aac::BitStream& stream, int profile, int sampleFrequencyIndex); + + int GetMaxSFB() const { return m_maxSFB; } + int GetWindowGroupCount() const { return m_numWindowGroups; } + const uint8_t* GetWindowGroupLengths() const { return m_windowGroupLen; } + WindowSequence GetWindowSequence() const { return m_windowSequence; } + int GetWindowCount() const { return m_numWindows; } + const uint16_t* GetSWBOffsets() const { return m_swbOffset; } + + void SetData(const ICSInfo& info); + +private: + void DecodePredictionData(bool commonWindow, + BitStream& stream, + int profile, + int sampleFrequencyIndex); + void DecodeLTPredictionData(BitStream& stream); + + WindowSequence m_windowSequence = ONLY_LONG_SEQUENCE; + int m_maxSFB = 0; + int m_numWindowGroups = 0; + uint8_t m_windowGroupLen[8] = {}; + const uint16_t* m_swbOffset = nullptr; + int m_numWindows = 0; +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/LFE.cpp b/src/aac/elements/LFE.cpp new file mode 100644 index 00000000..36676e1c --- /dev/null +++ b/src/aac/elements/LFE.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "LFE.h" + +#include "../BitStream.h" +#include "../elements/ICS.h" + +using namespace aac; +using namespace aac::elements; + +void LFE::Decode(BitStream& stream, int profile, int sampleFrequencyIndex) +{ + // 4 bits elem id + stream.SkipBits(4); + + ICS ics; + ics.Decode(false, stream, profile, sampleFrequencyIndex); +} diff --git a/src/aac/elements/LFE.h b/src/aac/elements/LFE.h new file mode 100644 index 00000000..065485eb --- /dev/null +++ b/src/aac/elements/LFE.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Low Frequency Enhancement (LFE) Channel Element +class LFE +{ +public: + LFE() = default; + virtual ~LFE() = default; + + void Decode(aac::BitStream& stream, int profile, int sampleFrequencyIndex); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/PCE.cpp b/src/aac/elements/PCE.cpp new file mode 100644 index 00000000..4fff0fc6 --- /dev/null +++ b/src/aac/elements/PCE.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "PCE.h" + +#include "../BitStream.h" + +using namespace aac; +using namespace aac::elements; + +void PCE::Decode(BitStream& stream) +{ + // 4 bits elem id + stream.SkipBits(4); + + m_profile = stream.ReadBits(2); + m_sampleFrequencyIndex = stream.ReadBits(4); + + const int frontChannelElementsCount = stream.ReadBits(4); + const int sideChannelElementsCount = stream.ReadBits(4); + const int backChannelElementsCount = stream.ReadBits(4); + const int lfeChannelElementsCount = stream.ReadBits(2); + const int assocDataElementsCount = stream.ReadBits(3); + const int validCCElementsCount = stream.ReadBits(4); + + const bool monoMixdownPresent = stream.ReadBool(); + if (monoMixdownPresent) + { + // 4 bits mono mixdown element number + stream.SkipBits(4); + } + + const bool stereoMixdownPresent = stream.ReadBool(); + if (stereoMixdownPresent) + { + // 4 bits stereo mixdown element number + stream.SkipBits(4); + } + + const bool matrixMixdownIdxPresent = stream.ReadBool(); + if (matrixMixdownIdxPresent) + { + // 2 bits matrix mixdown idx, 1 bit pseudo surround enable + stream.SkipBits(3); + } + + // for (int i = 0; i < frontChannelElementsCount; ++i) + // { + // // 1 bit front element is cpe, 4 bits front element tagselect + // stream.SkipBits(5); + // } + + // for (int i = 0; i < sideChannelElementsCount; ++i) + // { + // // 1 bit side element is cpe, 4 bits side element tagselect + // stream.SkipBits(5); + // } + + // for (int i = 0; i < backChannelElementsCount; ++i) + // { + // // 1 bit back element is cpe, 4 bits back element tagselect + // stream.SkipBits(5); + // } + + // for (int i = 0; i < lfeChannelElementsCount; ++i) + // { + // // 4 bits lfe element tag select + // stream.SkipBits(4); + // } + + // for (int i = 0; i < assocDataElementsCount; ++i) + // { + // // 4 bits assoc data element tag select + // stream.SkipBits(4); + // } + + // for (int i = 0; i < validCCElementsCount; ++i) + // { + // // 1 bit cc element is ind sw, 4 bits valid cc element tag select + // stream.SkipBits(5); + // } + + stream.SkipBits(5 * frontChannelElementsCount + 5 * sideChannelElementsCount + + 5 * backChannelElementsCount + 4 * lfeChannelElementsCount + + 4 * assocDataElementsCount + 5 * validCCElementsCount); + + stream.ByteAlign(); + + const int commentFieldBytes = stream.ReadBits(8); + // for(int i = 0; i < commentFieldBytes; ++i) + // { + // // 8 bits comment field data + // stream.SkipBits(8); + // } + stream.SkipBits(8 * commentFieldBytes); +} diff --git a/src/aac/elements/PCE.h b/src/aac/elements/PCE.h new file mode 100644 index 00000000..d4c6189c --- /dev/null +++ b/src/aac/elements/PCE.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Program Config Element( +class PCE +{ +public: + PCE() = default; + virtual ~PCE() = default; + + void Decode(aac::BitStream& stream); + + int GetProfile() const { return m_profile; } + int GetSampleFrequencyIndex() const { return m_sampleFrequencyIndex; } + +private: + int m_profile = 0; + int m_sampleFrequencyIndex = 0; +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/elements/SCE.cpp b/src/aac/elements/SCE.cpp new file mode 100644 index 00000000..1df5aa73 --- /dev/null +++ b/src/aac/elements/SCE.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "SCE.h" + +#include "../BitStream.h" +#include "../elements/ICS.h" + +using namespace aac; +using namespace aac::elements; + +void SCE::Decode(BitStream& stream, int profile, int sampleFrequencyIndex) +{ + // 4 bits elem id + stream.SkipBits(4); + + ICS ics; + ics.Decode(false, stream, profile, sampleFrequencyIndex); +} diff --git a/src/aac/elements/SCE.h b/src/aac/elements/SCE.h new file mode 100644 index 00000000..8c504de7 --- /dev/null +++ b/src/aac/elements/SCE.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace elements +{ + +// Single Channel Element +class SCE +{ +public: + SCE() = default; + virtual ~SCE() = default; + + void Decode(aac::BitStream& stream, int profile, int sampleFrequencyIndex); +}; + +} // namespace elements +} // namespace aac diff --git a/src/aac/huffman/Codebooks.h b/src/aac/huffman/Codebooks.h new file mode 100644 index 00000000..279e0bb2 --- /dev/null +++ b/src/aac/huffman/Codebooks.h @@ -0,0 +1,405 @@ +/* +* Copyright (C) 2005-2021 Team Kodi +* https://kodi.tv +* +* SPDX-License-Identifier: GPL-2.0-or-later +* See LICENSE.md for more information. +*/ + +#pragma once + +namespace aac +{ +namespace huffman +{ + +struct cb_table_entry +{ + int length = 0; + int codeword = 0; + int bit = 0; + int value1 = 0; + int value2 = 0; + int value3 = 0; +}; + +//codebooks: {bit length, codeword, values...} +static const cb_table_entry HCB1[] = { + {1, 0, 0, 0, 0, 0}, {5, 16, 1, 0, 0, 0}, {5, 17, -1, 0, 0, 0}, + {5, 18, 0, 0, 0, -1}, {5, 19, 0, 1, 0, 0}, {5, 20, 0, 0, 0, 1}, + {5, 21, 0, 0, -1, 0}, {5, 22, 0, 0, 1, 0}, {5, 23, 0, -1, 0, 0}, + {7, 96, 1, -1, 0, 0}, {7, 97, -1, 1, 0, 0}, {7, 98, 0, 0, -1, 1}, + {7, 99, 0, 1, -1, 0}, {7, 100, 0, -1, 1, 0}, {7, 101, 0, 0, 1, -1}, + {7, 102, 1, 1, 0, 0}, {7, 103, 0, 0, -1, -1}, {7, 104, -1, -1, 0, 0}, + {7, 105, 0, -1, -1, 0}, {7, 106, 1, 0, -1, 0}, {7, 107, 0, 1, 0, -1}, + {7, 108, -1, 0, 1, 0}, {7, 109, 0, 0, 1, 1}, {7, 110, 1, 0, 1, 0}, + {7, 111, 0, -1, 0, 1}, {7, 112, 0, 1, 1, 0}, {7, 113, 0, 1, 0, 1}, + {7, 114, -1, 0, -1, 0}, {7, 115, 1, 0, 0, 1}, {7, 116, -1, 0, 0, -1}, + {7, 117, 1, 0, 0, -1}, {7, 118, -1, 0, 0, 1}, {7, 119, 0, -1, 0, -1}, + {9, 480, 1, 1, -1, 0}, {9, 481, -1, 1, -1, 0}, {9, 482, 1, -1, 1, 0}, + {9, 483, 0, 1, 1, -1}, {9, 484, 0, 1, -1, 1}, {9, 485, 0, -1, 1, 1}, + {9, 486, 0, -1, 1, -1}, {9, 487, 1, -1, -1, 0}, {9, 488, 1, 0, -1, 1}, + {9, 489, 0, 1, -1, -1}, {9, 490, -1, 1, 1, 0}, {9, 491, -1, 0, 1, -1}, + {9, 492, -1, -1, 1, 0}, {9, 493, 0, -1, -1, 1}, {9, 494, 1, -1, 0, 1}, + {9, 495, 1, -1, 0, -1}, {9, 496, -1, 1, 0, -1}, {9, 497, -1, -1, -1, 0}, + {9, 498, 0, -1, -1, -1}, {9, 499, 0, 1, 1, 1}, {9, 500, 1, 0, 1, -1}, + {9, 501, 1, 1, 0, 1}, {9, 502, -1, 1, 0, 1}, {9, 503, 1, 1, 1, 0}, + {10, 1008, -1, -1, 0, 1}, {10, 1009, -1, 0, -1, -1}, {10, 1010, 1, 1, 0, -1}, + {10, 1011, 1, 0, -1, -1}, {10, 1012, -1, 0, -1, 1}, {10, 1013, -1, -1, 0, -1}, + {10, 1014, -1, 0, 1, 1}, {10, 1015, 1, 0, 1, 1}, {11, 2032, 1, -1, 1, -1}, + {11, 2033, -1, 1, -1, 1}, {11, 2034, -1, 1, 1, -1}, {11, 2035, 1, -1, -1, 1}, + {11, 2036, 1, 1, 1, 1}, {11, 2037, -1, -1, 1, 1}, {11, 2038, 1, 1, -1, -1}, + {11, 2039, -1, -1, 1, -1}, {11, 2040, -1, -1, -1, -1}, {11, 2041, 1, 1, -1, 1}, + {11, 2042, 1, -1, 1, 1}, {11, 2043, -1, 1, 1, 1}, {11, 2044, -1, 1, -1, -1}, + {11, 2045, -1, -1, -1, 1}, {11, 2046, 1, -1, -1, -1}, {11, 2047, 1, 1, 1, -1}}; + +static const cb_table_entry HCB2[] = { + {3, 0, 0, 0, 0, 0}, {4, 2, 1, 0, 0, 0}, {5, 6, -1, 0, 0, 0}, + {5, 7, 0, 0, 0, 1}, {5, 8, 0, 0, -1, 0}, {5, 9, 0, 0, 0, -1}, + {5, 10, 0, -1, 0, 0}, {5, 11, 0, 0, 1, 0}, {5, 12, 0, 1, 0, 0}, + {6, 26, 0, -1, 1, 0}, {6, 27, -1, 1, 0, 0}, {6, 28, 0, 1, -1, 0}, + {6, 29, 0, 0, 1, -1}, {6, 30, 0, 1, 0, -1}, {6, 31, 0, 0, -1, 1}, + {6, 32, -1, 0, 0, -1}, {6, 33, 1, -1, 0, 0}, {6, 34, 1, 0, -1, 0}, + {6, 35, -1, -1, 0, 0}, {6, 36, 0, 0, -1, -1}, {6, 37, 1, 0, 1, 0}, + {6, 38, 1, 0, 0, 1}, {6, 39, 0, -1, 0, 1}, {6, 40, -1, 0, 1, 0}, + {6, 41, 0, 1, 0, 1}, {6, 42, 0, -1, -1, 0}, {6, 43, -1, 0, 0, 1}, + {6, 44, 0, -1, 0, -1}, {6, 45, -1, 0, -1, 0}, {6, 46, 1, 1, 0, 0}, + {6, 47, 0, 1, 1, 0}, {6, 48, 0, 0, 1, 1}, {6, 49, 1, 0, 0, -1}, + {7, 100, 0, 1, -1, 1}, {7, 101, 1, 0, -1, 1}, {7, 102, -1, 1, -1, 0}, + {7, 103, 0, -1, 1, -1}, {7, 104, 1, -1, 1, 0}, {7, 105, 1, 1, 0, -1}, + {7, 106, 1, 0, 1, 1}, {7, 107, -1, 1, 1, 0}, {7, 108, 0, -1, -1, 1}, + {7, 109, 1, 1, 1, 0}, {7, 110, -1, 0, 1, -1}, {7, 111, -1, -1, -1, 0}, + {7, 112, -1, 0, -1, 1}, {7, 113, 1, -1, -1, 0}, {7, 114, 1, 1, -1, 0}, + {8, 230, 1, -1, 0, 1}, {8, 231, -1, 1, 0, -1}, {8, 232, -1, -1, 1, 0}, + {8, 233, -1, 0, 1, 1}, {8, 234, -1, -1, 0, 1}, {8, 235, -1, -1, 0, -1}, + {8, 236, 0, -1, -1, -1}, {8, 237, 1, 0, 1, -1}, {8, 238, 1, 0, -1, -1}, + {8, 239, 0, 1, -1, -1}, {8, 240, 0, 1, 1, 1}, {8, 241, -1, 1, 0, 1}, + {8, 242, -1, 0, -1, -1}, {8, 243, 0, 1, 1, -1}, {8, 244, 1, -1, 0, -1}, + {8, 245, 0, -1, 1, 1}, {8, 246, 1, 1, 0, 1}, {8, 247, 1, -1, 1, -1}, + {8, 248, -1, 1, -1, 1}, {9, 498, 1, -1, -1, 1}, {9, 499, -1, -1, -1, -1}, + {9, 500, -1, 1, 1, -1}, {9, 501, -1, 1, 1, 1}, {9, 502, 1, 1, 1, 1}, + {9, 503, -1, -1, 1, -1}, {9, 504, 1, -1, 1, 1}, {9, 505, -1, 1, -1, -1}, + {9, 506, -1, -1, 1, 1}, {9, 507, 1, 1, -1, -1}, {9, 508, 1, -1, -1, -1}, + {9, 509, -1, -1, -1, 1}, {9, 510, 1, 1, -1, 1}, {9, 511, 1, 1, 1, -1}}; + +static const cb_table_entry HCB3[] = { + {1, 0, 0, 0, 0, 0}, {4, 8, 1, 0, 0, 0}, {4, 9, 0, 0, 0, 1}, + {4, 10, 0, 1, 0, 0}, {4, 11, 0, 0, 1, 0}, {5, 24, 1, 1, 0, 0}, + {5, 25, 0, 0, 1, 1}, {6, 52, 0, 1, 1, 0}, {6, 53, 0, 1, 0, 1}, + {6, 54, 1, 0, 1, 0}, {6, 55, 0, 1, 1, 1}, {6, 56, 1, 0, 0, 1}, + {6, 57, 1, 1, 1, 0}, {7, 116, 1, 1, 1, 1}, {7, 117, 1, 0, 1, 1}, + {7, 118, 1, 1, 0, 1}, {8, 238, 2, 0, 0, 0}, {8, 239, 0, 0, 0, 2}, + {8, 240, 0, 0, 1, 2}, {8, 241, 2, 1, 0, 0}, {8, 242, 1, 2, 1, 0}, + {9, 486, 0, 0, 2, 1}, {9, 487, 0, 1, 2, 1}, {9, 488, 1, 2, 0, 0}, + {9, 489, 0, 1, 1, 2}, {9, 490, 2, 1, 1, 0}, {9, 491, 0, 0, 2, 0}, + {9, 492, 0, 2, 1, 0}, {9, 493, 0, 1, 2, 0}, {9, 494, 0, 2, 0, 0}, + {9, 495, 0, 1, 0, 2}, {9, 496, 2, 0, 1, 0}, {9, 497, 1, 2, 1, 1}, + {9, 498, 0, 2, 1, 1}, {9, 499, 1, 1, 2, 0}, {9, 500, 1, 1, 2, 1}, + {10, 1002, 1, 2, 0, 1}, {10, 1003, 1, 0, 2, 0}, {10, 1004, 1, 0, 2, 1}, + {10, 1005, 0, 2, 0, 1}, {10, 1006, 2, 1, 1, 1}, {10, 1007, 1, 1, 1, 2}, + {10, 1008, 2, 1, 0, 1}, {10, 1009, 1, 0, 1, 2}, {10, 1010, 0, 0, 2, 2}, + {10, 1011, 0, 1, 2, 2}, {10, 1012, 2, 2, 1, 0}, {10, 1013, 1, 2, 2, 0}, + {10, 1014, 1, 0, 0, 2}, {10, 1015, 2, 0, 0, 1}, {10, 1016, 0, 2, 2, 1}, + {11, 2034, 2, 2, 0, 0}, {11, 2035, 1, 2, 2, 1}, {11, 2036, 1, 1, 0, 2}, + {11, 2037, 2, 0, 1, 1}, {11, 2038, 1, 1, 2, 2}, {11, 2039, 2, 2, 1, 1}, + {11, 2040, 0, 2, 2, 0}, {11, 2041, 0, 2, 1, 2}, {12, 4084, 1, 0, 2, 2}, + {12, 4085, 2, 2, 0, 1}, {12, 4086, 2, 1, 2, 0}, {12, 4087, 2, 2, 2, 0}, + {12, 4088, 0, 2, 2, 2}, {12, 4089, 2, 2, 2, 1}, {12, 4090, 2, 1, 2, 1}, + {12, 4091, 1, 2, 1, 2}, {12, 4092, 1, 2, 2, 2}, {13, 8186, 0, 2, 0, 2}, + {13, 8187, 2, 0, 2, 0}, {13, 8188, 1, 2, 0, 2}, {14, 16378, 2, 0, 2, 1}, + {14, 16379, 2, 1, 1, 2}, {14, 16380, 2, 1, 0, 2}, {15, 32762, 2, 2, 2, 2}, + {15, 32763, 2, 2, 1, 2}, {15, 32764, 2, 1, 2, 2}, {15, 32765, 2, 0, 1, 2}, + {15, 32766, 2, 0, 0, 2}, {16, 65534, 2, 2, 0, 2}, {16, 65535, 2, 0, 2, 2}}; + +static const cb_table_entry HCB4[] = { + {4, 0, 1, 1, 1, 1}, {4, 1, 0, 1, 1, 1}, {4, 2, 1, 1, 0, 1}, {4, 3, 1, 1, 1, 0}, + {4, 4, 1, 0, 1, 1}, {4, 5, 1, 0, 0, 0}, {4, 6, 1, 1, 0, 0}, {4, 7, 0, 0, 0, 0}, + {4, 8, 0, 0, 1, 1}, {4, 9, 1, 0, 1, 0}, {5, 20, 1, 0, 0, 1}, {5, 21, 0, 1, 1, 0}, + {5, 22, 0, 0, 0, 1}, {5, 23, 0, 1, 0, 1}, {5, 24, 0, 0, 1, 0}, {5, 25, 0, 1, 0, 0}, + {7, 104, 2, 1, 1, 1}, {7, 105, 1, 1, 2, 1}, {7, 106, 1, 2, 1, 1}, {7, 107, 1, 1, 1, 2}, + {7, 108, 2, 1, 1, 0}, {7, 109, 2, 1, 0, 1}, {7, 110, 1, 2, 1, 0}, {7, 111, 2, 0, 1, 1}, + {7, 112, 0, 1, 2, 1}, {8, 226, 0, 1, 1, 2}, {8, 227, 1, 1, 2, 0}, {8, 228, 0, 2, 1, 1}, + {8, 229, 1, 0, 1, 2}, {8, 230, 1, 2, 0, 1}, {8, 231, 1, 1, 0, 2}, {8, 232, 1, 0, 2, 1}, + {8, 233, 2, 1, 0, 0}, {8, 234, 2, 0, 1, 0}, {8, 235, 1, 2, 0, 0}, {8, 236, 2, 0, 0, 1}, + {8, 237, 0, 1, 0, 2}, {8, 238, 0, 2, 1, 0}, {8, 239, 0, 0, 1, 2}, {8, 240, 0, 1, 2, 0}, + {8, 241, 0, 2, 0, 1}, {8, 242, 1, 0, 0, 2}, {8, 243, 0, 0, 2, 1}, {8, 244, 1, 0, 2, 0}, + {8, 245, 2, 0, 0, 0}, {8, 246, 0, 0, 0, 2}, {9, 494, 0, 2, 0, 0}, {9, 495, 0, 0, 2, 0}, + {9, 496, 1, 2, 2, 1}, {9, 497, 2, 2, 1, 1}, {9, 498, 2, 1, 2, 1}, {9, 499, 1, 1, 2, 2}, + {9, 500, 1, 2, 1, 2}, {9, 501, 2, 1, 1, 2}, {10, 1004, 1, 2, 2, 0}, {10, 1005, 2, 2, 1, 0}, + {10, 1006, 2, 1, 2, 0}, {10, 1007, 0, 2, 2, 1}, {10, 1008, 0, 1, 2, 2}, {10, 1009, 2, 2, 0, 1}, + {10, 1010, 0, 2, 1, 2}, {10, 1011, 2, 0, 2, 1}, {10, 1012, 1, 0, 2, 2}, {10, 1013, 2, 2, 2, 1}, + {10, 1014, 1, 2, 0, 2}, {10, 1015, 2, 0, 1, 2}, {10, 1016, 2, 1, 0, 2}, {10, 1017, 1, 2, 2, 2}, + {11, 2036, 2, 1, 2, 2}, {11, 2037, 2, 2, 1, 2}, {11, 2038, 0, 2, 2, 0}, {11, 2039, 2, 2, 0, 0}, + {11, 2040, 0, 0, 2, 2}, {11, 2041, 2, 0, 2, 0}, {11, 2042, 0, 2, 0, 2}, {11, 2043, 2, 0, 0, 2}, + {11, 2044, 2, 2, 2, 2}, {11, 2045, 0, 2, 2, 2}, {11, 2046, 2, 2, 2, 0}, {12, 4094, 2, 2, 0, 2}, + {12, 4095, 2, 0, 2, 2}}; + +static const cb_table_entry HCB5[] = { + {1, 0, 0, 0}, {4, 8, -1, 0}, {4, 9, 1, 0}, {4, 10, 0, 1}, + {4, 11, 0, -1}, {5, 24, 1, -1}, {5, 25, -1, 1}, {5, 26, -1, -1}, + {5, 27, 1, 1}, {7, 112, -2, 0}, {7, 113, 0, 2}, {7, 114, 2, 0}, + {7, 115, 0, -2}, {8, 232, -2, -1}, {8, 233, 2, 1}, {8, 234, -1, -2}, + {8, 235, 1, 2}, {8, 236, -2, 1}, {8, 237, 2, -1}, {8, 238, -1, 2}, + {8, 239, 1, -2}, {8, 240, -3, 0}, {8, 241, 3, 0}, {8, 242, 0, -3}, + {8, 243, 0, 3}, {9, 488, -3, -1}, {9, 489, 1, 3}, {9, 490, 3, 1}, + {9, 491, -1, -3}, {9, 492, -3, 1}, {9, 493, 3, -1}, {9, 494, 1, -3}, + {9, 495, -1, 3}, {9, 496, -2, 2}, {9, 497, 2, 2}, {9, 498, -2, -2}, + {9, 499, 2, -2}, {10, 1000, -3, -2}, {10, 1001, 3, -2}, {10, 1002, -2, 3}, + {10, 1003, 2, -3}, {10, 1004, 3, 2}, {10, 1005, 2, 3}, {10, 1006, -3, 2}, + {10, 1007, -2, -3}, {10, 1008, 0, -4}, {10, 1009, -4, 0}, {10, 1010, 4, 1}, + {10, 1011, 4, 0}, {11, 2024, -4, -1}, {11, 2025, 0, 4}, {11, 2026, 4, -1}, + {11, 2027, -1, -4}, {11, 2028, 1, 4}, {11, 2029, -1, 4}, {11, 2030, -4, 1}, + {11, 2031, 1, -4}, {11, 2032, 3, -3}, {11, 2033, -3, -3}, {11, 2034, -3, 3}, + {11, 2035, -2, 4}, {11, 2036, -4, -2}, {11, 2037, 4, 2}, {11, 2038, 2, -4}, + {11, 2039, 2, 4}, {11, 2040, 3, 3}, {11, 2041, -4, 2}, {12, 4084, -2, -4}, + {12, 4085, 4, -2}, {12, 4086, 3, -4}, {12, 4087, -4, -3}, {12, 4088, -4, 3}, + {12, 4089, 3, 4}, {12, 4090, -3, 4}, {12, 4091, 4, 3}, {12, 4092, 4, -3}, + {12, 4093, -3, -4}, {13, 8188, 4, -4}, {13, 8189, -4, 4}, {13, 8190, 4, 4}, + {13, 8191, -4, -4}}; + +static const cb_table_entry HCB6[] = { + {4, 0, 0, 0}, {4, 1, 1, 0}, {4, 2, 0, -1}, {4, 3, 0, 1}, {4, 4, -1, 0}, + {4, 5, 1, 1}, {4, 6, -1, 1}, {4, 7, 1, -1}, {4, 8, -1, -1}, {6, 36, 2, -1}, + {6, 37, 2, 1}, {6, 38, -2, 1}, {6, 39, -2, -1}, {6, 40, -2, 0}, {6, 41, -1, 2}, + {6, 42, 2, 0}, {6, 43, 1, -2}, {6, 44, 1, 2}, {6, 45, 0, -2}, {6, 46, -1, -2}, + {6, 47, 0, 2}, {6, 48, 2, -2}, {6, 49, -2, 2}, {6, 50, -2, -2}, {6, 51, 2, 2}, + {7, 104, -3, 1}, {7, 105, 3, 1}, {7, 106, 3, -1}, {7, 107, -1, 3}, {7, 108, -3, -1}, + {7, 109, 1, 3}, {7, 110, 1, -3}, {7, 111, -1, -3}, {7, 112, 3, 0}, {7, 113, -3, 0}, + {7, 114, 0, -3}, {7, 115, 0, 3}, {7, 116, 3, 2}, {8, 234, -3, -2}, {8, 235, -2, 3}, + {8, 236, 2, 3}, {8, 237, 3, -2}, {8, 238, 2, -3}, {8, 239, -2, -3}, {8, 240, -3, 2}, + {8, 241, 3, 3}, {9, 484, 3, -3}, {9, 485, -3, -3}, {9, 486, -3, 3}, {9, 487, 1, -4}, + {9, 488, -1, -4}, {9, 489, 4, 1}, {9, 490, -4, 1}, {9, 491, -4, -1}, {9, 492, 1, 4}, + {9, 493, 4, -1}, {9, 494, -1, 4}, {9, 495, 0, -4}, {9, 496, -4, 2}, {9, 497, -4, -2}, + {9, 498, 2, 4}, {9, 499, -2, -4}, {9, 500, -4, 0}, {9, 501, 4, 2}, {9, 502, 4, -2}, + {9, 503, -2, 4}, {9, 504, 4, 0}, {9, 505, 2, -4}, {9, 506, 0, 4}, {10, 1014, -3, -4}, + {10, 1015, -3, 4}, {10, 1016, 3, -4}, {10, 1017, 4, -3}, {10, 1018, 3, 4}, {10, 1019, 4, 3}, + {10, 1020, -4, 3}, {10, 1021, -4, -3}, {11, 2044, 4, 4}, {11, 2045, -4, 4}, {11, 2046, -4, -4}, + {11, 2047, 4, -4}}; + +static const cb_table_entry HCB7[] = { + {1, 0, 0, 0}, {3, 4, 1, 0}, {3, 5, 0, 1}, {4, 12, 1, 1}, {6, 52, 2, 1}, + {6, 53, 1, 2}, {6, 54, 2, 0}, {6, 55, 0, 2}, {7, 112, 3, 1}, {7, 113, 1, 3}, + {7, 114, 2, 2}, {7, 115, 3, 0}, {7, 116, 0, 3}, {8, 234, 2, 3}, {8, 235, 3, 2}, + {8, 236, 1, 4}, {8, 237, 4, 1}, {8, 238, 1, 5}, {8, 239, 5, 1}, {8, 240, 3, 3}, + {8, 241, 2, 4}, {8, 242, 0, 4}, {8, 243, 4, 0}, {9, 488, 4, 2}, {9, 489, 2, 5}, + {9, 490, 5, 2}, {9, 491, 0, 5}, {9, 492, 6, 1}, {9, 493, 5, 0}, {9, 494, 1, 6}, + {9, 495, 4, 3}, {9, 496, 3, 5}, {9, 497, 3, 4}, {9, 498, 5, 3}, {9, 499, 2, 6}, + {9, 500, 6, 2}, {9, 501, 1, 7}, {10, 1004, 3, 6}, {10, 1005, 0, 6}, {10, 1006, 6, 0}, + {10, 1007, 4, 4}, {10, 1008, 7, 1}, {10, 1009, 4, 5}, {10, 1010, 7, 2}, {10, 1011, 5, 4}, + {10, 1012, 6, 3}, {10, 1013, 2, 7}, {10, 1014, 7, 3}, {10, 1015, 6, 4}, {10, 1016, 5, 5}, + {10, 1017, 4, 6}, {10, 1018, 3, 7}, {11, 2038, 7, 0}, {11, 2039, 0, 7}, {11, 2040, 6, 5}, + {11, 2041, 5, 6}, {11, 2042, 7, 4}, {11, 2043, 4, 7}, {11, 2044, 5, 7}, {11, 2045, 7, 5}, + {12, 4092, 7, 6}, {12, 4093, 6, 6}, {12, 4094, 6, 7}, {12, 4095, 7, 7}}; + +static const cb_table_entry HCB8[] = { + {3, 0, 1, 1}, {4, 2, 2, 1}, {4, 3, 1, 0}, {4, 4, 1, 2}, {4, 5, 0, 1}, + {4, 6, 2, 2}, {5, 14, 0, 0}, {5, 15, 2, 0}, {5, 16, 0, 2}, {5, 17, 3, 1}, + {5, 18, 1, 3}, {5, 19, 3, 2}, {5, 20, 2, 3}, {6, 42, 3, 3}, {6, 43, 4, 1}, + {6, 44, 1, 4}, {6, 45, 4, 2}, {6, 46, 2, 4}, {6, 47, 3, 0}, {6, 48, 0, 3}, + {6, 49, 4, 3}, {6, 50, 3, 4}, {6, 51, 5, 2}, {7, 104, 5, 1}, {7, 105, 2, 5}, + {7, 106, 1, 5}, {7, 107, 5, 3}, {7, 108, 3, 5}, {7, 109, 4, 4}, {7, 110, 5, 4}, + {7, 111, 0, 4}, {7, 112, 4, 5}, {7, 113, 4, 0}, {7, 114, 2, 6}, {7, 115, 6, 2}, + {7, 116, 6, 1}, {7, 117, 1, 6}, {8, 236, 3, 6}, {8, 237, 6, 3}, {8, 238, 5, 5}, + {8, 239, 5, 0}, {8, 240, 6, 4}, {8, 241, 0, 5}, {8, 242, 4, 6}, {8, 243, 7, 1}, + {8, 244, 7, 2}, {8, 245, 2, 7}, {8, 246, 6, 5}, {8, 247, 7, 3}, {8, 248, 1, 7}, + {8, 249, 5, 6}, {8, 250, 3, 7}, {9, 502, 6, 6}, {9, 503, 7, 4}, {9, 504, 6, 0}, + {9, 505, 4, 7}, {9, 506, 0, 6}, {9, 507, 7, 5}, {9, 508, 7, 6}, {9, 509, 6, 7}, + {10, 1020, 5, 7}, {10, 1021, 7, 0}, {10, 1022, 0, 7}, {10, 1023, 7, 7}}; + +static const cb_table_entry HCB9[] = { + {1, 0, 0, 0}, {3, 4, 1, 0}, {3, 5, 0, 1}, {4, 12, 1, 1}, + {6, 52, 2, 1}, {6, 53, 1, 2}, {6, 54, 2, 0}, {6, 55, 0, 2}, + {7, 112, 3, 1}, {7, 113, 2, 2}, {7, 114, 1, 3}, {8, 230, 3, 0}, + {8, 231, 0, 3}, {8, 232, 2, 3}, {8, 233, 3, 2}, {8, 234, 1, 4}, + {8, 235, 4, 1}, {8, 236, 2, 4}, {8, 237, 1, 5}, {9, 476, 4, 2}, + {9, 477, 3, 3}, {9, 478, 0, 4}, {9, 479, 4, 0}, {9, 480, 5, 1}, + {9, 481, 2, 5}, {9, 482, 1, 6}, {9, 483, 3, 4}, {9, 484, 5, 2}, + {9, 485, 6, 1}, {9, 486, 4, 3}, {10, 974, 0, 5}, {10, 975, 2, 6}, + {10, 976, 5, 0}, {10, 977, 1, 7}, {10, 978, 3, 5}, {10, 979, 1, 8}, + {10, 980, 8, 1}, {10, 981, 4, 4}, {10, 982, 5, 3}, {10, 983, 6, 2}, + {10, 984, 7, 1}, {10, 985, 0, 6}, {10, 986, 8, 2}, {10, 987, 2, 8}, + {10, 988, 3, 6}, {10, 989, 2, 7}, {10, 990, 4, 5}, {10, 991, 9, 1}, + {10, 992, 1, 9}, {10, 993, 7, 2}, {11, 1988, 6, 0}, {11, 1989, 5, 4}, + {11, 1990, 6, 3}, {11, 1991, 8, 3}, {11, 1992, 0, 7}, {11, 1993, 9, 2}, + {11, 1994, 3, 8}, {11, 1995, 4, 6}, {11, 1996, 3, 7}, {11, 1997, 0, 8}, + {11, 1998, 10, 1}, {11, 1999, 6, 4}, {11, 2000, 2, 9}, {11, 2001, 5, 5}, + {11, 2002, 8, 0}, {11, 2003, 7, 0}, {11, 2004, 7, 3}, {11, 2005, 10, 2}, + {11, 2006, 9, 3}, {11, 2007, 8, 4}, {11, 2008, 1, 10}, {11, 2009, 7, 4}, + {11, 2010, 6, 5}, {11, 2011, 5, 6}, {11, 2012, 4, 8}, {11, 2013, 4, 7}, + {11, 2014, 3, 9}, {11, 2015, 11, 1}, {11, 2016, 5, 8}, {11, 2017, 9, 0}, + {11, 2018, 8, 5}, {12, 4038, 10, 3}, {12, 4039, 2, 10}, {12, 4040, 0, 9}, + {12, 4041, 11, 2}, {12, 4042, 9, 4}, {12, 4043, 6, 6}, {12, 4044, 12, 1}, + {12, 4045, 4, 9}, {12, 4046, 8, 6}, {12, 4047, 1, 11}, {12, 4048, 9, 5}, + {12, 4049, 10, 4}, {12, 4050, 5, 7}, {12, 4051, 7, 5}, {12, 4052, 2, 11}, + {12, 4053, 1, 12}, {12, 4054, 12, 2}, {12, 4055, 11, 3}, {12, 4056, 3, 10}, + {12, 4057, 5, 9}, {12, 4058, 6, 7}, {12, 4059, 8, 7}, {12, 4060, 11, 4}, + {12, 4061, 0, 10}, {12, 4062, 7, 6}, {12, 4063, 12, 3}, {12, 4064, 10, 0}, + {12, 4065, 10, 5}, {12, 4066, 4, 10}, {12, 4067, 6, 8}, {12, 4068, 2, 12}, + {12, 4069, 9, 6}, {12, 4070, 9, 7}, {12, 4071, 4, 11}, {12, 4072, 11, 0}, + {12, 4073, 6, 9}, {12, 4074, 3, 11}, {12, 4075, 5, 10}, {13, 8152, 8, 8}, + {13, 8153, 7, 8}, {13, 8154, 12, 5}, {13, 8155, 3, 12}, {13, 8156, 11, 5}, + {13, 8157, 7, 7}, {13, 8158, 12, 4}, {13, 8159, 11, 6}, {13, 8160, 10, 6}, + {13, 8161, 4, 12}, {13, 8162, 7, 9}, {13, 8163, 5, 11}, {13, 8164, 0, 11}, + {13, 8165, 12, 6}, {13, 8166, 6, 10}, {13, 8167, 12, 0}, {13, 8168, 10, 7}, + {13, 8169, 5, 12}, {13, 8170, 7, 10}, {13, 8171, 9, 8}, {13, 8172, 0, 12}, + {13, 8173, 11, 7}, {13, 8174, 8, 9}, {13, 8175, 9, 9}, {13, 8176, 10, 8}, + {13, 8177, 7, 11}, {13, 8178, 12, 7}, {13, 8179, 6, 11}, {13, 8180, 8, 11}, + {13, 8181, 11, 8}, {13, 8182, 7, 12}, {13, 8183, 6, 12}, {14, 16368, 8, 10}, + {14, 16369, 10, 9}, {14, 16370, 8, 12}, {14, 16371, 9, 10}, {14, 16372, 9, 11}, + {14, 16373, 9, 12}, {14, 16374, 10, 11}, {14, 16375, 12, 9}, {14, 16376, 10, 10}, + {14, 16377, 11, 9}, {14, 16378, 12, 8}, {14, 16379, 11, 10}, {14, 16380, 12, 10}, + {14, 16381, 12, 11}, {15, 32764, 10, 12}, {15, 32765, 11, 11}, {15, 32766, 11, 12}, + {15, 32767, 12, 12}}; + +static const cb_table_entry HCB10[] = { + {4, 0, 1, 1}, {4, 1, 1, 2}, {4, 2, 2, 1}, {5, 6, 2, 2}, + {5, 7, 1, 0}, {5, 8, 0, 1}, {5, 9, 1, 3}, {5, 10, 3, 2}, + {5, 11, 3, 1}, {5, 12, 2, 3}, {5, 13, 3, 3}, {6, 28, 2, 0}, + {6, 29, 0, 2}, {6, 30, 2, 4}, {6, 31, 4, 2}, {6, 32, 1, 4}, + {6, 33, 4, 1}, {6, 34, 0, 0}, {6, 35, 4, 3}, {6, 36, 3, 4}, + {6, 37, 3, 0}, {6, 38, 0, 3}, {6, 39, 4, 4}, {6, 40, 2, 5}, + {6, 41, 5, 2}, {7, 84, 1, 5}, {7, 85, 5, 1}, {7, 86, 5, 3}, + {7, 87, 3, 5}, {7, 88, 5, 4}, {7, 89, 4, 5}, {7, 90, 6, 2}, + {7, 91, 2, 6}, {7, 92, 6, 3}, {7, 93, 4, 0}, {7, 94, 6, 1}, + {7, 95, 0, 4}, {7, 96, 1, 6}, {7, 97, 3, 6}, {7, 98, 5, 5}, + {7, 99, 6, 4}, {7, 100, 4, 6}, {8, 202, 6, 5}, {8, 203, 7, 2}, + {8, 204, 3, 7}, {8, 205, 2, 7}, {8, 206, 5, 6}, {8, 207, 8, 2}, + {8, 208, 7, 3}, {8, 209, 5, 0}, {8, 210, 7, 1}, {8, 211, 0, 5}, + {8, 212, 8, 1}, {8, 213, 1, 7}, {8, 214, 8, 3}, {8, 215, 7, 4}, + {8, 216, 4, 7}, {8, 217, 2, 8}, {8, 218, 6, 6}, {8, 219, 7, 5}, + {8, 220, 1, 8}, {8, 221, 3, 8}, {8, 222, 8, 4}, {8, 223, 4, 8}, + {8, 224, 5, 7}, {8, 225, 8, 5}, {8, 226, 5, 8}, {9, 454, 7, 6}, + {9, 455, 6, 7}, {9, 456, 9, 2}, {9, 457, 6, 0}, {9, 458, 6, 8}, + {9, 459, 9, 3}, {9, 460, 3, 9}, {9, 461, 9, 1}, {9, 462, 2, 9}, + {9, 463, 0, 6}, {9, 464, 8, 6}, {9, 465, 9, 4}, {9, 466, 4, 9}, + {9, 467, 10, 2}, {9, 468, 1, 9}, {9, 469, 7, 7}, {9, 470, 8, 7}, + {9, 471, 9, 5}, {9, 472, 7, 8}, {9, 473, 10, 3}, {9, 474, 5, 9}, + {9, 475, 10, 4}, {9, 476, 2, 10}, {9, 477, 10, 1}, {9, 478, 3, 10}, + {9, 479, 9, 6}, {9, 480, 6, 9}, {9, 481, 8, 0}, {9, 482, 4, 10}, + {9, 483, 7, 0}, {9, 484, 11, 2}, {10, 970, 7, 9}, {10, 971, 11, 3}, + {10, 972, 10, 6}, {10, 973, 1, 10}, {10, 974, 11, 1}, {10, 975, 9, 7}, + {10, 976, 0, 7}, {10, 977, 8, 8}, {10, 978, 10, 5}, {10, 979, 3, 11}, + {10, 980, 5, 10}, {10, 981, 8, 9}, {10, 982, 11, 5}, {10, 983, 0, 8}, + {10, 984, 11, 4}, {10, 985, 2, 11}, {10, 986, 7, 10}, {10, 987, 6, 10}, + {10, 988, 10, 7}, {10, 989, 4, 11}, {10, 990, 1, 11}, {10, 991, 12, 2}, + {10, 992, 9, 8}, {10, 993, 12, 3}, {10, 994, 11, 6}, {10, 995, 5, 11}, + {10, 996, 12, 4}, {10, 997, 11, 7}, {10, 998, 12, 5}, {10, 999, 3, 12}, + {10, 1000, 6, 11}, {10, 1001, 9, 0}, {10, 1002, 10, 8}, {10, 1003, 10, 0}, + {10, 1004, 12, 1}, {10, 1005, 0, 9}, {10, 1006, 4, 12}, {10, 1007, 9, 9}, + {10, 1008, 12, 6}, {10, 1009, 2, 12}, {10, 1010, 8, 10}, {11, 2022, 9, 10}, + {11, 2023, 1, 12}, {11, 2024, 11, 8}, {11, 2025, 12, 7}, {11, 2026, 7, 11}, + {11, 2027, 5, 12}, {11, 2028, 6, 12}, {11, 2029, 10, 9}, {11, 2030, 8, 11}, + {11, 2031, 12, 8}, {11, 2032, 0, 10}, {11, 2033, 7, 12}, {11, 2034, 11, 0}, + {11, 2035, 10, 10}, {11, 2036, 11, 9}, {11, 2037, 11, 10}, {11, 2038, 0, 11}, + {11, 2039, 11, 11}, {11, 2040, 9, 11}, {11, 2041, 10, 11}, {11, 2042, 12, 0}, + {11, 2043, 8, 12}, {12, 4088, 12, 9}, {12, 4089, 10, 12}, {12, 4090, 9, 12}, + {12, 4091, 11, 12}, {12, 4092, 12, 11}, {12, 4093, 0, 12}, {12, 4094, 12, 10}, + {12, 4095, 12, 12}}; + +static const cb_table_entry HCB11[] = { + {4, 0, 0, 0}, {4, 1, 1, 1}, {5, 4, 16, 16}, {5, 5, 1, 0}, + {5, 6, 0, 1}, {5, 7, 2, 1}, {5, 8, 1, 2}, {5, 9, 2, 2}, + {6, 20, 1, 3}, {6, 21, 3, 1}, {6, 22, 3, 2}, {6, 23, 2, 0}, + {6, 24, 2, 3}, {6, 25, 0, 2}, {6, 26, 3, 3}, {7, 54, 4, 1}, + {7, 55, 1, 4}, {7, 56, 4, 2}, {7, 57, 2, 4}, {7, 58, 4, 3}, + {7, 59, 3, 4}, {7, 60, 3, 0}, {7, 61, 0, 3}, {7, 62, 5, 1}, + {7, 63, 5, 2}, {7, 64, 2, 5}, {7, 65, 4, 4}, {7, 66, 1, 5}, + {7, 67, 5, 3}, {7, 68, 3, 5}, {7, 69, 5, 4}, {8, 140, 4, 5}, + {8, 141, 6, 2}, {8, 142, 2, 6}, {8, 143, 6, 1}, {8, 144, 6, 3}, + {8, 145, 3, 6}, {8, 146, 1, 6}, {8, 147, 4, 16}, {8, 148, 3, 16}, + {8, 149, 16, 5}, {8, 150, 16, 3}, {8, 151, 16, 4}, {8, 152, 6, 4}, + {8, 153, 16, 6}, {8, 154, 4, 0}, {8, 155, 4, 6}, {8, 156, 0, 4}, + {8, 157, 2, 16}, {8, 158, 5, 5}, {8, 159, 5, 16}, {8, 160, 16, 7}, + {8, 161, 16, 2}, {8, 162, 16, 8}, {8, 163, 2, 7}, {8, 164, 7, 2}, + {8, 165, 3, 7}, {8, 166, 6, 5}, {8, 167, 5, 6}, {8, 168, 6, 16}, + {8, 169, 16, 10}, {8, 170, 7, 3}, {8, 171, 7, 1}, {8, 172, 16, 9}, + {8, 173, 7, 16}, {8, 174, 1, 16}, {8, 175, 1, 7}, {8, 176, 4, 7}, + {8, 177, 16, 11}, {8, 178, 7, 4}, {8, 179, 16, 12}, {8, 180, 8, 16}, + {8, 181, 16, 1}, {8, 182, 6, 6}, {8, 183, 9, 16}, {8, 184, 2, 8}, + {8, 185, 5, 7}, {8, 186, 10, 16}, {8, 187, 16, 13}, {8, 188, 8, 3}, + {8, 189, 8, 2}, {8, 190, 3, 8}, {8, 191, 5, 0}, {8, 192, 16, 14}, + {8, 193, 11, 16}, {8, 194, 7, 5}, {8, 195, 4, 8}, {8, 196, 6, 7}, + {8, 197, 7, 6}, {8, 198, 0, 5}, {9, 398, 8, 4}, {9, 399, 16, 15}, + {9, 400, 12, 16}, {9, 401, 1, 8}, {9, 402, 8, 1}, {9, 403, 14, 16}, + {9, 404, 5, 8}, {9, 405, 13, 16}, {9, 406, 3, 9}, {9, 407, 8, 5}, + {9, 408, 7, 7}, {9, 409, 2, 9}, {9, 410, 8, 6}, {9, 411, 9, 2}, + {9, 412, 9, 3}, {9, 413, 15, 16}, {9, 414, 4, 9}, {9, 415, 6, 8}, + {9, 416, 6, 0}, {9, 417, 9, 4}, {9, 418, 5, 9}, {9, 419, 8, 7}, + {9, 420, 7, 8}, {9, 421, 1, 9}, {9, 422, 10, 3}, {9, 423, 0, 6}, + {9, 424, 10, 2}, {9, 425, 9, 1}, {9, 426, 9, 5}, {9, 427, 4, 10}, + {9, 428, 2, 10}, {9, 429, 9, 6}, {9, 430, 3, 10}, {9, 431, 6, 9}, + {9, 432, 10, 4}, {9, 433, 8, 8}, {9, 434, 10, 5}, {9, 435, 9, 7}, + {9, 436, 11, 3}, {9, 437, 1, 10}, {9, 438, 7, 0}, {9, 439, 10, 6}, + {9, 440, 7, 9}, {9, 441, 3, 11}, {9, 442, 5, 10}, {9, 443, 10, 1}, + {9, 444, 4, 11}, {9, 445, 11, 2}, {9, 446, 13, 2}, {9, 447, 6, 10}, + {9, 448, 13, 3}, {9, 449, 2, 11}, {9, 450, 16, 0}, {9, 451, 5, 11}, + {9, 452, 11, 5}, {10, 906, 11, 4}, {10, 907, 9, 8}, {10, 908, 7, 10}, + {10, 909, 8, 9}, {10, 910, 0, 16}, {10, 911, 4, 13}, {10, 912, 0, 7}, + {10, 913, 3, 13}, {10, 914, 11, 6}, {10, 915, 13, 1}, {10, 916, 13, 4}, + {10, 917, 12, 3}, {10, 918, 2, 13}, {10, 919, 13, 5}, {10, 920, 8, 10}, + {10, 921, 6, 11}, {10, 922, 10, 8}, {10, 923, 10, 7}, {10, 924, 14, 2}, + {10, 925, 12, 4}, {10, 926, 1, 11}, {10, 927, 4, 12}, {10, 928, 11, 1}, + {10, 929, 3, 12}, {10, 930, 1, 13}, {10, 931, 12, 2}, {10, 932, 7, 11}, + {10, 933, 3, 14}, {10, 934, 5, 12}, {10, 935, 5, 13}, {10, 936, 14, 4}, + {10, 937, 4, 14}, {10, 938, 11, 7}, {10, 939, 14, 3}, {10, 940, 12, 5}, + {10, 941, 13, 6}, {10, 942, 12, 6}, {10, 943, 8, 0}, {10, 944, 11, 8}, + {10, 945, 2, 12}, {10, 946, 9, 9}, {10, 947, 14, 5}, {10, 948, 6, 13}, + {10, 949, 10, 10}, {10, 950, 15, 2}, {10, 951, 8, 11}, {10, 952, 9, 10}, + {10, 953, 14, 6}, {10, 954, 10, 9}, {10, 955, 5, 14}, {10, 956, 11, 9}, + {10, 957, 14, 1}, {10, 958, 2, 14}, {10, 959, 6, 12}, {10, 960, 1, 12}, + {10, 961, 13, 8}, {10, 962, 0, 8}, {10, 963, 13, 7}, {10, 964, 7, 12}, + {10, 965, 12, 7}, {10, 966, 7, 13}, {10, 967, 15, 3}, {10, 968, 12, 1}, + {10, 969, 6, 14}, {10, 970, 2, 15}, {10, 971, 15, 5}, {10, 972, 15, 4}, + {10, 973, 1, 14}, {10, 974, 9, 11}, {10, 975, 4, 15}, {10, 976, 14, 7}, + {10, 977, 8, 13}, {10, 978, 13, 9}, {10, 979, 8, 12}, {10, 980, 5, 15}, + {10, 981, 3, 15}, {10, 982, 10, 11}, {10, 983, 11, 10}, {10, 984, 12, 8}, + {10, 985, 15, 6}, {10, 986, 15, 7}, {10, 987, 8, 14}, {10, 988, 15, 1}, + {10, 989, 7, 14}, {10, 990, 9, 0}, {10, 991, 0, 9}, {10, 992, 9, 13}, + {10, 993, 9, 12}, {10, 994, 12, 9}, {10, 995, 14, 8}, {10, 996, 10, 13}, + {10, 997, 14, 9}, {10, 998, 12, 10}, {10, 999, 6, 15}, {10, 1000, 7, 15}, + {11, 2002, 9, 14}, {11, 2003, 15, 8}, {11, 2004, 11, 11}, {11, 2005, 11, 14}, + {11, 2006, 1, 15}, {11, 2007, 10, 12}, {11, 2008, 10, 14}, {11, 2009, 13, 11}, + {11, 2010, 13, 10}, {11, 2011, 11, 13}, {11, 2012, 11, 12}, {11, 2013, 8, 15}, + {11, 2014, 14, 11}, {11, 2015, 13, 12}, {11, 2016, 12, 13}, {11, 2017, 15, 9}, + {11, 2018, 14, 10}, {11, 2019, 10, 0}, {11, 2020, 12, 11}, {11, 2021, 9, 15}, + {11, 2022, 0, 10}, {11, 2023, 12, 12}, {11, 2024, 11, 0}, {11, 2025, 12, 14}, + {11, 2026, 10, 15}, {11, 2027, 13, 13}, {11, 2028, 0, 13}, {11, 2029, 14, 12}, + {11, 2030, 15, 10}, {11, 2031, 15, 11}, {11, 2032, 11, 15}, {11, 2033, 14, 13}, + {11, 2034, 13, 0}, {11, 2035, 0, 11}, {11, 2036, 13, 14}, {11, 2037, 15, 12}, + {11, 2038, 15, 13}, {11, 2039, 12, 15}, {11, 2040, 14, 0}, {11, 2041, 14, 14}, + {11, 2042, 13, 15}, {11, 2043, 12, 0}, {11, 2044, 14, 15}, {12, 4090, 0, 14}, + {12, 4091, 0, 12}, {12, 4092, 15, 14}, {12, 4093, 15, 0}, {12, 4094, 0, 15}, + {12, 4095, 15, 15}}; + +static const cb_table_entry HCB_SF[] = { + {1, 0, 60}, {3, 4, 59}, {4, 10, 61}, {4, 11, 58}, {4, 12, 62}, + {5, 26, 57}, {5, 27, 63}, {6, 56, 56}, {6, 57, 64}, {6, 58, 55}, + {6, 59, 65}, {7, 120, 66}, {7, 121, 54}, {7, 122, 67}, {8, 246, 53}, + {8, 247, 68}, {8, 248, 52}, {8, 249, 69}, {8, 250, 51}, {9, 502, 70}, + {9, 503, 50}, {9, 504, 49}, {9, 505, 71}, {10, 1012, 72}, {10, 1013, 48}, + {10, 1014, 73}, {10, 1015, 47}, {10, 1016, 74}, {10, 1017, 46}, {11, 2036, 76}, + {11, 2037, 75}, {11, 2038, 77}, {11, 2039, 78}, {11, 2040, 45}, {11, 2041, 43}, + {12, 4084, 44}, {12, 4085, 79}, {12, 4086, 42}, {12, 4087, 41}, {12, 4088, 80}, + {12, 4089, 40}, {13, 8180, 81}, {13, 8181, 39}, {13, 8182, 82}, {13, 8183, 38}, + {13, 8184, 83}, {14, 16370, 37}, {14, 16371, 35}, {14, 16372, 85}, {14, 16373, 33}, + {14, 16374, 36}, {14, 16375, 34}, {14, 16376, 84}, {14, 16377, 32}, {15, 32756, 87}, + {15, 32757, 89}, {15, 32758, 30}, {15, 32759, 31}, {16, 65520, 86}, {16, 65521, 29}, + {16, 65522, 26}, {16, 65523, 27}, {16, 65524, 28}, {16, 65525, 24}, {16, 65526, 88}, + {17, 131054, 25}, {17, 131055, 22}, {17, 131056, 23}, {18, 262114, 90}, {18, 262115, 21}, + {18, 262116, 19}, {18, 262117, 3}, {18, 262118, 1}, {18, 262119, 2}, {18, 262120, 0}, + {19, 524242, 98}, {19, 524243, 99}, {19, 524244, 100}, {19, 524245, 101}, {19, 524246, 102}, + {19, 524247, 117}, {19, 524248, 97}, {19, 524249, 91}, {19, 524250, 92}, {19, 524251, 93}, + {19, 524252, 94}, {19, 524253, 95}, {19, 524254, 96}, {19, 524255, 104}, {19, 524256, 111}, + {19, 524257, 112}, {19, 524258, 113}, {19, 524259, 114}, {19, 524260, 115}, {19, 524261, 116}, + {19, 524262, 110}, {19, 524263, 105}, {19, 524264, 106}, {19, 524265, 107}, {19, 524266, 108}, + {19, 524267, 109}, {19, 524268, 118}, {19, 524269, 6}, {19, 524270, 8}, {19, 524271, 9}, + {19, 524272, 10}, {19, 524273, 5}, {19, 524274, 103}, {19, 524275, 120}, {19, 524276, 119}, + {19, 524277, 4}, {19, 524278, 7}, {19, 524279, 15}, {19, 524280, 16}, {19, 524281, 18}, + {19, 524282, 20}, {19, 524283, 17}, {19, 524284, 11}, {19, 524285, 12}, {19, 524286, 14}, + {19, 524287, 13}}; + +static const cb_table_entry* CODEBOOKS[] = {HCB1, HCB2, HCB3, HCB4, HCB5, HCB6, + HCB7, HCB8, HCB9, HCB10, HCB11}; + +} // namespace huffman +} // namespace aac diff --git a/src/aac/huffman/Decoder.cpp b/src/aac/huffman/Decoder.cpp new file mode 100644 index 00000000..8e9b5698 --- /dev/null +++ b/src/aac/huffman/Decoder.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "Decoder.h" + +#include "../BitStream.h" +#include "Codebooks.h" + +#include +#include +#include + +using namespace aac; +using namespace aac::huffman; + +namespace +{ + +constexpr bool UNSIGNED[] = {false, false, true, true, false, false, true, true, true, true, true}; +constexpr int QUAD_LEN = 4; +constexpr int PAIR_LEN = 2; + +int FindOffset(BitStream& stream, const cb_table_entry table[]) +{ + int off = 0; + int len = table[off].length; + int cw = stream.ReadBits(len); + + while (cw != table[off].codeword) + { + off++; + const int j = table[off].length - len; + len = table[off].length; + cw <<= j; + cw |= stream.ReadBits(j); + } + + return off; +} + +void SignValues(BitStream& stream, int data[], int off, int len) +{ + for (int i = off; i < off + len; ++i) + { + if (data[i] != 0) + { + if (stream.ReadBool()) + data[i] = -data[i]; + } + } +} + +int GetEscape(BitStream& stream, int s) +{ + const bool neg = (s < 0); + + int i = 4; + while (stream.ReadBool()) + { + i++; + } + + const int j = stream.ReadBits(i) | (1 << i); + return (neg ? -j : j); +} + +} // namespace + +int Decoder::DecodeScaleFactor(BitStream& bitstream) +{ + const int offset = FindOffset(bitstream, HCB_SF); + return HCB_SF[offset].bit; +} + +void Decoder::DecodeSpectralData(BitStream& bitstream, int cb, int data[], int off) +{ + const cb_table_entry* HCB = CODEBOOKS[cb - 1]; + const int offset = FindOffset(bitstream, HCB); + + data[off] = HCB[offset].bit; + data[off + 1] = HCB[offset].value1; + if (cb < 5) + { + data[off + 2] = HCB[offset].value2; + data[off + 3] = HCB[offset].value3; + } + + if (cb < 11) + { + if (UNSIGNED[cb - 1]) + SignValues(bitstream, data, off, cb < 5 ? QUAD_LEN : PAIR_LEN); + } + else if (cb == 11 || cb > 15) + { + SignValues(bitstream, data, off, cb < 5 ? QUAD_LEN : PAIR_LEN); + if (std::abs(data[off]) == 16) + data[off] = GetEscape(bitstream, data[off]); + if (std::abs(data[off + 1]) == 16) + data[off + 1] = GetEscape(bitstream, data[off + 1]); + } + else + { + throw std::logic_error( + "aac::huffman::Decoder::DecodeSpectralData - Unknown spectral codebook: " + + std::to_string(cb)); + } +} diff --git a/src/aac/huffman/Decoder.h b/src/aac/huffman/Decoder.h new file mode 100644 index 00000000..87a123b9 --- /dev/null +++ b/src/aac/huffman/Decoder.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +namespace aac +{ + +class BitStream; + +namespace huffman +{ + +static constexpr int ZERO_HCB = 0; + +static constexpr int NOISE_HCB = 13; +static constexpr int INTENSITY_HCB2 = 14; +static constexpr int INTENSITY_HCB = 15; + +class Decoder +{ +public: + Decoder() = delete; + + static int DecodeScaleFactor(aac::BitStream& bitstream); + static void DecodeSpectralData(aac::BitStream& bitstream, int cb, int data[], int off); +}; + +} // namespace huffman +} // namespace aac diff --git a/src/tvheadend/HTSPDemuxer.cpp b/src/tvheadend/HTSPDemuxer.cpp index 0bd33679..3bbf9d4b 100644 --- a/src/tvheadend/HTSPDemuxer.cpp +++ b/src/tvheadend/HTSPDemuxer.cpp @@ -11,6 +11,7 @@ #include "HTSPConnection.h" #include "Settings.h" #include "utilities/Logger.h" +#include "utilities/RDSExtractor.h" #include "kodi/addon-instance/PVR.h" @@ -86,6 +87,7 @@ void HTSPDemuxer::Abort0() m_streams.clear(); m_streamStat.clear(); m_rdsIdx = 0; + m_rdsExtractor.reset(); m_seektime = nullptr; } @@ -468,50 +470,44 @@ void HTSPDemuxer::ProcessRDS(uint32_t idx, const void* bin, size_t binlen) if (idx != m_rdsIdx) return; - const uint8_t* data = static_cast(bin); - const size_t offset = binlen - 1; - - static const uint8_t RDS_IDENTIFIER = 0xFD; + if (!m_rdsExtractor) + return; - if (data[offset] == RDS_IDENTIFIER) + const uint8_t rdslen = m_rdsExtractor->Decode(static_cast(bin), binlen); + if (rdslen > 0) { - // RDS data present, obtain length. - uint8_t rdslen = data[offset - 1]; - if (rdslen > 0) + const uint32_t rdsIdx = idx - TVH_STREAM_INDEX_OFFSET; + if (m_streamStat.find(rdsIdx) == m_streamStat.end()) { - const uint32_t rdsIdx = idx - TVH_STREAM_INDEX_OFFSET; - if (m_streamStat.find(rdsIdx) == m_streamStat.end()) + // No RDS stream yet. Create and announce it. + if (!AddRDSStream(idx, rdsIdx)) { - // No RDS stream yet. Create and announce it. - if (!AddRDSStream(idx, rdsIdx)) - return; - - // Update streams. - Logger::Log(LogLevel::LEVEL_DEBUG, "demux stream change"); - - DEMUX_PACKET* pktSpecial = m_demuxPktHdl.AllocateDemuxPacket(0); - pktSpecial->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; - m_pktBuffer.Push(pktSpecial); + m_rdsExtractor->Reset(); + return; } - DEMUX_PACKET* pkt = m_demuxPktHdl.AllocateDemuxPacket(rdslen); - if (!pkt) - return; + // Update streams. + Logger::Log(LogLevel::LEVEL_DEBUG, "demux stream change"); - uint8_t* rdsdata = new uint8_t[rdslen]; + DEMUX_PACKET* pktSpecial = m_demuxPktHdl.AllocateDemuxPacket(0); + pktSpecial->iStreamId = DEMUX_SPECIALID_STREAMCHANGE; + m_pktBuffer.Push(pktSpecial); + } - // Reassemble UECP block. mpeg stream contains data in reverse order! - for (size_t i = offset - 2, j = 0; i > 3 && i > offset - 2 - rdslen; i--, j++) - rdsdata[j] = data[i]; + DEMUX_PACKET* pkt = m_demuxPktHdl.AllocateDemuxPacket(rdslen); + if (!pkt) + { + m_rdsExtractor->Reset(); + return; + } - std::memcpy(pkt->pData, rdsdata, rdslen); - pkt->iSize = rdslen; - pkt->iStreamId = rdsIdx; + std::memcpy(pkt->pData, m_rdsExtractor->GetRDSData(), rdslen); + pkt->iSize = rdslen; + pkt->iStreamId = rdsIdx; - m_pktBuffer.Push(pkt); - delete[] rdsdata; - } + m_pktBuffer.Push(pkt); } + m_rdsExtractor->Reset(); } void HTSPDemuxer::ParseMuxPacket(htsmsg_t* m) @@ -683,17 +679,39 @@ bool HTSPDemuxer::AddTVHStream(uint32_t idx, const char* type, htsmsg_field_t* f stream.SetLanguage(language); } + /* RDS */ + if (stream.GetCodecType() == PVR_CODEC_TYPE_RDS) + { + // Extra RDS streams have higher priority compared to RDS data enclosed in audio streams. + m_rdsIdx = idx; + m_rdsExtractor.reset(); + } + /* Audio data */ if (stream.GetCodecType() == PVR_CODEC_TYPE_AUDIO) { stream.SetChannels(htsmsg_get_u32_or_default(&f->hmf_msg, "channels", 2)); stream.SetSampleRate(htsmsg_get_u32_or_default(&f->hmf_msg, "rate", 48000)); - if (std::strcmp("MPEG2AUDIO", type) == 0) + if (m_rdsIdx == 0) { - // mpeg2 audio streams may contain embedded RDS data. - // We will find out when the first stream packet arrives. - m_rdsIdx = idx; + // MPEG2 and AAC audio streams may contain embedded RDS data. + // For older tvhs, which do not send the rds flag via HTSP, default + // to 1 and try to find RDS data in the submitted mux pakets later. + const uint32_t rds = htsmsg_get_u32_or_default(&f->hmf_msg, "rds_uecp", 1); + if (rds != 0) + { + if (std::strcmp("MPEG2AUDIO", type) == 0) + { + m_rdsIdx = idx; + m_rdsExtractor.reset(new utilities::RDSExtractorMP2()); + } + else if (std::strcmp("AAC", type) == 0) + { + m_rdsIdx = idx; + m_rdsExtractor.reset(new utilities::RDSExtractorAAC()); + } + } } } @@ -755,6 +773,7 @@ void HTSPDemuxer::ParseSubscriptionStart(htsmsg_t* m) m_streamStat.clear(); m_streams.clear(); m_rdsIdx = 0; + m_rdsExtractor.reset(); Logger::Log(LogLevel::LEVEL_DEBUG, "demux subscription start"); diff --git a/src/tvheadend/HTSPDemuxer.h b/src/tvheadend/HTSPDemuxer.h index dea8e182..6732395e 100644 --- a/src/tvheadend/HTSPDemuxer.h +++ b/src/tvheadend/HTSPDemuxer.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,11 @@ namespace tvheadend class HTSPConnection; class SubscriptionSeekTime; +namespace utilities +{ +class RDSExtractor; +} + /* * HTSP Demuxer - live streams */ @@ -121,6 +127,7 @@ class HTSPDemuxer std::atomic m_lastPkt; std::atomic m_startTime; uint32_t m_rdsIdx; + std::unique_ptr m_rdsExtractor; int32_t m_requestedSpeed = 1000; int32_t m_actualSpeed = 1000; diff --git a/src/tvheadend/utilities/RDSExtractor.cpp b/src/tvheadend/utilities/RDSExtractor.cpp new file mode 100644 index 00000000..28a3a144 --- /dev/null +++ b/src/tvheadend/utilities/RDSExtractor.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#include "RDSExtractor.h" + +#include "../../aac/Decoder.h" +#include "Logger.h" + +#include + +using namespace tvheadend::utilities; + +uint8_t RDSExtractorMP2::Decode(const uint8_t* data, size_t len) +{ + Reset(); + + const size_t offset = len - 1; + if (len > 1 && data[offset] == 0xfd) + { + m_rdsLen = data[offset - 1]; + if (m_rdsLen > 0) + { + m_rdsData = new uint8_t[m_rdsLen]; + + // Reassemble UECP block. mpeg stream contains data in reverse order! + for (size_t i = offset - 2, j = 0; i > 3 && i > offset - 2 - m_rdsLen; i--, j++) + m_rdsData[j] = data[i]; + } + } + return m_rdsLen; +} + +uint8_t RDSExtractorAAC::Decode(const uint8_t* data, size_t len) +{ + Reset(); + + try + { + aac::Decoder decoder(data, len); + m_rdsLen = decoder.DecodeRDS(m_rdsData); + } + catch (std::exception& e) + { + Logger::Log(LogLevel::LEVEL_ERROR, "AAC RDS extractor exception: %s", e.what()); + } + + return m_rdsLen; +} diff --git a/src/tvheadend/utilities/RDSExtractor.h b/src/tvheadend/utilities/RDSExtractor.h new file mode 100644 index 00000000..54f5e96e --- /dev/null +++ b/src/tvheadend/utilities/RDSExtractor.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-2021 Team Kodi + * https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSE.md for more information. + */ + +#pragma once + +#include +#include + +namespace tvheadend +{ +namespace utilities +{ + +class RDSExtractor +{ +public: + RDSExtractor() = default; + virtual ~RDSExtractor() = default; + + virtual uint8_t Decode(const uint8_t* data, size_t len) = 0; + + uint8_t GetRDSDataLength() const { return m_rdsLen; } + uint8_t* GetRDSData() const { return m_rdsData; } + + void Reset() + { + m_rdsLen = 0; + delete[] m_rdsData; + m_rdsData = nullptr; + } + +protected: + uint8_t m_rdsLen = 0; + uint8_t* m_rdsData = nullptr; +}; + +class RDSExtractorMP2 : public RDSExtractor +{ +public: + uint8_t Decode(const uint8_t* data, size_t len) override; +}; + +class RDSExtractorAAC : public RDSExtractor +{ +public: + uint8_t Decode(const uint8_t* data, size_t len) override; +}; + +} // namespace utilities +} // namespace tvheadend From 4e768a96fc7de15550ae032616b03d0c057d7382 Mon Sep 17 00:00:00 2001 From: Kai Sommerfeld Date: Wed, 18 Aug 2021 15:40:18 +0200 Subject: [PATCH 2/2] add-on version bump and changelog. --- pvr.hts/addon.xml.in | 2 +- pvr.hts/changelog.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pvr.hts/addon.xml.in b/pvr.hts/addon.xml.in index 9679c4e4..258dbaf4 100644 --- a/pvr.hts/addon.xml.in +++ b/pvr.hts/addon.xml.in @@ -1,7 +1,7 @@ @ADDON_DEPENDS@ diff --git a/pvr.hts/changelog.txt b/pvr.hts/changelog.txt index 4b4b28bf..66efa9c4 100644 --- a/pvr.hts/changelog.txt +++ b/pvr.hts/changelog.txt @@ -1,3 +1,7 @@ +v8.4.0 +- Add support for RDS data contained in AAC streams. +- Translations updates from Weblate + v8.3.4 - Fixed 'Use HTTPS' setting init/write