fixup! Reset dump values when pause function is called
[platform/core/api/audio-io.git] / src / cpp / CPulseAudioClient.cpp
1 /*
2  git st* Copyright (c) 2015 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17
18 #include "CAudioIODef.h"
19 #include <unistd.h>
20 #include <inttypes.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <algorithm>
24 #include <vector>
25
26 #ifdef ENABLE_DPM
27 #include <dpm/restriction.h>
28 #endif
29
30 using namespace std;
31 using namespace tizen_media_audio;
32
33 /**
34  * class CMainloopLocker
35  */
36 CPulseAudioClient::CPulseThreadLocker::CPulseThreadLocker(pa_threaded_mainloop* mainloop) : ml(nullptr) {
37     if (!mainloop || pa_threaded_mainloop_in_thread(mainloop))
38         return;
39
40     pa_threaded_mainloop_lock(mainloop);
41     ml = mainloop;
42 }
43
44 CPulseAudioClient::CPulseThreadLocker::~CPulseThreadLocker() {
45     if (ml)
46         pa_threaded_mainloop_unlock(const_cast<pa_threaded_mainloop*>(ml));
47     ml = nullptr;
48 }
49
50 /**
51  * class CPulseAudioClient
52  */
53 const char* CPulseAudioClient::CLIENT_NAME = "AUDIO_IO_PA_CLIENT";
54 const char* ec_method = "default";
55 static constexpr auto drain_wait_interval = 10000;
56 static constexpr auto drain_wait_max_count = 30;
57
58 CPulseAudioClient::CPulseAudioClient(
59         EStreamDirection      direction,
60         CPulseStreamSpec&     spec,
61         IPulseStreamListener* listener) :
62     __mDirection(direction),
63     __mSpec(spec),
64     __mpListener(listener) {
65 }
66
67 CPulseAudioClient::~CPulseAudioClient() {
68     try {
69         finalize();
70     } catch (const CAudioError& e) {
71         AUDIO_IO_LOGE("%s", e.getErrorMsg()); //LCOV_EXCL_LINE
72     }
73 }
74
75 void CPulseAudioClient::__contextStateChangeCb(pa_context* c, void* user_data) {
76     auto pClient = static_cast<CPulseAudioClient*>(user_data);
77     assert(pClient);
78     assert(c);
79
80     switch (pa_context_get_state(c)) {
81     case PA_CONTEXT_READY:
82         AUDIO_IO_LOGD("pa_context[%p] is ready", c);
83         pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
84         break;
85
86     case PA_CONTEXT_FAILED:
87     case PA_CONTEXT_TERMINATED:
88         AUDIO_IO_LOGD("pa_context[%p] is lost", c);
89         pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
90         break;
91
92     case PA_CONTEXT_UNCONNECTED:
93     case PA_CONTEXT_CONNECTING:
94     case PA_CONTEXT_AUTHORIZING:
95     case PA_CONTEXT_SETTING_NAME:
96         break;
97     }
98 }
99
100 //LCOV_EXCL_START
101 static bool __is_microphone_restricted(void) {
102     int state = 1;
103 #ifdef ENABLE_DPM
104     device_policy_manager_h dpm_h = nullptr;
105     int ret = 0;
106
107     if ((dpm_h = dpm_manager_create())) {
108         /* state: 0(disallowed), 1(allowed) */
109         if ((ret = dpm_restriction_get_microphone_state(dpm_h, &state)))
110             AUDIO_IO_LOGE("Failed to dpm_restriction_get_microphone_state(), ret(0x%x)", ret);
111         dpm_manager_destroy(dpm_h);
112         AUDIO_IO_LOGD("microphone restriction state: %d(1:allowed, 0:disallowed)", state);
113     } else {
114         AUDIO_IO_LOGE("Failed to dpm_manager_create()");
115     }
116 #endif
117     return (state ? false : true);
118 }
119 //LCOV_EXCL_STOP
120
121 void CPulseAudioClient::__streamStateChangeCb(pa_stream* s, void* user_data) {
122     assert(s);
123     assert(user_data);
124
125     auto pClient = static_cast<CPulseAudioClient*>(user_data);
126     pa_stream_state_t state = pa_stream_get_state(s);
127
128     switch (state) {
129     case PA_STREAM_UNCONNECTED:
130         break;
131
132     case PA_STREAM_CREATING:
133         break;
134
135     case PA_STREAM_READY:
136         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] is READY[%d]", pClient, s, PA_STREAM_READY);
137         pClient->__mpListener->setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
138         pClient->__mpListener->onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
139         if (pClient->__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK)
140             pClient->__mIsFirstStream = true;
141         pClient->__mIsInit = true;
142
143         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] now signaling...", pClient, s);
144         pa_threaded_mainloop_signal(pClient->__mpMainloop, 1);
145         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] signal accepted", pClient, s);
146         break;
147
148 //LCOV_EXCL_START
149     case PA_STREAM_FAILED:
150         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] is FAILED[%d]", pClient, s, PA_STREAM_FAILED);
151         pClient->__mpListener->setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
152         pClient->__mpListener->onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE,
153                                               __is_microphone_restricted());
154
155         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] now signaling...", pClient, s);
156         pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
157         break;
158 //LCOV_EXCL_STOP
159
160     case PA_STREAM_TERMINATED:
161         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] is TERMINATED[%d]", pClient, s, PA_STREAM_TERMINATED);
162         pClient->__mpListener->setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
163         pClient->__mpListener->onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
164
165         AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] now signaling...", pClient, s);
166         pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
167         break;
168
169     default:
170         AUDIO_IO_LOGW("Unexpected state[%d]", state);
171         break;
172     }
173
174     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] state[%d] done", pClient, s, state);
175 }
176
177 void CPulseAudioClient::__streamCaptureCb(pa_stream* s, size_t length, void* user_data) {
178     assert(s);
179     assert(user_data);
180
181     auto pClient = static_cast<CPulseAudioClient*>(user_data);
182     assert(pClient->__mpListener);
183     assert(pClient->__mpMainloop);
184
185     if (pClient->__mIsUsedSyncRead)
186         pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
187
188     pClient->__mpListener->onStream(pClient, length);
189 }
190
191 static void __dummy_write(pa_stream* s, size_t length) {
192     pa_stream_write(s, std::vector<char>(length).data(), length, NULL, 0LL, PA_SEEK_RELATIVE);
193 }
194
195 void CPulseAudioClient::__streamPlaybackCb(pa_stream* s, size_t length, void* user_data) {
196     assert(s);
197     assert(user_data);
198
199     auto pClient = static_cast<CPulseAudioClient*>(user_data);
200     assert(pClient->__mpListener);
201
202 #ifndef DISABLE_MOBILE_BACK_COMP
203     if (!pClient->__mIsInit) {
204         AUDIO_IO_LOGD("pa_stream[%p] is on the way to create : Write dummy, length[%zu]", s, length);
205         __dummy_write(s, length);
206         return;
207     }
208     if (pClient->isCorked()) {
209         AUDIO_IO_LOGD("pa_stream[%p] is CORKED : Write dummy, length[%zu]", s, length);
210         __dummy_write(s, length);
211         return;
212     }
213 #endif
214
215     pClient->__mpListener->onStream(pClient, length);
216
217 #ifndef DISABLE_MOBILE_BACK_COMP
218     /* If stream is not written in first callback during prepare,
219        then write dummy data to ensure the start */
220     if (pClient->__mSpec.getStreamLatency() == CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT_ASYNC &&
221         pClient->__mIsFirstStream) {
222         AUDIO_IO_LOGW("[async] pa_stream[%p] : not written in the first callback during prepare, write dummy of length[%zu]",
223                       s, length);
224         __dummy_write(s, length);
225         pClient->__mIsFirstStream = false;
226     }
227 #endif
228 }
229
230 void CPulseAudioClient::__streamLatencyUpdateCb(pa_stream* s, void* user_data) {
231     assert(s);
232     assert(user_data);
233
234     auto pClient = static_cast<CPulseAudioClient*>(user_data);
235
236 #ifdef _AUDIO_IO_DEBUG_TIMING_
237     /* FIXME : print some useful latency info */
238     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] latency updated", pClient, s);
239 #endif
240
241     pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
242 }
243
244 void CPulseAudioClient::__streamStartedCb(pa_stream* s, void* user_data) {
245     assert(s);
246     assert(user_data);
247
248     auto pClient = static_cast<CPulseAudioClient*>(user_data);
249
250     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] started", pClient, s);
251
252     pClient->__mIsStarted = true;
253 }
254
255 void CPulseAudioClient::__streamUnderflowCb(pa_stream* s, void* user_data) {
256     assert(s);
257     assert(user_data);
258
259     auto pClient = static_cast<CPulseAudioClient*>(user_data);
260
261     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] UnderFlow...", pClient, s);
262
263     pClient->__mIsStarted = false;
264 }
265
266 void CPulseAudioClient::__streamOverflowCb(pa_stream* s, void* user_data) {
267     assert(s);
268     assert(user_data);
269
270     auto pClient = static_cast<CPulseAudioClient*>(user_data);
271
272     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] OverFlow...", pClient, s);
273 }
274
275 //LCOV_EXCL_START
276 void CPulseAudioClient::__streamEventCb(pa_stream* s, const char* name, pa_proplist* pl, void* user_data) {
277     assert(s);
278     assert(user_data);
279
280     auto pClient = static_cast<CPulseAudioClient*>(user_data);
281
282     AUDIO_IO_LOGW("pClient[%p] pa_stream[%p] : name[%s], proplist[%p]", pClient, s, name, pl);
283
284     if (strcmp(name, PA_STREAM_EVENT_POP_TIMEOUT) != 0)
285         return;
286
287     if (!pClient->streamCork(true, __successCorkCb, pClient))
288         AUDIO_IO_LOGE("pa_stream[%p] : cork failed", pClient->__mpStream);
289 }
290 //LCOV_EXCL_STOP
291
292 void CPulseAudioClient::notifyCorkStatus() {
293     assert(__mpListener);
294     assert(__mpStream);
295
296     __mpListener->onCorked(this, static_cast<bool>(pa_stream_is_corked(__mpStream)));
297 }
298
299 void CPulseAudioClient::__successCorkCb(pa_stream* s, int success, void* user_data) {
300     assert(s);
301     assert(user_data);
302
303     auto pClient = static_cast<CPulseAudioClient*>(user_data);
304
305     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] success[%d] user_data[%p]", pClient, s, success, user_data);
306
307     if (success)
308         pClient->notifyCorkStatus();
309 }
310
311 void CPulseAudioClient::__successCorkCbSignal(pa_stream* s, int success, void* user_data) {
312     assert(s);
313     assert(user_data);
314
315     auto pClient = static_cast<CPulseAudioClient*>(user_data);
316
317     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] success[%d] user_data[%p]", pClient, s, success, user_data);
318
319     if (success)
320         pClient->notifyCorkStatus();
321
322     pClient->__mIsOperationSuccess = static_cast<bool>(success);
323
324     /* FIXME : verify following action without any waiting */
325     pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
326 }
327
328 void CPulseAudioClient::__successStreamCb(pa_stream* s, int success, void* user_data) {
329     assert(s);
330     assert(user_data);
331
332     auto pClient = static_cast<CPulseAudioClient*>(user_data);
333
334     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] success[%d] user_data[%p]", pClient, s, success, user_data);
335
336     pClient->__mIsOperationSuccess = static_cast<bool>(success);
337
338     /* FIXME : verify following action without any waiting */
339     pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
340 }
341
342 //LCOV_EXCL_START
343 void CPulseAudioClient::__successDrainCbInThread(pa_stream* s, int success, void* user_data) {
344     assert(s);
345     assert(user_data);
346
347     auto pClient = static_cast<CPulseAudioClient*>(user_data);
348
349     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] success[%d] user_data[%p]", pClient, s, success, user_data);
350
351     pClient->__mIsOperationSuccess = static_cast<bool>(success);
352     pClient->__mIsDraining = false;
353 }
354 //LCOV_EXCL_STOP
355
356 void CPulseAudioClient::__successDrainCb(pa_stream* s, int success, void* user_data) {
357     assert(s);
358     assert(user_data);
359
360     auto pClient = static_cast<CPulseAudioClient*>(user_data);
361
362     AUDIO_IO_LOGD("pClient[%p] pa_stream[%p] success[%d] user_data[%p]", pClient, s, success, user_data);
363
364     pClient->__mIsOperationSuccess = static_cast<bool>(success);
365     pClient->__mIsDraining = false;
366
367     pa_threaded_mainloop_signal(pClient->__mpMainloop, 0);
368 }
369
370 void CPulseAudioClient::__successVolumeCb(pa_context* c, int success, void* user_data) {
371     AUDIO_IO_LOGD("pa_context[%p] success[%d] user_data[%p]", c, success, user_data);
372 }
373
374 void CPulseAudioClient::resetStreamCallbacks() {
375     if (!__mpStream)
376         return;
377
378     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK)
379         pa_stream_set_write_callback(__mpStream, NULL, NULL);
380     else
381         pa_stream_set_read_callback(__mpStream, NULL, NULL);
382
383     pa_stream_set_latency_update_callback(__mpStream, NULL, NULL);
384     pa_stream_set_event_callback(__mpStream, NULL, NULL);
385 }
386
387 void CPulseAudioClient::resetInternalObjects() {
388     if (__mpMainloop)
389         pa_threaded_mainloop_stop(__mpMainloop);
390
391     if (__mpStream) {
392         pa_stream_disconnect(__mpStream);
393         pa_stream_unref(__mpStream);
394         __mpStream = nullptr;
395     }
396
397     if (__mpContext) {
398         pa_context_disconnect(__mpContext);
399         pa_context_unref(__mpContext);
400         __mpContext = nullptr;
401     }
402
403     if (__mpMainloop) {
404         pa_threaded_mainloop_free(__mpMainloop);
405         __mpMainloop = nullptr;
406     }
407
408     if (__mpPropList) {
409         pa_proplist_free(__mpPropList);
410         __mpPropList = nullptr;
411     }
412 }
413
414 void CPulseAudioClient::initialize() {
415     if (__mIsInit)
416         return;
417
418     int ret = 0;
419     int err = 0;
420
421     try {
422         // Allocates PA proplist
423         __mpPropList = pa_proplist_new();
424         if (!__mpPropList)
425             THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed pa_proplist_new()"); //LCOV_EXCL_LINE
426
427         // Adds values on proplist for delivery to PULSEAUDIO
428         auto [ streamType, streamIndex ] = __mSpec.getAudioInfo().getStreamProperties();
429
430         pa_proplist_sets(__mpPropList, PA_PROP_MEDIA_ROLE, streamType);
431         if (streamIndex >= 0)
432             pa_proplist_setf(__mpPropList, PA_PROP_MEDIA_PARENT_ID, "%d", streamIndex);
433
434         if (__mDirection == EStreamDirection::STREAM_DIRECTION_RECORD) {
435             /* Noise-suppression effect should be set first. */
436             std::string ns_method = __mSpec.getAudioInfo().getEffectNoiseSuppressionMethod();
437             auto [ method_reference, device_id ] = __mSpec.getAudioInfo().getEffectMethodWithReference();
438             std::string agc_method = __mSpec.getAudioInfo().getEffectAutomaticGainControlMethod();
439
440             std::string method_all = ns_method + method_reference + agc_method;
441
442             if (!method_all.empty()) {
443                 pa_proplist_setf(__mpPropList, PA_PROP_MEDIA_PREPROCESSOR_METHOD, "%s", method_all.c_str());
444                 pa_proplist_setf(__mpPropList, PA_PROP_MEDIA_ECHO_CANCEL_REFERENCE_DEVICE, "%d", device_id);
445
446                 AUDIO_IO_LOGD("preprocess will be set. method[%s], device_id[%d]", method_all.c_str(), device_id);
447             }
448         }
449
450         // Adds latency on proplist for delivery to PULSEAUDIO
451         AUDIO_IO_LOGD("LATENCY : %s[%d]", __mSpec.getStreamLatencyToString(), static_cast<int>(__mSpec.getStreamLatency()));
452         pa_proplist_setf(__mpPropList, PA_PROP_MEDIA_TIZEN_AUDIO_LATENCY, "%s", __mSpec.getStreamLatencyToString());
453
454         // Allocates PA mainloop
455         __mpMainloop = pa_threaded_mainloop_new();
456         if (!__mpMainloop)
457             THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed pa_threaded_mainloop_new()"); //LCOV_EXCL_LINE
458
459         // Allocates PA context
460         __mpContext = pa_context_new(pa_threaded_mainloop_get_api(__mpMainloop), CLIENT_NAME);
461         if (!__mpContext)
462             THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed pa_context_new()"); //LCOV_EXCL_LINE
463
464         // Sets context state changed callback
465         pa_context_set_state_callback(__mpContext, __contextStateChangeCb, this);
466
467         // Connects this client with PA server
468         if (pa_context_connect(__mpContext, NULL, PA_CONTEXT_NOFLAGS, NULL) < 0)
469             THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed pa_context_connect()"); //LCOV_EXCL_LINE
470
471         // LOCK for synchronous connection
472         CPulseThreadLocker locker{__mpMainloop};
473
474         // Start mainloop
475         if (pa_threaded_mainloop_start(__mpMainloop) < 0) {
476 //LCOV_EXCL_START
477             THROW_ERROR_MSG(CAudioError::EError::ERROR_FAILED_OPERATION, "Failed pa_threaded_mainloop_start()");
478 //LCOV_EXCL_STOP
479         }
480
481         // Connection process is asynchronously
482         // So, this function will be waited when occurred context state change event
483         // If I got a signal, do next processing
484         while (true) {
485             pa_context_state_t state;
486             state = pa_context_get_state(__mpContext);
487
488             if (state == PA_CONTEXT_READY)
489                 break;
490
491             if (!PA_CONTEXT_IS_GOOD(state)) {
492 //LCOV_EXCL_START
493                 err = pa_context_errno(__mpContext);
494                 THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
495                                        "pa_context's state is not good : err[%d]", err);
496 //LCOV_EXCL_STOP
497             }
498
499             /* Wait until the context is ready */
500             pa_threaded_mainloop_wait(__mpMainloop);
501         }
502
503         AUDIO_IO_LOGD("pa_context[%p] now ready", __mpContext);
504
505         // Allocates PA stream
506         pa_sample_spec ss   = __mSpec.getSampleSpec();
507         pa_channel_map map  = __mSpec.getChannelMap();
508
509         __mpStream = pa_stream_new_with_proplist(__mpContext, __mSpec.getStreamName(), &ss, &map, __mpPropList);
510         if (!__mpStream) {
511 //LCOV_EXCL_START
512             THROW_ERROR_MSG(CAudioError::EError::ERROR_FAILED_OPERATION, "Failed pa_stream_new_with_proplist()");
513 //LCOV_EXCL_STOP
514         }
515
516         // Sets stream callbacks
517         pa_stream_set_state_callback(__mpStream, __streamStateChangeCb, this);
518         pa_stream_set_read_callback(__mpStream, __streamCaptureCb, this);
519         pa_stream_set_write_callback(__mpStream, __streamPlaybackCb, this);
520         pa_stream_set_latency_update_callback(__mpStream, __streamLatencyUpdateCb, this);
521         pa_stream_set_event_callback(__mpStream, __streamEventCb, this);
522         if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK) {
523             pa_stream_set_started_callback(__mpStream, __streamStartedCb, this);
524             pa_stream_set_underflow_callback(__mpStream, __streamUnderflowCb, this);
525             pa_stream_set_overflow_callback(__mpStream, __streamOverflowCb, this);
526         }
527
528         // Connect stream with PA Server
529
530         if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK) {
531             auto flags = static_cast<pa_stream_flags_t>(
532                     PA_STREAM_INTERPOLATE_TIMING |
533                     PA_STREAM_ADJUST_LATENCY     |
534 #ifndef DISABLE_MOBILE_BACK_COMP
535                     PA_STREAM_START_CORKED       |
536 #endif
537                     PA_STREAM_AUTO_TIMING_UPDATE);
538
539             ret = pa_stream_connect_playback(__mpStream, NULL, NULL, flags, NULL, NULL);
540         } else {
541             auto flags = static_cast<pa_stream_flags_t>(
542                     PA_STREAM_INTERPOLATE_TIMING |
543                     PA_STREAM_ADJUST_LATENCY     |
544 #ifndef DISABLE_MOBILE_BACK_COMP
545                     PA_STREAM_START_CORKED       |
546 #endif
547                     PA_STREAM_AUTO_TIMING_UPDATE);
548
549             ret = pa_stream_connect_record(__mpStream, NULL, NULL, flags);
550         }
551         if (ret != 0) {
552 //LCOV_EXCL_START
553             err = pa_context_errno(__mpContext);
554             THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION,
555                                    "Failed pa_stream_connect() : err[%d]", err);
556 //LCOV_EXCL_STOP
557         }
558
559         AUDIO_IO_LOGD("pa_stream[%p] now wait for stream ready...", __mpStream);
560
561         while (true) {
562             pa_stream_state_t state = pa_stream_get_state(__mpStream);
563             AUDIO_IO_LOGD("pa_stream[%p] current state[%d]", __mpStream, state);
564
565             if (state == PA_STREAM_READY)
566                 break;
567
568             if (!PA_STREAM_IS_GOOD(state)) {
569                 err = pa_context_errno(__mpContext);
570                 if (err == PA_ERR_ACCESS)
571                     THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_DEVICE_POLICY_RESTRICTION,
572                                            "pa_stream's state is not good : err[%d]", err);
573                 else
574                     THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,   //LCOV_EXCL_LINE
575                                            "pa_stream's state is not good : err[%d]", err); //LCOV_EXCL_LINE
576             }
577
578             /* Wait until the stream is ready */
579             AUDIO_IO_LOGD("pa_mainloop[%p] signal wait...", __mpMainloop);
580             pa_threaded_mainloop_wait(__mpMainloop);
581             AUDIO_IO_LOGD("pa_mainloop[%p] signal wait done", __mpMainloop);
582         }
583         pa_threaded_mainloop_accept(__mpMainloop);
584
585         // __mIsInit = true;  // Moved to __streamStateChangeCb()
586     } catch (const CAudioError& e) {
587 //LCOV_EXCL_START
588         resetStreamCallbacks();
589         resetInternalObjects();
590         throw;
591 //LCOV_EXCL_END
592     }
593
594     AUDIO_IO_LOGD("Done : pa_context[%p], pa_mainloop[%p], pa_stream[%p]", __mpContext, __mpMainloop, __mpStream);
595 }
596
597 void CPulseAudioClient::finalize() {
598     AUDIO_IO_LOGD("pa_context[%p], pa_mainloop[%p], pa_stream[%p]", __mpContext, __mpMainloop, __mpStream);
599
600     if (!__mpMainloop)
601         return;
602
603     bool is_in_thread = isInThread();
604     if (!is_in_thread)
605         pa_threaded_mainloop_lock(__mpMainloop);
606
607     __mIsInit = false;
608
609     resetStreamCallbacks();
610
611     if (!is_in_thread)
612         pa_threaded_mainloop_unlock(__mpMainloop);
613
614     /* Wait for drain complete if draining before finalize */
615     if (__mIsDraining) {
616         unsigned int drain_wait_count = 0;
617         while (__mIsDraining && drain_wait_count++ < drain_wait_max_count)
618             usleep(drain_wait_interval);
619         AUDIO_IO_LOGD("wait for drain complete!!!! [%d * %d usec]",
620                       drain_wait_count, drain_wait_interval);
621     }
622
623     resetInternalObjects();
624
625     AUDIO_IO_LOGD("Done");
626 }
627
628 int CPulseAudioClient::read(void* buffer, size_t length) {
629     if (!__mIsInit)
630         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); //LCOV_EXCL_LINE
631
632     checkRunningState();
633
634     if (!buffer)
635         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT, "invalid parameter : buffer[%p]", buffer); //LCOV_EXCL_LINE
636
637     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK)
638         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "playback client can't use this function"); //LCOV_EXCL_LINE
639
640     size_t lengthIter = length;
641     int ret = 0;
642
643     __mIsUsedSyncRead = true;
644
645     try {
646         CPulseThreadLocker locker{__mpMainloop};
647
648         while (lengthIter > 0) {
649             size_t l;
650
651             while (!__mpSyncReadDataPtr) {
652                 ret = pa_stream_peek(__mpStream, &__mpSyncReadDataPtr, &__mSyncReadLength);
653                 if (ret != 0)
654                     THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION, "Failed pa_stream_peek() : ret[%d]", ret); //LCOV_EXCL_LINE
655
656                 if (__mSyncReadLength <= 0) {
657 #ifdef _AUDIO_IO_DEBUG_TIMING_
658                     AUDIO_IO_LOGD("readLength(%zu byte) is not valid. wait...", __mSyncReadLength);
659 #endif
660                     pa_threaded_mainloop_wait(__mpMainloop);
661                 } else if (!__mpSyncReadDataPtr) {
662 // LCOV_EXCL_START
663                     // Data peeked, but it doesn't have any data
664                     ret = pa_stream_drop(__mpStream);
665                     if (ret != 0)
666                         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION, "Failed pa_stream_drop() : ret[%d]", ret);
667 //LCOV_EXCL_STOP
668                 } else {
669                     __mSyncReadIndex = 0;
670                 }
671             }
672
673             l = min(__mSyncReadLength, lengthIter);
674
675             // Copy partial pcm data on out parameter
676 #ifdef _AUDIO_IO_DEBUG_TIMING_
677             AUDIO_IO_LOGD("memcpy() that a peeked buffer[%p], index[%zu], length[%zu] on out buffer", (const uint8_t*)(__mpSyncReadDataPtr) + __mSyncReadIndex, __mSyncReadIndex, l);
678 #endif
679             memcpy(buffer, (const uint8_t*)__mpSyncReadDataPtr + __mSyncReadIndex, l);
680
681             // Move next position
682             buffer = (uint8_t*)buffer + l;
683             lengthIter -= l;
684
685             // Adjusts the rest length
686             __mSyncReadIndex  += l;
687             __mSyncReadLength -= l;
688
689             if (__mSyncReadLength == 0) {
690 #ifdef _AUDIO_IO_DEBUG_TIMING_
691                 AUDIO_IO_LOGD("__mSyncReadLength is zero, do drop()");
692 #endif
693                 ret = pa_stream_drop(__mpStream);
694                 if (ret != 0)
695                     THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION, //LCOV_EXCL_LINE
696                                            "Failed pa_stream_drop() : ret[%d]", ret);     //LCOV_EXCL_LINE
697
698                 // Reset the internal pointer
699                 __mpSyncReadDataPtr = nullptr;
700                 __mSyncReadLength   = 0;
701                 __mSyncReadIndex    = 0;
702             }
703         }  // End of while (lengthIter > 0)
704
705         __mIsUsedSyncRead = false;
706     } catch (const CAudioError& e) {
707 // LCOV_EXCL_START
708         __mIsUsedSyncRead = false;
709         throw;
710 // LCOV_EXCL_STOP
711     }
712
713     return length;
714 }
715
716 int CPulseAudioClient::peek(const void** buffer, size_t* length) {
717     if (!__mIsInit)
718         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
719
720     checkRunningState();
721
722     if (!buffer || !length)
723         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,                   // LCOV_EXCL_LINE
724                                "invalid parameter : buffer[%p], length[%p]", buffer, length); // LCOV_EXCL_LINE
725
726     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK)
727         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "playback can't use this function"); // LCOV_EXCL_LINE
728
729     CPulseThreadLocker locker{__mpMainloop};
730
731     int ret = pa_stream_peek(__mpStream, buffer, length);
732
733
734 #ifdef _AUDIO_IO_DEBUG_TIMING_
735     AUDIO_IO_LOGD("buffer[%p], length[%zu]", *buffer, *length);
736 #endif
737
738     if (ret < 0)
739         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION, "Failed pa_stream_peek() : err[%d]", ret); //LCOV_EXCL_LINE
740
741     return ret;
742 }
743
744 int CPulseAudioClient::drop() {
745     if (!__mIsInit)
746         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
747
748 #ifdef _AUDIO_IO_DEBUG_TIMING_
749     AUDIO_IO_LOGD("");
750 #endif
751
752     checkRunningState();
753
754     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK)
755         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "playback can't use this function"); // LCOV_EXCL_LINE
756
757     CPulseThreadLocker locker{__mpMainloop};
758
759     int ret = pa_stream_drop(__mpStream);
760     if (ret < 0)
761         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION, "Failed pa_stream_drop() : err[%d]", ret); //LCOV_EXCL_LINE
762
763     return ret;
764 }
765
766 int CPulseAudioClient::write(const void* data, size_t length) {
767     if (!data)
768         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_ARGUMENT, "invalid parameter"); // LCOV_EXCL_LINE
769
770     if (__mDirection == EStreamDirection::STREAM_DIRECTION_RECORD)
771         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "playback can't use this function"); // LCOV_EXCL_LINE
772
773     int ret = 0;
774
775 #ifdef _AUDIO_IO_DEBUG_TIMING_
776     AUDIO_IO_LOGD("data[%p], length[%zu], First[%d]", data, length, __mIsFirstStream);
777 #endif
778
779     if (!isInThread()) {
780         CPulseThreadLocker locker{__mpMainloop};
781         if (pa_stream_is_corked(__mpStream)) {
782             AUDIO_IO_LOGW("pa_stream[%p] is corked...do uncork here first!!!!", __mpStream);
783             if (!streamCork(false, __successCorkCb, this))
784                 AUDIO_IO_LOGE("pa_stream[%p] : uncork failed", __mpStream);
785         }
786
787         ret = pa_stream_write(__mpStream, data, length, NULL, 0LL, PA_SEEK_RELATIVE);
788     } else {
789 // LCOV_EXCL_START
790         if (pa_stream_is_corked(__mpStream)) {
791             AUDIO_IO_LOGW("pa_stream[%p] is corked...do uncork here first!!!!", __mpStream);
792
793             if (!streamCork(false, __successCorkCb, this))
794                 AUDIO_IO_LOGE("pa_stream[%p] : uncork failed", __mpStream);
795         }
796
797         if (__mIsFirstStream) {
798             const pa_buffer_attr* attr = pa_stream_get_buffer_attr(__mpStream);
799             uint32_t prebuf = attr->prebuf;
800             // Compensate amount of prebuf in first stream callback when audio-io use prebuf(-1)
801             // Because a stream will never start when an application wrote less than prebuf at first
802             if (length < prebuf)
803                 __dummy_write(__mpStream, prebuf - length);
804
805             __mIsFirstStream = false;
806             AUDIO_IO_LOGW("FIRST STREAM CALLBACK : length[%zu], prebuf[%d], dummy[%zu]",
807                           length, prebuf, (length < prebuf) ? prebuf - length : 0);
808         }
809         ret = pa_stream_write(__mpStream, data, length, NULL, 0LL, PA_SEEK_RELATIVE);
810 // LCOV_EXCL_STOP
811     }
812
813     if (ret < 0)
814         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION, "Failed pa_stream_write() : err[%d]", ret); //LCOV_EXCL_LINE
815
816     return ret;
817 }
818
819 bool CPulseAudioClient::streamCork(bool cork, pa_stream_success_cb_t cb, void* user_data) {
820     pa_operation* o = pa_stream_cork(__mpStream, static_cast<int>(cork), cb, user_data);
821     if (!o)
822         return false;
823     pa_operation_unref(o);
824
825     return true;
826 }
827
828 void CPulseAudioClient::cork(bool cork) {
829     AUDIO_IO_LOGD("cork[%d]", cork);
830
831     if (!__mIsInit)
832         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
833
834     if (isInThread())
835         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "This operation is not supported in callback"); // LCOV_EXCL_LINE
836
837     checkRunningState();
838
839     // Set __mIsFirstStream flag when uncork(resume) stream, because prebuf will be enable again
840     if (!cork)
841         __mIsFirstStream = true;
842
843     CPulseThreadLocker locker{__mpMainloop};
844
845     /* FIXME: wait for completion like drain? */
846     if (!streamCork(cork, __successCorkCbSignal, this)) {
847         AUDIO_IO_LOGE("pa_stream[%p] : cork[%d] failed", __mpStream, cork);
848         return;
849     }
850
851     AUDIO_IO_LOGD("cork[%d] requested, is-stream-started[%d]", cork, __mIsStarted);
852
853     /* FIXME: need to consider manual pa_stream_trigger() when uncorking but stream is not started yet,
854               because even uncorked here, it is still prebuf state so, empty-pop will not be checked in this situation */
855 }
856
857 bool CPulseAudioClient::isCorked() {
858     if (!__mIsInit)
859         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
860
861     checkRunningState();
862
863     CPulseThreadLocker locker{__mpMainloop};
864
865     int isCorked = pa_stream_is_corked(__mpStream);
866
867 #ifdef _AUDIO_IO_DEBUG_TIMING_
868     AUDIO_IO_LOGD("isCorked[%d]", isCorked);
869 #endif
870     return static_cast<bool>(isCorked);
871 }
872
873 bool CPulseAudioClient::drain() {
874     if (!__mIsInit)
875         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
876
877     checkRunningState();
878
879     if (isCorked()) {
880         AUDIO_IO_LOGW("Corked...");
881         return true;
882     }
883
884     if (__mIsDraining) {
885         AUDIO_IO_LOGW("already draining...");
886         return true;
887     }
888
889     if (!__mIsStarted) {
890         AUDIO_IO_LOGW("stream not started yet...skip drain");
891         return true;
892     }
893
894     if (!isInThread()) {
895         AUDIO_IO_LOGD("drain");
896         CPulseThreadLocker locker{__mpMainloop};
897
898         pa_operation* o = pa_stream_drain(__mpStream, __successDrainCb, this);
899         __mIsDraining = true;
900         while (pa_operation_get_state(o) == PA_OPERATION_RUNNING)
901             pa_threaded_mainloop_wait(__mpMainloop);
902         pa_operation_unref(o);
903
904         AUDIO_IO_LOGD("drain done");
905     } else {
906         AUDIO_IO_LOGD("drain in thread");
907         pa_operation* o = pa_stream_drain(__mpStream, __successDrainCbInThread, this);
908         if (!o) {
909             AUDIO_IO_LOGE("pa_stream[%p] : drain failed", __mpStream);
910             return false;
911         }
912         pa_operation_unref(o);
913         __mIsDraining = true;
914     }
915
916     return true;
917 }
918
919 bool CPulseAudioClient::flush() {
920     if (!__mIsInit)
921         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
922
923     checkRunningState();
924
925     CPulseThreadLocker locker{__mpMainloop};
926
927     /* FIXME: wait for completion like drain? */
928     pa_operation* o = pa_stream_flush(__mpStream, __successStreamCb, this);
929     if (!o) {
930         AUDIO_IO_LOGE("pa_stream[%p] : flush failed", __mpStream);
931         return false;
932     }
933     pa_operation_unref(o);
934
935     return true;
936 }
937
938 size_t CPulseAudioClient::getWritableSize() {
939     if (!__mIsInit)
940         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized"); // LCOV_EXCL_LINE
941
942     checkRunningState();
943
944     if (__mDirection != EStreamDirection::STREAM_DIRECTION_PLAYBACK)
945         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "This client is used for Playback"); // LCOV_EXCL_LINE
946
947     CPulseThreadLocker locker{__mpMainloop};
948
949     return pa_stream_writable_size(__mpStream);
950 }
951
952 void CPulseAudioClient::checkRunningState() {
953     if (!__mpContext || !PA_CONTEXT_IS_GOOD(pa_context_get_state(__mpContext)))
954         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_NOT_INITIALIZED,                       // LCOV_EXCL_LINE
955                                "The context[%p] is not created or not good state", __mpContext); // LCOV_EXCL_LINE
956
957     if (!__mpStream || !PA_STREAM_IS_GOOD(pa_stream_get_state(__mpStream)))
958         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_NOT_INITIALIZED,                     // LCOV_EXCL_LINE
959                                "The stream[%p] is not created or not good state", __mpStream); // LCOV_EXCL_LINE
960
961     if (pa_context_get_state(__mpContext) != PA_CONTEXT_READY || pa_stream_get_state(__mpStream) != PA_STREAM_READY)
962         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_NOT_INITIALIZED,                                   // LCOV_EXCL_LINE
963                                "The context[%p] or stream[%p] state is not ready", __mpContext, __mpStream); // LCOV_EXCL_LINE
964
965 #ifdef _AUDIO_IO_DEBUG_TIMING_
966     AUDIO_IO_LOGD("This client is running");
967 #endif
968 }
969
970 bool CPulseAudioClient::isInThread() const {
971     if (!__mIsInit)
972         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "__mIsInit is null");
973
974     if (!__mpMainloop)
975         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "__mpMainloop is null");
976
977     int ret = pa_threaded_mainloop_in_thread(__mpMainloop);
978
979 #ifdef _AUDIO_IO_DEBUG_TIMING_
980     AUDIO_IO_LOGD("isInThread[%d]", ret);
981 #endif
982     return static_cast<bool>(ret);
983 }
984
985 //LCOV_EXCL_START
986 size_t CPulseAudioClient::getReadableSize() {
987     if (!__mIsInit)
988         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized");
989
990     checkRunningState();
991
992     if (__mDirection != EStreamDirection::STREAM_DIRECTION_RECORD)
993         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "This client is used for Capture");
994
995     CPulseThreadLocker locker{__mpMainloop};
996
997     return pa_stream_readable_size(__mpStream);
998 }
999
1000 size_t CPulseAudioClient::getBufferSize() {
1001     if (!__mIsInit)
1002         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized");
1003
1004     checkRunningState();
1005
1006     size_t buffer_size = 0;
1007     CPulseThreadLocker locker{__mpMainloop};
1008
1009     const pa_buffer_attr* attr = pa_stream_get_buffer_attr(__mpStream);
1010     if (!attr)
1011         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION,
1012                                "Failed pa_stream_get_buffer_attr() : err[%d]", pa_context_errno(__mpContext));
1013
1014     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK) {
1015         buffer_size = attr->tlength;
1016         AUDIO_IO_LOGD("PLAYBACK buffer size[%zu]", buffer_size);
1017     } else {
1018         buffer_size = attr->fragsize;
1019         AUDIO_IO_LOGD("RECORD buffer size[%zu]", buffer_size);
1020     }
1021
1022     return buffer_size;
1023 }
1024
1025 pa_usec_t CPulseAudioClient::getLatency() {
1026     if (!__mIsInit)
1027         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized");
1028
1029     checkRunningState();
1030
1031     pa_usec_t latency = 0;
1032     int negative = 0;
1033
1034     if (!isInThread()) {
1035         if (pa_stream_get_latency(__mpStream, &latency, &negative) < 0) {
1036             int _err = pa_context_errno(__mpContext);
1037             if (_err != PA_ERR_NODATA)
1038                 THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION,
1039                                        "Failed pa_stream_get_latency() : err[%d]", _err);
1040         }
1041         return negative ? 0 : latency;
1042     }
1043
1044     CPulseThreadLocker locker{__mpMainloop};
1045
1046     while (pa_stream_get_latency(__mpStream, &latency, &negative) < 0) {
1047         int _err = pa_context_errno(__mpContext);
1048         if (_err != PA_ERR_NODATA)
1049             THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_FAILED_OPERATION,
1050                                    "Failed pa_stream_get_latency() : err[%d]", _err);
1051         /* Wait until latency data is available again */
1052         pa_threaded_mainloop_wait(__mpMainloop);
1053     }
1054
1055     return negative ? 0 : latency;
1056 }
1057
1058 pa_usec_t CPulseAudioClient::getFinalLatency() {
1059     if (!__mIsInit)
1060         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized");
1061
1062     checkRunningState();
1063
1064     pa_usec_t latency = 0;
1065
1066     CPulseThreadLocker locker{__mpMainloop};
1067
1068     uint32_t ver = pa_context_get_server_protocol_version(__mpContext);
1069     if (ver < 13)
1070         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_NOT_SUPPORTED, "This version(ver.%d) is not supported", ver);
1071
1072     const pa_buffer_attr* buffer_attr = pa_stream_get_buffer_attr(__mpStream);
1073     const pa_sample_spec* sample_spec = pa_stream_get_sample_spec(__mpStream);
1074     const pa_timing_info* timing_info = pa_stream_get_timing_info(__mpStream);
1075
1076     if (!buffer_attr || !sample_spec || !timing_info)
1077         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_OUT_OF_MEMORY,
1078                 "Failed to get buffer_attr[%p] or sample_spec[%p] or timing_info[%p] from a pa_stream",
1079                 buffer_attr, sample_spec, timing_info);
1080
1081     if (__mDirection == EStreamDirection::STREAM_DIRECTION_PLAYBACK) {
1082         latency = (pa_bytes_to_usec(buffer_attr->tlength, sample_spec) + timing_info->configured_sink_usec);
1083         AUDIO_IO_LOGD("FINAL PLAYBACK LATENCY[%" PRIu64 "]", latency);
1084     } else {
1085         latency = (pa_bytes_to_usec(buffer_attr->fragsize, sample_spec) + timing_info->configured_source_usec);
1086         AUDIO_IO_LOGD("FINAL RECORD LATENCY[%" PRIu64 "]", latency);
1087     }
1088
1089     return latency;
1090 }
1091 //LCOV_EXCL_STOP
1092
1093 void CPulseAudioClient::applyRecordVolume(double volume) {
1094
1095     if (!__mIsInit)
1096         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "CPulseAudioClient is not initialized");
1097
1098     checkRunningState();
1099
1100     if (__mDirection != EStreamDirection::STREAM_DIRECTION_RECORD)
1101         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_SUPPORTED, "The Playback client can't use this function"); //LCOV_EXCL_LINE
1102
1103     CPulseThreadLocker locker{__mpMainloop};
1104
1105     pa_cvolume cv = { 0, };
1106     pa_volume_t v = PA_VOLUME_NORM * volume;
1107
1108     pa_cvolume_set(&cv, __mSpec.getChannelMap().channels, v);
1109
1110     pa_operation_unref(pa_context_set_source_output_volume(__mpContext,
1111             pa_stream_get_index(__mpStream), &cv, __successVolumeCb, NULL));
1112 }