Merge pull request #26287 from CrystalP/ref-savefilestatejob
[xbmc.git] / xbmc / cores / AudioEngine / Utils / PackerMAT.cpp
blobe6a5d621b3236ff64b7cf751f4d5743135748506
1 /*
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.
7 */
9 #include "PackerMAT.h"
11 #include "utils/log.h"
13 #include <array>
14 #include <assert.h>
15 #include <utility>
17 extern "C"
19 #include <libavutil/common.h>
20 #include <libavutil/intreadwrite.h>
23 namespace
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};
43 } // namespace
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)
61 if (size < 10)
62 return false;
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
73 return false;
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");
97 m_state = {};
98 m_state.init = true;
99 m_buffer.clear();
100 m_bufferCount = 0;
101 return false;
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
109 if (GetCount() == 0)
111 WriteHeader();
113 // initial header, don't count it for the frame size
114 if (m_state.init == false)
116 m_state.init = true;
117 m_state.matFramesize = 0;
121 // write padding of the previous frame (if any)
122 while (m_state.padding > 0)
124 WritePadding();
126 assert(m_state.padding == 0 || GetCount() == MAT_BUFFER_SIZE);
128 // Buffer is full, submit it
129 if (GetCount() == MAT_BUFFER_SIZE)
131 FlushPacket();
133 // and setup a new buffer
134 WriteHeader();
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
145 FlushPacket();
147 if (remaining)
149 // setup a new buffer
150 WriteHeader();
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())
172 return {};
174 buffer = std::move(m_outputQueue.front());
176 m_outputQueue.pop_front();
178 return buffer;
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;
207 else
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);
211 m_state.padding = 0;
216 void CPackerMAT::WritePadding()
218 if (m_state.padding == 0)
219 return;
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
226 if (remaining >= 0)
228 m_state.padding = remaining;
229 m_state.matFramesize = 0;
231 else
233 // more padding then requested was written, eg. there was a MAT middle/end marker
234 // that needed to be written
235 m_state.padding = 0;
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)
253 return size;
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
276 if (remaining > 0)
277 remaining = FillDataBuffer(data + nBytesBefore, remaining, type);
279 return remaining;
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
301 return remaining;
304 AppendData(data, size, type);
306 return 0;
309 void CPackerMAT::FlushPacket()
311 if (GetCount() == 0)
312 return;
314 assert(GetCount() == MAT_BUFFER_SIZE);
316 // push MAT packet to output queue
317 m_outputQueue.emplace_back(std::move(m_buffer));
319 m_buffer.clear();
320 m_bufferCount = 0;