Add writable-size wait timeout log / Terminate write operation with accumulated timeouts
[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         {
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::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
95 }
96
97 void CAudioOutput::finalize() {
98     if (!__IsInit()) {
99         AUDIO_IO_LOGD("Did not initialize");
100         return;
101     }
102
103     CAudioIO::finalize();
104     __setInit(false);
105 }
106
107 void CAudioOutput::prepare() {
108     if (!__IsInit())
109         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, "Did not initialize CAudioOutput"); //LCOV_EXCL_LINE
110
111     if (__IsReady()) {
112 //LCOV_EXCL_START
113         AUDIO_IO_LOGD("Already prepared CAudioOutput");
114         CAudioIO::prepare();
115         return;
116 //LCOV_EXCL_STOP
117     }
118
119     /* Check invalid AudioType */
120     CAudioInfo::EAudioType audioType = mAudioInfo.getAudioType();
121     if (audioType < CAudioInfo::EAudioType::AUDIO_OUT_TYPE_MEDIA ||
122         audioType >= CAudioInfo::EAudioType::AUDIO_TYPE_MAX)
123         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,
124                                "The audioType is invalid [type:%d]", static_cast<int>(audioType));
125
126     try {
127         /* Init StreamSpec */
128         CPulseStreamSpec::EStreamLatency streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT;
129 #ifndef DISABLE_MOBILE_BACK_COMP
130         if (!mStreamCallback.onStream) {
131             AUDIO_IO_LOGD("Set Stream Spec : CPulseStreamSpec::STREAM_LATENCY_OUTPUT_DEFAULT");
132             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT;
133         } else {
134             AUDIO_IO_LOGD("Set Stream Spec : CPulseStreamSpec::STREAM_LATENCY_OUTPUT_DEFAULT_ASYNC");
135             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_DEFAULT_ASYNC;
136         }
137 #endif
138         /* Override the default value by audio type */
139         if (audioType == CAudioInfo::EAudioType::AUDIO_OUT_TYPE_VOIP)
140             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_VOIP;
141         else if (audioType == CAudioInfo::EAudioType::AUDIO_OUT_TYPE_MEDIA_NETWORK_SOURCE)
142             streamSpec = CPulseStreamSpec::EStreamLatency::STREAM_LATENCY_OUTPUT_HIGH;
143
144         CPulseStreamSpec spec(streamSpec, mAudioInfo);
145
146         std::unique_lock<std::mutex> mutex(mMutex);
147         mpPulseAudioClient = new CPulseAudioClient(CPulseAudioClient::EStreamDirection::STREAM_DIRECTION_PLAYBACK,
148                                                    spec, this);
149         mpPulseAudioClient->initialize();
150 #ifndef DISABLE_MOBILE_BACK_COMP
151         /* Uncork stream which is created with CORKED flag */
152         mpPulseAudioClient->cork(false);
153 #endif
154         mutex.unlock();
155
156         CAudioIO::prepare();
157     } catch (const CAudioError& e) {
158 //LCOV_EXCL_START
159         SAFE_FINALIZE(mpPulseAudioClient);
160         SAFE_DELETE(mpPulseAudioClient);
161         throw;
162 //LCOV_EXCL_STOP
163     } catch (const std::bad_alloc&) {
164         THROW_ERROR_MSG(CAudioError::EError::ERROR_OUT_OF_MEMORY, "Failed to allocate CPulseAudioClient object");
165     }
166 }
167
168 void CAudioOutput::unprepare() {
169     if (!__IsInit())
170         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, //LCOV_EXCL_LINE
171                         "Did not initialize CAudioOutput");         //LCOV_EXCL_LINE
172
173     if (!__IsReady()) {
174         AUDIO_IO_LOGD("Already unprepared");
175         return;
176     }
177
178     CAudioIO::unprepare();
179
180     std::unique_lock<std::mutex> mutex(mMutex);
181     if (mpPulseAudioClient && mpPulseAudioClient->isInThread())
182         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't unprepare inside pulseaudio thread");
183     SAFE_FINALIZE(mpPulseAudioClient);
184     SAFE_DELETE(mpPulseAudioClient);
185     mutex.unlock();
186
187     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_IDLE);
188 }
189
190 void CAudioOutput::pause() {
191     if (!__IsInit() || !__IsReady())
192         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
193                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
194
195     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING)
196         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_STATE,
197                         "Can't pause if not in Running state");
198
199     if (mpPulseAudioClient->isInThread() )
200         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't pause in thread"); //LCOV_EXCL_LINE
201
202     CAudioIO::pause();
203     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED);
204 }
205
206 void CAudioOutput::resume() {
207     if (!__IsInit() || !__IsReady())
208         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
209                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
210
211     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED)
212         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_STATE,
213                         "Can't resume if not in Paused state");
214
215     if (mpPulseAudioClient->isInThread())
216         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't resume in thread"); //LCOV_EXCL_LINE
217
218     CAudioIO::resume();
219     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
220 }
221
222 void CAudioOutput::drain() {
223     if (!__IsInit() || !__IsReady())
224         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
225                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
226
227     if (mStreamCallback.onStream)
228         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION, "async type don't support drain");
229
230     std::unique_lock<std::mutex> defer_mutex(mMutex, std::defer_lock);
231     if (!mpPulseAudioClient->isInThread())
232         defer_mutex.lock();
233
234     mpPulseAudioClient->drain();
235 }
236
237 void CAudioOutput::flush() {
238     if (!__IsInit() || !__IsReady())
239         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
240                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
241
242     CAudioIO::flush();
243 }
244
245 int CAudioOutput::getBufferSize() {
246     if (!__IsInit())
247         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
248                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
249
250     /* FIXME : return calculated size here to satisfy backward compatibility */
251     return (mAudioInfo.getSampleRate() * DEFAULT_PERIOD_SIZE) / 1000 * mAudioInfo.getSampleSize();
252 }
253
254 size_t CAudioOutput::write(const void* buffer, size_t length) {
255     if (!__IsInit() || !__IsReady())
256         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
257                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
258
259     if (!buffer)
260         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,
261                                "Parameters are invalid - buffer:%p, length:%zu", buffer, length);
262
263     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING)
264         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION,
265                         "Can't write if not in Running state");
266
267     /* When write() is called in PulseAudio callback, bypass a pcm data to CPulseAudioClient (For Asynchronous) */
268     if (mpPulseAudioClient && mpPulseAudioClient->isInThread()) {
269         int ret = mpPulseAudioClient->write(buffer, length);
270         if (ret < 0)
271             THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
272                                    "The written result is invalid ret:%d", ret);
273 #ifdef _AUDIO_IO_DEBUG_TIMING_
274         AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, length);
275 #endif
276         return length;
277     }
278
279     try {
280         /* For synchronization */
281         std::unique_lock<std::mutex> mutex(mMutex);
282
283         // If another thread did call unprepare, do not write
284         if (!mpPulseAudioClient)
285             THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, //LCOV_EXCL_LINE
286                             "Did not initialize CPulseAudioClient");    //LCOV_EXCL_LINE
287
288         // Sets synchronous flag
289         __mIsUsedSyncWrite = true;
290         size_t lengthIter = length;
291
292         while (lengthIter > 0) {
293             size_t l;
294             unsigned int timeouts = 0;
295
296             while ((l = mpPulseAudioClient->getWritableSize()) == 0) {
297 #ifdef _AUDIO_IO_DEBUG_TIMING_
298                 AUDIO_IO_LOGD("writableSize is [%zu].. wait", l);
299 #endif
300                 std::unique_lock<std::mutex> cond_mutex(mCondMutex);
301                 if (mCond.wait_for(cond_mutex, cond_wait_ms) == std::cv_status::timeout) {
302 //LCOV_EXCL_START
303                     AUDIO_IO_LOGW("[%2u] timeout expired for waiting %lld ms of write available...", ++timeouts,
304                                   std::chrono::duration_cast<std::chrono::milliseconds>(cond_wait_ms).count());
305                     if ((cond_wait_ms * timeouts) > writable_timeout_s)
306                         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
307                                                "writable size is 0 for more than %lld seconds...",
308                                                std::chrono::duration_cast<std::chrono::seconds>(writable_timeout_s).count());
309 //LCOV_EXCL_STOP
310                 }
311             }
312
313             if (l > lengthIter)
314                 l = lengthIter;
315
316 #ifdef _AUDIO_IO_DEBUG_TIMING_
317             AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, l);
318 #endif
319
320             int ret = mpPulseAudioClient->write(buffer, l);
321             if (ret < 0)
322                 THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,//LCOV_EXCL_LINE
323                                        "The written result is invalid ret:%d", ret); //LCOV_EXCL_LINE
324
325             buffer = static_cast<const uint8_t*>(buffer) + l;
326             lengthIter -= l;
327         }  // End of while (length > 0)
328
329         __mIsUsedSyncWrite = false;
330         mutex.unlock();
331
332         sched_yield();
333     } catch (const CAudioError& e) {
334         __mIsUsedSyncWrite = false;
335         throw;
336     }
337
338     return length;
339 }