1 /*
2 * Copyright (C) 2010-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "AEChannelInfo.h"
10
11 #include <algorithm>
12 #include <assert.h>
13 #include <limits>
14 #include <string.h>
15
CAEChannelInfo()16 CAEChannelInfo::CAEChannelInfo()
17 {
18 Reset();
19 }
20
CAEChannelInfo(const enum AEChannel * rhs)21 CAEChannelInfo::CAEChannelInfo(const enum AEChannel* rhs)
22 {
23 *this = rhs;
24 }
25
CAEChannelInfo(const AEStdChLayout rhs)26 CAEChannelInfo::CAEChannelInfo(const AEStdChLayout rhs)
27 {
28 *this = rhs;
29 }
30
ResolveChannels(const CAEChannelInfo & rhs)31 void CAEChannelInfo::ResolveChannels(const CAEChannelInfo& rhs)
32 {
33 /* mono gets upmixed to dual mono */
34 if (m_channelCount == 1 && m_channels[0] == AE_CH_FC)
35 {
36 Reset();
37 *this += AE_CH_FL;
38 *this += AE_CH_FR;
39 return;
40 }
41
42 bool srcHasSL = false;
43 bool srcHasSR = false;
44 bool srcHasRL = false;
45 bool srcHasRR = false;
46 bool srcHasBC = false;
47
48 bool dstHasSL = false;
49 bool dstHasSR = false;
50 bool dstHasRL = false;
51 bool dstHasRR = false;
52 bool dstHasBC = false;
53
54 for (unsigned int c = 0; c < rhs.m_channelCount; ++c)
55 switch(rhs.m_channels[c])
56 {
57 case AE_CH_SL: dstHasSL = true; break;
58 case AE_CH_SR: dstHasSR = true; break;
59 case AE_CH_BL: dstHasRL = true; break;
60 case AE_CH_BR: dstHasRR = true; break;
61 case AE_CH_BC: dstHasBC = true; break;
62 default:
63 break;
64 }
65
66 CAEChannelInfo newInfo;
67 for (unsigned int i = 0; i < m_channelCount; ++i)
68 {
69 switch (m_channels[i])
70 {
71 case AE_CH_SL: srcHasSL = true; break;
72 case AE_CH_SR: srcHasSR = true; break;
73 case AE_CH_BL: srcHasRL = true; break;
74 case AE_CH_BR: srcHasRR = true; break;
75 case AE_CH_BC: srcHasBC = true; break;
76 default:
77 break;
78 }
79
80 bool found = false;
81 for (unsigned int c = 0; c < rhs.m_channelCount; ++c)
82 if (m_channels[i] == rhs.m_channels[c])
83 {
84 found = true;
85 break;
86 }
87
88 if (found)
89 newInfo += m_channels[i];
90 }
91
92 /* we need to ensure we end up with rear or side channels for downmix to work */
93 if (srcHasSL && !dstHasSL && dstHasRL && !newInfo.HasChannel(AE_CH_BL))
94 newInfo += AE_CH_BL;
95 if (srcHasSR && !dstHasSR && dstHasRR && !newInfo.HasChannel(AE_CH_BR))
96 newInfo += AE_CH_BR;
97 if (srcHasRL && !dstHasRL && dstHasSL && !newInfo.HasChannel(AE_CH_SL))
98 newInfo += AE_CH_SL;
99 if (srcHasRR && !dstHasRR && dstHasSR && !newInfo.HasChannel(AE_CH_SR))
100 newInfo += AE_CH_SR;
101
102 // mix back center if not available in destination layout
103 // prefer mixing into backs if available
104 if (srcHasBC && !dstHasBC)
105 {
106 if (dstHasRL && !newInfo.HasChannel(AE_CH_BL))
107 newInfo += AE_CH_BL;
108 else if (dstHasSL && !newInfo.HasChannel(AE_CH_SL))
109 newInfo += AE_CH_SL;
110
111 if (dstHasRR && !newInfo.HasChannel(AE_CH_BR))
112 newInfo += AE_CH_BR;
113 else if (dstHasSR && !newInfo.HasChannel(AE_CH_SR))
114 newInfo += AE_CH_SR;
115 }
116
117 *this = newInfo;
118 }
119
Reset()120 void CAEChannelInfo::Reset()
121 {
122 m_channelCount = 0;
123 for (AEChannel& channel : m_channels)
124 channel = AE_CH_NULL;
125 }
126
operator =(const CAEChannelInfo & rhs)127 CAEChannelInfo& CAEChannelInfo::operator=(const CAEChannelInfo& rhs)
128 {
129 if (this == &rhs)
130 return *this;
131
132 /* clone the information */
133 m_channelCount = rhs.m_channelCount;
134 memcpy(m_channels, rhs.m_channels, sizeof(enum AEChannel) * rhs.m_channelCount);
135
136 return *this;
137 }
138
operator =(const enum AEChannel * rhs)139 CAEChannelInfo& CAEChannelInfo::operator=(const enum AEChannel* rhs)
140 {
141 Reset();
142 if (rhs == NULL)
143 return *this;
144
145 while (m_channelCount < AE_CH_MAX && rhs[m_channelCount] != AE_CH_NULL)
146 {
147 m_channels[m_channelCount] = rhs[m_channelCount];
148 ++m_channelCount;
149 }
150
151 /* the last entry should be NULL, if not we were passed a non null terminated list */
152 assert(rhs[m_channelCount] == AE_CH_NULL);
153
154 return *this;
155 }
156
operator =(const enum AEStdChLayout rhs)157 CAEChannelInfo& CAEChannelInfo::operator=(const enum AEStdChLayout rhs)
158 {
159 assert(rhs > AE_CH_LAYOUT_INVALID && rhs < AE_CH_LAYOUT_MAX);
160
161 static enum AEChannel layouts[AE_CH_LAYOUT_MAX][9] = {
162 {AE_CH_FC, AE_CH_NULL},
163 {AE_CH_FL, AE_CH_FR, AE_CH_NULL},
164 {AE_CH_FL, AE_CH_FR, AE_CH_LFE, AE_CH_NULL},
165 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_NULL},
166 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_LFE, AE_CH_NULL},
167 {AE_CH_FL, AE_CH_FR, AE_CH_BL , AE_CH_BR , AE_CH_NULL},
168 {AE_CH_FL, AE_CH_FR, AE_CH_BL , AE_CH_BR , AE_CH_LFE, AE_CH_NULL},
169 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_BL , AE_CH_BR , AE_CH_NULL},
170 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_LFE, AE_CH_BL , AE_CH_BR , AE_CH_NULL},
171 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_BL , AE_CH_BR , AE_CH_SL , AE_CH_SR, AE_CH_NULL},
172 {AE_CH_FL, AE_CH_FR, AE_CH_FC , AE_CH_LFE, AE_CH_BL , AE_CH_BR , AE_CH_SL , AE_CH_SR, AE_CH_NULL}
173 };
174
175 *this = layouts[rhs];
176 return *this;
177 }
178
operator ==(const CAEChannelInfo & rhs) const179 bool CAEChannelInfo::operator==(const CAEChannelInfo& rhs) const
180 {
181 /* if the channel count doesn't match, no need to check further */
182 if (m_channelCount != rhs.m_channelCount)
183 return false;
184
185 /* make sure the channel order is the same */
186 for (unsigned int i = 0; i < m_channelCount; ++i)
187 if (m_channels[i] != rhs.m_channels[i])
188 return false;
189
190 return true;
191 }
192
operator !=(const CAEChannelInfo & rhs) const193 bool CAEChannelInfo::operator!=(const CAEChannelInfo& rhs) const
194 {
195 return !(*this == rhs);
196 }
197
operator +=(const enum AEChannel & rhs)198 CAEChannelInfo& CAEChannelInfo::operator+=(const enum AEChannel& rhs)
199 {
200 assert(m_channelCount < AE_CH_MAX);
201 assert(rhs > AE_CH_NULL && rhs < AE_CH_MAX);
202
203 m_channels[m_channelCount++] = rhs;
204 return *this;
205 }
206
operator -=(const enum AEChannel & rhs)207 CAEChannelInfo& CAEChannelInfo::operator-=(const enum AEChannel& rhs)
208 {
209 assert(rhs > AE_CH_NULL && rhs < AE_CH_MAX);
210
211 unsigned int i = 0;
212 while(i < m_channelCount && m_channels[i] != rhs)
213 i++;
214 if (i >= m_channelCount)
215 return *this; // Channel not found
216
217 for(; i < m_channelCount-1; i++)
218 m_channels[i] = m_channels[i+1];
219
220 m_channels[i] = AE_CH_NULL;
221 m_channelCount--;
222 return *this;
223 }
224
operator [](unsigned int i) const225 enum AEChannel CAEChannelInfo::operator[](unsigned int i) const
226 {
227 assert(i < m_channelCount);
228 return m_channels[i];
229 }
230
operator std::string() const231 CAEChannelInfo::operator std::string() const
232 {
233 if (m_channelCount == 0)
234 return "NULL";
235
236 std::string s;
237 for (unsigned int i = 0; i < m_channelCount - 1; ++i)
238 {
239 s.append(GetChName(m_channels[i]));
240 s.append(", ");
241 }
242 s.append(GetChName(m_channels[m_channelCount-1]));
243
244 return s;
245 }
246
IsChannelValid(const unsigned int pos)247 bool CAEChannelInfo::IsChannelValid(const unsigned int pos)
248 {
249 assert(pos < m_channelCount);
250 bool isValid = false;
251 if (m_channels[pos] > AE_CH_NULL && m_channels[pos] < AE_CH_UNKNOWN1)
252 isValid = true;
253
254 return isValid;
255 }
256
IsLayoutValid()257 bool CAEChannelInfo::IsLayoutValid()
258 {
259 if (m_channelCount == 0)
260 return false;
261
262 for (unsigned int i = 0; i < m_channelCount; ++i)
263 {
264 // we need at least one valid channel
265 if (IsChannelValid(i))
266 return true;
267 }
268 return false;
269 }
270
GetChName(const enum AEChannel ch)271 const char* CAEChannelInfo::GetChName(const enum AEChannel ch)
272 {
273 assert(ch >= 0 && ch < AE_CH_MAX);
274
275 static const char* channels[AE_CH_MAX] =
276 {
277 "RAW" ,
278 "FL" , "FR" , "FC" , "LFE", "BL" , "BR" , "FLOC",
279 "FROC", "BC" , "SL" , "SR" , "TFL" , "TFR" , "TFC" ,
280 "TC" , "TBL", "TBR", "TBC", "BLOC", "BROC",
281
282 /* p16v devices */
283 "UNKNOWN1" , "UNKNOWN2" , "UNKNOWN3" , "UNKNOWN4" ,
284 "UNKNOWN5" , "UNKNOWN6" , "UNKNOWN7" , "UNKNOWN8" ,
285 "UNKNOWN9" , "UNKNOWN10", "UNKNOWN11", "UNKNOWN12",
286 "UNKNOWN13", "UNKNOWN14", "UNKNOWN15", "UNKNOWN16",
287 "UNKNOWN17", "UNKNOWN18", "UNKNOWN19", "UNKNOWN20",
288 "UNKNOWN21", "UNKNOWN22", "UNKNOWN23", "UNKNOWN24",
289 "UNKNOWN25", "UNKNOWN26", "UNKNOWN27", "UNKNOWN28",
290 "UNKNOWN29", "UNKNOWN30", "UNKNOWN31", "UNKNOWN32",
291 "UNKNOWN33", "UNKNOWN34", "UNKNOWN35", "UNKNOWN36",
292 "UNKNOWN37", "UNKNOWN38", "UNKNOWN39", "UNKNOWN40",
293 "UNKNOWN41", "UNKNOWN42", "UNKNOWN43", "UNKNOWN44",
294 "UNKNOWN45", "UNKNOWN46", "UNKNOWN47", "UNKNOWN48",
295 "UNKNOWN49", "UNKNOWN50", "UNKNOWN51", "UNKNOWN52",
296 "UNKNOWN53", "UNKNOWN54", "UNKNOWN55", "UNKNOWN56",
297 "UNKNOWN57", "UNKNOWN58", "UNKNOWN59", "UNKNOWN60",
298 "UNKNOWN61", "UNKNOWN62", "UNKNOWN63", "UNKNOWN64"
299 };
300
301 return channels[ch];
302 }
303
HasChannel(const enum AEChannel ch) const304 bool CAEChannelInfo::HasChannel(const enum AEChannel ch) const
305 {
306 for (unsigned int i = 0; i < m_channelCount; ++i)
307 if (m_channels[i] == ch)
308 return true;
309 return false;
310 }
311
ContainsChannels(const CAEChannelInfo & rhs) const312 bool CAEChannelInfo::ContainsChannels(const CAEChannelInfo& rhs) const
313 {
314 for (unsigned int i = 0; i < rhs.m_channelCount; ++i)
315 {
316 if (!HasChannel(rhs.m_channels[i]))
317 return false;
318 }
319 return true;
320 }
321
ReplaceChannel(const enum AEChannel from,const enum AEChannel to)322 void CAEChannelInfo::ReplaceChannel(const enum AEChannel from, const enum AEChannel to)
323 {
324 for (unsigned int i = 0; i < m_channelCount; ++i)
325 {
326 if (m_channels[i] == from)
327 {
328 m_channels[i] = to;
329 break;
330 }
331 }
332 }
333
BestMatch(const std::vector<CAEChannelInfo> & dsts,int * score) const334 int CAEChannelInfo::BestMatch(const std::vector<CAEChannelInfo>& dsts, int* score) const
335 {
336 CAEChannelInfo availableDstChannels;
337 for (unsigned int i = 0; i < dsts.size(); i++)
338 availableDstChannels.AddMissingChannels(dsts[i]);
339
340 /* if we have channels not existing in any destination layout but that
341 * are remappable (e.g. RC => RL+RR), do those remaps */
342 CAEChannelInfo src(*this);
343 src.ResolveChannels(availableDstChannels);
344
345 bool remapped = (src != *this);
346 /* good enough approximation (does not account for added channels) */
347 int dropped = std::max((int)src.Count() - (int)Count(), 0);
348
349 int bestScore = std::numeric_limits<int>::min();
350 int bestMatch = -1;
351
352 for (unsigned int i = 0; i < dsts.size(); i++)
353 {
354 const CAEChannelInfo& dst = dsts[i];
355 int okChannels = 0;
356
357 for (unsigned int j = 0; j < src.Count(); j++)
358 okChannels += dst.HasChannel(src[j]);
359
360 int missingChannels = src.Count() - okChannels;
361 int extraChannels = dst.Count() - okChannels;
362
363 int curScore = 0 - (missingChannels + dropped) * 1000 - extraChannels * 10 - (remapped ? 1 : 0);
364
365 if (curScore > bestScore)
366 {
367 bestScore = curScore;
368 bestMatch = i;
369 if (curScore == 0)
370 break;
371 }
372 }
373
374 if (score)
375 *score = bestScore;
376
377 return bestMatch;
378 }
379
AddMissingChannels(const CAEChannelInfo & rhs)380 void CAEChannelInfo::AddMissingChannels(const CAEChannelInfo& rhs)
381 {
382 for (unsigned int i = 0; i < rhs.Count(); i++)
383 if (!HasChannel(rhs[i]))
384 *this += rhs[i];
385 }
386