1 /*
2 * Copyright (c) 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
16 #ifndef LOG_TAG
17 #define LOG_TAG "AudioVolume"
18 #endif
19
20 #include "audio_volume.h"
21 #include "audio_volume_c.h"
22 #include "audio_common_log.h"
23 #include "audio_utils.h"
24 #include "audio_stream_info.h"
25 #include "media_monitor_manager.h"
26 #include "event_bean.h"
27
28 namespace OHOS {
29 namespace AudioStandard {
30 static const std::unordered_map<std::string, AudioStreamType> STREAM_TYPE_STRING_ENUM_MAP = {
31 {"voice_call", STREAM_VOICE_CALL},
32 {"voice_call_assistant", STREAM_VOICE_CALL_ASSISTANT},
33 {"music", STREAM_MUSIC},
34 {"ring", STREAM_RING},
35 {"media", STREAM_MEDIA},
36 {"voice_assistant", STREAM_VOICE_ASSISTANT},
37 {"system", STREAM_SYSTEM},
38 {"alarm", STREAM_ALARM},
39 {"notification", STREAM_NOTIFICATION},
40 {"bluetooth_sco", STREAM_BLUETOOTH_SCO},
41 {"enforced_audible", STREAM_ENFORCED_AUDIBLE},
42 {"dtmf", STREAM_DTMF},
43 {"tts", STREAM_TTS},
44 {"accessibility", STREAM_ACCESSIBILITY},
45 {"recording", STREAM_RECORDING},
46 {"movie", STREAM_MOVIE},
47 {"game", STREAM_GAME},
48 {"speech", STREAM_SPEECH},
49 {"system_enforced", STREAM_SYSTEM_ENFORCED},
50 {"ultrasonic", STREAM_ULTRASONIC},
51 {"wakeup", STREAM_WAKEUP},
52 {"voice_message", STREAM_VOICE_MESSAGE},
53 {"navigation", STREAM_NAVIGATION}
54 };
55
GetInstance()56 AudioVolume *AudioVolume::GetInstance()
57 {
58 static AudioVolume instance;
59 return &instance;
60 }
61
AudioVolume()62 AudioVolume::AudioVolume()
63 {
64 AUDIO_INFO_LOG("AudioVolume construct");
65 }
66
~AudioVolume()67 AudioVolume::~AudioVolume()
68 {
69 streamVolume_.clear();
70 systemVolume_.clear();
71 historyVolume_.clear();
72 monitorVolume_.clear();
73 }
74
GetVolume(uint32_t sessionId,int32_t volumeType,const std::string & deviceClass)75 float AudioVolume::GetVolume(uint32_t sessionId, int32_t volumeType, const std::string &deviceClass)
76 {
77 Trace trace("AudioVolume::GetVolume sessionId:" + std::to_string(sessionId));
78 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
79 float volumeStream = 1.0f;
80 auto it = streamVolume_.find(sessionId);
81 if (it != streamVolume_.end()) {
82 volumeStream =
83 it->second.isMuted_ ? 0.0f : it->second.volume_ * it->second.duckFactor_ * it->second.lowPowerFactor_;
84 AUDIO_DEBUG_LOG("stream volume, sessionId:%{public}u, volume:%{public}f, duck:%{public}f, lowPower:%{public}f,"
85 " isMuted:%{public}d, streamVolumeSize:%{public}zu",
86 sessionId, it->second.volume_, it->second.duckFactor_, it->second.lowPowerFactor_, it->second.isMuted_,
87 streamVolume_.size());
88 } else {
89 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u, streamVolumeSize:%{public}zu",
90 sessionId, streamVolume_.size());
91 }
92
93 std::shared_lock<std::shared_mutex> lockSystem(systemMutex_);
94 int32_t volumeLevel = 0;
95 float volumeSystem = 1.0f;
96 std::string key = std::to_string(volumeType) + deviceClass;
97 auto itSV = systemVolume_.find(key);
98 if (itSV != systemVolume_.end()) {
99 volumeLevel = itSV->second.volumeLevel_;
100 volumeSystem = itSV->second.isMuted_ ? 0.0f : itSV->second.volume_;
101 AUDIO_DEBUG_LOG("system volume, volumeType:%{public}d, deviceClass:%{public}s,"
102 " volume:%{public}f, isMuted:%{public}d, systemVolumeSize:%{public}zu",
103 volumeType, deviceClass.c_str(), itSV->second.volume_, itSV->second.isMuted_, systemVolume_.size());
104 } else {
105 AUDIO_ERR_LOG("system volume not exist, volumeType:%{public}d, deviceClass:%{public}s,"
106 " systemVolumeSize:%{public}zu", volumeType, deviceClass.c_str(), systemVolume_.size());
107 }
108 float volumeFloat = volumeStream * volumeSystem;
109 if (monitorVolume_.find(sessionId) != monitorVolume_.end()) {
110 monitorVolume_[sessionId] = {volumeFloat, volumeLevel};
111 }
112 return volumeFloat;
113 }
114
GetHistoryVolume(uint32_t sessionId)115 float AudioVolume::GetHistoryVolume(uint32_t sessionId)
116 {
117 Trace trace("AudioVolume::GetHistoryVolume sessionId:" + std::to_string(sessionId));
118 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
119 auto it = historyVolume_.find(sessionId);
120 if (it != historyVolume_.end()) {
121 return it->second;
122 }
123 return 0.0f;
124 }
125
SetHistoryVolume(uint32_t sessionId,float volume)126 void AudioVolume::SetHistoryVolume(uint32_t sessionId, float volume)
127 {
128 AUDIO_INFO_LOG("history volume, sessionId:%{public}u, volume:%{public}f", sessionId, volume);
129 Trace trace("AudioVolume::SetHistoryVolume sessionId:" + std::to_string(sessionId));
130 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
131 auto it = historyVolume_.find(sessionId);
132 if (it != historyVolume_.end()) {
133 it->second = volume;
134 }
135 }
136
AddStreamVolume(uint32_t sessionId,int32_t streamType,int32_t streamUsage,int32_t uid,int32_t pid)137 void AudioVolume::AddStreamVolume(uint32_t sessionId, int32_t streamType, int32_t streamUsage,
138 int32_t uid, int32_t pid)
139 {
140 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u", sessionId);
141 std::unique_lock<std::shared_mutex> lock(volumeMutex_);
142 auto it = streamVolume_.find(sessionId);
143 if (it == streamVolume_.end()) {
144 streamVolume_.emplace(sessionId, StreamVolume(sessionId, streamType, streamUsage, uid, pid));
145 historyVolume_.emplace(sessionId, 0.0f);
146 monitorVolume_.emplace(sessionId, std::make_pair(0.0f, 0));
147 } else {
148 AUDIO_ERR_LOG("stream volume already exist, sessionId:%{public}u", sessionId);
149 }
150 }
151
RemoveStreamVolume(uint32_t sessionId)152 void AudioVolume::RemoveStreamVolume(uint32_t sessionId)
153 {
154 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u", sessionId);
155 std::unique_lock<std::shared_mutex> lock(volumeMutex_);
156 auto it = streamVolume_.find(sessionId);
157 if (it != streamVolume_.end()) {
158 streamVolume_.erase(sessionId);
159 } else {
160 AUDIO_ERR_LOG("stream volume already delete, sessionId:%{public}u", sessionId);
161 }
162 auto itHistory = historyVolume_.find(sessionId);
163 if (itHistory != historyVolume_.end()) {
164 historyVolume_.erase(sessionId);
165 }
166 auto itMonitor = monitorVolume_.find(sessionId);
167 if (itMonitor != monitorVolume_.end()) {
168 monitorVolume_.erase(sessionId);
169 }
170 }
171
SetStreamVolume(uint32_t sessionId,float volume)172 void AudioVolume::SetStreamVolume(uint32_t sessionId, float volume)
173 {
174 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u, volume:%{public}f", sessionId, volume);
175 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
176 auto it = streamVolume_.find(sessionId);
177 if (it != streamVolume_.end()) {
178 it->second.volume_ = volume;
179 } else {
180 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
181 }
182 }
183
SetStreamVolumeDuckFactor(uint32_t sessionId,float duckFactor)184 void AudioVolume::SetStreamVolumeDuckFactor(uint32_t sessionId, float duckFactor)
185 {
186 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u, duckFactor:%{public}f", sessionId, duckFactor);
187 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
188 auto it = streamVolume_.find(sessionId);
189 if (it != streamVolume_.end()) {
190 it->second.duckFactor_ = duckFactor;
191 } else {
192 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
193 }
194 }
195
SetStreamVolumeLowPowerFactor(uint32_t sessionId,float lowPowerFactor)196 void AudioVolume::SetStreamVolumeLowPowerFactor(uint32_t sessionId, float lowPowerFactor)
197 {
198 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u, lowPowerFactor:%{public}f", sessionId, lowPowerFactor);
199 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
200 auto it = streamVolume_.find(sessionId);
201 if (it != streamVolume_.end()) {
202 it->second.lowPowerFactor_ = lowPowerFactor;
203 } else {
204 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
205 }
206 }
207
SetStreamVolumeMute(uint32_t sessionId,bool isMuted)208 void AudioVolume::SetStreamVolumeMute(uint32_t sessionId, bool isMuted)
209 {
210 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u, isMuted:%{public}d", sessionId, isMuted);
211 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
212 auto it = streamVolume_.find(sessionId);
213 if (it != streamVolume_.end()) {
214 it->second.isMuted_ = isMuted;
215 }
216 }
217
SetStreamVolumeFade(uint32_t sessionId,float fadeBegin,float fadeEnd)218 void AudioVolume::SetStreamVolumeFade(uint32_t sessionId, float fadeBegin, float fadeEnd)
219 {
220 AUDIO_INFO_LOG("stream volume, sessionId:%{public}u, fadeBegin:%{public}f, fadeEnd:%{public}f",
221 sessionId, fadeBegin, fadeEnd);
222 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
223 auto it = streamVolume_.find(sessionId);
224 if (it != streamVolume_.end()) {
225 it->second.fadeBegin_ = fadeBegin;
226 it->second.fadeEnd_ = fadeEnd;
227 } else {
228 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
229 }
230 }
231
GetStreamVolumeFade(uint32_t sessionId)232 std::pair<float, float> AudioVolume::GetStreamVolumeFade(uint32_t sessionId)
233 {
234 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
235 auto it = streamVolume_.find(sessionId);
236 if (it != streamVolume_.end()) {
237 return {it->second.fadeBegin_, it->second.fadeEnd_};
238 } else {
239 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
240 }
241 return {1.0f, 1.0f};
242 }
243
SetSystemVolume(SystemVolume & systemVolume)244 void AudioVolume::SetSystemVolume(SystemVolume &systemVolume)
245 {
246 auto volumeType = systemVolume.GetVolumeType();
247 auto deviceClass = systemVolume.GetDeviceClass();
248 std::string key = std::to_string(volumeType) + deviceClass;
249 bool haveSystemVolume = true;
250 {
251 std::shared_lock<std::shared_mutex> lock(systemMutex_);
252 auto it = systemVolume_.find(key);
253 if (it != systemVolume_.end()) {
254 it->second.volume_ = systemVolume.volume_;
255 it->second.volumeLevel_ = systemVolume.volumeLevel_;
256 it->second.isMuted_ = systemVolume.isMuted_;
257 } else {
258 haveSystemVolume = false;
259 }
260 }
261 if (!haveSystemVolume) {
262 std::unique_lock<std::shared_mutex> lock(systemMutex_);
263 systemVolume_.emplace(key, systemVolume);
264 }
265 AUDIO_INFO_LOG("system volume, volumeType:%{public}d, deviceClass:%{public}s,"
266 " volume:%{public}f, volumeLevel:%{public}d, isMuted:%{public}d, systemVolumeSize:%{public}zu",
267 volumeType, deviceClass.c_str(), systemVolume.volume_, systemVolume.volumeLevel_, systemVolume.isMuted_,
268 systemVolume_.size());
269 }
270
SetSystemVolume(int32_t volumeType,const std::string & deviceClass,float volume,int32_t volumeLevel)271 void AudioVolume::SetSystemVolume(int32_t volumeType, const std::string &deviceClass, float volume, int32_t volumeLevel)
272 {
273 std::string key = std::to_string(volumeType) + deviceClass;
274 bool haveSystemVolume = true;
275 {
276 std::shared_lock<std::shared_mutex> lock(systemMutex_);
277 auto it = systemVolume_.find(key);
278 if (it != systemVolume_.end()) {
279 it->second.volume_ = volume;
280 it->second.volumeLevel_ = volumeLevel;
281 } else {
282 haveSystemVolume = false;
283 }
284 }
285 if (!haveSystemVolume) {
286 std::unique_lock<std::shared_mutex> lock(systemMutex_);
287 SystemVolume systemVolume(volumeType, deviceClass, volume, volumeLevel, false);
288 systemVolume_.emplace(key, systemVolume);
289 }
290 AUDIO_INFO_LOG("system volume, volumeType:%{public}d, deviceClass:%{public}s,"
291 " volume:%{public}f, volumeLevel:%{public}d, systemVolumeSize:%{public}zu",
292 volumeType, deviceClass.c_str(), volume, volumeLevel, systemVolume_.size());
293 }
294
SetSystemVolumeMute(int32_t volumeType,const std::string & deviceClass,bool isMuted)295 void AudioVolume::SetSystemVolumeMute(int32_t volumeType, const std::string &deviceClass, bool isMuted)
296 {
297 AUDIO_INFO_LOG("system volume, volumeType:%{public}d, deviceClass:%{public}s, isMuted:%{public}d",
298 volumeType, deviceClass.c_str(), isMuted);
299 std::string key = std::to_string(volumeType) + deviceClass;
300 bool haveSystemVolume = true;
301 {
302 std::shared_lock<std::shared_mutex> lock(systemMutex_);
303 auto it = systemVolume_.find(key);
304 if (it != systemVolume_.end()) {
305 it->second.isMuted_ = isMuted;
306 } else {
307 haveSystemVolume = false;
308 }
309 }
310 if (!haveSystemVolume) {
311 std::unique_lock<std::shared_mutex> lock(systemMutex_);
312 SystemVolume systemVolume(volumeType, deviceClass, 0.0f, 0, isMuted);
313 systemVolume_.emplace(key, systemVolume);
314 }
315 }
316
ConvertStreamTypeStrToInt(const std::string & streamType)317 int32_t AudioVolume::ConvertStreamTypeStrToInt(const std::string &streamType)
318 {
319 AudioStreamType stream = STREAM_MUSIC;
320 if (STREAM_TYPE_STRING_ENUM_MAP.find(streamType) != STREAM_TYPE_STRING_ENUM_MAP.end()) {
321 stream = STREAM_TYPE_STRING_ENUM_MAP.at(streamType);
322 } else {
323 AUDIO_WARNING_LOG("Invalid stream type [%{public}s]. Use default type", streamType.c_str());
324 }
325 return stream;
326 }
327
IsSameVolume(float x,float y)328 bool AudioVolume::IsSameVolume(float x, float y)
329 {
330 return (std::abs((x) - (y)) <= std::abs(FLOAT_EPS));
331 }
332
Dump(std::string & dumpString)333 void AudioVolume::Dump(std::string &dumpString)
334 {
335 AUDIO_INFO_LOG("AudioVolume dump begin");
336 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
337 // dump system volume
338 std::vector<SystemVolume> systemVolumeList;
339 for (auto &systemVolume : systemVolume_) {
340 systemVolumeList.push_back(systemVolume.second);
341 }
342 std::sort(systemVolumeList.begin(), systemVolumeList.end(), [](SystemVolume &a, SystemVolume &b) {
343 return a.GetVolumeType() < b.GetVolumeType();
344 });
345 AppendFormat(dumpString, "\n - audio system volume size: %zu\n", systemVolumeList.size());
346 for (auto &systemVolume : systemVolumeList) {
347 AppendFormat(dumpString, " streamtype: %d ", systemVolume.GetVolumeType());
348 AppendFormat(dumpString, " isMute: %s ", (systemVolume.isMuted_ ? "true" : "false"));
349 AppendFormat(dumpString, " volFloat: %f ", systemVolume.volume_);
350 AppendFormat(dumpString, " volInt: %d ", systemVolume.volumeLevel_);
351 AppendFormat(dumpString, " device class: %s \n", systemVolume.GetDeviceClass().c_str());
352 }
353
354 // dump stream volume
355 std::vector<StreamVolume> streamVolumeList;
356 for (auto &streamVolume : streamVolume_) {
357 streamVolumeList.push_back(streamVolume.second);
358 }
359 std::sort(streamVolumeList.begin(), streamVolumeList.end(), [](StreamVolume &a, StreamVolume &b) {
360 return a.GetSessionId() < b.GetSessionId();
361 });
362 AppendFormat(dumpString, "\n - audio stream volume size: %zu, his volume size: %zu, mon volume size: %zu\n",
363 streamVolumeList.size(), historyVolume_.size(), monitorVolume_.size());
364 for (auto &streamVolume : streamVolumeList) {
365 auto monVol = monitorVolume_.find(streamVolume.GetSessionId());
366 AppendFormat(dumpString, " sessionId: %u ", streamVolume.GetSessionId());
367 AppendFormat(dumpString, " streamType: %d ", streamVolume.GetStreamType());
368 AppendFormat(dumpString, " streamUsage: %d ", streamVolume.GetStreamUsage());
369 AppendFormat(dumpString, " appUid: %d ", streamVolume.GetAppUid());
370 AppendFormat(dumpString, " appPid: %d ", streamVolume.GetAppPid());
371 AppendFormat(dumpString, " volume: %f ", monVol != monitorVolume_.end() ? monVol->second.first : 0.0f);
372 AppendFormat(dumpString, " volumeLevel: %d ", monVol != monitorVolume_.end() ? monVol->second.second : 0);
373 AppendFormat(dumpString, " volFactor: %f ", streamVolume.volume_);
374 AppendFormat(dumpString, " duckFactor: %f ", streamVolume.duckFactor_);
375 AppendFormat(dumpString, " powerFactor: %f ", streamVolume.lowPowerFactor_);
376 AppendFormat(dumpString, " fadeBegin: %f ", streamVolume.fadeBegin_);
377 AppendFormat(dumpString, " fadeEnd: %f \n", streamVolume.fadeEnd_);
378 }
379 }
380
Monitor(uint32_t sessionId,bool isOutput)381 void AudioVolume::Monitor(uint32_t sessionId, bool isOutput)
382 {
383 std::shared_lock<std::shared_mutex> lock(volumeMutex_);
384 auto streamVolume = streamVolume_.find(sessionId);
385 if (streamVolume != streamVolume_.end()) {
386 auto monVol = monitorVolume_.find(sessionId);
387 std::shared_ptr<Media::MediaMonitor::EventBean> bean = std::make_shared<Media::MediaMonitor::EventBean>(
388 Media::MediaMonitor::AUDIO, Media::MediaMonitor::VOLUME_CHANGE,
389 Media::MediaMonitor::BEHAVIOR_EVENT);
390 bean->Add("ISOUTPUT", isOutput ? 1 : 0);
391 bean->Add("STREAMID", static_cast<int32_t>(sessionId));
392 bean->Add("APP_UID", streamVolume->second.GetAppUid());
393 bean->Add("APP_PID", streamVolume->second.GetAppPid());
394 bean->Add("STREAMTYPE", streamVolume->second.GetStreamType());
395 bean->Add("STREAM_TYPE", streamVolume->second.GetStreamUsage());
396 bean->Add("VOLUME", monVol != monitorVolume_.end() ? monVol->second.first : 0.0f);
397 bean->Add("SYSVOLUME", monVol != monitorVolume_.end() ? monVol->second.second : 0);
398 bean->Add("VOLUMEFACTOR", streamVolume->second.volume_);
399 bean->Add("POWERVOLUMEFACTOR", streamVolume->second.lowPowerFactor_);
400 Media::MediaMonitor::MediaMonitorManager::GetInstance().WriteLogMsg(bean);
401 } else {
402 AUDIO_ERR_LOG("stream volume not exist, sessionId:%{public}u", sessionId);
403 }
404 }
405 } // namespace AudioStandard
406 } // namespace OHOS
407
408 #ifdef __cplusplus
409 extern "C" {
410 #endif
411 using namespace OHOS::AudioStandard;
412
GetCurVolume(uint32_t sessionId,const char * streamType,const char * deviceClass)413 float GetCurVolume(uint32_t sessionId, const char *streamType, const char *deviceClass)
414 {
415 CHECK_AND_RETURN_RET_LOG(streamType != nullptr, 1.0f, "streamType is nullptr");
416 CHECK_AND_RETURN_RET_LOG(deviceClass != nullptr, 1.0f, "deviceClass is nullptr");
417 std::string tmpStreamType = streamType;
418 // Set voice call assistant stream type to full volume
419 if (tmpStreamType == "voice_call_assistant") {
420 return 1.0f;
421 }
422 int32_t stream = AudioVolume::GetInstance()->ConvertStreamTypeStrToInt(streamType);
423 AudioStreamType volumeType = VolumeUtils::GetVolumeTypeFromStreamType(static_cast<AudioStreamType>(stream));
424 return AudioVolume::GetInstance()->GetVolume(sessionId, volumeType, deviceClass);
425 }
426
GetPreVolume(uint32_t sessionId)427 float GetPreVolume(uint32_t sessionId)
428 {
429 return AudioVolume::GetInstance()->GetHistoryVolume(sessionId);
430 }
431
SetPreVolume(uint32_t sessionId,float volume)432 void SetPreVolume(uint32_t sessionId, float volume)
433 {
434 AudioVolume::GetInstance()->SetHistoryVolume(sessionId, volume);
435 }
436
GetStreamVolumeFade(uint32_t sessionId,float * fadeBegin,float * fadeEnd)437 void GetStreamVolumeFade(uint32_t sessionId, float *fadeBegin, float *fadeEnd)
438 {
439 auto fade = AudioVolume::GetInstance()->GetStreamVolumeFade(sessionId);
440 *fadeBegin = fade.first;
441 *fadeEnd = fade.second;
442 }
443
SetStreamVolumeFade(uint32_t sessionId,float fadeBegin,float fadeEnd)444 void SetStreamVolumeFade(uint32_t sessionId, float fadeBegin, float fadeEnd)
445 {
446 AudioVolume::GetInstance()->SetStreamVolumeFade(sessionId, fadeBegin, fadeEnd);
447 }
448
IsSameVolume(float x,float y)449 bool IsSameVolume(float x, float y)
450 {
451 return AudioVolume::GetInstance()->IsSameVolume(x, y);
452 }
453
MonitorVolume(uint32_t sessionId,bool isOutput)454 void MonitorVolume(uint32_t sessionId, bool isOutput)
455 {
456 AudioVolume::GetInstance()->Monitor(sessionId, isOutput);
457 }
458 #ifdef __cplusplus
459 }
460 #endif