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