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