2 * Copyright (C) 2024 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
11 #include "utils/log.h"
19 #include <libavutil/common.h>
20 #include <libavutil/intreadwrite.h>
25 constexpr uint32_t FORMAT_MAJOR_SYNC
= 0xf8726fba;
27 constexpr auto BURST_HEADER_SIZE
= 8;
28 constexpr auto MAT_BUFFER_SIZE
= 61440;
29 constexpr auto MAT_BUFFER_LIMIT
= MAT_BUFFER_SIZE
- 24; // MAT end code size
30 constexpr auto MAT_POS_MIDDLE
= 30708 + BURST_HEADER_SIZE
; // middle point + IEC header in front
32 // magic MAT format values, meaning is unknown at this point
33 constexpr std::array
<uint8_t, 20> mat_start_code
= {0x07, 0x9E, 0x00, 0x03, 0x84, 0x01, 0x01,
34 0x01, 0x80, 0x00, 0x56, 0xA5, 0x3B, 0xF4,
35 0x81, 0x83, 0x49, 0x80, 0x77, 0xE0};
37 constexpr std::array
<uint8_t, 12> mat_middle_code
= {0xC3, 0xC1, 0x42, 0x49, 0x3B, 0xFA,
38 0x82, 0x83, 0x49, 0x80, 0x77, 0xE0};
40 constexpr std::array
<uint8_t, 24> mat_end_code
= {0xC3, 0xC2, 0xC0, 0xC4, 0x00, 0x00, 0x00, 0x00,
41 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x11,
42 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
45 CPackerMAT::CPackerMAT()
47 m_buffer
.reserve(MAT_BUFFER_SIZE
);
50 // On a high level, a MAT frame consists of a sequence of padded TrueHD frames
51 // The size of the padded frame can be determined from the frame time/sequence code in the frame header,
52 // since it varies to accommodate spikes in bitrate.
53 // In average all frames are always padded to 2560 bytes, so that 24 frames fit in one MAT frame, however
54 // due to bitrate spikes single sync frames have been observed to use up to twice that size, in which
55 // case they'll be preceded by smaller frames to keep the average bitrate constant.
56 // A constant padding to 2560 bytes can work (this is how the ffmpeg spdifenc module works), however
57 // high-bitrate streams can overshoot this size and therefor require proper handling of dynamic padding.
58 bool CPackerMAT::PackTrueHD(const uint8_t* data
, int size
)
60 // discard too small packets (cannot be valid)
64 // get the ratebits from the major sync frame
65 if (AV_RB32(data
+ 4) == FORMAT_MAJOR_SYNC
)
67 // read audio_sampling_frequency (high nibble after format major sync)
68 m_state
.ratebits
= data
[8] >> 4;
70 else if (!m_state
.prevFrametimeValid
)
72 // only start streaming on a major sync frame
76 const uint16_t frameTime
= AV_RB16(data
+ 2);
77 uint32_t spaceSize
= 0;
79 // compute final padded size for the previous frame, if any
80 if (m_state
.prevFrametimeValid
)
81 spaceSize
= uint16_t(frameTime
- m_state
.prevFrametime
) * (64 >> (m_state
.ratebits
& 7));
83 // compute padding (ie. difference to the size of the previous frame)
84 assert(!m_state
.prevFrametimeValid
|| spaceSize
>= m_state
.prevMatFramesize
);
86 // if for some reason the spaceSize fails, align the actual frame size
87 if (spaceSize
< m_state
.prevMatFramesize
)
88 spaceSize
= FFALIGN(m_state
.prevMatFramesize
, (64 >> (m_state
.ratebits
& 7)));
90 m_state
.padding
+= (spaceSize
- m_state
.prevMatFramesize
);
92 // detect seeks and re-initialize internal state i.e. skip stream
93 // until the next major sync frame
94 if (m_state
.padding
> MAT_BUFFER_SIZE
* 5)
96 CLog::Log(LOGINFO
, "CPackerMAT::PackTrueHD: seek detected, re-initializing MAT packer state");
104 // store frame time of the previous frame
105 m_state
.prevFrametime
= frameTime
;
106 m_state
.prevFrametimeValid
= true;
108 // Write the MAT header into the fresh buffer
113 // initial header, don't count it for the frame size
114 if (m_state
.init
== false)
117 m_state
.matFramesize
= 0;
121 // write padding of the previous frame (if any)
122 while (m_state
.padding
> 0)
126 assert(m_state
.padding
== 0 || GetCount() == MAT_BUFFER_SIZE
);
128 // Buffer is full, submit it
129 if (GetCount() == MAT_BUFFER_SIZE
)
133 // and setup a new buffer
138 // write actual audio data to the buffer
139 int remaining
= FillDataBuffer(data
, size
, Type::DATA
);
141 // not all data could be written, or the buffer is full
142 if (remaining
|| GetCount() == MAT_BUFFER_SIZE
)
144 // flush out old data
149 // setup a new buffer
152 // and write the remaining data
153 remaining
= FillDataBuffer(data
+ (size
- remaining
), remaining
, Type::DATA
);
155 assert(remaining
== 0);
159 // store the size of the current MAT frame, so we can add padding later
160 m_state
.prevMatFramesize
= m_state
.matFramesize
;
161 m_state
.matFramesize
= 0;
163 // return true if have MAT packet
164 return !m_outputQueue
.empty();
167 std::vector
<uint8_t> CPackerMAT::GetOutputFrame()
169 std::vector
<uint8_t> buffer
;
171 if (m_outputQueue
.empty())
174 buffer
= std::move(m_outputQueue
.front());
176 m_outputQueue
.pop_front();
181 void CPackerMAT::WriteHeader()
183 m_buffer
.resize(MAT_BUFFER_SIZE
);
185 // reserve size for the IEC header and the MAT start code
186 const size_t size
= BURST_HEADER_SIZE
+ mat_start_code
.size();
188 // write MAT start code. IEC header written later, skip space only
189 memcpy(m_buffer
.data() + BURST_HEADER_SIZE
, mat_start_code
.data(), mat_start_code
.size());
190 m_bufferCount
= size
;
192 // unless the start code falls into the padding, it's considered part of the current MAT frame
193 // Note that audio frames are not always aligned with MAT frames, so we might already have a partial
194 // frame at this point
195 m_state
.matFramesize
+= size
;
197 // The MAT metadata counts as padding, if we're scheduled to write any, which mean the start bytes
198 // should reduce any further padding.
199 if (m_state
.padding
> 0)
201 // if the header fits into the padding of the last frame, just reduce the amount of needed padding
202 if (m_state
.padding
> size
)
204 m_state
.padding
-= size
;
205 m_state
.matFramesize
= 0;
209 // otherwise, consume all padding and set the size of the next MAT frame to the remaining data
210 m_state
.matFramesize
= (size
- m_state
.padding
);
216 void CPackerMAT::WritePadding()
218 if (m_state
.padding
== 0)
221 // for padding not writes any data (nullptr) as buffer is already zeroed
222 // only counts/skip bytes
223 const int remaining
= FillDataBuffer(nullptr, m_state
.padding
, Type::PADDING
);
225 // not all padding could be written to the buffer, write it later
228 m_state
.padding
= remaining
;
229 m_state
.matFramesize
= 0;
233 // more padding then requested was written, eg. there was a MAT middle/end marker
234 // that needed to be written
236 m_state
.matFramesize
= -remaining
;
240 void CPackerMAT::AppendData(const uint8_t* data
, int size
, Type type
)
242 // for padding not write anything, only skip bytes
243 if (type
== Type::DATA
)
244 memcpy(m_buffer
.data() + m_bufferCount
, data
, size
);
246 m_state
.matFramesize
+= size
;
247 m_bufferCount
+= size
;
250 int CPackerMAT::FillDataBuffer(const uint8_t* data
, int size
, Type type
)
252 if (GetCount() >= MAT_BUFFER_LIMIT
)
255 int remaining
= size
;
257 // Write MAT middle marker, if needed
258 // The MAT middle marker always needs to be in the exact same spot, any audio data will be split.
259 // If we're currently writing padding, then the marker will be considered as padding data and
260 // reduce the amount of padding still required.
261 if (GetCount() <= MAT_POS_MIDDLE
&& GetCount() + size
> MAT_POS_MIDDLE
)
263 // write as much data before the middle code as we can
264 int nBytesBefore
= MAT_POS_MIDDLE
- GetCount();
265 AppendData(data
, nBytesBefore
, type
);
266 remaining
-= nBytesBefore
;
268 // write the MAT middle code
269 AppendData(mat_middle_code
.data(), mat_middle_code
.size(), Type::DATA
);
271 // if we're writing padding, deduct the size of the code from it
272 if (type
== Type::PADDING
)
273 remaining
-= mat_middle_code
.size();
275 // write remaining data after the MAT marker
277 remaining
= FillDataBuffer(data
+ nBytesBefore
, remaining
, type
);
282 // not enough room in the buffer to write all the data,
283 // write as much as we can and add the MAT footer
284 if (GetCount() + size
>= MAT_BUFFER_LIMIT
)
286 // write as much data before the middle code as we can
287 int nBytesBefore
= MAT_BUFFER_LIMIT
- GetCount();
288 AppendData(data
, nBytesBefore
, type
);
289 remaining
-= nBytesBefore
;
291 // write the MAT end code
292 AppendData(mat_end_code
.data(), mat_end_code
.size(), Type::DATA
);
294 assert(GetCount() == MAT_BUFFER_SIZE
);
296 // MAT markers don't displace padding, so reduce the amount of padding
297 if (type
== Type::PADDING
)
298 remaining
-= mat_end_code
.size();
300 // any remaining data will be written in future calls
304 AppendData(data
, size
, type
);
309 void CPackerMAT::FlushPacket()
314 assert(GetCount() == MAT_BUFFER_SIZE
);
316 // push MAT packet to output queue
317 m_outputQueue
.emplace_back(std::move(m_buffer
));