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