summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drm/mediadrm/plugins/clearkey/DrmPlugin.cpp5
-rw-r--r--drm/mediadrm/plugins/clearkey/DrmPlugin.h3
-rw-r--r--drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp14
-rw-r--r--drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h3
-rw-r--r--include/media/AudioRecord.h11
-rw-r--r--include/media/AudioTrack.h1
-rw-r--r--include/media/IDrm.h3
-rw-r--r--include/media/IResourceManagerClient.h47
-rw-r--r--include/media/IResourceManagerService.h66
-rw-r--r--include/media/MediaResource.h51
-rw-r--r--include/media/MediaResourcePolicy.h45
-rw-r--r--include/media/stagefright/MediaClock.h24
-rw-r--r--include/media/stagefright/MediaSync.h239
-rw-r--r--media/libmedia/Android.mk4
-rw-r--r--media/libmedia/AudioRecord.cpp8
-rw-r--r--media/libmedia/AudioTrack.cpp3
-rw-r--r--media/libmedia/IDrm.cpp14
-rw-r--r--media/libmedia/IResourceManagerClient.cpp70
-rw-r--r--media/libmedia/IResourceManagerService.cpp169
-rw-r--r--media/libmedia/MediaResource.cpp65
-rw-r--r--media/libmedia/MediaResourcePolicy.cpp49
-rw-r--r--media/libmediaplayerservice/Drm.cpp6
-rw-r--r--media/libmediaplayerservice/Drm.h3
-rw-r--r--media/libstagefright/Android.mk1
-rw-r--r--media/libstagefright/MediaClock.cpp15
-rw-r--r--media/libstagefright/MediaSync.cpp541
-rw-r--r--media/mediaserver/Android.mk2
-rw-r--r--media/ndk/NdkMediaDrm.cpp5
-rw-r--r--services/audioflinger/PlaybackTracks.h5
-rw-r--r--services/audioflinger/Tracks.cpp77
-rw-r--r--services/audiopolicy/service/AudioPolicyService.cpp2
-rw-r--r--services/mediaresourcemanager/Android.mk18
-rw-r--r--services/mediaresourcemanager/ResourceManagerService.cpp345
-rw-r--r--services/mediaresourcemanager/ResourceManagerService.h106
-rw-r--r--services/mediaresourcemanager/test/Android.mk25
-rw-r--r--services/mediaresourcemanager/test/ResourceManagerService_test.cpp464
36 files changed, 2435 insertions, 74 deletions
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
index 96fca94..6b8c772 100644
--- a/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.cpp
@@ -48,12 +48,13 @@ status_t DrmPlugin::getKeyRequest(
KeyType keyType,
const KeyedVector<String8, String8>& optionalParameters,
Vector<uint8_t>& request,
- String8& defaultUrl) {
+ String8& defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType) {
UNUSED(optionalParameters);
if (keyType != kKeyType_Streaming) {
return android::ERROR_DRM_CANNOT_HANDLE;
}
-
+ *keyRequestType = DrmPlugin::kKeyRequestType_Initial;
sp<Session> session = mSessionLibrary->findSession(scope);
defaultUrl.clear();
return session->getKeyRequest(initData, initDataType, &request);
diff --git a/drm/mediadrm/plugins/clearkey/DrmPlugin.h b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
index 6139f1f..ba4aefe 100644
--- a/drm/mediadrm/plugins/clearkey/DrmPlugin.h
+++ b/drm/mediadrm/plugins/clearkey/DrmPlugin.h
@@ -54,7 +54,8 @@ public:
KeyType keyType,
const KeyedVector<String8, String8>& optionalParameters,
Vector<uint8_t>& request,
- String8& defaultUrl);
+ String8& defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType);
virtual status_t provideKeyResponse(
const Vector<uint8_t>& scope,
diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
index 7eac0a1..9b786c5 100644
--- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
+++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
@@ -111,7 +111,8 @@ namespace android {
Vector<uint8_t> const &initData,
String8 const &mimeType, KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl)
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ KeyRequestType *keyRequestType)
{
Mutex::Autolock lock(mLock);
ALOGD("MockDrmPlugin::getKeyRequest(sessionId=%s, initData=%s, mimeType=%s"
@@ -149,6 +150,7 @@ namespace android {
// Properties used in mock test, set by cts test app returned from mock plugin
// byte[] mock-request -> request
// string mock-default-url -> defaultUrl
+ // string mock-key-request-type -> keyRequestType
index = mByteArrayProperties.indexOfKey(String8("mock-request"));
if (index < 0) {
@@ -165,6 +167,16 @@ namespace android {
} else {
defaultUrl = mStringProperties.valueAt(index);
}
+
+ index = mStringProperties.indexOfKey(String8("mock-keyRequestType"));
+ if (index < 0) {
+ ALOGD("Missing 'mock-keyRequestType' parameter for mock");
+ return BAD_VALUE;
+ } else {
+ *keyRequestType = static_cast<KeyRequestType>(
+ atoi(mStringProperties.valueAt(index).string()));
+ }
+
return OK;
}
diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h
index d1d8058..d0f2ddb 100644
--- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h
+++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.h
@@ -62,7 +62,8 @@ namespace android {
Vector<uint8_t> const &initData,
String8 const &mimeType, KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl);
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ KeyRequestType *keyRequestType);
status_t provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response,
diff --git a/include/media/AudioRecord.h b/include/media/AudioRecord.h
index c503f25..fce5840 100644
--- a/include/media/AudioRecord.h
+++ b/include/media/AudioRecord.h
@@ -183,6 +183,7 @@ public:
/* Initialize an AudioRecord that was created using the AudioRecord() constructor.
* Don't call set() more than once, or after an AudioRecord() constructor that takes parameters.
+ * set() is not multi-thread safe.
* Returned status (from utils/Errors.h) can be:
* - NO_ERROR: successful intialization
* - INVALID_OPERATION: AudioRecord is already initialized or record device is already in use
@@ -249,11 +250,6 @@ public:
*/
uint32_t getSampleRate() const { return mSampleRate; }
- /* Return the notification frame count.
- * This is approximately how often the callback is invoked, for transfer type TRANSFER_CALLBACK.
- */
- size_t notificationFrames() const { return mNotificationFramesAct; }
-
/* Sets marker position. When record reaches the number of frames specified,
* a callback with event type EVENT_MARKER is called. Calling setMarkerPosition
* with marker == 0 cancels marker notification callback.
@@ -406,8 +402,11 @@ public:
* WOULD_BLOCK when obtainBuffer() returns same, or
* AudioRecord was stopped during the read
* or any other error code returned by IAudioRecord::start() or restoreRecord_l().
+ * Default behavior is to only return when all data has been transferred. Set 'blocking' to
+ * false for the method to return immediately without waiting to try multiple times to read
+ * the full content of the buffer.
*/
- ssize_t read(void* buffer, size_t size);
+ ssize_t read(void* buffer, size_t size, bool blocking = true);
/* Return the number of input frames lost in the audio driver since the last call of this
* function. Audio driver is expected to reset the value to 0 and restart counting upon
diff --git a/include/media/AudioTrack.h b/include/media/AudioTrack.h
index 81b1181..818c8cd 100644
--- a/include/media/AudioTrack.h
+++ b/include/media/AudioTrack.h
@@ -238,6 +238,7 @@ public:
/* Initialize an AudioTrack that was created using the AudioTrack() constructor.
* Don't call set() more than once, or after the AudioTrack() constructors that take parameters.
+ * set() is not multi-thread safe.
* Returned status (from utils/Errors.h) can be:
* - NO_ERROR: successful initialization
* - INVALID_OPERATION: AudioTrack is already initialized
diff --git a/include/media/IDrm.h b/include/media/IDrm.h
index affcbd7..9449beb 100644
--- a/include/media/IDrm.h
+++ b/include/media/IDrm.h
@@ -47,7 +47,8 @@ struct IDrm : public IInterface {
Vector<uint8_t> const &initData,
String8 const &mimeType, DrmPlugin::KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl) = 0;
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType) = 0;
virtual status_t provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response,
diff --git a/include/media/IResourceManagerClient.h b/include/media/IResourceManagerClient.h
new file mode 100644
index 0000000..3587aea
--- /dev/null
+++ b/include/media/IResourceManagerClient.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IRESOURCEMANAGERCLIENT_H
+#define ANDROID_IRESOURCEMANAGERCLIENT_H
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+namespace android {
+
+class IResourceManagerClient: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(ResourceManagerClient);
+
+ virtual bool reclaimResource() = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnResourceManagerClient: public BnInterface<IResourceManagerClient>
+{
+public:
+ virtual status_t onTransact(uint32_t code,
+ const Parcel &data,
+ Parcel *reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IRESOURCEMANAGERCLIENT_H
diff --git a/include/media/IResourceManagerService.h b/include/media/IResourceManagerService.h
new file mode 100644
index 0000000..067392c
--- /dev/null
+++ b/include/media/IResourceManagerService.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_IRESOURCEMANAGERSERVICE_H
+#define ANDROID_IRESOURCEMANAGERSERVICE_H
+
+#include <utils/Errors.h> // for status_t
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <media/IResourceManagerClient.h>
+#include <media/MediaResource.h>
+#include <media/MediaResourcePolicy.h>
+
+namespace android {
+
+class IResourceManagerService: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(ResourceManagerService);
+
+ virtual void config(const Vector<MediaResourcePolicy> &policies) = 0;
+
+ virtual void addResource(
+ int pid,
+ int64_t clientId,
+ const sp<IResourceManagerClient> client,
+ const Vector<MediaResource> &resources) = 0;
+
+ virtual void removeResource(int64_t clientId) = 0;
+
+ virtual bool reclaimResource(
+ int callingPid,
+ const Vector<MediaResource> &resources) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnResourceManagerService: public BnInterface<IResourceManagerService>
+{
+public:
+ virtual status_t onTransact(uint32_t code,
+ const Parcel &data,
+ Parcel *reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IRESOURCEMANAGERSERVICE_H
diff --git a/include/media/MediaResource.h b/include/media/MediaResource.h
new file mode 100644
index 0000000..0b57c84
--- /dev/null
+++ b/include/media/MediaResource.h
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_MEDIA_RESOURCE_H
+#define ANDROID_MEDIA_RESOURCE_H
+
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+namespace android {
+
+extern const char kResourceSecureCodec[];
+extern const char kResourceNonSecureCodec[];
+extern const char kResourceGraphicMemory[];
+
+class MediaResource {
+public:
+ MediaResource();
+ MediaResource(String8 type, uint64_t value);
+ MediaResource(String8 type, String8 subType, uint64_t value);
+
+ void readFromParcel(const Parcel &parcel);
+ void writeToParcel(Parcel *parcel) const;
+
+ String8 toString() const;
+
+ bool operator==(const MediaResource &other) const;
+ bool operator!=(const MediaResource &other) const;
+
+ String8 mType;
+ String8 mSubType;
+ uint64_t mValue;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MEDIA_RESOURCE_H
diff --git a/include/media/MediaResourcePolicy.h b/include/media/MediaResourcePolicy.h
new file mode 100644
index 0000000..1e1c341
--- /dev/null
+++ b/include/media/MediaResourcePolicy.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_MEDIA_RESOURCE_POLICY_H
+#define ANDROID_MEDIA_RESOURCE_POLICY_H
+
+#include <binder/Parcel.h>
+#include <utils/String8.h>
+
+namespace android {
+
+extern const char kPolicySupportsMultipleSecureCodecs[];
+extern const char kPolicySupportsSecureWithNonSecureCodec[];
+
+class MediaResourcePolicy {
+public:
+ MediaResourcePolicy();
+ MediaResourcePolicy(String8 type, uint64_t value);
+
+ void readFromParcel(const Parcel &parcel);
+ void writeToParcel(Parcel *parcel) const;
+
+ String8 toString() const;
+
+ String8 mType;
+ uint64_t mValue;
+};
+
+}; // namespace android
+
+#endif // ANDROID_MEDIA_RESOURCE_POLICY_H
diff --git a/include/media/stagefright/MediaClock.h b/include/media/stagefright/MediaClock.h
index 660764f..e9c09a1 100644
--- a/include/media/stagefright/MediaClock.h
+++ b/include/media/stagefright/MediaClock.h
@@ -35,9 +35,9 @@ struct MediaClock : public RefBase {
// It's required to use timestamp of just rendered frame as
// anchor time in paused state.
void updateAnchor(
- int64_t anchorTimeMediaUs,
- int64_t anchorTimeRealUs,
- int64_t maxTimeMediaUs = INT64_MAX);
+ int64_t anchorTimeMediaUs,
+ int64_t anchorTimeRealUs,
+ int64_t maxTimeMediaUs = INT64_MAX);
void updateMaxTimeMedia(int64_t maxTimeMediaUs);
@@ -45,22 +45,24 @@ struct MediaClock : public RefBase {
// query media time corresponding to real time |realUs|, and save the
// result in |outMediaUs|.
- status_t getMediaTime(int64_t realUs,
- int64_t *outMediaUs,
- bool allowPastMaxTime = false);
+ status_t getMediaTime(
+ int64_t realUs,
+ int64_t *outMediaUs,
+ bool allowPastMaxTime = false) const;
// query real time corresponding to media time |targetMediaUs|.
// The result is saved in |outRealUs|.
- status_t getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs);
+ status_t getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs) const;
protected:
virtual ~MediaClock();
private:
- status_t getMediaTime_l(int64_t realUs,
- int64_t *outMediaUs,
- bool allowPastMaxTime);
+ status_t getMediaTime_l(
+ int64_t realUs,
+ int64_t *outMediaUs,
+ bool allowPastMaxTime) const;
- Mutex mLock;
+ mutable Mutex mLock;
int64_t mAnchorTimeMediaUs;
int64_t mAnchorTimeRealUs;
diff --git a/include/media/stagefright/MediaSync.h b/include/media/stagefright/MediaSync.h
new file mode 100644
index 0000000..8bb8c7f
--- /dev/null
+++ b/include/media/stagefright/MediaSync.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MEDIA_SYNC_H
+#define MEDIA_SYNC_H
+
+#include <gui/IConsumerListener.h>
+#include <gui/IProducerListener.h>
+
+#include <media/stagefright/foundation/AHandler.h>
+
+#include <utils/Condition.h>
+#include <utils/Mutex.h>
+
+namespace android {
+
+class AudioTrack;
+class BufferItem;
+class Fence;
+class GraphicBuffer;
+class IGraphicBufferConsumer;
+class IGraphicBufferProducer;
+struct MediaClock;
+
+// MediaSync manages media playback and its synchronization to a media clock
+// source. It can be also used for video-only playback.
+//
+// For video playback, it requires an output surface and provides an input
+// surface. It then controls the rendering of input buffers (buffer queued to
+// the input surface) on the output surface to happen at the appropriate time.
+//
+// For audio playback, it requires an audio track and takes updates of
+// information of rendered audio data to maintain media clock when audio track
+// serves as media clock source. (TODO: move audio rendering from JAVA to
+// native code).
+//
+// It can use the audio or video track as media clock source, as well as an
+// external clock. (TODO: actually support external clock as media clock
+// sources; use video track as media clock source for audio-and-video stream).
+//
+// In video-only mode, MediaSync will playback every video frame even though
+// a video frame arrives late based on its timestamp and last frame's.
+//
+// The client needs to configure surface (for output video rendering) and audio
+// track (for querying information of audio rendering) for MediaSync.
+//
+// Then the client needs to obtain a surface from MediaSync and render video
+// frames onto that surface. Internally, the MediaSync will receive those video
+// frames and render them onto the output surface at the appropriate time.
+//
+// The client needs to call updateQueuedAudioData() immediately after it writes
+// audio data to the audio track. Such information will be used to update media
+// clock.
+//
+class MediaSync : public AHandler {
+public:
+ // Create an instance of MediaSync.
+ static sp<MediaSync> create();
+
+ // Called when MediaSync is used to render video. It should be called
+ // before createInputSurface().
+ status_t configureSurface(const sp<IGraphicBufferProducer> &output);
+
+ // Called when audio track is used as media clock source. It should be
+ // called before updateQueuedAudioData().
+ // |nativeSampleRateInHz| is the sample rate of audio data fed into audio
+ // track. It's the same number used to create AudioTrack.
+ status_t configureAudioTrack(
+ const sp<AudioTrack> &audioTrack, uint32_t nativeSampleRateInHz);
+
+ // Create a surface for client to render video frames. This is the surface
+ // on which the client should render video frames. Those video frames will
+ // be internally directed to output surface for rendering at appropriate
+ // time.
+ status_t createInputSurface(sp<IGraphicBufferProducer> *outBufferProducer);
+
+ // Update just-rendered audio data size and the presentation timestamp of
+ // the first frame of that audio data. It should be called immediately
+ // after the client write audio data into AudioTrack.
+ // This function assumes continous audio stream.
+ // TODO: support gap or backwards updates.
+ status_t updateQueuedAudioData(
+ size_t sizeInBytes, int64_t presentationTimeUs);
+
+ // Set the consumer name of the input queue.
+ void setName(const AString &name);
+
+ // Set the playback in a desired speed.
+ // This method can be called any time.
+ // |rate| is the ratio between desired speed and the normal one, and should
+ // be non-negative. The meaning of rate values:
+ // 1.0 -- normal playback
+ // 0.0 -- stop or pause
+ // larger than 1.0 -- faster than normal speed
+ // between 0.0 and 1.0 -- slower than normal speed
+ status_t setPlaybackRate(float rate);
+
+ // Get the media clock used by the MediaSync so that the client can obtain
+ // corresponding media time or real time via
+ // MediaClock::getMediaTime() and MediaClock::getRealTimeFor().
+ sp<const MediaClock> getMediaClock();
+
+protected:
+ virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+ enum {
+ kWhatDrainVideo = 'dVid',
+ };
+
+ static const int MAX_OUTSTANDING_BUFFERS = 2;
+
+ // This is a thin wrapper class that lets us listen to
+ // IConsumerListener::onFrameAvailable from mInput.
+ class InputListener : public BnConsumerListener,
+ public IBinder::DeathRecipient {
+ public:
+ InputListener(const sp<MediaSync> &sync);
+ virtual ~InputListener();
+
+ // From IConsumerListener
+ virtual void onFrameAvailable(const BufferItem &item);
+
+ // From IConsumerListener
+ // We don't care about released buffers because we detach each buffer as
+ // soon as we acquire it. See the comment for onBufferReleased below for
+ // some clarifying notes about the name.
+ virtual void onBuffersReleased() {}
+
+ // From IConsumerListener
+ // We don't care about sideband streams, since we won't relay them.
+ virtual void onSidebandStreamChanged();
+
+ // From IBinder::DeathRecipient
+ virtual void binderDied(const wp<IBinder> &who);
+
+ private:
+ sp<MediaSync> mSync;
+ };
+
+ // This is a thin wrapper class that lets us listen to
+ // IProducerListener::onBufferReleased from mOutput.
+ class OutputListener : public BnProducerListener,
+ public IBinder::DeathRecipient {
+ public:
+ OutputListener(const sp<MediaSync> &sync);
+ virtual ~OutputListener();
+
+ // From IProducerListener
+ virtual void onBufferReleased();
+
+ // From IBinder::DeathRecipient
+ virtual void binderDied(const wp<IBinder> &who);
+
+ private:
+ sp<MediaSync> mSync;
+ };
+
+ // mIsAbandoned is set to true when the input or output dies.
+ // Once the MediaSync has been abandoned by one side, it will disconnect
+ // from the other side and not attempt to communicate with it further.
+ bool mIsAbandoned;
+
+ mutable Mutex mMutex;
+ Condition mReleaseCondition;
+ size_t mNumOutstandingBuffers;
+ sp<IGraphicBufferConsumer> mInput;
+ sp<IGraphicBufferProducer> mOutput;
+
+ sp<AudioTrack> mAudioTrack;
+ uint32_t mNativeSampleRateInHz;
+ int64_t mNumFramesWritten;
+ bool mHasAudio;
+
+ int64_t mNextBufferItemMediaUs;
+ List<BufferItem> mBufferItems;
+ sp<ALooper> mLooper;
+ float mPlaybackRate;
+
+ sp<MediaClock> mMediaClock;
+
+ MediaSync();
+
+ // Must be accessed through RefBase
+ virtual ~MediaSync();
+
+ int64_t getRealTime(int64_t mediaTimeUs, int64_t nowUs);
+ int64_t getDurationIfPlayedAtNativeSampleRate_l(int64_t numFrames);
+ int64_t getPlayedOutAudioDurationMedia_l(int64_t nowUs);
+
+ void onDrainVideo_l();
+
+ // This implements the onFrameAvailable callback from IConsumerListener.
+ // It gets called from an InputListener.
+ // During this callback, we detach the buffer from the input, and queue
+ // it for rendering on the output. This call can block if there are too
+ // many outstanding buffers. If it blocks, it will resume when
+ // onBufferReleasedByOutput releases a buffer back to the input.
+ void onFrameAvailableFromInput();
+
+ // Send |bufferItem| to the output for rendering.
+ void renderOneBufferItem_l(const BufferItem &bufferItem);
+
+ // This implements the onBufferReleased callback from IProducerListener.
+ // It gets called from an OutputListener.
+ // During this callback, we detach the buffer from the output, and release
+ // it to the input. A blocked onFrameAvailable call will be allowed to proceed.
+ void onBufferReleasedByOutput();
+
+ // Return |buffer| back to the input.
+ void returnBufferToInput_l(const sp<GraphicBuffer> &buffer, const sp<Fence> &fence);
+
+ // When this is called, the MediaSync disconnects from (i.e., abandons) its
+ // input or output, and signals any waiting onFrameAvailable calls to wake
+ // up. This must be called with mMutex locked.
+ void onAbandoned_l(bool isInput);
+
+ // helper.
+ bool isPlaying() { return mPlaybackRate != 0.0; }
+
+ DISALLOW_EVIL_CONSTRUCTORS(MediaSync);
+};
+
+} // namespace android
+
+#endif
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 5378bf2..3b260d6 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -36,6 +36,8 @@ LOCAL_SRC_FILES:= \
IMediaRecorder.cpp \
IRemoteDisplay.cpp \
IRemoteDisplayClient.cpp \
+ IResourceManagerClient.cpp \
+ IResourceManagerService.cpp \
IStreamSource.cpp \
MediaCodecInfo.cpp \
Metadata.cpp \
@@ -53,6 +55,8 @@ LOCAL_SRC_FILES:= \
CharacterEncodingDetector.cpp \
IMediaDeathNotifier.cpp \
MediaProfiles.cpp \
+ MediaResource.cpp \
+ MediaResourcePolicy.cpp \
IEffect.cpp \
IEffectClient.cpp \
AudioEffect.cpp \
diff --git a/media/libmedia/AudioRecord.cpp b/media/libmedia/AudioRecord.cpp
index 355baf0..100a914 100644
--- a/media/libmedia/AudioRecord.cpp
+++ b/media/libmedia/AudioRecord.cpp
@@ -161,8 +161,6 @@ status_t AudioRecord::set(
}
mTransfer = transferType;
- AutoMutex lock(mLock);
-
// invariant that mAudioRecord != 0 is true only after set() returns successfully
if (mAudioRecord != 0) {
ALOGE("Track already in use");
@@ -235,6 +233,7 @@ status_t AudioRecord::set(
if (cbf != NULL) {
mAudioRecordThread = new AudioRecordThread(*this, threadCanCallJava);
mAudioRecordThread->run("AudioRecord", ANDROID_PRIORITY_AUDIO);
+ // thread begins in paused state, and will not reference us until start()
}
// create the IAudioRecord
@@ -725,7 +724,7 @@ audio_io_handle_t AudioRecord::getInputPrivate() const
// -------------------------------------------------------------------------
-ssize_t AudioRecord::read(void* buffer, size_t userSize)
+ssize_t AudioRecord::read(void* buffer, size_t userSize, bool blocking)
{
if (mTransfer != TRANSFER_SYNC) {
return INVALID_OPERATION;
@@ -744,7 +743,8 @@ ssize_t AudioRecord::read(void* buffer, size_t userSize)
while (userSize >= mFrameSize) {
audioBuffer.frameCount = userSize / mFrameSize;
- status_t err = obtainBuffer(&audioBuffer, &ClientProxy::kForever);
+ status_t err = obtainBuffer(&audioBuffer,
+ blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
if (err < 0) {
if (read > 0) {
break;
diff --git a/media/libmedia/AudioTrack.cpp b/media/libmedia/AudioTrack.cpp
index 8fd5278..98f64fe 100644
--- a/media/libmedia/AudioTrack.cpp
+++ b/media/libmedia/AudioTrack.cpp
@@ -274,8 +274,6 @@ status_t AudioTrack::set(
ALOGV("set() streamType %d frameCount %zu flags %04x", streamType, frameCount, flags);
- AutoMutex lock(mLock);
-
// invariant that mAudioTrack != 0 is true only after set() returns successfully
if (mAudioTrack != 0) {
ALOGE("Track already in use");
@@ -401,6 +399,7 @@ status_t AudioTrack::set(
if (cbf != NULL) {
mAudioTrackThread = new AudioTrackThread(*this, threadCanCallJava);
mAudioTrackThread->run("AudioTrack", ANDROID_PRIORITY_AUDIO, 0 /*stack*/);
+ // thread begins in paused state, and will not reference us until start()
}
// create the IAudioTrack
diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp
index b08fa82..714a0b3 100644
--- a/media/libmedia/IDrm.cpp
+++ b/media/libmedia/IDrm.cpp
@@ -125,7 +125,8 @@ struct BpDrm : public BpInterface<IDrm> {
Vector<uint8_t> const &initData,
String8 const &mimeType, DrmPlugin::KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl) {
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType) {
Parcel data, reply;
data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
@@ -143,6 +144,7 @@ struct BpDrm : public BpInterface<IDrm> {
readVector(reply, request);
defaultUrl = reply.readString8();
+ *keyRequestType = static_cast<DrmPlugin::KeyRequestType>(reply.readInt32());
return reply.readInt32();
}
@@ -562,13 +564,15 @@ status_t BnDrm::onTransact(
Vector<uint8_t> request;
String8 defaultUrl;
+ DrmPlugin::KeyRequestType keyRequestType;
+
+ status_t result = getKeyRequest(sessionId, initData, mimeType,
+ keyType, optionalParameters, request, defaultUrl,
+ &keyRequestType);
- status_t result = getKeyRequest(sessionId, initData,
- mimeType, keyType,
- optionalParameters,
- request, defaultUrl);
writeVector(reply, request);
reply->writeString8(defaultUrl);
+ reply->writeInt32(static_cast<int32_t>(keyRequestType));
reply->writeInt32(result);
return OK;
}
diff --git a/media/libmedia/IResourceManagerClient.cpp b/media/libmedia/IResourceManagerClient.cpp
new file mode 100644
index 0000000..6fa56fc
--- /dev/null
+++ b/media/libmedia/IResourceManagerClient.cpp
@@ -0,0 +1,70 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <media/IResourceManagerClient.h>
+
+namespace android {
+
+enum {
+ RECLAIM_RESOURCE = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpResourceManagerClient: public BpInterface<IResourceManagerClient>
+{
+public:
+ BpResourceManagerClient(const sp<IBinder> &impl)
+ : BpInterface<IResourceManagerClient>(impl)
+ {
+ }
+
+ virtual bool reclaimResource() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IResourceManagerClient::getInterfaceDescriptor());
+
+ bool ret = false;
+ status_t status = remote()->transact(RECLAIM_RESOURCE, data, &reply);
+ if (status == NO_ERROR) {
+ ret = (bool)reply.readInt32();
+ }
+ return ret;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(ResourceManagerClient, "android.media.IResourceManagerClient");
+
+// ----------------------------------------------------------------------
+
+status_t BnResourceManagerClient::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
+{
+ switch (code) {
+ case RECLAIM_RESOURCE: {
+ CHECK_INTERFACE(IResourceManagerClient, data, reply);
+ bool ret = reclaimResource();
+ reply->writeInt32(ret);
+ return NO_ERROR;
+ } break;
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+}; // namespace android
diff --git a/media/libmedia/IResourceManagerService.cpp b/media/libmedia/IResourceManagerService.cpp
new file mode 100644
index 0000000..95a2d1c
--- /dev/null
+++ b/media/libmedia/IResourceManagerService.cpp
@@ -0,0 +1,169 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IResourceManagerService"
+#include <utils/Log.h>
+
+#include "media/IResourceManagerService.h"
+
+#include <binder/Parcel.h>
+
+#include <stdint.h>
+#include <sys/types.h>
+
+namespace android {
+
+enum {
+ CONFIG = IBinder::FIRST_CALL_TRANSACTION,
+ ADD_RESOURCE,
+ REMOVE_RESOURCE,
+ RECLAIM_RESOURCE,
+};
+
+template <typename T>
+static void writeToParcel(Parcel *data, const Vector<T> &items) {
+ size_t size = items.size();
+ size_t sizePosition = data->dataPosition();
+ // truncates size, but should be okay for this usecase
+ data->writeUint32(static_cast<uint32_t>(size));
+ for (size_t i = 0; i < size; i++) {
+ size_t position = data->dataPosition();
+ items[i].writeToParcel(data);
+ }
+}
+
+template <typename T>
+static void readFromParcel(const Parcel &data, Vector<T> *items) {
+ size_t size = (size_t)data.readUint32();
+ for (size_t i = 0; i < size; i++) {
+ T item;
+ item.readFromParcel(data);
+ items->add(item);
+ }
+}
+
+class BpResourceManagerService : public BpInterface<IResourceManagerService>
+{
+public:
+ BpResourceManagerService(const sp<IBinder> &impl)
+ : BpInterface<IResourceManagerService>(impl)
+ {
+ }
+
+ virtual void config(const Vector<MediaResourcePolicy> &policies) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IResourceManagerService::getInterfaceDescriptor());
+ writeToParcel(&data, policies);
+ remote()->transact(CONFIG, data, &reply);
+ }
+
+ virtual void addResource(
+ int pid,
+ int64_t clientId,
+ const sp<IResourceManagerClient> client,
+ const Vector<MediaResource> &resources) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IResourceManagerService::getInterfaceDescriptor());
+ data.writeInt32(pid);
+ data.writeInt64(clientId);
+ data.writeStrongBinder(IInterface::asBinder(client));
+ writeToParcel(&data, resources);
+
+ remote()->transact(ADD_RESOURCE, data, &reply);
+ }
+
+ virtual void removeResource(int64_t clientId) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IResourceManagerService::getInterfaceDescriptor());
+ data.writeInt64(clientId);
+
+ remote()->transact(REMOVE_RESOURCE, data, &reply);
+ }
+
+ virtual bool reclaimResource(int callingPid, const Vector<MediaResource> &resources) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IResourceManagerService::getInterfaceDescriptor());
+ data.writeInt32(callingPid);
+ writeToParcel(&data, resources);
+
+ bool ret = false;
+ status_t status = remote()->transact(RECLAIM_RESOURCE, data, &reply);
+ if (status == NO_ERROR) {
+ ret = (bool)reply.readInt32();
+ }
+ return ret;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(ResourceManagerService, "android.media.IResourceManagerService");
+
+// ----------------------------------------------------------------------
+
+
+status_t BnResourceManagerService::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags)
+{
+ switch (code) {
+ case CONFIG: {
+ CHECK_INTERFACE(IResourceManagerService, data, reply);
+ int pid = data.readInt32();
+ sp<IResourceManagerClient> client(
+ interface_cast<IResourceManagerClient>(data.readStrongBinder()));
+ Vector<MediaResourcePolicy> policies;
+ readFromParcel(data, &policies);
+ config(policies);
+ return NO_ERROR;
+ } break;
+
+ case ADD_RESOURCE: {
+ CHECK_INTERFACE(IResourceManagerService, data, reply);
+ int pid = data.readInt32();
+ int64_t clientId = data.readInt64();
+ sp<IResourceManagerClient> client(
+ interface_cast<IResourceManagerClient>(data.readStrongBinder()));
+ Vector<MediaResource> resources;
+ readFromParcel(data, &resources);
+ addResource(pid, clientId, client, resources);
+ return NO_ERROR;
+ } break;
+
+ case REMOVE_RESOURCE: {
+ CHECK_INTERFACE(IResourceManagerService, data, reply);
+ int64_t clientId = data.readInt64();
+ removeResource(clientId);
+ return NO_ERROR;
+ } break;
+
+ case RECLAIM_RESOURCE: {
+ CHECK_INTERFACE(IResourceManagerService, data, reply);
+ int callingPid = data.readInt32();
+ Vector<MediaResource> resources;
+ readFromParcel(data, &resources);
+ bool ret = reclaimResource(callingPid, resources);
+ reply->writeInt32(ret);
+ return NO_ERROR;
+ } break;
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
diff --git a/media/libmedia/MediaResource.cpp b/media/libmedia/MediaResource.cpp
new file mode 100644
index 0000000..8be01bc
--- /dev/null
+++ b/media/libmedia/MediaResource.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaResource"
+#include <utils/Log.h>
+#include <media/MediaResource.h>
+
+namespace android {
+
+const char kResourceSecureCodec[] = "secure-codec";
+const char kResourceNonSecureCodec[] = "non-secure-codec";
+const char kResourceGraphicMemory[] = "graphic-memory";
+
+MediaResource::MediaResource() : mValue(0) {}
+
+MediaResource::MediaResource(String8 type, uint64_t value)
+ : mType(type),
+ mValue(value) {}
+
+MediaResource::MediaResource(String8 type, String8 subType, uint64_t value)
+ : mType(type),
+ mSubType(subType),
+ mValue(value) {}
+
+void MediaResource::readFromParcel(const Parcel &parcel) {
+ mType = parcel.readString8();
+ mSubType = parcel.readString8();
+ mValue = parcel.readUint64();
+}
+
+void MediaResource::writeToParcel(Parcel *parcel) const {
+ parcel->writeString8(mType);
+ parcel->writeString8(mSubType);
+ parcel->writeUint64(mValue);
+}
+
+String8 MediaResource::toString() const {
+ String8 str;
+ str.appendFormat("%s/%s:%llu", mType.string(), mSubType.string(), mValue);
+ return str;
+}
+
+bool MediaResource::operator==(const MediaResource &other) const {
+ return (other.mType == mType) && (other.mSubType == mSubType) && (other.mValue == mValue);
+}
+
+bool MediaResource::operator!=(const MediaResource &other) const {
+ return !(*this == other);
+}
+
+}; // namespace android
diff --git a/media/libmedia/MediaResourcePolicy.cpp b/media/libmedia/MediaResourcePolicy.cpp
new file mode 100644
index 0000000..2bb996a
--- /dev/null
+++ b/media/libmedia/MediaResourcePolicy.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaResourcePolicy"
+#include <utils/Log.h>
+#include <media/MediaResourcePolicy.h>
+
+namespace android {
+
+const char kPolicySupportsMultipleSecureCodecs[] = "supports-multiple-secure-codecs";
+const char kPolicySupportsSecureWithNonSecureCodec[] = "supports-secure-with-non-secure-codec";
+
+MediaResourcePolicy::MediaResourcePolicy() : mValue(0) {}
+
+MediaResourcePolicy::MediaResourcePolicy(String8 type, uint64_t value)
+ : mType(type),
+ mValue(value) {}
+
+void MediaResourcePolicy::readFromParcel(const Parcel &parcel) {
+ mType = parcel.readString8();
+ mValue = parcel.readUint64();
+}
+
+void MediaResourcePolicy::writeToParcel(Parcel *parcel) const {
+ parcel->writeString8(mType);
+ parcel->writeUint64(mValue);
+}
+
+String8 MediaResourcePolicy::toString() const {
+ String8 str;
+ str.appendFormat("%s:%llu", mType.string(), mValue);
+ return str;
+}
+
+}; // namespace android
diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp
index d4f6fab..49e01d1 100644
--- a/media/libmediaplayerservice/Drm.cpp
+++ b/media/libmediaplayerservice/Drm.cpp
@@ -358,7 +358,8 @@ status_t Drm::getKeyRequest(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &initData,
String8 const &mimeType, DrmPlugin::KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl) {
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType) {
Mutex::Autolock autoLock(mLock);
if (mInitCheck != OK) {
@@ -372,7 +373,8 @@ status_t Drm::getKeyRequest(Vector<uint8_t> const &sessionId,
DrmSessionManager::Instance()->useSession(sessionId);
return mPlugin->getKeyRequest(sessionId, initData, mimeType, keyType,
- optionalParameters, request, defaultUrl);
+ optionalParameters, request, defaultUrl,
+ keyRequestType);
}
status_t Drm::provideKeyResponse(Vector<uint8_t> const &sessionId,
diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h
index 0cea639..7e8f246 100644
--- a/media/libmediaplayerservice/Drm.h
+++ b/media/libmediaplayerservice/Drm.h
@@ -53,7 +53,8 @@ struct Drm : public BnDrm,
Vector<uint8_t> const &initData,
String8 const &mimeType, DrmPlugin::KeyType keyType,
KeyedVector<String8, String8> const &optionalParameters,
- Vector<uint8_t> &request, String8 &defaultUrl);
+ Vector<uint8_t> &request, String8 &defaultUrl,
+ DrmPlugin::KeyRequestType *keyRequestType);
virtual status_t provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response,
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
index 177293d..a2cbdaf 100644
--- a/media/libstagefright/Android.mk
+++ b/media/libstagefright/Android.mk
@@ -37,6 +37,7 @@ LOCAL_SRC_FILES:= \
MediaCodecSource.cpp \
MediaDefs.cpp \
MediaExtractor.cpp \
+ MediaSync.cpp \
MidiExtractor.cpp \
http/MediaHTTP.cpp \
MediaMuxer.cpp \
diff --git a/media/libstagefright/MediaClock.cpp b/media/libstagefright/MediaClock.cpp
index 38db5e4..433f555 100644
--- a/media/libstagefright/MediaClock.cpp
+++ b/media/libstagefright/MediaClock.cpp
@@ -93,13 +93,17 @@ void MediaClock::setPlaybackRate(float rate) {
}
status_t MediaClock::getMediaTime(
- int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) {
+ int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const {
+ if (outMediaUs == NULL) {
+ return BAD_VALUE;
+ }
+
Mutex::Autolock autoLock(mLock);
return getMediaTime_l(realUs, outMediaUs, allowPastMaxTime);
}
status_t MediaClock::getMediaTime_l(
- int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) {
+ int64_t realUs, int64_t *outMediaUs, bool allowPastMaxTime) const {
if (mAnchorTimeRealUs == -1) {
return NO_INIT;
}
@@ -119,7 +123,12 @@ status_t MediaClock::getMediaTime_l(
return OK;
}
-status_t MediaClock::getRealTimeFor(int64_t targetMediaUs, int64_t *outRealUs) {
+status_t MediaClock::getRealTimeFor(
+ int64_t targetMediaUs, int64_t *outRealUs) const {
+ if (outRealUs == NULL) {
+ return BAD_VALUE;
+ }
+
Mutex::Autolock autoLock(mLock);
if (mPlaybackRate == 0.0) {
return NO_INIT;
diff --git a/media/libstagefright/MediaSync.cpp b/media/libstagefright/MediaSync.cpp
new file mode 100644
index 0000000..7b6c7d9
--- /dev/null
+++ b/media/libstagefright/MediaSync.cpp
@@ -0,0 +1,541 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaSync"
+#include <inttypes.h>
+
+#include <gui/BufferQueue.h>
+#include <gui/IGraphicBufferConsumer.h>
+#include <gui/IGraphicBufferProducer.h>
+
+#include <media/AudioTrack.h>
+#include <media/stagefright/MediaClock.h>
+#include <media/stagefright/MediaSync.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+
+#include <ui/GraphicBuffer.h>
+
+// Maximum late time allowed for a video frame to be rendered. When a video
+// frame arrives later than this number, it will be discarded without rendering.
+static const int64_t kMaxAllowedVideoLateTimeUs = 40000ll;
+
+namespace android {
+
+// static
+sp<MediaSync> MediaSync::create() {
+ sp<MediaSync> sync = new MediaSync();
+ sync->mLooper->registerHandler(sync);
+ return sync;
+}
+
+MediaSync::MediaSync()
+ : mIsAbandoned(false),
+ mMutex(),
+ mReleaseCondition(),
+ mNumOutstandingBuffers(0),
+ mNativeSampleRateInHz(0),
+ mNumFramesWritten(0),
+ mHasAudio(false),
+ mNextBufferItemMediaUs(-1),
+ mPlaybackRate(0.0) {
+ mMediaClock = new MediaClock;
+
+ mLooper = new ALooper;
+ mLooper->setName("MediaSync");
+ mLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
+}
+
+MediaSync::~MediaSync() {
+ if (mInput != NULL) {
+ mInput->consumerDisconnect();
+ }
+ if (mOutput != NULL) {
+ mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
+ }
+
+ if (mLooper != NULL) {
+ mLooper->unregisterHandler(id());
+ mLooper->stop();
+ }
+}
+
+status_t MediaSync::configureSurface(const sp<IGraphicBufferProducer> &output) {
+ Mutex::Autolock lock(mMutex);
+
+ // TODO: support suface change.
+ if (mOutput != NULL) {
+ ALOGE("configureSurface: output surface has already been configured.");
+ return INVALID_OPERATION;
+ }
+
+ if (output != NULL) {
+ IGraphicBufferProducer::QueueBufferOutput queueBufferOutput;
+ sp<OutputListener> listener(new OutputListener(this));
+ IInterface::asBinder(output)->linkToDeath(listener);
+ status_t status =
+ output->connect(listener,
+ NATIVE_WINDOW_API_MEDIA,
+ true /* producerControlledByApp */,
+ &queueBufferOutput);
+ if (status != NO_ERROR) {
+ ALOGE("configureSurface: failed to connect (%d)", status);
+ return status;
+ }
+
+ mOutput = output;
+ }
+
+ return NO_ERROR;
+}
+
+// |audioTrack| is used only for querying information.
+status_t MediaSync::configureAudioTrack(
+ const sp<AudioTrack> &audioTrack, uint32_t nativeSampleRateInHz) {
+ Mutex::Autolock lock(mMutex);
+
+ // TODO: support audio track change.
+ if (mAudioTrack != NULL) {
+ ALOGE("configureAudioTrack: audioTrack has already been configured.");
+ return INVALID_OPERATION;
+ }
+
+ mAudioTrack = audioTrack;
+ mNativeSampleRateInHz = nativeSampleRateInHz;
+
+ return NO_ERROR;
+}
+
+status_t MediaSync::createInputSurface(
+ sp<IGraphicBufferProducer> *outBufferProducer) {
+ if (outBufferProducer == NULL) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+
+ if (mOutput == NULL) {
+ return NO_INIT;
+ }
+
+ if (mInput != NULL) {
+ return INVALID_OPERATION;
+ }
+
+ sp<IGraphicBufferProducer> bufferProducer;
+ sp<IGraphicBufferConsumer> bufferConsumer;
+ BufferQueue::createBufferQueue(&bufferProducer, &bufferConsumer);
+
+ sp<InputListener> listener(new InputListener(this));
+ IInterface::asBinder(bufferConsumer)->linkToDeath(listener);
+ status_t status =
+ bufferConsumer->consumerConnect(listener, false /* controlledByApp */);
+ if (status == NO_ERROR) {
+ bufferConsumer->setConsumerName(String8("MediaSync"));
+ *outBufferProducer = bufferProducer;
+ mInput = bufferConsumer;
+ }
+ return status;
+}
+
+status_t MediaSync::setPlaybackRate(float rate) {
+ if (rate < 0.0) {
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock lock(mMutex);
+
+ if (rate > mPlaybackRate) {
+ mNextBufferItemMediaUs = -1;
+ }
+ mPlaybackRate = rate;
+ mMediaClock->setPlaybackRate(rate);
+ onDrainVideo_l();
+
+ return OK;
+}
+
+sp<const MediaClock> MediaSync::getMediaClock() {
+ return mMediaClock;
+}
+
+status_t MediaSync::updateQueuedAudioData(
+ size_t sizeInBytes, int64_t presentationTimeUs) {
+ if (sizeInBytes == 0) {
+ return OK;
+ }
+
+ Mutex::Autolock lock(mMutex);
+
+ if (mAudioTrack == NULL) {
+ ALOGW("updateQueuedAudioData: audioTrack has NOT been configured.");
+ return INVALID_OPERATION;
+ }
+
+ int64_t numFrames = sizeInBytes / mAudioTrack->frameSize();
+ int64_t maxMediaTimeUs = presentationTimeUs
+ + getDurationIfPlayedAtNativeSampleRate_l(numFrames);
+ mNumFramesWritten += numFrames;
+
+ int64_t nowUs = ALooper::GetNowUs();
+ int64_t nowMediaUs = maxMediaTimeUs
+ - getDurationIfPlayedAtNativeSampleRate_l(mNumFramesWritten)
+ + getPlayedOutAudioDurationMedia_l(nowUs);
+
+ int64_t oldRealTime = -1;
+ if (mNextBufferItemMediaUs != -1) {
+ oldRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
+ }
+
+ mMediaClock->updateAnchor(nowMediaUs, nowUs, maxMediaTimeUs);
+ mHasAudio = true;
+
+ if (oldRealTime != -1) {
+ int64_t newRealTime = getRealTime(mNextBufferItemMediaUs, nowUs);
+ if (newRealTime < oldRealTime) {
+ mNextBufferItemMediaUs = -1;
+ onDrainVideo_l();
+ }
+ }
+
+ return OK;
+}
+
+void MediaSync::setName(const AString &name) {
+ Mutex::Autolock lock(mMutex);
+ mInput->setConsumerName(String8(name.c_str()));
+}
+
+int64_t MediaSync::getRealTime(int64_t mediaTimeUs, int64_t nowUs) {
+ int64_t realUs;
+ if (mMediaClock->getRealTimeFor(mediaTimeUs, &realUs) != OK) {
+ // If failed to get current position, e.g. due to audio clock is
+ // not ready, then just play out video immediately without delay.
+ return nowUs;
+ }
+ return realUs;
+}
+
+int64_t MediaSync::getDurationIfPlayedAtNativeSampleRate_l(int64_t numFrames) {
+ return (numFrames * 1000000LL / mNativeSampleRateInHz);
+}
+
+int64_t MediaSync::getPlayedOutAudioDurationMedia_l(int64_t nowUs) {
+ CHECK(mAudioTrack != NULL);
+
+ uint32_t numFramesPlayed;
+ int64_t numFramesPlayedAt;
+ AudioTimestamp ts;
+ static const int64_t kStaleTimestamp100ms = 100000;
+
+ status_t res = mAudioTrack->getTimestamp(ts);
+ if (res == OK) {
+ // case 1: mixing audio tracks.
+ numFramesPlayed = ts.mPosition;
+ numFramesPlayedAt =
+ ts.mTime.tv_sec * 1000000LL + ts.mTime.tv_nsec / 1000;
+ const int64_t timestampAge = nowUs - numFramesPlayedAt;
+ if (timestampAge > kStaleTimestamp100ms) {
+ // This is an audio FIXME.
+ // getTimestamp returns a timestamp which may come from audio
+ // mixing threads. After pausing, the MixerThread may go idle,
+ // thus the mTime estimate may become stale. Assuming that the
+ // MixerThread runs 20ms, with FastMixer at 5ms, the max latency
+ // should be about 25ms with an average around 12ms (to be
+ // verified). For safety we use 100ms.
+ ALOGV("getTimestamp: returned stale timestamp nowUs(%lld) "
+ "numFramesPlayedAt(%lld)",
+ (long long)nowUs, (long long)numFramesPlayedAt);
+ numFramesPlayedAt = nowUs - kStaleTimestamp100ms;
+ }
+ //ALOGD("getTimestamp: OK %d %lld",
+ // numFramesPlayed, (long long)numFramesPlayedAt);
+ } else if (res == WOULD_BLOCK) {
+ // case 2: transitory state on start of a new track
+ numFramesPlayed = 0;
+ numFramesPlayedAt = nowUs;
+ //ALOGD("getTimestamp: WOULD_BLOCK %d %lld",
+ // numFramesPlayed, (long long)numFramesPlayedAt);
+ } else {
+ // case 3: transitory at new track or audio fast tracks.
+ res = mAudioTrack->getPosition(&numFramesPlayed);
+ CHECK_EQ(res, (status_t)OK);
+ numFramesPlayedAt = nowUs;
+ numFramesPlayedAt += 1000LL * mAudioTrack->latency() / 2; /* XXX */
+ //ALOGD("getPosition: %d %lld", numFramesPlayed, numFramesPlayedAt);
+ }
+
+ //can't be negative until 12.4 hrs, test.
+ //CHECK_EQ(numFramesPlayed & (1 << 31), 0);
+ int64_t durationUs =
+ getDurationIfPlayedAtNativeSampleRate_l(numFramesPlayed)
+ + nowUs - numFramesPlayedAt;
+ if (durationUs < 0) {
+ // Occurs when numFramesPlayed position is very small and the following:
+ // (1) In case 1, the time nowUs is computed before getTimestamp() is
+ // called and numFramesPlayedAt is greater than nowUs by time more
+ // than numFramesPlayed.
+ // (2) In case 3, using getPosition and adding mAudioTrack->latency()
+ // to numFramesPlayedAt, by a time amount greater than
+ // numFramesPlayed.
+ //
+ // Both of these are transitory conditions.
+ ALOGV("getPlayedOutAudioDurationMedia_l: negative duration %lld "
+ "set to zero", (long long)durationUs);
+ durationUs = 0;
+ }
+ ALOGV("getPlayedOutAudioDurationMedia_l(%lld) nowUs(%lld) frames(%u) "
+ "framesAt(%lld)",
+ (long long)durationUs, (long long)nowUs, numFramesPlayed,
+ (long long)numFramesPlayedAt);
+ return durationUs;
+}
+
+void MediaSync::onDrainVideo_l() {
+ if (!isPlaying()) {
+ return;
+ }
+
+ int64_t nowUs = ALooper::GetNowUs();
+
+ while (!mBufferItems.empty()) {
+ BufferItem *bufferItem = &*mBufferItems.begin();
+ int64_t itemMediaUs = bufferItem->mTimestamp / 1000;
+ int64_t itemRealUs = getRealTime(itemMediaUs, nowUs);
+ if (itemRealUs <= nowUs) {
+ if (mHasAudio) {
+ if (nowUs - itemRealUs <= kMaxAllowedVideoLateTimeUs) {
+ renderOneBufferItem_l(*bufferItem);
+ } else {
+ // too late.
+ returnBufferToInput_l(
+ bufferItem->mGraphicBuffer, bufferItem->mFence);
+ }
+ } else {
+ // always render video buffer in video-only mode.
+ renderOneBufferItem_l(*bufferItem);
+
+ // smooth out videos >= 10fps
+ mMediaClock->updateAnchor(
+ itemMediaUs, nowUs, itemMediaUs + 100000);
+ }
+
+ mBufferItems.erase(mBufferItems.begin());
+
+ if (mBufferItems.empty()) {
+ mNextBufferItemMediaUs = -1;
+ }
+ } else {
+ if (mNextBufferItemMediaUs == -1
+ || mNextBufferItemMediaUs != itemMediaUs) {
+ sp<AMessage> msg = new AMessage(kWhatDrainVideo, this);
+ msg->post(itemRealUs - nowUs);
+ }
+ break;
+ }
+ }
+}
+
+void MediaSync::onFrameAvailableFromInput() {
+ Mutex::Autolock lock(mMutex);
+
+ // If there are too many outstanding buffers, wait until a buffer is
+ // released back to the input in onBufferReleased.
+ while (mNumOutstandingBuffers >= MAX_OUTSTANDING_BUFFERS) {
+ mReleaseCondition.wait(mMutex);
+
+ // If the sync is abandoned while we are waiting, the release
+ // condition variable will be broadcast, and we should just return
+ // without attempting to do anything more (since the input queue will
+ // also be abandoned).
+ if (mIsAbandoned) {
+ return;
+ }
+ }
+ ++mNumOutstandingBuffers;
+
+ // Acquire and detach the buffer from the input.
+ BufferItem bufferItem;
+ status_t status = mInput->acquireBuffer(&bufferItem, 0 /* presentWhen */);
+ if (status != NO_ERROR) {
+ ALOGE("acquiring buffer from input failed (%d)", status);
+ return;
+ }
+
+ ALOGV("acquired buffer %#llx from input", (long long)bufferItem.mGraphicBuffer->getId());
+
+ status = mInput->detachBuffer(bufferItem.mBuf);
+ if (status != NO_ERROR) {
+ ALOGE("detaching buffer from input failed (%d)", status);
+ if (status == NO_INIT) {
+ // If the input has been abandoned, move on.
+ onAbandoned_l(true /* isInput */);
+ }
+ return;
+ }
+
+ mBufferItems.push_back(bufferItem);
+ onDrainVideo_l();
+}
+
+void MediaSync::renderOneBufferItem_l( const BufferItem &bufferItem) {
+ IGraphicBufferProducer::QueueBufferInput queueInput(
+ bufferItem.mTimestamp,
+ bufferItem.mIsAutoTimestamp,
+ bufferItem.mDataSpace,
+ bufferItem.mCrop,
+ static_cast<int32_t>(bufferItem.mScalingMode),
+ bufferItem.mTransform,
+ bufferItem.mIsDroppable,
+ bufferItem.mFence);
+
+ // Attach and queue the buffer to the output.
+ int slot;
+ status_t status = mOutput->attachBuffer(&slot, bufferItem.mGraphicBuffer);
+ ALOGE_IF(status != NO_ERROR, "attaching buffer to output failed (%d)", status);
+ if (status == NO_ERROR) {
+ IGraphicBufferProducer::QueueBufferOutput queueOutput;
+ status = mOutput->queueBuffer(slot, queueInput, &queueOutput);
+ ALOGE_IF(status != NO_ERROR, "queueing buffer to output failed (%d)", status);
+ }
+
+ if (status != NO_ERROR) {
+ returnBufferToInput_l(bufferItem.mGraphicBuffer, bufferItem.mFence);
+ if (status == NO_INIT) {
+ // If the output has been abandoned, move on.
+ onAbandoned_l(false /* isInput */);
+ }
+ return;
+ }
+
+ ALOGV("queued buffer %#llx to output", (long long)bufferItem.mGraphicBuffer->getId());
+}
+
+void MediaSync::onBufferReleasedByOutput() {
+ Mutex::Autolock lock(mMutex);
+
+ sp<GraphicBuffer> buffer;
+ sp<Fence> fence;
+ status_t status = mOutput->detachNextBuffer(&buffer, &fence);
+ ALOGE_IF(status != NO_ERROR, "detaching buffer from output failed (%d)", status);
+
+ if (status == NO_INIT) {
+ // If the output has been abandoned, we can't do anything else,
+ // since buffer is invalid.
+ onAbandoned_l(false /* isInput */);
+ return;
+ }
+
+ ALOGV("detached buffer %#llx from output", (long long)buffer->getId());
+
+ // If we've been abandoned, we can't return the buffer to the input, so just
+ // move on.
+ if (mIsAbandoned) {
+ return;
+ }
+
+ returnBufferToInput_l(buffer, fence);
+}
+
+void MediaSync::returnBufferToInput_l(
+ const sp<GraphicBuffer> &buffer, const sp<Fence> &fence) {
+ // Attach and release the buffer back to the input.
+ int consumerSlot;
+ status_t status = mInput->attachBuffer(&consumerSlot, buffer);
+ ALOGE_IF(status != NO_ERROR, "attaching buffer to input failed (%d)", status);
+ if (status == NO_ERROR) {
+ status = mInput->releaseBuffer(consumerSlot, 0 /* frameNumber */,
+ EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, fence);
+ ALOGE_IF(status != NO_ERROR, "releasing buffer to input failed (%d)", status);
+ }
+
+ if (status != NO_ERROR) {
+ // TODO: do we need to try to return this buffer later?
+ return;
+ }
+
+ ALOGV("released buffer %#llx to input", (long long)buffer->getId());
+
+ // Notify any waiting onFrameAvailable calls.
+ --mNumOutstandingBuffers;
+ mReleaseCondition.signal();
+}
+
+void MediaSync::onAbandoned_l(bool isInput) {
+ ALOGE("the %s has abandoned me", (isInput ? "input" : "output"));
+ if (!mIsAbandoned) {
+ if (isInput) {
+ mOutput->disconnect(NATIVE_WINDOW_API_MEDIA);
+ } else {
+ mInput->consumerDisconnect();
+ }
+ mIsAbandoned = true;
+ }
+ mReleaseCondition.broadcast();
+}
+
+void MediaSync::onMessageReceived(const sp<AMessage> &msg) {
+ switch (msg->what()) {
+ case kWhatDrainVideo:
+ {
+ Mutex::Autolock lock(mMutex);
+ onDrainVideo_l();
+ break;
+ }
+
+ default:
+ TRESPASS();
+ break;
+ }
+}
+
+MediaSync::InputListener::InputListener(const sp<MediaSync> &sync)
+ : mSync(sync) {}
+
+MediaSync::InputListener::~InputListener() {}
+
+void MediaSync::InputListener::onFrameAvailable(const BufferItem &/* item */) {
+ mSync->onFrameAvailableFromInput();
+}
+
+// We don't care about sideband streams, since we won't relay them.
+void MediaSync::InputListener::onSidebandStreamChanged() {
+ ALOGE("onSidebandStreamChanged: got sideband stream unexpectedly.");
+}
+
+
+void MediaSync::InputListener::binderDied(const wp<IBinder> &/* who */) {
+ Mutex::Autolock lock(mSync->mMutex);
+ mSync->onAbandoned_l(true /* isInput */);
+}
+
+MediaSync::OutputListener::OutputListener(const sp<MediaSync> &sync)
+ : mSync(sync) {}
+
+MediaSync::OutputListener::~OutputListener() {}
+
+void MediaSync::OutputListener::onBufferReleased() {
+ mSync->onBufferReleasedByOutput();
+}
+
+void MediaSync::OutputListener::binderDied(const wp<IBinder> &/* who */) {
+ Mutex::Autolock lock(mSync->mMutex);
+ mSync->onAbandoned_l(false /* isInput */);
+}
+
+} // namespace android
diff --git a/media/mediaserver/Android.mk b/media/mediaserver/Android.mk
index 0ad0bf3..baf65f6 100644
--- a/media/mediaserver/Android.mk
+++ b/media/mediaserver/Android.mk
@@ -19,6 +19,7 @@ LOCAL_SHARED_LIBRARIES := \
libcamera_metadata\
libcameraservice \
libmedialogservice \
+ libresourcemanagerservice \
libcutils \
libnbaio \
libmedia \
@@ -38,6 +39,7 @@ LOCAL_C_INCLUDES := \
frameworks/av/services/audioflinger \
frameworks/av/services/audiopolicy \
frameworks/av/services/camera/libcameraservice \
+ frameworks/av/services/mediaresourcemanager \
$(call include-path-for, audio-utils) \
frameworks/av/services/soundtrigger \
frameworks/av/services/radio
diff --git a/media/ndk/NdkMediaDrm.cpp b/media/ndk/NdkMediaDrm.cpp
index 7a1048c..83a5ba1 100644
--- a/media/ndk/NdkMediaDrm.cpp
+++ b/media/ndk/NdkMediaDrm.cpp
@@ -312,8 +312,10 @@ media_status_t AMediaDrm_getKeyRequest(AMediaDrm *mObj, const AMediaDrmScope *sc
String8(optionalParameters[i].mValue));
}
String8 defaultUrl;
+ DrmPlugin::KeyRequestType keyRequestType;
status_t status = mObj->mDrm->getKeyRequest(*iter, mdInit, String8(mimeType),
- mdKeyType, mdOptionalParameters, mObj->mKeyRequest, defaultUrl);
+ mdKeyType, mdOptionalParameters, mObj->mKeyRequest, defaultUrl,
+ &keyRequestType);
if (status != OK) {
return translateStatus(status);
} else {
@@ -725,4 +727,3 @@ media_status_t AMediaDrm_verify(AMediaDrm *mObj, const AMediaDrmSessionId *sessi
}
} // extern "C"
-
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 902d5e4..45df6a9 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -157,8 +157,9 @@ private:
bool mFlushHwPending; // track requests for thread flush
// for last call to getTimestamp
- bool mPreviousValid;
- uint32_t mPreviousFramesWritten;
+ bool mPreviousTimestampValid;
+ // This is either the first timestamp or one that has passed
+ // the check to prevent retrograde motion.
AudioTimestamp mPreviousTimestamp;
}; // end of Track
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 38667b9..7692315 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -405,9 +405,7 @@ AudioFlinger::PlaybackThread::Track::Track(
mAudioTrackServerProxy(NULL),
mResumeToStopping(false),
mFlushHwPending(false),
- mPreviousValid(false),
- mPreviousFramesWritten(0)
- // mPreviousTimestamp
+ mPreviousTimestampValid(false)
{
// client == 0 implies sharedBuffer == 0
ALOG_ASSERT(!(client == 0 && sharedBuffer != 0));
@@ -864,7 +862,7 @@ void AudioFlinger::PlaybackThread::Track::reset()
if (mState == FLUSHED) {
mState = IDLE;
}
- mPreviousValid = false;
+ mPreviousTimestampValid = false;
}
}
@@ -886,19 +884,22 @@ status_t AudioFlinger::PlaybackThread::Track::getTimestamp(AudioTimestamp& times
{
// Client should implement this using SSQ; the unpresented frame count in latch is irrelevant
if (isFastTrack()) {
- // FIXME no lock held to set mPreviousValid = false
+ // FIXME no lock held to set mPreviousTimestampValid = false
return INVALID_OPERATION;
}
sp<ThreadBase> thread = mThread.promote();
if (thread == 0) {
- // FIXME no lock held to set mPreviousValid = false
+ // FIXME no lock held to set mPreviousTimestampValid = false
return INVALID_OPERATION;
}
+
Mutex::Autolock _l(thread->mLock);
PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+
+ status_t result = INVALID_OPERATION;
if (!isOffloaded() && !isDirect()) {
if (!playbackThread->mLatchQValid) {
- mPreviousValid = false;
+ mPreviousTimestampValid = false;
return INVALID_OPERATION;
}
uint32_t unpresentedFrames =
@@ -914,36 +915,54 @@ status_t AudioFlinger::PlaybackThread::Track::getTimestamp(AudioTimestamp& times
uint32_t framesWritten = i >= 0 ?
playbackThread->mLatchQ.mFramesReleased[i] :
mAudioTrackServerProxy->framesReleased();
- bool checkPreviousTimestamp = mPreviousValid && framesWritten >= mPreviousFramesWritten;
if (framesWritten < unpresentedFrames) {
- mPreviousValid = false;
- return INVALID_OPERATION;
+ mPreviousTimestampValid = false;
+ // return invalid result
+ } else {
+ timestamp.mPosition = framesWritten - unpresentedFrames;
+ timestamp.mTime = playbackThread->mLatchQ.mTimestamp.mTime;
+ result = NO_ERROR;
}
- mPreviousFramesWritten = framesWritten;
- uint32_t position = framesWritten - unpresentedFrames;
- struct timespec time = playbackThread->mLatchQ.mTimestamp.mTime;
- if (checkPreviousTimestamp) {
- if (time.tv_sec < mPreviousTimestamp.mTime.tv_sec ||
- (time.tv_sec == mPreviousTimestamp.mTime.tv_sec &&
- time.tv_nsec < mPreviousTimestamp.mTime.tv_nsec)) {
- ALOGW("Time is going backwards");
+ } else { // offloaded or direct
+ result = playbackThread->getTimestamp_l(timestamp);
+ }
+
+ // Prevent retrograde motion in timestamp.
+ if (result == NO_ERROR) {
+ if (mPreviousTimestampValid) {
+ if (timestamp.mTime.tv_sec < mPreviousTimestamp.mTime.tv_sec ||
+ (timestamp.mTime.tv_sec == mPreviousTimestamp.mTime.tv_sec &&
+ timestamp.mTime.tv_nsec < mPreviousTimestamp.mTime.tv_nsec)) {
+ ALOGW("WARNING - retrograde timestamp time");
+ // FIXME Consider blocking this from propagating upwards.
}
+
+ // Looking at signed delta will work even when the timestamps
+ // are wrapping around.
+ int32_t deltaPosition = static_cast<int32_t>(timestamp.mPosition
+ - mPreviousTimestamp.mPosition);
// position can bobble slightly as an artifact; this hides the bobble
- static const uint32_t MINIMUM_POSITION_DELTA = 8u;
- if ((position <= mPreviousTimestamp.mPosition) ||
- (position - mPreviousTimestamp.mPosition) < MINIMUM_POSITION_DELTA) {
- position = mPreviousTimestamp.mPosition;
- time = mPreviousTimestamp.mTime;
+ static const int32_t MINIMUM_POSITION_DELTA = 8;
+ if (deltaPosition < 0) {
+#define TIME_TO_NANOS(time) ((uint64_t)time.tv_sec * 1000000000 + time.tv_nsec)
+ ALOGW("WARNING - retrograde timestamp position corrected,"
+ " %d = %u - %u, (at %llu, %llu nanos)",
+ deltaPosition,
+ timestamp.mPosition,
+ mPreviousTimestamp.mPosition,
+ TIME_TO_NANOS(timestamp.mTime),
+ TIME_TO_NANOS(mPreviousTimestamp.mTime));
+#undef TIME_TO_NANOS
+ }
+ if (deltaPosition < MINIMUM_POSITION_DELTA) {
+ // Current timestamp is bad. Use last valid timestamp.
+ timestamp = mPreviousTimestamp;
}
}
- timestamp.mPosition = position;
- timestamp.mTime = time;
mPreviousTimestamp = timestamp;
- mPreviousValid = true;
- return NO_ERROR;
+ mPreviousTimestampValid = true;
}
-
- return playbackThread->getTimestamp_l(timestamp);
+ return result;
}
status_t AudioFlinger::PlaybackThread::Track::attachAuxEffect(int EffectId)
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index eb9116d..00f188f 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -532,7 +532,7 @@ bool AudioPolicyService::AudioCommandThread::threadLoop()
mLock.unlock();
svc.clear();
mLock.lock();
- if (!exitPending() && mAudioCommands.isEmpty()) {
+ if (!exitPending() && (mAudioCommands.isEmpty() || waitTime != INT64_MAX)) {
// release delayed commands wake lock
release_wake_lock(mName.string());
ALOGV("AudioCommandThread() going to sleep");
diff --git a/services/mediaresourcemanager/Android.mk b/services/mediaresourcemanager/Android.mk
new file mode 100644
index 0000000..84218cf
--- /dev/null
+++ b/services/mediaresourcemanager/Android.mk
@@ -0,0 +1,18 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := ResourceManagerService.cpp
+
+LOCAL_SHARED_LIBRARIES := libmedia libstagefright libbinder libutils liblog
+
+LOCAL_MODULE:= libresourcemanagerservice
+
+LOCAL_32_BIT_ONLY := true
+
+LOCAL_C_INCLUDES += \
+ $(TOPDIR)frameworks/av/include
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/services/mediaresourcemanager/ResourceManagerService.cpp b/services/mediaresourcemanager/ResourceManagerService.cpp
new file mode 100644
index 0000000..7296d47
--- /dev/null
+++ b/services/mediaresourcemanager/ResourceManagerService.cpp
@@ -0,0 +1,345 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ResourceManagerService"
+#include <utils/Log.h>
+
+#include <binder/IServiceManager.h>
+#include <dirent.h>
+#include <media/stagefright/ProcessInfo.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include "ResourceManagerService.h"
+
+namespace android {
+
+template <typename T>
+static String8 getString(const Vector<T> &items) {
+ String8 itemsStr;
+ for (size_t i = 0; i < items.size(); ++i) {
+ itemsStr.appendFormat("%s ", items[i].toString().string());
+ }
+ return itemsStr;
+}
+
+static bool hasResourceType(String8 type, Vector<MediaResource> resources) {
+ for (size_t i = 0; i < resources.size(); ++i) {
+ if (resources[i].mType == type) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool hasResourceType(String8 type, ResourceInfos infos) {
+ for (size_t i = 0; i < infos.size(); ++i) {
+ if (hasResourceType(type, infos[i].resources)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static ResourceInfos& getResourceInfosForEdit(
+ int pid,
+ PidResourceInfosMap& map) {
+ ssize_t index = map.indexOfKey(pid);
+ if (index < 0) {
+ // new pid
+ ResourceInfos infosForPid;
+ map.add(pid, infosForPid);
+ }
+
+ return map.editValueFor(pid);
+}
+
+static ResourceInfo& getResourceInfoForEdit(
+ int64_t clientId,
+ const sp<IResourceManagerClient> client,
+ ResourceInfos& infos) {
+ for (size_t i = 0; i < infos.size(); ++i) {
+ if (infos[i].clientId == clientId) {
+ return infos.editItemAt(i);
+ }
+ }
+ ResourceInfo info;
+ info.clientId = clientId;
+ info.client = client;
+ infos.push_back(info);
+ return infos.editItemAt(infos.size() - 1);
+}
+
+ResourceManagerService::ResourceManagerService()
+ : mProcessInfo(new ProcessInfo()),
+ mSupportsMultipleSecureCodecs(true),
+ mSupportsSecureWithNonSecureCodec(true) {}
+
+ResourceManagerService::ResourceManagerService(sp<ProcessInfoInterface> processInfo)
+ : mProcessInfo(processInfo),
+ mSupportsMultipleSecureCodecs(true),
+ mSupportsSecureWithNonSecureCodec(true) {}
+
+ResourceManagerService::~ResourceManagerService() {}
+
+void ResourceManagerService::config(const Vector<MediaResourcePolicy> &policies) {
+ ALOGV("config(%s)", getString(policies).string());
+
+ Mutex::Autolock lock(mLock);
+ for (size_t i = 0; i < policies.size(); ++i) {
+ String8 type = policies[i].mType;
+ uint64_t value = policies[i].mValue;
+ if (type == kPolicySupportsMultipleSecureCodecs) {
+ mSupportsMultipleSecureCodecs = (value != 0);
+ } else if (type == kPolicySupportsSecureWithNonSecureCodec) {
+ mSupportsSecureWithNonSecureCodec = (value != 0);
+ }
+ }
+}
+
+void ResourceManagerService::addResource(
+ int pid,
+ int64_t clientId,
+ const sp<IResourceManagerClient> client,
+ const Vector<MediaResource> &resources) {
+ ALOGV("addResource(pid %d, clientId %lld, resources %s)",
+ pid, (long long) clientId, getString(resources).string());
+
+ Mutex::Autolock lock(mLock);
+ ResourceInfos& infos = getResourceInfosForEdit(pid, mMap);
+ ResourceInfo& info = getResourceInfoForEdit(clientId, client, infos);
+ info.resources.appendVector(resources);
+}
+
+void ResourceManagerService::removeResource(int64_t clientId) {
+ ALOGV("removeResource(%lld)", (long long) clientId);
+
+ Mutex::Autolock lock(mLock);
+ bool found = false;
+ for (size_t i = 0; i < mMap.size(); ++i) {
+ ResourceInfos &infos = mMap.editValueAt(i);
+ for (size_t j = 0; j < infos.size();) {
+ if (infos[j].clientId == clientId) {
+ j = infos.removeAt(j);
+ found = true;
+ } else {
+ ++j;
+ }
+ }
+ if (found) {
+ break;
+ }
+ }
+ if (!found) {
+ ALOGV("didn't find client");
+ }
+}
+
+bool ResourceManagerService::reclaimResource(
+ int callingPid, const Vector<MediaResource> &resources) {
+ ALOGV("reclaimResource(callingPid %d, resources %s)",
+ callingPid, getString(resources).string());
+
+ Vector<sp<IResourceManagerClient>> clients;
+ {
+ Mutex::Autolock lock(mLock);
+ // first pass to handle secure/non-secure codec conflict
+ for (size_t i = 0; i < resources.size(); ++i) {
+ String8 type = resources[i].mType;
+ if (type == kResourceSecureCodec) {
+ if (!mSupportsMultipleSecureCodecs) {
+ if (!getAllClients_l(callingPid, String8(kResourceSecureCodec), &clients)) {
+ return false;
+ }
+ }
+ if (!mSupportsSecureWithNonSecureCodec) {
+ if (!getAllClients_l(callingPid, String8(kResourceNonSecureCodec), &clients)) {
+ return false;
+ }
+ }
+ } else if (type == kResourceNonSecureCodec) {
+ if (!mSupportsSecureWithNonSecureCodec) {
+ if (!getAllClients_l(callingPid, String8(kResourceSecureCodec), &clients)) {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (clients.size() == 0) {
+ // if no secure/non-secure codec conflict, run second pass to handle other resources.
+ for (size_t i = 0; i < resources.size(); ++i) {
+ String8 type = resources[i].mType;
+ if (type == kResourceGraphicMemory) {
+ sp<IResourceManagerClient> client;
+ if (!getLowestPriorityBiggestClient_l(callingPid, type, &client)) {
+ return false;
+ }
+ clients.push_back(client);
+ }
+ }
+ }
+ }
+
+ if (clients.size() == 0) {
+ return false;
+ }
+
+ for (size_t i = 0; i < clients.size(); ++i) {
+ ALOGV("reclaimResource from client %p", clients[i].get());
+ if (!clients[i]->reclaimResource()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool ResourceManagerService::getAllClients_l(
+ int callingPid, const String8 &type, Vector<sp<IResourceManagerClient>> *clients) {
+ Vector<sp<IResourceManagerClient>> temp;
+ for (size_t i = 0; i < mMap.size(); ++i) {
+ ResourceInfos &infos = mMap.editValueAt(i);
+ for (size_t j = 0; j < infos.size(); ++j) {
+ if (hasResourceType(type, infos[j].resources)) {
+ if (!isCallingPriorityHigher_l(callingPid, mMap.keyAt(i))) {
+ // some higher/equal priority process owns the resource,
+ // this request can't be fulfilled.
+ ALOGE("getAllClients_l: can't reclaim resource %s from pid %d",
+ type.string(), mMap.keyAt(i));
+ return false;
+ }
+ temp.push_back(infos[j].client);
+ }
+ }
+ }
+ if (temp.size() == 0) {
+ ALOGV("getAllClients_l: didn't find any resource %s", type.string());
+ return true;
+ }
+ clients->appendVector(temp);
+ return true;
+}
+
+bool ResourceManagerService::getLowestPriorityBiggestClient_l(
+ int callingPid, const String8 &type, sp<IResourceManagerClient> *client) {
+ int lowestPriorityPid;
+ int lowestPriority;
+ int callingPriority;
+ if (!mProcessInfo->getPriority(callingPid, &callingPriority)) {
+ ALOGE("getLowestPriorityBiggestClient_l: can't get process priority for pid %d",
+ callingPid);
+ return false;
+ }
+ if (!getLowestPriorityPid_l(type, &lowestPriorityPid, &lowestPriority)) {
+ return false;
+ }
+ if (lowestPriority <= callingPriority) {
+ ALOGE("getLowestPriorityBiggestClient_l: lowest priority %d vs caller priority %d",
+ lowestPriority, callingPriority);
+ return false;
+ }
+
+ if (!getBiggestClient_l(lowestPriorityPid, type, client)) {
+ return false;
+ }
+ return true;
+}
+
+bool ResourceManagerService::getLowestPriorityPid_l(
+ const String8 &type, int *lowestPriorityPid, int *lowestPriority) {
+ int pid = -1;
+ int priority = -1;
+ for (size_t i = 0; i < mMap.size(); ++i) {
+ if (mMap.valueAt(i).size() == 0) {
+ // no client on this process.
+ continue;
+ }
+ if (!hasResourceType(type, mMap.valueAt(i))) {
+ // doesn't have the requested resource type
+ continue;
+ }
+ int tempPid = mMap.keyAt(i);
+ int tempPriority;
+ if (!mProcessInfo->getPriority(tempPid, &tempPriority)) {
+ ALOGV("getLowestPriorityPid_l: can't get priority of pid %d, skipped", tempPid);
+ // TODO: remove this pid from mMap?
+ continue;
+ }
+ if (pid == -1 || tempPriority > priority) {
+ // initial the value
+ pid = tempPid;
+ priority = tempPriority;
+ }
+ }
+ if (pid != -1) {
+ *lowestPriorityPid = pid;
+ *lowestPriority = priority;
+ }
+ return (pid != -1);
+}
+
+bool ResourceManagerService::isCallingPriorityHigher_l(int callingPid, int pid) {
+ int callingPidPriority;
+ if (!mProcessInfo->getPriority(callingPid, &callingPidPriority)) {
+ return false;
+ }
+
+ int priority;
+ if (!mProcessInfo->getPriority(pid, &priority)) {
+ return false;
+ }
+
+ return (callingPidPriority < priority);
+}
+
+bool ResourceManagerService::getBiggestClient_l(
+ int pid, const String8 &type, sp<IResourceManagerClient> *client) {
+ ssize_t index = mMap.indexOfKey(pid);
+ if (index < 0) {
+ ALOGE("getBiggestClient_l: can't find resource info for pid %d", pid);
+ return false;
+ }
+
+ sp<IResourceManagerClient> clientTemp;
+ uint64_t largestValue = 0;
+ const ResourceInfos &infos = mMap.valueAt(index);
+ for (size_t i = 0; i < infos.size(); ++i) {
+ Vector<MediaResource> resources = infos[i].resources;
+ for (size_t j = 0; j < resources.size(); ++j) {
+ if (resources[j].mType == type) {
+ if (resources[j].mValue > largestValue) {
+ largestValue = resources[j].mValue;
+ clientTemp = infos[i].client;
+ }
+ }
+ }
+ }
+
+ if (clientTemp == NULL) {
+ ALOGE("getBiggestClient_l: can't find resource type %s for pid %d", type.string(), pid);
+ return false;
+ }
+
+ *client = clientTemp;
+ return true;
+}
+
+} // namespace android
diff --git a/services/mediaresourcemanager/ResourceManagerService.h b/services/mediaresourcemanager/ResourceManagerService.h
new file mode 100644
index 0000000..2ed9bf8
--- /dev/null
+++ b/services/mediaresourcemanager/ResourceManagerService.h
@@ -0,0 +1,106 @@
+/*
+**
+** Copyright 2015, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef ANDROID_RESOURCEMANAGERSERVICE_H
+#define ANDROID_RESOURCEMANAGERSERVICE_H
+
+#include <arpa/inet.h>
+#include <binder/BinderService.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+#include <utils/Vector.h>
+
+#include <media/IResourceManagerService.h>
+
+namespace android {
+
+struct ProcessInfoInterface;
+
+struct ResourceInfo {
+ int64_t clientId;
+ sp<IResourceManagerClient> client;
+ Vector<MediaResource> resources;
+};
+
+typedef Vector<ResourceInfo> ResourceInfos;
+typedef KeyedVector<int, ResourceInfos> PidResourceInfosMap;
+
+class ResourceManagerService
+ : public BinderService<ResourceManagerService>,
+ public BnResourceManagerService
+{
+public:
+ static char const *getServiceName() { return "media.resource_manager"; }
+
+ ResourceManagerService();
+ ResourceManagerService(sp<ProcessInfoInterface> processInfo);
+
+ // IResourceManagerService interface
+ virtual void config(const Vector<MediaResourcePolicy> &policies);
+
+ virtual void addResource(
+ int pid,
+ int64_t clientId,
+ const sp<IResourceManagerClient> client,
+ const Vector<MediaResource> &resources);
+
+ virtual void removeResource(int64_t clientId);
+
+ virtual bool reclaimResource(int callingPid, const Vector<MediaResource> &resources);
+
+protected:
+ virtual ~ResourceManagerService();
+
+private:
+ friend class ResourceManagerServiceTest;
+
+ // Gets the list of all the clients who own the specified resource type.
+ // Returns false if any client belongs to a process with higher priority than the
+ // calling process. The clients will remain unchanged if returns false.
+ bool getAllClients_l(int callingPid, const String8 &type,
+ Vector<sp<IResourceManagerClient>> *clients);
+
+ // Gets the client who owns specified resource type from lowest possible priority process.
+ // Returns false if the calling process priority is not higher than the lowest process
+ // priority. The client will remain unchanged if returns false.
+ bool getLowestPriorityBiggestClient_l(int callingPid, const String8 &type,
+ sp<IResourceManagerClient> *client);
+
+ // Gets lowest priority process that has the specified resource type.
+ // Returns false if failed. The output parameters will remain unchanged if failed.
+ bool getLowestPriorityPid_l(const String8 &type, int *pid, int *priority);
+
+ // Gets the client who owns biggest piece of specified resource type from pid.
+ // Returns false if failed. The client will remain unchanged if failed.
+ bool getBiggestClient_l(int pid, const String8 &type, sp<IResourceManagerClient> *client);
+
+ bool isCallingPriorityHigher_l(int callingPid, int pid);
+
+ mutable Mutex mLock;
+ sp<ProcessInfoInterface> mProcessInfo;
+ PidResourceInfosMap mMap;
+ bool mSupportsMultipleSecureCodecs;
+ bool mSupportsSecureWithNonSecureCodec;
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_RESOURCEMANAGERSERVICE_H
diff --git a/services/mediaresourcemanager/test/Android.mk b/services/mediaresourcemanager/test/Android.mk
new file mode 100644
index 0000000..228b62a
--- /dev/null
+++ b/services/mediaresourcemanager/test/Android.mk
@@ -0,0 +1,25 @@
+# Build the unit tests.
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := ResourceManagerService_test
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := \
+ ResourceManagerService_test.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ liblog \
+ libmedia \
+ libresourcemanagerservice \
+ libutils \
+
+LOCAL_C_INCLUDES := \
+ frameworks/av/include \
+ frameworks/av/services/mediaresourcemanager \
+
+LOCAL_32_BIT_ONLY := true
+
+include $(BUILD_NATIVE_TEST)
diff --git a/services/mediaresourcemanager/test/ResourceManagerService_test.cpp b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
new file mode 100644
index 0000000..b73e1bc
--- /dev/null
+++ b/services/mediaresourcemanager/test/ResourceManagerService_test.cpp
@@ -0,0 +1,464 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ResourceManagerService_test"
+#include <utils/Log.h>
+
+#include <gtest/gtest.h>
+
+#include "ResourceManagerService.h"
+#include <media/IResourceManagerService.h>
+#include <media/MediaResource.h>
+#include <media/MediaResourcePolicy.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/ProcessInfoInterface.h>
+
+namespace android {
+
+struct TestProcessInfo : public ProcessInfoInterface {
+ TestProcessInfo() {}
+ virtual ~TestProcessInfo() {}
+
+ virtual bool getPriority(int pid, int *priority) {
+ // For testing, use pid as priority.
+ // Lower the value higher the priority.
+ *priority = pid;
+ return true;
+ }
+
+private:
+ DISALLOW_EVIL_CONSTRUCTORS(TestProcessInfo);
+};
+
+struct TestClient : public BnResourceManagerClient {
+ TestClient(sp<ResourceManagerService> service)
+ : mReclaimed(false), mService(service) {}
+
+ virtual bool reclaimResource() {
+ sp<IResourceManagerClient> client(this);
+ mService->removeResource((int64_t) client.get());
+ mReclaimed = true;
+ return true;
+ }
+
+ bool reclaimed() const {
+ return mReclaimed;
+ }
+
+ void reset() {
+ mReclaimed = false;
+ }
+
+protected:
+ virtual ~TestClient() {}
+
+private:
+ bool mReclaimed;
+ sp<ResourceManagerService> mService;
+ DISALLOW_EVIL_CONSTRUCTORS(TestClient);
+};
+
+static const int kTestPid1 = 30;
+static const int kTestPid2 = 20;
+
+class ResourceManagerServiceTest : public ::testing::Test {
+public:
+ ResourceManagerServiceTest()
+ : mService(new ResourceManagerService(new TestProcessInfo)),
+ mTestClient1(new TestClient(mService)),
+ mTestClient2(new TestClient(mService)),
+ mTestClient3(new TestClient(mService)) {
+ }
+
+protected:
+ static bool isEqualResources(const Vector<MediaResource> &resources1,
+ const Vector<MediaResource> &resources2) {
+ if (resources1.size() != resources2.size()) {
+ return false;
+ }
+ for (size_t i = 0; i < resources1.size(); ++i) {
+ if (resources1[i] != resources2[i]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ static void expectEqResourceInfo(const ResourceInfo &info, sp<IResourceManagerClient> client,
+ const Vector<MediaResource> &resources) {
+ EXPECT_EQ(client, info.client);
+ EXPECT_TRUE(isEqualResources(resources, info.resources));
+ }
+
+ void verifyClients(bool c1, bool c2, bool c3) {
+ TestClient *client1 = static_cast<TestClient*>(mTestClient1.get());
+ TestClient *client2 = static_cast<TestClient*>(mTestClient2.get());
+ TestClient *client3 = static_cast<TestClient*>(mTestClient3.get());
+
+ EXPECT_EQ(c1, client1->reclaimed());
+ EXPECT_EQ(c2, client2->reclaimed());
+ EXPECT_EQ(c3, client3->reclaimed());
+
+ client1->reset();
+ client2->reset();
+ client3->reset();
+ }
+
+ void addResource() {
+ // kTestPid1 mTestClient1
+ Vector<MediaResource> resources1;
+ resources1.push_back(MediaResource(String8(kResourceSecureCodec), 1));
+ mService->addResource(kTestPid1, (int64_t) mTestClient1.get(), mTestClient1, resources1);
+ resources1.push_back(MediaResource(String8(kResourceGraphicMemory), 200));
+ Vector<MediaResource> resources11;
+ resources11.push_back(MediaResource(String8(kResourceGraphicMemory), 200));
+ mService->addResource(kTestPid1, (int64_t) mTestClient1.get(), mTestClient1, resources11);
+
+ // kTestPid2 mTestClient2
+ Vector<MediaResource> resources2;
+ resources2.push_back(MediaResource(String8(kResourceNonSecureCodec), 1));
+ resources2.push_back(MediaResource(String8(kResourceGraphicMemory), 300));
+ mService->addResource(kTestPid2, (int64_t) mTestClient2.get(), mTestClient2, resources2);
+
+ // kTestPid2 mTestClient3
+ Vector<MediaResource> resources3;
+ mService->addResource(kTestPid2, (int64_t) mTestClient3.get(), mTestClient3, resources3);
+ resources3.push_back(MediaResource(String8(kResourceSecureCodec), 1));
+ resources3.push_back(MediaResource(String8(kResourceGraphicMemory), 100));
+ mService->addResource(kTestPid2, (int64_t) mTestClient3.get(), mTestClient3, resources3);
+
+ const PidResourceInfosMap &map = mService->mMap;
+ EXPECT_EQ(2u, map.size());
+ ssize_t index1 = map.indexOfKey(kTestPid1);
+ ASSERT_GE(index1, 0);
+ const ResourceInfos &infos1 = map[index1];
+ EXPECT_EQ(1u, infos1.size());
+ expectEqResourceInfo(infos1[0], mTestClient1, resources1);
+
+ ssize_t index2 = map.indexOfKey(kTestPid2);
+ ASSERT_GE(index2, 0);
+ const ResourceInfos &infos2 = map[index2];
+ EXPECT_EQ(2u, infos2.size());
+ expectEqResourceInfo(infos2[0], mTestClient2, resources2);
+ expectEqResourceInfo(infos2[1], mTestClient3, resources3);
+ }
+
+ void testConfig() {
+ EXPECT_TRUE(mService->mSupportsMultipleSecureCodecs);
+ EXPECT_TRUE(mService->mSupportsSecureWithNonSecureCodec);
+
+ Vector<MediaResourcePolicy> policies1;
+ policies1.push_back(MediaResourcePolicy(String8(kPolicySupportsMultipleSecureCodecs), 1));
+ policies1.push_back(
+ MediaResourcePolicy(String8(kPolicySupportsSecureWithNonSecureCodec), 0));
+ mService->config(policies1);
+ EXPECT_TRUE(mService->mSupportsMultipleSecureCodecs);
+ EXPECT_FALSE(mService->mSupportsSecureWithNonSecureCodec);
+
+ Vector<MediaResourcePolicy> policies2;
+ policies2.push_back(MediaResourcePolicy(String8(kPolicySupportsMultipleSecureCodecs), 0));
+ policies2.push_back(
+ MediaResourcePolicy(String8(kPolicySupportsSecureWithNonSecureCodec), 1));
+ mService->config(policies2);
+ EXPECT_FALSE(mService->mSupportsMultipleSecureCodecs);
+ EXPECT_TRUE(mService->mSupportsSecureWithNonSecureCodec);
+ }
+
+ void testRemoveResource() {
+ addResource();
+
+ mService->removeResource((int64_t) mTestClient2.get());
+
+ const PidResourceInfosMap &map = mService->mMap;
+ EXPECT_EQ(2u, map.size());
+ const ResourceInfos &infos1 = map.valueFor(kTestPid1);
+ const ResourceInfos &infos2 = map.valueFor(kTestPid2);
+ EXPECT_EQ(1u, infos1.size());
+ EXPECT_EQ(1u, infos2.size());
+ // mTestClient2 has been removed.
+ EXPECT_EQ(mTestClient3, infos2[0].client);
+ }
+
+ void testGetAllClients() {
+ addResource();
+
+ String8 type = String8(kResourceSecureCodec);
+ String8 unknowType = String8("unknowType");
+ Vector<sp<IResourceManagerClient> > clients;
+ int lowPriorityPid = 100;
+ EXPECT_FALSE(mService->getAllClients_l(lowPriorityPid, type, &clients));
+ int midPriorityPid = 25;
+ EXPECT_FALSE(mService->getAllClients_l(lowPriorityPid, type, &clients));
+ int highPriorityPid = 10;
+ EXPECT_TRUE(mService->getAllClients_l(10, unknowType, &clients));
+ EXPECT_TRUE(mService->getAllClients_l(10, type, &clients));
+
+ EXPECT_EQ(2u, clients.size());
+ EXPECT_EQ(mTestClient3, clients[0]);
+ EXPECT_EQ(mTestClient1, clients[1]);
+ }
+
+ void testReclaimResourceSecure() {
+ Vector<MediaResource> resources;
+ resources.push_back(MediaResource(String8(kResourceSecureCodec), 1));
+ resources.push_back(MediaResource(String8(kResourceGraphicMemory), 150));
+
+ // ### secure codec can't coexist and secure codec can coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsMultipleSecureCodecs = false;
+ mService->mSupportsSecureWithNonSecureCodec = true;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+ EXPECT_FALSE(mService->reclaimResource(25, resources));
+
+ // reclaim all secure codecs
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(true, false, true);
+
+ // call again should reclaim one largest graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, true, false);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+
+ // ### secure codecs can't coexist and secure codec can't coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsMultipleSecureCodecs = false;
+ mService->mSupportsSecureWithNonSecureCodec = false;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+ EXPECT_FALSE(mService->reclaimResource(25, resources));
+
+ // reclaim all secure and non-secure codecs
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(true, true, true);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+
+
+ // ### secure codecs can coexist but secure codec can't coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsMultipleSecureCodecs = true;
+ mService->mSupportsSecureWithNonSecureCodec = false;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+ EXPECT_FALSE(mService->reclaimResource(25, resources));
+
+ // reclaim all non-secure codecs
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, true, false);
+
+ // call again should reclaim one largest graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(true, false, false);
+
+ // call again should reclaim another largest graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, false, true);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+
+ // ### secure codecs can coexist and secure codec can coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsMultipleSecureCodecs = true;
+ mService->mSupportsSecureWithNonSecureCodec = true;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ // one largest graphic memory from lowest process got reclaimed
+ verifyClients(true, false, false);
+
+ // call again should reclaim another graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, true, false);
+
+ // call again should reclaim another graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, false, true);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+ }
+
+ void testReclaimResourceNonSecure() {
+ Vector<MediaResource> resources;
+ resources.push_back(MediaResource(String8(kResourceNonSecureCodec), 1));
+ resources.push_back(MediaResource(String8(kResourceGraphicMemory), 150));
+
+ // ### secure codec can't coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsSecureWithNonSecureCodec = false;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+ EXPECT_FALSE(mService->reclaimResource(25, resources));
+
+ // reclaim all secure codecs
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(true, false, true);
+
+ // call again should reclaim one graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, true, false);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+
+
+ // ### secure codec can coexist with non-secure codec ###
+ {
+ addResource();
+ mService->mSupportsSecureWithNonSecureCodec = true;
+
+ // priority too low
+ EXPECT_FALSE(mService->reclaimResource(40, resources));
+
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ // one largest graphic memory from lowest process got reclaimed
+ verifyClients(true, false, false);
+
+ // call again should reclaim another graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, true, false);
+
+ // call again should reclaim another graphic memory from lowest process
+ EXPECT_TRUE(mService->reclaimResource(10, resources));
+ verifyClients(false, false, true);
+
+ // nothing left
+ EXPECT_FALSE(mService->reclaimResource(10, resources));
+ }
+ }
+
+ void testGetLowestPriorityBiggestClient() {
+ String8 type = String8(kResourceGraphicMemory);
+ sp<IResourceManagerClient> client;
+ EXPECT_FALSE(mService->getLowestPriorityBiggestClient_l(10, type, &client));
+
+ addResource();
+
+ EXPECT_FALSE(mService->getLowestPriorityBiggestClient_l(100, type, &client));
+ EXPECT_TRUE(mService->getLowestPriorityBiggestClient_l(10, type, &client));
+
+ // kTestPid1 is the lowest priority process with kResourceGraphicMemory.
+ // mTestClient1 has the largest kResourceGraphicMemory within kTestPid1.
+ EXPECT_EQ(mTestClient1, client);
+ }
+
+ void testGetLowestPriorityPid() {
+ int pid;
+ int priority;
+ TestProcessInfo processInfo;
+
+ String8 type = String8(kResourceGraphicMemory);
+ EXPECT_FALSE(mService->getLowestPriorityPid_l(type, &pid, &priority));
+
+ addResource();
+
+ EXPECT_TRUE(mService->getLowestPriorityPid_l(type, &pid, &priority));
+ EXPECT_EQ(kTestPid1, pid);
+ int priority1;
+ processInfo.getPriority(kTestPid1, &priority1);
+ EXPECT_EQ(priority1, priority);
+
+ type = String8(kResourceNonSecureCodec);
+ EXPECT_TRUE(mService->getLowestPriorityPid_l(type, &pid, &priority));
+ EXPECT_EQ(kTestPid2, pid);
+ int priority2;
+ processInfo.getPriority(kTestPid2, &priority2);
+ EXPECT_EQ(priority2, priority);
+ }
+
+ void testGetBiggestClient() {
+ String8 type = String8(kResourceGraphicMemory);
+ sp<IResourceManagerClient> client;
+ EXPECT_FALSE(mService->getBiggestClient_l(kTestPid2, type, &client));
+
+ addResource();
+
+ EXPECT_TRUE(mService->getBiggestClient_l(kTestPid2, type, &client));
+ EXPECT_EQ(mTestClient2, client);
+ }
+
+ void testIsCallingPriorityHigher() {
+ EXPECT_FALSE(mService->isCallingPriorityHigher_l(101, 100));
+ EXPECT_FALSE(mService->isCallingPriorityHigher_l(100, 100));
+ EXPECT_TRUE(mService->isCallingPriorityHigher_l(99, 100));
+ }
+
+ sp<ResourceManagerService> mService;
+ sp<IResourceManagerClient> mTestClient1;
+ sp<IResourceManagerClient> mTestClient2;
+ sp<IResourceManagerClient> mTestClient3;
+};
+
+TEST_F(ResourceManagerServiceTest, config) {
+ testConfig();
+}
+
+TEST_F(ResourceManagerServiceTest, addResource) {
+ addResource();
+}
+
+TEST_F(ResourceManagerServiceTest, removeResource) {
+ testRemoveResource();
+}
+
+TEST_F(ResourceManagerServiceTest, reclaimResource) {
+ testReclaimResourceSecure();
+ testReclaimResourceNonSecure();
+}
+
+TEST_F(ResourceManagerServiceTest, getAllClients_l) {
+ testGetAllClients();
+}
+
+TEST_F(ResourceManagerServiceTest, getLowestPriorityBiggestClient_l) {
+ testGetLowestPriorityBiggestClient();
+}
+
+TEST_F(ResourceManagerServiceTest, getLowestPriorityPid_l) {
+ testGetLowestPriorityPid();
+}
+
+TEST_F(ResourceManagerServiceTest, getBiggestClient_l) {
+ testGetBiggestClient();
+}
+
+TEST_F(ResourceManagerServiceTest, isCallingPriorityHigher_l) {
+ testIsCallingPriorityHigher();
+}
+
+} // namespace android