move __IsReady function into the critical section
[platform/core/api/audio-io.git] / src / cpp / CAudioOutput.cpp
1 /*
2  * 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 <new>
19
20 #include "CAudioIODef.h"
21 #include <sched.h>
22 #include <chrono>
23
24 using namespace std;
25 using namespace tizen_media_audio;
26
27 static constexpr auto cond_wait_ms = 200ms;
28 static constexpr auto writable_timeout_s = 5s;
29
30 /**
31  * class CAudioOutput
32  */
33
34 CAudioOutput::CAudioOutput(CAudioInfo& info) :
35     CAudioIO(info),
36     __mIsUsedSyncWrite(false),
37     __mIsInit(false) {
38     mDirection = CAudioInfo::EAudioDirection::AUDIO_DIRECTION_OUT;
39 }
40
41 void CAudioOutput::onStream(CPulseAudioClient* pClient, size_t length) {
42     assert(pClient);
43
44     /*
45      * Does not call CAudioIO::onStream() for synchronization
46      * if a user is using write()
47      */
48     if (__mIsUsedSyncWrite) {
49 #ifdef _AUDIO_IO_DEBUG_TIMING_
50         AUDIO_IO_LOGD("Sync Write Mode! - signal! - pClient:[%p], length:[%zu]", pClient, length);
51 #endif
52         { /* NOTE: this block needs to unlock right after lock */
53             std::lock_guard<std::mutex> cond_guard(mCondMutex);
54         }
55         mCond.notify_one();
56         return;
57     }
58
59     /*
60      * Accrues callback function
61      */
62 #ifdef _AUDIO_IO_DEBUG_TIMING_
63     AUDIO_IO_LOGD("pClient:[%p], length:[%zu]", pClient, length);
64 #endif
65     CAudioIO::onStream(pClient, length);
66 }
67
68 void CAudioOutput::__setInit(bool flag) noexcept {
69     __mIsInit = flag;
70 }
71
72 bool CAudioOutput::__IsInit() noexcept {
73     return (CAudioIO::isInit() && __mIsInit);
74 }
75
76 bool CAudioOutput::__IsReady() noexcept {
77     return CAudioIO::IsReady();
78 }
79
80 void CAudioOutput::initialize() {
81     if (__IsInit())
82         return;
83
84     try {
85         CAudioIO::initialize();
86         __setInit(true);
87     } catch (const CAudioError& e) {
88 //LCOV_EXCL_START
89         finalize();
90         throw;
91 //LCOV_EXCL_STOP
92     }
93
94     CAudioIO::setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
95     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
96 }
97
98 void CAudioOutput::finalize() {
99     if (!__IsInit()) {
100         AUDIO_IO_LOGD("Did not initialize");
101         return;
102     }
103
104     CAudioIO::finalize();
105     __setInit(false);
106 }
107
108 void CAudioOutput::prepare() {
109     std::lock_guard<std::mutex> mutex(mMutex);
110
111     if (!__IsInit())
112         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "Did not initialize CAudioOutput"); //LCOV_EXCL_LINE
113
114     if (__IsReady()) {
115 //LCOV_EXCL_START
116         AUDIO_IO_LOGD("Already prepared CAudioOutput");
117         CAudioIO::prepare();
118         return;
119 //LCOV_EXCL_STOP
120     }
121
122     /* Check invalid AudioType */
123     CAudioInfo::EAudioType audioType = mAudioInfo.getAudioType();
124     if (audioType < CAudioInfo::EAudioType::AUDIO_OUT_TYPE_MEDIA ||
125         audioType >= CAudioInfo::EAudioType::AUDIO_TYPE_MAX)
126         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,
127                                "The audioType is invalid [type:%d]", static_cast<int>(audioType));
128
129     try {
130         /* Init StreamSpec */
131         CPulseStreamSpec::EStreamLatency streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT;
132 #ifndef DISABLE_MOBILE_BACK_COMP
133         if (!mStreamCallback.onStream) {
134             AUDIO_IO_LOGD("Set Stream Spec : CPulseStreamSpec::STREAM_LATENCY_OUTPUT_DEFAULT");
135             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT;
136         } else {
137             AUDIO_IO_LOGD("Set Stream Spec : CPulseStreamSpec::STREAM_LATENCY_OUTPUT_DEFAULT_ASYNC");
138             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT_ASYNC;
139         }
140 #endif
141         /* Override the default value by audio type */
142         if (audioType == CAudioInfo::EAudioType::AUDIO_OUT_TYPE_VOIP)
143             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_VOIP;
144         else if (audioType == CAudioInfo::EAudioType::AUDIO_OUT_TYPE_MEDIA_NETWORK_SOURCE)
145             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_HIGH;
146
147         CPulseStreamSpec spec(streamSpec, mAudioInfo);
148
149         mpPulseAudioClient = new CPulseAudioClient(CPulseAudioClient::EStreamDirection::STREAM_DIRECTION_PLAYBACK,
150                                                    spec, this);
151         mpPulseAudioClient->initialize();
152 #ifndef DISABLE_MOBILE_BACK_COMP
153         /* Uncork stream which is created with CORKED flag */
154         mpPulseAudioClient->cork(false);
155 #endif
156
157         CAudioIO::prepare();
158     } catch (const CAudioError& e) {
159 //LCOV_EXCL_START
160         SAFE_FINALIZE(mpPulseAudioClient);
161         SAFE_DELETE(mpPulseAudioClient);
162         throw;
163 //LCOV_EXCL_STOP
164     } catch (const std::bad_alloc&) {
165         THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed to allocate CPulseAudioClient object");
166     }
167 }
168
169 void CAudioOutput::unprepare() {
170     std::unique_lock<std::mutex> mutex(mMutex);
171
172     if (!__IsInit())
173         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, //LCOV_EXCL_LINE
174                         "Did not initialize CAudioOutput");         //LCOV_EXCL_LINE
175
176     if (!__IsReady()) {
177         AUDIO_IO_LOGD("Already unprepared");
178         return;
179     }
180
181     CAudioIO::unprepare();
182
183     if (mpPulseAudioClient && mpPulseAudioClient->isInThread())
184         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't unprepare inside pulseaudio thread");
185
186     SAFE_FINALIZE(mpPulseAudioClient);
187     SAFE_DELETE(mpPulseAudioClient);
188
189     CAudioIO::setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
190
191     mutex.unlock();
192
193     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
194 }
195
196 void CAudioOutput::pause() {
197     std::unique_lock<std::mutex> mutex(mMutex);
198
199     if (!__IsInit() || !__IsReady())
200         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
201                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
202
203     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING)
204         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_STATE,
205                         "Can't pause if not in Running state");
206
207     if (mpPulseAudioClient->isInThread())
208         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't pause in thread"); //LCOV_EXCL_LINE
209
210     CAudioIO::pause();
211     CAudioIO::setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED);
212
213     mutex.unlock();
214
215     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED);
216 }
217
218 void CAudioOutput::resume() {
219     std::unique_lock<std::mutex> mutex(mMutex);
220
221     if (!__IsInit() || !__IsReady())
222         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
223                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
224
225     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED)
226         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_STATE,
227                         "Can't resume if not in Paused state");
228
229     if (mpPulseAudioClient->isInThread())
230         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't resume in thread"); //LCOV_EXCL_LINE
231
232     CAudioIO::resume();
233     CAudioIO::setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
234
235     mutex.unlock();
236
237     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
238 }
239
240 void CAudioOutput::drain() {
241     std::lock_guard<std::mutex> mutex(mMutex);
242
243     if (!__IsInit() || !__IsReady())
244         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
245                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
246
247     if (mStreamCallback.onStream)
248         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION, "async type don't support drain");
249
250     mpPulseAudioClient->drain();
251 }
252
253 void CAudioOutput::flush() {
254     std::lock_guard<std::mutex> mutex(mMutex);
255
256     if (!__IsInit() || !__IsReady())
257         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
258                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
259
260     CAudioIO::flush();
261 }
262
263 int CAudioOutput::getBufferSize() {
264     if (!__IsInit())
265         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
266                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
267
268     /* FIXME : return calculated size here to satisfy backward compatibility */
269     return (mAudioInfo.getSampleRate() * DEFAULT_PERIOD_SIZE) / 1000 * mAudioInfo.getSampleSize();
270 }
271
272 size_t CAudioOutput::write(const void* buffer, size_t length) {
273     std::unique_lock<std::mutex> mutex(mMutex, std::defer_lock);
274
275     if (mpPulseAudioClient && !mpPulseAudioClient->isInThread())
276         mutex.lock();
277
278     if (!__IsInit() || !__IsReady())
279         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
280                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
281
282     if (!buffer)
283         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,
284                                "Parameters are invalid - buffer:%p, length:%zu", buffer, length);
285
286     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING)
287         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION,
288                         "Can't write if not in Running state");
289
290     /* When write() is called in PulseAudio callback, bypass a pcm data to CPulseAudioClient (For Asynchronous) */
291     if (mpPulseAudioClient && mpPulseAudioClient->isInThread()) {
292         int ret = mpPulseAudioClient->write(buffer, length);
293         if (ret < 0)
294             THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
295                                    "The written result is invalid ret:%d", ret);
296 #ifdef _AUDIO_IO_DEBUG_TIMING_
297         AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, length);
298 #endif
299         return length;
300     }
301
302     try {
303
304         // If another thread did call unprepare, do not write
305         if (!mpPulseAudioClient)
306             THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, //LCOV_EXCL_LINE
307                             "Did not initialize CPulseAudioClient");    //LCOV_EXCL_LINE
308
309         // Sets synchronous flag
310         __mIsUsedSyncWrite = true;
311         size_t lengthIter = length;
312
313         while (lengthIter > 0) {
314             size_t l;
315             unsigned int timeouts = 0;
316
317             while ((l = mpPulseAudioClient->getWritableSize()) == 0) {
318 #ifdef _AUDIO_IO_DEBUG_TIMING_
319                 AUDIO_IO_LOGD("writableSize is [%zu].. wait", l);
320 #endif
321                 std::unique_lock<std::mutex> cond_mutex(mCondMutex);
322                 if (mCond.wait_for(cond_mutex, cond_wait_ms) == std::cv_status::timeout) {
323 //LCOV_EXCL_START
324                     AUDIO_IO_LOGW("[%2u] timeout expired for waiting %zu ms of write available...", ++timeouts,
325                                   static_cast<size_t>(std::chrono::duration_cast<std::chrono::milliseconds>(cond_wait_ms).count()));
326                     if ((cond_wait_ms * timeouts) > writable_timeout_s)
327                         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
328                                                "writable size is 0 for more than %zu seconds...",
329                                                static_cast<size_t>(std::chrono::duration_cast<std::chrono::seconds>(writable_timeout_s).count()));
330 //LCOV_EXCL_STOP
331                 }
332             }
333
334             if (l > lengthIter)
335                 l = lengthIter;
336
337 #ifdef _AUDIO_IO_DEBUG_TIMING_
338             AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, l);
339 #endif
340
341             int ret = mpPulseAudioClient->write(buffer, l);
342             if (ret < 0)
343                 THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,//LCOV_EXCL_LINE
344                                        "The written result is invalid ret:%d", ret); //LCOV_EXCL_LINE
345
346             buffer = static_cast<const uint8_t*>(buffer) + l;
347             lengthIter -= l;
348         }  // End of while (length > 0)
349
350         __mIsUsedSyncWrite = false;
351         mutex.unlock();
352
353         sched_yield();
354     } catch (const CAudioError& e) {
355         __mIsUsedSyncWrite = false;
356         throw;
357     }
358
359     return length;
360 }