1/*
2**
3** Copyright (C) 2008 The Android Open Source Project
4**
5** Licensed under the Apache License, Version 2.0 (the "License");
6** you may not use this file except in compliance with the License.
7** You may obtain a copy of the License at
8**
9**     http://www.apache.org/licenses/LICENSE-2.0
10**
11** Unless required by applicable law or agreed to in writing, software
12** distributed under the License is distributed on an "AS IS" BASIS,
13** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14** See the License for the specific language governing permissions and
15** limitations under the License.
16*/
17
18#include <stdint.h>
19#include <sys/types.h>
20#include <binder/Parcel.h>
21#include <media/IMediaMetadataRetriever.h>
22#include <utils/String8.h>
23#include <utils/KeyedVector.h>
24
25// The binder is supposed to propagate the scheduler group across
26// the binder interface so that remote calls are executed with
27// the same priority as local calls. This is currently not working
28// so this change puts in a temporary hack to fix the issue with
29// metadata retrieval which can be a huge CPU hit if done on a
30// foreground thread.
31#ifndef DISABLE_GROUP_SCHEDULE_HACK
32
33#undef LOG_TAG
34#define LOG_TAG "IMediaMetadataRetriever"
35#include <utils/Log.h>
36#include <cutils/sched_policy.h>
37
38namespace android {
39
40static void sendSchedPolicy(Parcel& data)
41{
42    SchedPolicy policy;
43    get_sched_policy(gettid(), &policy);
44    data.writeInt32(policy);
45}
46
47static void setSchedPolicy(const Parcel& data)
48{
49    SchedPolicy policy = (SchedPolicy) data.readInt32();
50    set_sched_policy(gettid(), policy);
51}
52static void restoreSchedPolicy()
53{
54    set_sched_policy(gettid(), SP_FOREGROUND);
55}
56}; // end namespace android
57#endif
58
59namespace android {
60
61enum {
62    DISCONNECT = IBinder::FIRST_CALL_TRANSACTION,
63    SET_DATA_SOURCE_URL,
64    SET_DATA_SOURCE_FD,
65    GET_FRAME_AT_TIME,
66    EXTRACT_ALBUM_ART,
67    EXTRACT_METADATA,
68};
69
70class BpMediaMetadataRetriever: public BpInterface<IMediaMetadataRetriever>
71{
72public:
73    BpMediaMetadataRetriever(const sp<IBinder>& impl)
74        : BpInterface<IMediaMetadataRetriever>(impl)
75    {
76    }
77
78    // disconnect from media metadata retriever service
79    void disconnect()
80    {
81        Parcel data, reply;
82        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
83        remote()->transact(DISCONNECT, data, &reply);
84    }
85
86    status_t setDataSource(
87            const char *srcUrl, const KeyedVector<String8, String8> *headers)
88    {
89        Parcel data, reply;
90        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
91        data.writeCString(srcUrl);
92
93        if (headers == NULL) {
94            data.writeInt32(0);
95        } else {
96            // serialize the headers
97            data.writeInt32(headers->size());
98            for (size_t i = 0; i < headers->size(); ++i) {
99                data.writeString8(headers->keyAt(i));
100                data.writeString8(headers->valueAt(i));
101            }
102        }
103
104        remote()->transact(SET_DATA_SOURCE_URL, data, &reply);
105        return reply.readInt32();
106    }
107
108    status_t setDataSource(int fd, int64_t offset, int64_t length)
109    {
110        Parcel data, reply;
111        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
112        data.writeFileDescriptor(fd);
113        data.writeInt64(offset);
114        data.writeInt64(length);
115        remote()->transact(SET_DATA_SOURCE_FD, data, &reply);
116        return reply.readInt32();
117    }
118
119    sp<IMemory> getFrameAtTime(int64_t timeUs, int option)
120    {
121        ALOGV("getTimeAtTime: time(%lld us) and option(%d)", timeUs, option);
122        Parcel data, reply;
123        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
124        data.writeInt64(timeUs);
125        data.writeInt32(option);
126#ifndef DISABLE_GROUP_SCHEDULE_HACK
127        sendSchedPolicy(data);
128#endif
129        remote()->transact(GET_FRAME_AT_TIME, data, &reply);
130        status_t ret = reply.readInt32();
131        if (ret != NO_ERROR) {
132            return NULL;
133        }
134        return interface_cast<IMemory>(reply.readStrongBinder());
135    }
136
137    sp<IMemory> extractAlbumArt()
138    {
139        Parcel data, reply;
140        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
141#ifndef DISABLE_GROUP_SCHEDULE_HACK
142        sendSchedPolicy(data);
143#endif
144        remote()->transact(EXTRACT_ALBUM_ART, data, &reply);
145        status_t ret = reply.readInt32();
146        if (ret != NO_ERROR) {
147            return NULL;
148        }
149        return interface_cast<IMemory>(reply.readStrongBinder());
150    }
151
152    const char* extractMetadata(int keyCode)
153    {
154        Parcel data, reply;
155        data.writeInterfaceToken(IMediaMetadataRetriever::getInterfaceDescriptor());
156#ifndef DISABLE_GROUP_SCHEDULE_HACK
157        sendSchedPolicy(data);
158#endif
159        data.writeInt32(keyCode);
160        remote()->transact(EXTRACT_METADATA, data, &reply);
161        status_t ret = reply.readInt32();
162        if (ret != NO_ERROR) {
163            return NULL;
164        }
165        const char* str = reply.readCString();
166        if (str != NULL) {
167            String8 value(str);
168            if (mMetadata.indexOfKey(keyCode) < 0) {
169                mMetadata.add(keyCode, value);
170            } else {
171                mMetadata.replaceValueFor(keyCode, value);
172            }
173            return mMetadata.valueFor(keyCode).string();
174        } else {
175            return NULL;
176        }
177    }
178
179private:
180    KeyedVector<int, String8> mMetadata;
181};
182
183IMPLEMENT_META_INTERFACE(MediaMetadataRetriever, "android.media.IMediaMetadataRetriever");
184
185// ----------------------------------------------------------------------
186
187status_t BnMediaMetadataRetriever::onTransact(
188    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
189{
190    switch (code) {
191        case DISCONNECT: {
192            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
193            disconnect();
194            return NO_ERROR;
195        } break;
196        case SET_DATA_SOURCE_URL: {
197            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
198            const char* srcUrl = data.readCString();
199
200            KeyedVector<String8, String8> headers;
201            int32_t numHeaders = data.readInt32();
202            for (int i = 0; i < numHeaders; ++i) {
203                String8 key = data.readString8();
204                String8 value = data.readString8();
205                headers.add(key, value);
206            }
207
208            reply->writeInt32(
209                    setDataSource(srcUrl, numHeaders > 0 ? &headers : NULL));
210
211            return NO_ERROR;
212        } break;
213        case SET_DATA_SOURCE_FD: {
214            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
215            int fd = dup(data.readFileDescriptor());
216            int64_t offset = data.readInt64();
217            int64_t length = data.readInt64();
218            reply->writeInt32(setDataSource(fd, offset, length));
219            return NO_ERROR;
220        } break;
221        case GET_FRAME_AT_TIME: {
222            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
223            int64_t timeUs = data.readInt64();
224            int option = data.readInt32();
225            ALOGV("getTimeAtTime: time(%lld us) and option(%d)", timeUs, option);
226#ifndef DISABLE_GROUP_SCHEDULE_HACK
227            setSchedPolicy(data);
228#endif
229            sp<IMemory> bitmap = getFrameAtTime(timeUs, option);
230            if (bitmap != 0) {  // Don't send NULL across the binder interface
231                reply->writeInt32(NO_ERROR);
232                reply->writeStrongBinder(bitmap->asBinder());
233            } else {
234                reply->writeInt32(UNKNOWN_ERROR);
235            }
236#ifndef DISABLE_GROUP_SCHEDULE_HACK
237            restoreSchedPolicy();
238#endif
239            return NO_ERROR;
240        } break;
241        case EXTRACT_ALBUM_ART: {
242            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
243#ifndef DISABLE_GROUP_SCHEDULE_HACK
244            setSchedPolicy(data);
245#endif
246            sp<IMemory> albumArt = extractAlbumArt();
247            if (albumArt != 0) {  // Don't send NULL across the binder interface
248                reply->writeInt32(NO_ERROR);
249                reply->writeStrongBinder(albumArt->asBinder());
250            } else {
251                reply->writeInt32(UNKNOWN_ERROR);
252            }
253#ifndef DISABLE_GROUP_SCHEDULE_HACK
254            restoreSchedPolicy();
255#endif
256            return NO_ERROR;
257        } break;
258        case EXTRACT_METADATA: {
259            CHECK_INTERFACE(IMediaMetadataRetriever, data, reply);
260#ifndef DISABLE_GROUP_SCHEDULE_HACK
261            setSchedPolicy(data);
262#endif
263            int keyCode = data.readInt32();
264            const char* value = extractMetadata(keyCode);
265            if (value != NULL) {  // Don't send NULL across the binder interface
266                reply->writeInt32(NO_ERROR);
267                reply->writeCString(value);
268            } else {
269                reply->writeInt32(UNKNOWN_ERROR);
270            }
271#ifndef DISABLE_GROUP_SCHEDULE_HACK
272            restoreSchedPolicy();
273#endif
274            return NO_ERROR;
275        } break;
276        default:
277            return BBinder::onTransact(code, data, reply, flags);
278    }
279}
280
281// ----------------------------------------------------------------------------
282
283}; // namespace android
284