1 /*
2 * Copyright 2017, 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 "RemoteMediaExtractor"
19
20 #include <list>
21 #include <pthread.h>
22 #include <condition_variable>
23 #include <mutex>
24
25 #include <utils/Log.h>
26
27 #include <binder/IPCThreadState.h>
28 #include <cutils/properties.h>
29 #include <media/stagefright/InterfaceUtils.h>
30 #include <media/MediaMetricsItem.h>
31 #include <media/stagefright/MediaSource.h>
32 #include <media/stagefright/RemoteMediaExtractor.h>
33
34 // still doing some on/off toggling here.
35 #define MEDIA_LOG 1
36
37 namespace android {
38
39 // key for media statistics
40 static const char *kKeyExtractor = "extractor";
41
42 // attrs for media statistics
43 // NB: these are matched with public Java API constants defined
44 // in frameworks/base/media/java/android/media/MediaExtractor.java
45 // These must be kept synchronized with the constants there.
46 static const char *kExtractorFormat = "android.media.mediaextractor.fmt";
47 static const char *kExtractorMime = "android.media.mediaextractor.mime";
48 static const char *kExtractorTracks = "android.media.mediaextractor.ntrk";
49
50 // The following are not available in frameworks/base/media/java/android/media/MediaExtractor.java
51 // because they are not applicable or useful to that API.
52 static const char *kExtractorEntryPoint = "android.media.mediaextractor.entry";
53 static const char *kExtractorLogSessionId = "android.media.mediaextractor.logSessionId";
54
55 static const char *kEntryPointSdk = "sdk";
56 static const char *kEntryPointWithJvm = "ndk-with-jvm";
57 static const char *kEntryPointNoJvm = "ndk-no-jvm";
58 static const char *kEntryPointOther = "other";
59
RemoteMediaExtractor(MediaExtractor * extractor,const sp<DataSource> & source,const sp<RefBase> & plugin)60 RemoteMediaExtractor::RemoteMediaExtractor(
61 MediaExtractor *extractor,
62 const sp<DataSource> &source,
63 const sp<RefBase> &plugin)
64 :mExtractor(extractor),
65 mSource(source),
66 mExtractorPlugin(plugin) {
67
68 mMetricsItem = nullptr;
69 if (MEDIA_LOG) {
70 mMetricsItem = mediametrics::Item::create(kKeyExtractor);
71
72 // we're in the extractor service, we want to attribute to the app
73 // that invoked us.
74 int uid = IPCThreadState::self()->getCallingUid();
75 mMetricsItem->setUid(uid);
76
77 // track the container format (mpeg, aac, wvm, etc)
78 size_t ntracks = extractor->countTracks();
79 mMetricsItem->setCString(kExtractorFormat, extractor->name());
80 // tracks (size_t)
81 mMetricsItem->setInt32(kExtractorTracks, ntracks);
82 // metadata
83 MetaDataBase pMetaData;
84 if (extractor->getMetaData(pMetaData) == OK) {
85 String8 xx = pMetaData.toString();
86 // 'titl' -- but this verges into PII
87 // 'mime'
88 const char *mime = nullptr;
89 if (pMetaData.findCString(kKeyMIMEType, &mime)) {
90 mMetricsItem->setCString(kExtractorMime, mime);
91 }
92 // what else is interesting and not already available?
93 }
94 // By default, we set the entry point to be "other". Clients of this
95 // class will override this value by calling setEntryPoint.
96 mMetricsItem->setCString(kExtractorEntryPoint, kEntryPointOther);
97 }
98 }
99
100 static pthread_t myThread;
101 static std::list<sp<DataSource>> pending;
102 static std::mutex pending_mutex;
103 static std::condition_variable pending_added;
104
closingThreadWorker(void * arg)105 static void* closingThreadWorker(void *arg) {
106 // simplifies debugging to name the thread
107 if (pthread_setname_np(pthread_self(), "mediaCloser")) {
108 ALOGW("Failed to set thread name on thread for closing data sources");
109 }
110
111 while (true) {
112 sp<DataSource> ds = nullptr;
113 std::unique_lock _lk(pending_mutex);
114 pending_added.wait(_lk, []{return !pending.empty();});
115 ALOGV("worker thread wake up with %zu entries", pending.size());
116 if (!pending.empty()) {
117 ds = pending.front();
118 (void) pending.pop_front();
119 }
120 _lk.unlock(); // unique_lock is not scoped
121 if (ds != nullptr) {
122 ds->close();
123 }
124 }
125
126 ALOGE("[unexpected] worker thread quit");
127 return arg;
128 }
129
130 // this can be '&ds' as long as the pending.push_back() bumps the
131 // reference counts to ensure the object lives long enough
asyncDataSourceClose(sp<DataSource> & ds)132 static void asyncDataSourceClose(sp<DataSource> &ds) {
133
134 // make sure we have our (single) worker thread
135 static std::once_flag sCheckOnce;
136 std::call_once(sCheckOnce, [&](){
137 pthread_attr_t attr;
138 pthread_attr_init(&attr);
139 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
140 pthread_create(&myThread, &attr, closingThreadWorker, nullptr);
141 pthread_attr_destroy(&attr);
142 });
143
144 {
145 std::lock_guard _lm(pending_mutex); // scoped, no explicit unlock
146 pending.push_back(ds);
147 }
148 pending_added.notify_one(); // get the worker thread going
149 }
150
~RemoteMediaExtractor()151 RemoteMediaExtractor::~RemoteMediaExtractor() {
152 delete mExtractor;
153 // TODO(287851984) hook for changing behavior this dynamically, drop after testing
154 int8_t new_scheme = property_get_bool("debug.mediaextractor.delayedclose", 1);
155 if (new_scheme != 0) {
156 ALOGV("deferred close()");
157 asyncDataSourceClose(mSource);
158 mSource.clear();
159 } else {
160 ALOGV("immediate close()");
161 mSource->close();
162 mSource.clear();
163 }
164 mExtractorPlugin = nullptr;
165 // log the current record, provided it has some information worth recording
166 if (MEDIA_LOG) {
167 if (mMetricsItem != nullptr) {
168 if (mMetricsItem->count() > 0) {
169 mMetricsItem->selfrecord();
170 }
171 }
172 }
173 if (mMetricsItem != nullptr) {
174 delete mMetricsItem;
175 mMetricsItem = nullptr;
176 }
177 }
178
countTracks()179 size_t RemoteMediaExtractor::countTracks() {
180 return mExtractor->countTracks();
181 }
182
getTrack(size_t index)183 sp<IMediaSource> RemoteMediaExtractor::getTrack(size_t index) {
184 MediaTrack *source = mExtractor->getTrack(index);
185 return (source == nullptr)
186 ? nullptr : CreateIMediaSourceFromMediaSourceBase(this, source, mExtractorPlugin);
187 }
188
getTrackMetaData(size_t index,uint32_t flags)189 sp<MetaData> RemoteMediaExtractor::getTrackMetaData(size_t index, uint32_t flags) {
190 sp<MetaData> meta = new MetaData();
191 if (mExtractor->getTrackMetaData(*meta.get(), index, flags) == OK) {
192 return meta;
193 }
194 return nullptr;
195 }
196
getMetaData()197 sp<MetaData> RemoteMediaExtractor::getMetaData() {
198 sp<MetaData> meta = new MetaData();
199 if (mExtractor->getMetaData(*meta.get()) == OK) {
200 return meta;
201 }
202 return nullptr;
203 }
204
getMetrics(Parcel * reply)205 status_t RemoteMediaExtractor::getMetrics(Parcel *reply) {
206 if (mMetricsItem == nullptr || reply == nullptr) {
207 return UNKNOWN_ERROR;
208 }
209
210 mMetricsItem->writeToParcel(reply);
211 return OK;
212 }
213
flags() const214 uint32_t RemoteMediaExtractor::flags() const {
215 return mExtractor->flags();
216 }
217
setMediaCas(const HInterfaceToken & casToken)218 status_t RemoteMediaExtractor::setMediaCas(const HInterfaceToken &casToken) {
219 return mExtractor->setMediaCas((uint8_t*)casToken.data(), casToken.size());
220 }
221
name()222 String8 RemoteMediaExtractor::name() {
223 return String8(mExtractor->name());
224 }
225
setEntryPoint(EntryPoint entryPoint)226 status_t RemoteMediaExtractor::setEntryPoint(EntryPoint entryPoint) {
227 const char* entryPointString;
228 switch (entryPoint) {
229 case EntryPoint::SDK:
230 entryPointString = kEntryPointSdk;
231 break;
232 case EntryPoint::NDK_WITH_JVM:
233 entryPointString = kEntryPointWithJvm;
234 break;
235 case EntryPoint::NDK_NO_JVM:
236 entryPointString = kEntryPointNoJvm;
237 break;
238 case EntryPoint::OTHER:
239 entryPointString = kEntryPointOther;
240 break;
241 default:
242 return BAD_VALUE;
243 }
244 mMetricsItem->setCString(kExtractorEntryPoint, entryPointString);
245 return OK;
246 }
247
setLogSessionId(const String8 & logSessionId)248 status_t RemoteMediaExtractor::setLogSessionId(const String8& logSessionId) {
249 mMetricsItem->setCString(kExtractorLogSessionId, logSessionId.c_str());
250 return OK;
251 }
252
253 ////////////////////////////////////////////////////////////////////////////////
254
255 // static
wrap(MediaExtractor * extractor,const sp<DataSource> & source,const sp<RefBase> & plugin)256 sp<IMediaExtractor> RemoteMediaExtractor::wrap(
257 MediaExtractor *extractor,
258 const sp<DataSource> &source,
259 const sp<RefBase> &plugin) {
260 if (extractor == nullptr) {
261 return nullptr;
262 }
263 return new RemoteMediaExtractor(extractor, source, plugin);
264 }
265
266 } // namespace android
267