CAudioInfo: Refactor getters to return in group
[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     locker.unlock();
222
223     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED);
224 }
225
226 void CAudioOutput::resume() {
227     CAudioTimedLocker locker(__func__, mMutex);
228
229     if (!__IsInit() || !__IsReady())
230         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
231                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
232
233     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_PAUSED)
234         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_STATE,
235                         "Can't resume if not in Paused state");
236
237     if (mpPulseAudioClient->isInThread())
238         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_OPERATION, "Can't resume in thread"); //LCOV_EXCL_LINE
239
240     __dumpStat();
241
242     CAudioIO::resume();
243     CAudioIO::setState(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
244
245     locker.unlock();
246
247     CAudioIO::onStateChanged(CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING);
248 }
249
250 void CAudioOutput::drain() {
251     CAudioTimedLocker locker(__func__, mMutex);
252
253     if (!__IsInit() || !__IsReady())
254         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
255                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
256
257     if (mStreamCallback.onStream)
258         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION, "async type don't support drain");
259
260     mpPulseAudioClient->drain();
261 }
262
263 void CAudioOutput::flush() {
264     CAudioTimedLocker locker(__func__, mMutex);
265
266     if (!__IsInit() || !__IsReady())
267         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
268                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
269
270     CAudioIO::flush();
271 }
272
273 int CAudioOutput::getBufferSize() {
274     if (!__IsInit())
275         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
276                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
277
278     /* FIXME : return calculated size here to satisfy backward compatibility */
279     return mAudioInfo.msToBytes(DEFAULT_PERIOD_SIZE);
280 }
281
282 size_t CAudioOutput::write(const void* buffer, size_t length) {
283     CAudioTimedLocker locker(__func__);
284
285     if (mpPulseAudioClient && !mpPulseAudioClient->isInThread())
286         locker.lock(mMutex);
287
288     if (!__IsInit() || !__IsReady())
289         THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED,    //LCOV_EXCL_LINE
290                         "Did not initialize or prepare CAudioOutput"); //LCOV_EXCL_LINE
291
292     if (!buffer)
293         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INVALID_ARGUMENT,
294                                "Parameters are invalid - buffer:%p, length:%zu", buffer, length);
295
296     if (CAudioIO::getState() != CAudioInfo::EAudioIOState::AUDIO_IO_STATE_RUNNING)
297         THROW_ERROR_MSG(CAudioError::EError::ERROR_INVALID_OPERATION,
298                         "Can't write if not in Running state");
299
300     /* When write() is called in PulseAudio callback, bypass a pcm data to CPulseAudioClient (For Asynchronous) */
301     if (mpPulseAudioClient && mpPulseAudioClient->isInThread()) {
302         int ret = mpPulseAudioClient->write(buffer, length);
303         if (ret < 0)
304             THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
305                                    "The written result is invalid ret:%d", ret);
306 #ifdef _AUDIO_IO_DEBUG_TIMING_
307         AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, length);
308 #endif
309         __mTotalWrittenCount++;
310         __mTotalWrittenBytes += length;
311
312         __dumpStat(length);
313
314         return length;
315     }
316
317     try {
318
319         // If another thread did call unprepare, do not write
320         if (!mpPulseAudioClient)
321             THROW_ERROR_MSG(CAudioError::EError::ERROR_NOT_INITIALIZED, //LCOV_EXCL_LINE
322                             "Did not initialize CPulseAudioClient");    //LCOV_EXCL_LINE
323
324         // Sets synchronous flag
325         __mIsUsedSyncWrite = true;
326         size_t lengthIter = length;
327
328         while (lengthIter > 0) {
329             size_t l;
330             unsigned int timeouts = 0;
331
332             while ((l = mpPulseAudioClient->getWritableSize()) == 0) {
333 #ifdef _AUDIO_IO_DEBUG_TIMING_
334                 AUDIO_IO_LOGD("writableSize is [%zu].. wait", l);
335 #endif
336                 std::unique_lock<std::mutex> cond_mutex(mCondMutex);
337                 if (mCond.wait_for(cond_mutex, cond_wait_ms) == std::cv_status::timeout) {
338 //LCOV_EXCL_START
339                     AUDIO_IO_LOGW("[%2u] timeout expired for waiting %zu ms of write available...", ++timeouts,
340                                   static_cast<size_t>(std::chrono::duration_cast<std::chrono::milliseconds>(cond_wait_ms).count()));
341                     if ((cond_wait_ms * timeouts) > writable_timeout_s)
342                         THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,
343                                                "writable size is 0 for more than %zu seconds...",
344                                                static_cast<size_t>(std::chrono::duration_cast<std::chrono::seconds>(writable_timeout_s).count()));
345 //LCOV_EXCL_STOP
346                 }
347             }
348
349             if (timeouts > 0)
350                 AUDIO_IO_LOGD("writableSize(%zu) is now available after (%u) timeouts!!!", l, timeouts);
351
352             if (l > lengthIter)
353                 l = lengthIter;
354
355 #ifdef _AUDIO_IO_DEBUG_TIMING_
356             AUDIO_IO_LOGD("CPulseAudioClient->write(buffer:%p, length:%zu)", buffer, l);
357             __dumpStat();
358 #endif
359
360             int ret = mpPulseAudioClient->write(buffer, l);
361             if (ret < 0)
362                 THROW_ERROR_MSG_FORMAT(CAudioError::EError::ERROR_INTERNAL_OPERATION,//LCOV_EXCL_LINE
363                                        "The written result is invalid ret:%d", ret); //LCOV_EXCL_LINE
364
365             buffer = static_cast<const uint8_t*>(buffer) + l;
366             lengthIter -= l;
367
368             __mTotalWrittenCount++;
369             __mTotalWrittenBytes += l;
370         }  // End of while (lengthIter > 0)
371
372         __mIsUsedSyncWrite = false;
373         locker.unlock();
374
375         sched_yield();
376     } catch (const CAudioError& e) {
377         __mIsUsedSyncWrite = false;
378         throw;
379     }
380
381     __dumpStat(length);
382
383     return length;
384 }
385
386 void CAudioOutput::__dumpStat() noexcept {
387     AUDIO_IO_LOGD("pClient[%p] : total written %5" PRIu64 " times, %10" PRIu64 " bytes, %7" PRIu64 " ms",
388                   mpPulseAudioClient, __mTotalWrittenCount, __mTotalWrittenBytes, mAudioInfo.bytesToMs(__mTotalWrittenBytes));
389 }
390
391 void CAudioOutput::__dumpStat(size_t length) noexcept {
392     static constexpr auto PRINT_INTERVAL_BYTES = 256 * 1024; // 256k
393     static constexpr auto MIN_PRINTS = 20;
394     static uint64_t writtenBytes = 0;
395
396     if (__mTotalWrittenCount <= MIN_PRINTS) {
397         __dumpStat();
398     } else if ((writtenBytes += length) > PRINT_INTERVAL_BYTES) {
399         __dumpStat();
400         writtenBytes = 0;
401     }
402 }