1 /*
2  * Copyright (C) 2020 The Android Open Source Project
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 //#define LOG_NDEBUG 0
18 #define LOG_TAG "SimulatedTranscoder"
19 #include "SimulatedTranscoder.h"
20 
21 #include <utils/Log.h>
22 
23 #include <thread>
24 
25 namespace android {
26 
27 //static
toString(Event::Type type)28 const char* SimulatedTranscoder::toString(Event::Type type) {
29     switch (type) {
30     case Event::Start:
31         return "Start";
32     case Event::Pause:
33         return "Pause";
34     case Event::Resume:
35         return "Resume";
36     case Event::Stop:
37         return "Stop";
38     case Event::Finished:
39         return "Finished";
40     case Event::Failed:
41         return "Failed";
42     case Event::Abandon:
43         return "Abandon";
44     default:
45         break;
46     }
47     return "(unknown)";
48 }
49 
SimulatedTranscoder(const std::shared_ptr<TranscoderCallbackInterface> & cb)50 SimulatedTranscoder::SimulatedTranscoder(const std::shared_ptr<TranscoderCallbackInterface>& cb)
51       : mCallback(cb), mLooperReady(false) {
52     ALOGV("SimulatedTranscoder CTOR: %p", this);
53 }
54 
~SimulatedTranscoder()55 SimulatedTranscoder::~SimulatedTranscoder() {
56     ALOGV("SimulatedTranscoder DTOR: %p", this);
57 }
58 
start(ClientIdType clientId,SessionIdType sessionId,const TranscodingRequestParcel & request,uid_t,const std::shared_ptr<ITranscodingClientCallback> &)59 void SimulatedTranscoder::start(
60         ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& request,
61         uid_t /*callingUid*/,
62         const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
63     {
64         auto lock = std::scoped_lock(mLock);
65         int64_t processingTimeUs = kSessionDurationUs;
66         if (request.testConfig.has_value() && request.testConfig->processingTotalTimeMs > 0) {
67             processingTimeUs = request.testConfig->processingTotalTimeMs * 1000;
68         }
69         ALOGI("%s: session {%lld, %d}: processingTimeUs: %lld", __FUNCTION__, (long long)clientId,
70               sessionId, (long long)processingTimeUs);
71         SessionKeyType key = std::make_pair(clientId, sessionId);
72         mRemainingTimeMap.emplace(key, processingTimeUs);
73     }
74 
75     queueEvent(Event::Start, clientId, sessionId, [=] {
76         auto callback = mCallback.lock();
77         if (callback != nullptr) {
78             callback->onStarted(clientId, sessionId);
79         }
80     });
81 }
82 
pause(ClientIdType clientId,SessionIdType sessionId)83 void SimulatedTranscoder::pause(ClientIdType clientId, SessionIdType sessionId) {
84     queueEvent(Event::Pause, clientId, sessionId, [=] {
85         auto callback = mCallback.lock();
86         if (callback != nullptr) {
87             callback->onPaused(clientId, sessionId);
88         }
89     });
90 }
91 
resume(ClientIdType clientId,SessionIdType sessionId,const TranscodingRequestParcel &,uid_t,const std::shared_ptr<ITranscodingClientCallback> &)92 void SimulatedTranscoder::resume(
93         ClientIdType clientId, SessionIdType sessionId, const TranscodingRequestParcel& /*request*/,
94         uid_t /*callingUid*/,
95         const std::shared_ptr<ITranscodingClientCallback>& /*clientCallback*/) {
96     queueEvent(Event::Resume, clientId, sessionId, [=] {
97         auto callback = mCallback.lock();
98         if (callback != nullptr) {
99             callback->onResumed(clientId, sessionId);
100         }
101     });
102 }
103 
stop(ClientIdType clientId,SessionIdType sessionId,bool abandon)104 void SimulatedTranscoder::stop(ClientIdType clientId, SessionIdType sessionId, bool abandon) {
105     queueEvent(Event::Stop, clientId, sessionId, nullptr);
106 
107     if (abandon) {
108         queueEvent(Event::Abandon, 0, 0, nullptr);
109     }
110 }
111 
queueEvent(Event::Type type,ClientIdType clientId,SessionIdType sessionId,std::function<void ()> runnable)112 void SimulatedTranscoder::queueEvent(Event::Type type, ClientIdType clientId,
113                                      SessionIdType sessionId, std::function<void()> runnable) {
114     ALOGV("%s: session {%lld, %d}: %s", __FUNCTION__, (long long)clientId, sessionId,
115           toString(type));
116 
117     auto lock = std::scoped_lock(mLock);
118 
119     if (!mLooperReady) {
120         // A shared_ptr to ourselves is given to the thread's stack, so that SimulatedTranscoder
121         // object doesn't go away until the thread exits. When a watchdog timeout happens, this
122         // allows the session controller to release its reference to the TranscoderWrapper object
123         // without blocking on the thread exits.
124         std::thread([owner = shared_from_this()]() { owner->threadLoop(); }).detach();
125         mLooperReady = true;
126     }
127 
128     mQueue.push_back({type, clientId, sessionId, runnable});
129     mCondition.notify_one();
130 }
131 
threadLoop()132 void SimulatedTranscoder::threadLoop() {
133     bool running = false;
134     std::chrono::steady_clock::time_point lastRunningTime;
135     Event lastRunningEvent;
136 
137     std::unique_lock<std::mutex> lock(mLock);
138     // SimulatedTranscoder currently lives in the transcoding service, as long as
139     // MediaTranscodingService itself.
140     while (true) {
141         // Wait for the next event.
142         while (mQueue.empty()) {
143             if (!running) {
144                 mCondition.wait(lock);
145                 continue;
146             }
147             // If running, wait for the remaining life of this session. Report finish if timed out.
148             SessionKeyType key =
149                     std::make_pair(lastRunningEvent.clientId, lastRunningEvent.sessionId);
150             std::cv_status status = mCondition.wait_for(lock, mRemainingTimeMap[key]);
151             if (status == std::cv_status::timeout) {
152                 running = false;
153 
154                 auto callback = mCallback.lock();
155                 if (callback != nullptr) {
156                     mRemainingTimeMap.erase(key);
157 
158                     lock.unlock();
159                     callback->onFinish(lastRunningEvent.clientId, lastRunningEvent.sessionId);
160                     lock.lock();
161                 }
162             } else {
163                 // Advance last running time and remaining time. This is needed to guard
164                 // against bad events (which will be ignored) or spurious wakeups, in that
165                 // case we don't want to wait for the same time again.
166                 auto now = std::chrono::steady_clock::now();
167                 mRemainingTimeMap[key] -= std::chrono::duration_cast<std::chrono::microseconds>(
168                         now - lastRunningTime);
169                 lastRunningTime = now;
170             }
171         }
172 
173         // Handle the events, adjust state and send updates to client accordingly.
174         Event event = *mQueue.begin();
175         mQueue.pop_front();
176 
177         ALOGD("%s: session {%lld, %d}: %s", __FUNCTION__, (long long)event.clientId,
178               event.sessionId, toString(event.type));
179 
180         if (event.type == Event::Abandon) {
181             break;
182         }
183 
184         SessionKeyType key = std::make_pair(event.clientId, event.sessionId);
185         if (!running && (event.type == Event::Start || event.type == Event::Resume)) {
186             running = true;
187             lastRunningTime = std::chrono::steady_clock::now();
188             lastRunningEvent = event;
189             ALOGV("%s: session {%lld, %d}: remaining time: %lld", __FUNCTION__,
190                   (long long)event.clientId, event.sessionId,
191                   (long long)mRemainingTimeMap[key].count());
192 
193         } else if (running && (event.type == Event::Pause || event.type == Event::Stop)) {
194             running = false;
195             if (event.type == Event::Stop) {
196                 mRemainingTimeMap.erase(key);
197             } else {
198                 mRemainingTimeMap[key] -= std::chrono::duration_cast<std::chrono::microseconds>(
199                         std::chrono::steady_clock::now() - lastRunningTime);
200             }
201         } else {
202             ALOGW("%s: discarding bad event: session {%lld, %d}: %s", __FUNCTION__,
203                   (long long)event.clientId, event.sessionId, toString(event.type));
204             continue;
205         }
206 
207         if (event.runnable != nullptr) {
208             lock.unlock();
209             event.runnable();
210             lock.lock();
211         }
212     }
213 }
214 
215 }  // namespace android
216