1 /*
2  * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #ifndef LOG_TAG
16 #define LOG_TAG "VolumeTools"
17 #endif
18 
19 #include <cmath>
20 
21 #include "volume_tools.h"
22 #include "volume_tools_c.h"
23 #include "audio_errors.h"
24 #include "audio_service_log.h"
25 
26 namespace {
27 static const int32_t UINT8_SHIFT = 0x80;
28 static const int32_t INT24_SHIFT = 8;
29 static const int32_t INT24_MAX_VALUE = 8388607;
30 static const uint32_t SHIFT_EIGHT = 8;
31 static const uint32_t SHIFT_SIXTEEN = 16;
32 static const uint32_t ARRAY_INDEX_TWO = 2;
33 static const size_t MIN_FRAME_SIZE = 1;
34 static const size_t MAX_FRAME_SIZE = 100000; // max to about 2s for 48khz
35 }
36 namespace OHOS {
37 namespace AudioStandard {
IsVolumeValid(float volFloat)38 bool VolumeTools::IsVolumeValid(float volFloat)
39 {
40     return volFloat >= 0.0 && volFloat <= 1.0;
41 }
42 
IsVolumeValid(int32_t volInt)43 bool VolumeTools::IsVolumeValid(int32_t volInt)
44 {
45     return volInt >= INT32_VOLUME_MIN && volInt <= INT32_VOLUME_MAX;
46 }
IsVolumeValid(ChannelVolumes vols)47 bool VolumeTools::IsVolumeValid(ChannelVolumes vols)
48 {
49     if (vols.channel > CHANNEL_16 || vols.channel < MONO) {
50         return false;
51     }
52     for (size_t i = 0; i < vols.channel; i++) {
53         if (!IsVolumeValid(vols.volStart[i]) || !IsVolumeValid(vols.volEnd[i])) {
54             return false;
55         }
56     }
57 
58     return true;
59 }
60 
GetInt32Vol(float volFloat)61 int32_t VolumeTools::GetInt32Vol(float volFloat)
62 {
63     if (IsVolumeValid(volFloat)) {
64         return volFloat * INT32_VOLUME_MAX;
65     }
66     if (volFloat < 0.0) {
67         return INT32_VOLUME_MIN;
68     }
69     return INT32_VOLUME_MAX;
70 }
71 
GetChannelVolumes(AudioChannel channel,int32_t volStart,int32_t volEnd)72 ChannelVolumes VolumeTools::GetChannelVolumes(AudioChannel channel, int32_t volStart, int32_t volEnd)
73 {
74     ChannelVolumes vols = {};
75     if (!IsVolumeValid(volStart) || !IsVolumeValid(volEnd) || channel > CHANNEL_16 || channel < MONO) {
76         AUDIO_ERR_LOG("GetChannelVolumes failed with invalid vol:%{public}d %{public}d channel: %{public}d", volStart,
77             volEnd, channel);
78         return vols;
79     }
80     for (size_t i = 0; i < channel; i++) {
81         vols.volStart[i] = volStart;
82         vols.volEnd[i] = volEnd;
83     }
84     vols.channel = channel;
85     return vols;
86 }
87 
GetChannelVolumes(AudioChannel channel,float volStart,float volEnd)88 ChannelVolumes VolumeTools::GetChannelVolumes(AudioChannel channel, float volStart, float volEnd)
89 {
90     ChannelVolumes vols = {};
91     if (!IsVolumeValid(volStart) || !IsVolumeValid(volEnd) || channel > CHANNEL_16 || channel < MONO) {
92         AUDIO_ERR_LOG("GetChannelVolumes failed with invalid vol:%{public}f %{public}f channel: %{public}d", volStart,
93             volEnd, channel);
94         return vols;
95     }
96     for (size_t i = 0; i < channel; i++) {
97         vols.volStart[i] = GetInt32Vol(volStart);
98         vols.volEnd[i] = GetInt32Vol(volEnd);
99     }
100     vols.channel = channel;
101     return vols;
102 }
103 
GetByteSize(AudioSampleFormat format)104 size_t VolumeTools::GetByteSize(AudioSampleFormat format)
105 {
106     size_t bitWidthSize = 0;
107     switch (format) {
108         case SAMPLE_U8:
109             bitWidthSize = 1; // size is 1
110             break;
111         case SAMPLE_S16LE:
112             bitWidthSize = 2; // size is 2
113             break;
114         case SAMPLE_S24LE:
115             bitWidthSize = 3; // size is 3
116             break;
117         case SAMPLE_S32LE:
118             bitWidthSize = 4; // size is 4
119             break;
120         case SAMPLE_F32LE:
121             bitWidthSize = 4; // size is 4
122             break;
123         default:
124             bitWidthSize = 2; // default size is 2
125             break;
126     }
127     return bitWidthSize;
128 }
129 
ReadInt24LE(const uint8_t * p)130 static inline uint32_t ReadInt24LE(const uint8_t *p)
131 {
132     return ((uint32_t) p[ARRAY_INDEX_TWO] << SHIFT_SIXTEEN) | ((uint32_t) p[1] << SHIFT_EIGHT) | ((uint32_t) p[0]);
133 }
134 
WriteInt24LE(uint8_t * p,uint32_t u)135 static inline void WriteInt24LE(uint8_t *p, uint32_t u)
136 {
137     p[ARRAY_INDEX_TWO] = (uint8_t) (u >> SHIFT_SIXTEEN);
138     p[1] = (uint8_t) (u >> SHIFT_EIGHT);
139     p[0] = (uint8_t) u;
140 }
141 
VolumeFlatten(int32_t vol)142 inline int32_t VolumeFlatten(int32_t vol)
143 {
144     return vol < INT32_VOLUME_MIN ? 0 : (vol > INT32_VOLUME_MAX ? INT32_VOLUME_MAX : vol);
145 }
146 
ProcessOneFrame(uint8_t * ptr,AudioSampleFormat format,int32_t vol)147 void ProcessOneFrame(uint8_t *ptr, AudioSampleFormat format, int32_t vol)
148 {
149     int64_t temp = 0;
150     int16_t *raw16 = nullptr;
151     int32_t *raw32 = nullptr;
152     float *rawFloat = nullptr;
153     switch (format) {
154         case SAMPLE_U8:
155             temp = *ptr - UINT8_SHIFT;
156             temp = (temp * vol) >> VOLUME_SHIFT;
157             temp = temp < INT8_MIN ? INT8_MIN : (temp > INT8_MAX ? INT8_MAX : temp);
158             *ptr = static_cast<uint8_t>(temp + UINT8_SHIFT);
159             break;
160         case SAMPLE_S16LE:
161             raw16 = reinterpret_cast<int16_t *>(ptr);
162             temp = (*raw16 * static_cast<int64_t>(vol)) >> VOLUME_SHIFT;
163             *raw16 = temp > INT16_MAX ? INT16_MAX : (temp < INT16_MIN ? INT16_MIN : temp);
164             break;
165         case SAMPLE_S24LE:
166             temp = static_cast<int32_t>(ReadInt24LE(ptr) << INT24_SHIFT) * static_cast<int64_t>(vol) >> VOLUME_SHIFT;
167             WriteInt24LE(ptr, (static_cast<uint32_t>(temp) >> INT24_SHIFT));
168             break;
169         case SAMPLE_S32LE:
170             raw32 = reinterpret_cast<int32_t *>(ptr);
171             // int32_t * int16_t, max result is int48_t
172             temp = (*raw32 * static_cast<int64_t>(vol)) >> VOLUME_SHIFT;
173             *raw32 = temp > INT32_MAX ? INT32_MAX : (temp < INT32_MIN ? INT32_MIN : temp);
174             break;
175         case SAMPLE_F32LE:
176             rawFloat = reinterpret_cast<float *>(ptr);
177             *rawFloat = *rawFloat * (static_cast<float>(vol) / INT32_VOLUME_MAX);
178             break;
179         default:
180             AUDIO_ERR_LOG("ProcessOneFrame with invalid format");
181             break;
182     }
183 }
184 
185 // |---------frame1--------|---------frame2--------|---------frame3--------|
186 // |ch1-ch2-ch3-ch4-ch5-ch6|ch1-ch2-ch3-ch4-ch5-ch6|ch1-ch2-ch3-ch4-ch5-ch6|
Process(const BufferDesc & buffer,AudioSampleFormat format,ChannelVolumes vols)187 int32_t VolumeTools::Process(const BufferDesc &buffer, AudioSampleFormat format, ChannelVolumes vols)
188 {
189     // parms check
190     if (format > SAMPLE_F32LE || !IsVolumeValid(vols)) {
191         AUDIO_ERR_LOG("Process failed with invalid params");
192         return ERR_INVALID_PARAM;
193     }
194     size_t byteSizePerData = GetByteSize(format);
195     size_t byteSizePerFrame = byteSizePerData * vols.channel;
196     if (buffer.buffer == nullptr || buffer.bufLength % byteSizePerFrame != 0) {
197         AUDIO_ERR_LOG("Process failed with invalid buffer, size is %{public}zu", buffer.bufLength);
198         return ERR_INVALID_PARAM;
199     }
200 
201     size_t frameSize = buffer.bufLength / byteSizePerFrame;
202     if (frameSize < MIN_FRAME_SIZE) {
203         AUDIO_ERR_LOG("Process failed with invalid frameSize, size is %{public}zu", frameSize);
204         return ERR_INVALID_PARAM;
205     }
206 
207     float volStep[CHANNEL_MAX] = {};
208     for (size_t channelIdx = 0; channelIdx < vols.channel; channelIdx++) {
209         if (vols.volEnd[channelIdx] == vols.volStart[channelIdx] || frameSize == MIN_FRAME_SIZE) {
210             volStep[channelIdx] = 0.0;
211         } else {
212             volStep[channelIdx] = (static_cast<float>(vols.volEnd[channelIdx] - vols.volStart[channelIdx])) /
213                 (frameSize - MIN_FRAME_SIZE);
214         }
215     }
216     for (size_t frameIndex = 0; frameIndex < frameSize; frameIndex++) {
217         for (size_t channelIdx = 0; channelIdx < vols.channel; channelIdx++) {
218             int32_t vol = volStep[channelIdx] * frameIndex + vols.volStart[channelIdx];
219             vol = VolumeFlatten(vol);
220             uint8_t *samplePtr = buffer.buffer + frameIndex * byteSizePerFrame + channelIdx * byteSizePerData;
221             ProcessOneFrame(samplePtr, format, vol);
222         }
223     }
224 
225     return SUCCESS;
226 }
227 
GetVolDb(AudioSampleFormat format,int32_t vol)228 double VolumeTools::GetVolDb(AudioSampleFormat format, int32_t vol)
229 {
230     double volume = static_cast<double>(vol);
231     switch (format) {
232         case SAMPLE_U8:
233             volume = volume / INT8_MAX;
234             break;
235         case SAMPLE_S16LE:
236             volume = volume / INT16_MAX;
237             break;
238         case SAMPLE_S24LE:
239             volume = volume / INT24_MAX_VALUE;
240             break;
241         case SAMPLE_S32LE:
242             volume = volume / INT32_MAX;
243             break;
244         case SAMPLE_F32LE:
245             volume = volume / INT32_MAX;
246             break;
247         default:
248             break;
249     }
250     return std::log10(volume);
251 }
252 
CountU8Volume(const BufferDesc & buffer,AudioChannel channel,ChannelVolumes & volMaps,size_t split,AudioSampleFormat format)253 static void CountU8Volume(const BufferDesc &buffer, AudioChannel channel, ChannelVolumes &volMaps, size_t split,
254     AudioSampleFormat format)
255 {
256     if (split <= 0) {
257         AUDIO_ERR_LOG("invalid split");
258         return;
259     }
260     size_t byteSizePerData = VolumeTools::GetByteSize(format);
261     size_t byteSizePerFrame = byteSizePerData * channel;
262     if (buffer.buffer == nullptr || byteSizePerFrame == 0 || buffer.bufLength % byteSizePerFrame != 0) {
263         AUDIO_ERR_LOG("invalid buffer, size is %{public}zu", buffer.bufLength);
264         return;
265     }
266     size_t frameSize = buffer.bufLength / byteSizePerFrame;
267     if (frameSize <= MIN_FRAME_SIZE || frameSize >= MAX_FRAME_SIZE) {
268         AUDIO_ERR_LOG("invalid frameSize, size is %{public}zu", frameSize);
269         return;
270     }
271 
272     // reset maps
273     for (size_t index = 0; index < channel; index++) {
274         volMaps.volStart[index] = 0;
275         volMaps.volEnd[index] = 0;
276     }
277     uint8_t *raw8 = buffer.buffer;
278     for (size_t frameIndex = 0; frameIndex < frameSize - (split - 1); frameIndex += split) {
279         for (size_t channelIdx = 0; channelIdx < channel; channelIdx++) {
280             volMaps.volStart[channelIdx] += (*raw8 >= UINT8_SHIFT ? *raw8 - UINT8_SHIFT : UINT8_SHIFT - *raw8);
281             raw8++;
282         }
283         raw8 += (split - 1) * channel;
284     }
285     // Calculate the average value
286     size_t size = frameSize / split;
287     if (size == 0) {
288         AUDIO_ERR_LOG("invalid size");
289         return;
290     }
291     for (size_t index = 0; index < channel; index++) {
292         volMaps.volStart[index] /= static_cast<int32_t>(size);
293     }
294     return;
295 }
296 
CountS16Volume(const BufferDesc & buffer,AudioChannel channel,ChannelVolumes & volMaps,size_t split,AudioSampleFormat format)297 static void CountS16Volume(const BufferDesc &buffer, AudioChannel channel, ChannelVolumes &volMaps, size_t split,
298     AudioSampleFormat format)
299 {
300     if (split <= 0) {
301         AUDIO_ERR_LOG("invalid split");
302         return;
303     }
304     size_t byteSizePerData = VolumeTools::GetByteSize(format);
305     size_t byteSizePerFrame = byteSizePerData * channel;
306     if (buffer.buffer == nullptr || byteSizePerFrame == 0 || buffer.bufLength % byteSizePerFrame != 0) {
307         AUDIO_ERR_LOG("invalid buffer, size is %{public}zu", buffer.bufLength);
308         return;
309     }
310     size_t frameSize = buffer.bufLength / byteSizePerFrame;
311     if (frameSize <= MIN_FRAME_SIZE || frameSize >= MAX_FRAME_SIZE) {
312         AUDIO_ERR_LOG("invalid frameSize, size is %{public}zu", frameSize);
313         return;
314     }
315 
316     // reset maps
317     for (size_t index = 0; index < channel; index++) {
318         volMaps.volStart[index] = 0;
319         volMaps.volEnd[index] = 0;
320     }
321     int16_t *raw16 = reinterpret_cast<int16_t *>(buffer.buffer);
322     for (size_t frameIndex = 0; frameIndex < frameSize - (split - 1); frameIndex += split) {
323         for (size_t channelIdx = 0; channelIdx < channel; channelIdx++) {
324             volMaps.volStart[channelIdx] += (*raw16 >= 0 ? *raw16: (-*raw16));
325             raw16++;
326         }
327         raw16 += (split - 1) * channel;
328     }
329     // Calculate the average value
330     size_t size = frameSize / split;
331     if (size == 0) {
332         AUDIO_ERR_LOG("invalid size");
333         return;
334     }
335     for (size_t index = 0; index < channel; index++) {
336         volMaps.volStart[index] /= static_cast<int32_t>(size);
337     }
338     return;
339 }
340 
CountS24Volume(const BufferDesc & buffer,AudioChannel channel,ChannelVolumes & volMaps,size_t split,AudioSampleFormat format)341 static void CountS24Volume(const BufferDesc &buffer, AudioChannel channel, ChannelVolumes &volMaps, size_t split,
342     AudioSampleFormat format)
343 {
344     if (split <= 0) {
345         AUDIO_ERR_LOG("invalid split");
346         return;
347     }
348     const size_t byteSizePerData = VolumeTools::GetByteSize(format);
349     size_t byteSizePerFrame = byteSizePerData * channel;
350     if (buffer.buffer == nullptr || byteSizePerFrame == 0 || buffer.bufLength % byteSizePerFrame != 0) {
351         AUDIO_ERR_LOG("invalid buffer, size is %{public}zu", buffer.bufLength);
352         return;
353     }
354     size_t frameSize = buffer.bufLength / byteSizePerFrame;
355     if (frameSize <= MIN_FRAME_SIZE || frameSize >= MAX_FRAME_SIZE) {
356         AUDIO_ERR_LOG("invalid frameSize, size is %{public}zu", frameSize);
357         return;
358     }
359 
360     // reset maps
361     for (size_t index = 0; index < channel; index++) {
362         volMaps.volStart[index] = 0;
363         volMaps.volEnd[index] = 0;
364     }
365     uint8_t *raw8 = buffer.buffer;
366     for (size_t frameIndex = 0; frameIndex < frameSize - (split - 1); frameIndex += split) {
367         for (size_t channelIdx = 0; channelIdx < channel; channelIdx++) {
368             int32_t sample = static_cast<int32_t>(ReadInt24LE(raw8));
369             uint32_t sampleAbs = static_cast<uint32_t>(sample >= 0 ? sample: (-sample)) >>
370                 SHIFT_EIGHT;
371             volMaps.volStart[channelIdx] +=  static_cast<int32_t>(sampleAbs);
372             raw8 += byteSizePerData;
373         }
374         raw8 += (split - 1) * channel * byteSizePerData;
375     }
376     // Calculate the average value
377     size_t size = frameSize / split;
378     if (size == 0) {
379         AUDIO_ERR_LOG("invalid size");
380         return;
381     }
382     for (size_t index = 0; index < channel; index++) {
383         volMaps.volStart[index] /= static_cast<int32_t>(size);
384     }
385     return;
386 }
387 
CountS32Volume(const BufferDesc & buffer,AudioChannel channel,ChannelVolumes & volMaps,size_t split,AudioSampleFormat format)388 static void CountS32Volume(const BufferDesc &buffer, AudioChannel channel, ChannelVolumes &volMaps, size_t split,
389     AudioSampleFormat format)
390 {
391     if (split <= 0) {
392         AUDIO_ERR_LOG("invalid split");
393         return;
394     }
395     const size_t byteSizePerData = VolumeTools::GetByteSize(format);
396     size_t byteSizePerFrame = byteSizePerData * channel;
397     if (buffer.buffer == nullptr || byteSizePerFrame == 0 || buffer.bufLength % byteSizePerFrame != 0) {
398         AUDIO_ERR_LOG("invalid buffer, size is %{public}zu", buffer.bufLength);
399         return;
400     }
401     size_t frameSize = buffer.bufLength / byteSizePerFrame;
402     if (frameSize <= MIN_FRAME_SIZE || frameSize >= MAX_FRAME_SIZE) {
403         AUDIO_ERR_LOG("invalid frameSize, size is %{public}zu", frameSize);
404         return;
405     }
406 
407     // reset maps
408     int64_t volSums[CHANNEL_MAX] = {0};
409     for (size_t index = 0; index < CHANNEL_MAX; index++) {
410         volSums[index] = 0;
411     }
412     int32_t *raw32 = reinterpret_cast<int32_t *>(buffer.buffer);
413     for (size_t frameIndex = 0; frameIndex < frameSize - (split - 1); frameIndex += split) {
414         for (size_t channelIdx = 0; channelIdx < channel; channelIdx++) {
415             uint32_t sampleAbs = static_cast<uint32_t>(*raw32 >= 0 ? *raw32: (-*raw32)) >>
416                 SHIFT_SIXTEEN;
417             volSums[channelIdx] += static_cast<int32_t>(sampleAbs);
418             raw32++;
419         }
420         raw32 += (split - 1) * channel;
421     }
422 
423     // Calculate the average value
424     size_t size = frameSize / split;
425     if (size == 0) {
426         AUDIO_ERR_LOG("invalid size");
427         return;
428     }
429     for (size_t index = 0; index < channel; index++) {
430         volSums[index] /= static_cast<int32_t>(size);
431         volMaps.volStart[index] = volSums[index];
432     }
433     return;
434 }
435 
CountF32Volume(const BufferDesc & buffer,AudioChannel channel,ChannelVolumes & volMaps,size_t split,AudioSampleFormat format)436 static void CountF32Volume(const BufferDesc &buffer, AudioChannel channel, ChannelVolumes &volMaps, size_t split,
437     AudioSampleFormat format)
438 {
439     if (split <= 0) {
440         AUDIO_ERR_LOG("invalid split");
441         return;
442     }
443     size_t byteSizePerData = VolumeTools::GetByteSize(format);
444     size_t byteSizePerFrame = byteSizePerData * channel;
445     if (buffer.buffer == nullptr || byteSizePerFrame == 0 || buffer.bufLength % byteSizePerFrame != 0) {
446         AUDIO_ERR_LOG("invalid buffer, size is %{public}zu", buffer.bufLength);
447         return;
448     }
449     size_t frameSize = buffer.bufLength / byteSizePerFrame;
450     if (frameSize <= MIN_FRAME_SIZE || frameSize >= MAX_FRAME_SIZE) {
451         AUDIO_ERR_LOG("invalid frameSize, size is %{public}zu", frameSize);
452         return;
453     }
454 
455     // reset maps
456     double volSums[CHANNEL_MAX] = {0};
457     for (size_t index = 0; index < CHANNEL_MAX; index++) {
458         volSums[index] = 0.0;
459     }
460     float *raw32 = reinterpret_cast<float *>(buffer.buffer);
461     for (size_t frameIndex = 0; frameIndex < frameSize - (split - 1); frameIndex += split) {
462         for (size_t channelIdx = 0; channelIdx < channel; channelIdx++) {
463             volSums[channelIdx] += (*raw32 >= 0 ? *raw32: (-*raw32));
464             raw32++;
465         }
466         raw32 += (split - 1) * channel;
467     }
468     // Calculate the average value
469     size_t size = frameSize / split;
470     if (size == 0) {
471         AUDIO_ERR_LOG("invalid size");
472         return;
473     }
474     for (size_t index = 0; index < channel; index++) {
475         volSums[index] /= static_cast<int32_t>(size);
476         volMaps.volStart[index] = static_cast<int32_t>(volSums[index]);
477     }
478     return;
479 }
480 
CountVolumeLevel(const BufferDesc & buffer,AudioSampleFormat format,AudioChannel channel,size_t split)481 ChannelVolumes VolumeTools::CountVolumeLevel(const BufferDesc &buffer, AudioSampleFormat format, AudioChannel channel,
482     size_t split)
483 {
484     ChannelVolumes channelVols = {};
485     channelVols.channel = channel;
486     if (format > SAMPLE_F32LE || channel > CHANNEL_16) {
487         AUDIO_ERR_LOG("failed with invalid params");
488         return channelVols;
489     }
490     switch (format) {
491         case SAMPLE_U8:
492             CountU8Volume(buffer, channel, channelVols, split, format);
493             break;
494         case SAMPLE_S16LE:
495             CountS16Volume(buffer, channel, channelVols, split, format);
496             break;
497         case SAMPLE_S24LE:
498             CountS24Volume(buffer, channel, channelVols, split, format);
499             break;
500         case SAMPLE_S32LE:
501             CountS32Volume(buffer, channel, channelVols, split, format);
502             break;
503         case SAMPLE_F32LE:
504             CountF32Volume(buffer, channel, channelVols, split, format);
505             break;
506         default:
507             break;
508     }
509 
510     return channelVols;
511 }
512 } // namespace AudioStandard
513 } // namespace OHOS
514 
515 #ifdef __cplusplus
516 extern "C" {
517 #endif
518 using namespace OHOS::AudioStandard;
519 
ProcessVol(uint8_t * buffer,size_t length,AudioRawFormat rawformat,float volStart,float volEnd)520 int32_t ProcessVol(uint8_t *buffer, size_t length, AudioRawFormat rawformat, float volStart, float volEnd)
521 {
522     BufferDesc desc = {0};
523     desc.buffer = buffer;
524     desc.bufLength = length;
525     desc.dataLength = length;
526     ChannelVolumes mapVols = VolumeTools::GetChannelVolumes(static_cast<AudioChannel>(rawformat.channels), volStart,
527         volEnd);
528     return VolumeTools::Process(desc, static_cast<AudioSampleFormat>(rawformat.format), mapVols);
529 }
530 
531 #ifdef __cplusplus
532 }
533 #endif