Merge "Support multiple clients attaching to a module"
diff --git a/include/media/IMediaAnalyticsService.h b/include/media/IMediaAnalyticsService.h
new file mode 100644
index 0000000..21da6ad
--- /dev/null
+++ b/include/media/IMediaAnalyticsService.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 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_IMEDIAANALYTICSSERVICE_H
+#define ANDROID_IMEDIAANALYTICSSERVICE_H
+
+#include <utils/String8.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <sys/types.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/RefBase.h>
+#include <utils/List.h>
+
+#include <binder/IServiceManager.h>
+
+#include <media/MediaAnalyticsItem.h>
+// nope...#include <media/MediaAnalytics.h>
+
+namespace android {
+
+class IMediaAnalyticsService: public IInterface
+{
+public:
+ DECLARE_META_INTERFACE(MediaAnalyticsService);
+
+ // generate a unique sessionID to use across multiple requests
+ // 'unique' is within this device, since last reboot
+ virtual MediaAnalyticsItem::SessionID_t generateUniqueSessionID() = 0;
+
+ // submit the indicated record to the mediaanalytics service, where
+ // it will be merged (if appropriate) with incomplete records that
+ // share the same key and sessionid.
+ // 'forcenew' marks any matching incomplete record as complete before
+ // inserting this new record.
+ // returns the sessionID associated with that item.
+ virtual MediaAnalyticsItem::SessionID_t submit(sp<MediaAnalyticsItem> item, bool forcenew) = 0;
+
+
+ // return lists of records that match the supplied parameters.
+ // finished [or not] records since time 'ts' with key 'key'
+ // timestamp 'ts' is nanoseconds, unix time.
+ virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, int64_t ts) = 0;
+ virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key) = 0;
+
+};
+
+// ----------------------------------------------------------------------------
+
+class BnMediaAnalyticsService: public BnInterface<IMediaAnalyticsService>
+{
+public:
+ virtual status_t onTransact( uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IMEDIASTATISTICSSERVICE_H
diff --git a/include/media/MediaAnalyticsItem.h b/include/media/MediaAnalyticsItem.h
new file mode 100644
index 0000000..73c9dd4
--- /dev/null
+++ b/include/media/MediaAnalyticsItem.h
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2016 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_MEDIAANALYTICSITEM_H
+#define ANDROID_MEDIA_MEDIAANALYTICSITEM_H
+
+#include <cutils/properties.h>
+#include <sys/types.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+
+#include <media/stagefright/foundation/AString.h>
+
+namespace android {
+
+
+
+class IMediaAnalyticsService;
+
+// the class interface
+//
+
+class MediaAnalyticsItem : public RefBase {
+
+ friend class MediaAnalyticsService;
+ friend class IMediaAnalyticsService;
+
+ public:
+
+ // sessionid
+ // unique within device, within boot,
+ typedef int64_t SessionID_t;
+ static constexpr SessionID_t SessionIDInvalid = -1;
+ static constexpr SessionID_t SessionIDNone = 0;
+
+ // Key: the record descriminator
+ // values for the record discriminator
+ // values can be "component/component"
+ // basic values: "video", "audio", "drm"
+ // XXX: need to better define the format
+ typedef AString Key;
+ static const Key kKeyNone; // ""
+ static const Key kKeyAny; // "*"
+
+ // Attr: names for attributes within a record
+ // format "prop1" or "prop/subprop"
+ // XXX: need to better define the format
+ typedef AString Attr;
+
+
+ public:
+
+ // access functions for the class
+ MediaAnalyticsItem();
+ MediaAnalyticsItem(Key);
+ ~MediaAnalyticsItem();
+
+ // so clients can send intermediate values to be overlaid later
+ MediaAnalyticsItem &setFinalized(bool);
+ bool getFinalized() const;
+
+ // SessionID ties multiple submissions for same key together
+ // so that if video "height" and "width" are known at one point
+ // and "framerate" is only known later, they can be be brought
+ // together.
+ MediaAnalyticsItem &setSessionID(SessionID_t);
+ MediaAnalyticsItem &clearSessionID();
+ SessionID_t getSessionID() const;
+ // generates and stores a new ID iff mSessionID == SessionIDNone
+ SessionID_t generateSessionID();
+
+ // reset all contents, discarding any extra data
+ void clear();
+
+ // set the key discriminator for the record.
+ // most often initialized as part of the constructor
+ MediaAnalyticsItem &setKey(MediaAnalyticsItem::Key);
+ MediaAnalyticsItem::Key getKey();
+
+ // # of attributes in the record
+ int32_t count() const;
+
+ // set values appropriately
+ // return values tell us whether we overwrote an existing value
+ bool setInt32(Attr, int32_t value);
+ bool setInt64(Attr, int64_t value);
+ bool setDouble(Attr, double value);
+ bool setCString(Attr, const char *value);
+
+ // fused get/add/set; if attr wasn't there, it's a simple set.
+ // type-mismatch counts as "wasn't there".
+ // return value tells us whether we overwrote an existing value
+ bool addInt32(Attr, int32_t value);
+ bool addInt64(Attr, int64_t value);
+ bool addDouble(Attr, double value);
+
+ // find & extract values
+ // return indicates whether attr exists (and thus value filled in)
+ bool getInt32(Attr, int32_t *value);
+ bool getInt64(Attr, int64_t *value);
+ bool getDouble(Attr, double *value);
+ bool getCString(Attr, char **value);
+
+ // parameter indicates whether to close any existing open
+ // record with same key before establishing a new record
+ bool selfrecord(bool);
+ bool selfrecord();
+
+ // remove indicated attributes and their values
+ // filterNot() could also be called keepOnly()
+ // return value is # attributes removed
+ // XXX: perhaps 'remove' instead of 'filter'
+ // XXX: filterNot would become 'keep'
+ int32_t filter(int count, Attr attrs[]);
+ int32_t filterNot(int count, Attr attrs[]);
+ int32_t filter(Attr attr);
+
+ // below here are used on server side or to talk to server
+ // clients need not worry about these.
+
+ // timestamp, pid, and uid only used on server side
+ // timestamp is in 'nanoseconds, unix time'
+ MediaAnalyticsItem &setTimestamp(nsecs_t);
+ nsecs_t getTimestamp() const;
+
+ MediaAnalyticsItem &setPid(pid_t);
+ pid_t getPid() const;
+
+ MediaAnalyticsItem &setUid(uid_t);
+ uid_t getUid() const;
+
+ // our serialization code for binder calls
+ int32_t writeToParcel(Parcel *);
+ int32_t readFromParcel(const Parcel&);
+
+ AString toString();
+
+ // are we collecting analytics data
+ static bool isEnabled();
+
+ protected:
+
+ // merge fields from arg into this
+ // with rules for first/last/add, etc
+ // XXX: document semantics and how they are indicated
+ bool merge(sp<MediaAnalyticsItem> );
+
+ // enabled 1, disabled 0
+ static const char * const EnabledProperty;
+ static const char * const EnabledPropertyPersist;
+ static const int EnabledProperty_default;
+
+ private:
+
+ // to help validate that A doesn't mess with B's records
+ pid_t mPid;
+ uid_t mUid;
+
+ // let's reuse a binder connection
+ static sp<IMediaAnalyticsService> sAnalyticsService;
+ static sp<IMediaAnalyticsService> getInstance();
+
+ // tracking information
+ SessionID_t mSessionID; // grouping similar records
+ nsecs_t mTimestamp; // ns, system_time_monotonic
+
+ // will this record accept further updates
+ bool mFinalized;
+
+ Key mKey;
+
+ class Item : public RefBase {
+
+ public:
+
+ enum Type {
+ kTypeNone = 0,
+ kTypeInt32 = 1,
+ kTypeInt64 = 2,
+ kTypeDouble = 3,
+ kTypeCString = 4,
+ };
+
+ Item();
+ ~Item();
+ void clear();
+
+ Type mType;
+ union {
+ int32_t int32Value;
+ int64_t int64Value;
+ double doubleValue;
+ char *CStringValue;
+ } u;
+ };
+ KeyedVector<Attr, sp<Item>> mItems;
+
+};
+
+} // namespace android
+
+#endif
diff --git a/include/media/stagefright/DataSource.h b/include/media/stagefright/DataSource.h
index 3479f76..a8dcbe0 100644
--- a/include/media/stagefright/DataSource.h
+++ b/include/media/stagefright/DataSource.h
@@ -44,6 +44,7 @@
kStreamedFromLocalHost = 2,
kIsCachingDataSource = 4,
kIsHTTPBasedSource = 8,
+ kIsLocalFileSource = 16,
};
static sp<DataSource> CreateFromURI(
diff --git a/include/media/stagefright/FileSource.h b/include/media/stagefright/FileSource.h
index b6349e0..9f3bb5e 100644
--- a/include/media/stagefright/FileSource.h
+++ b/include/media/stagefright/FileSource.h
@@ -39,6 +39,10 @@
virtual status_t getSize(off64_t *size);
+ virtual uint32_t flags() {
+ return kIsLocalFileSource;
+ }
+
virtual sp<DecryptHandle> DrmInitialization(const char *mime);
virtual void getDrmInfo(sp<DecryptHandle> &handle, DrmManagerClient **client);
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index 3c7e8b7..aef7dfb 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -671,6 +671,8 @@
mState = STATE_STOPPING;
} else {
mState = STATE_STOPPED;
+ ALOGD_IF(mSharedBuffer == nullptr,
+ "stop() called with %u frames delivered", mReleased.value());
mReleased = 0;
}
diff --git a/media/libaudiohal/Android.mk b/media/libaudiohal/Android.mk
index 2d6b3f5..63aa9c6 100644
--- a/media/libaudiohal/Android.mk
+++ b/media/libaudiohal/Android.mk
@@ -22,9 +22,10 @@
StreamHalHidl.cpp
LOCAL_SHARED_LIBRARIES += \
- libhwbinder \
- libhidlbase \
- libbase \
+ libhwbinder \
+ libhidlbase \
+ libhidltransport \
+ libbase \
android.hardware.audio@2.0 \
android.hardware.audio.common@2.0 \
android.hardware.audio.common@2.0-util \
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 2249222..02947b0 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -26,6 +26,7 @@
IMediaPlayer.cpp \
IMediaRecorder.cpp \
IMediaSource.cpp \
+ IMediaAnalyticsService.cpp \
IRemoteDisplay.cpp \
IRemoteDisplayClient.cpp \
IResourceManagerClient.cpp \
@@ -34,6 +35,7 @@
MediaCodecBuffer.cpp \
MediaCodecInfo.cpp \
MediaDefs.cpp \
+ MediaAnalyticsItem.cpp \
MediaUtils.cpp \
Metadata.cpp \
mediarecorder.cpp \
diff --git a/media/libmedia/IMediaAnalyticsService.cpp b/media/libmedia/IMediaAnalyticsService.cpp
new file mode 100644
index 0000000..afe9c36
--- /dev/null
+++ b/media/libmedia/IMediaAnalyticsService.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2016 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_TAG "MediaAnalytics"
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include <binder/Parcel.h>
+#include <binder/IMemory.h>
+#include <binder/IPCThreadState.h>
+#include <media/IHDCP.h>
+#include <media/IMediaCodecList.h>
+#include <media/IMediaHTTPService.h>
+#include <media/IMediaPlayerService.h>
+#include <media/IMediaRecorder.h>
+#include <media/IOMX.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+#include <media/IStreamSource.h>
+
+#include <utils/Errors.h> // for status_t
+#include <utils/List.h>
+#include <utils/Log.h>
+#include <utils/String8.h>
+
+#include <media/MediaAnalyticsItem.h>
+#include <media/IMediaAnalyticsService.h>
+
+#define DEBUGGING 0
+#define DEBUGGING_FLOW 0
+#define DEBUGGING_RETURNS 0
+
+namespace android {
+
+enum {
+ GENERATE_UNIQUE_SESSIONID = IBinder::FIRST_CALL_TRANSACTION,
+ SUBMIT_ITEM,
+ GET_ITEM_LIST,
+};
+
+class BpMediaAnalyticsService: public BpInterface<IMediaAnalyticsService>
+{
+public:
+ explicit BpMediaAnalyticsService(const sp<IBinder>& impl)
+ : BpInterface<IMediaAnalyticsService>(impl)
+ {
+ }
+
+ virtual MediaAnalyticsItem::SessionID_t generateUniqueSessionID() {
+ Parcel data, reply;
+ status_t err;
+ MediaAnalyticsItem::SessionID_t sessionid =
+ MediaAnalyticsItem::SessionIDInvalid;
+
+ data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+ err = remote()->transact(GENERATE_UNIQUE_SESSIONID, data, &reply);
+ if (err != NO_ERROR) {
+ ALOGW("bad response from service");
+ return MediaAnalyticsItem::SessionIDInvalid;
+ }
+ sessionid = reply.readInt64();
+ if (DEBUGGING_RETURNS) {
+ ALOGD("the caller gets a sessionid of %" PRId64 " back", sessionid);
+ }
+ return sessionid;
+ }
+
+ virtual MediaAnalyticsItem::SessionID_t submit(sp<MediaAnalyticsItem> item, bool forcenew)
+ {
+ // have this record submit itself
+ // this will be a binder call with appropriate timing
+ // return value is the uuid that the system generated for it.
+ // the return value 0 and -1 are reserved.
+ // -1 to indicate that there was a problem recording...
+
+ Parcel data, reply;
+ status_t err;
+
+ if (item == NULL) {
+ return MediaAnalyticsItem::SessionIDInvalid;
+ }
+
+ data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+ if(DEBUGGING_FLOW) {
+ ALOGD("client offers record: %s", item->toString().c_str());
+ }
+ data.writeBool(forcenew);
+ item->writeToParcel(&data);
+
+ err = remote()->transact(SUBMIT_ITEM, data, &reply);
+ if (err != NO_ERROR) {
+ return MediaAnalyticsItem::SessionIDInvalid;
+ }
+
+ // get an answer out of 'reply'
+ int64_t sessionid = reply.readInt64();
+ if (DEBUGGING_RETURNS) {
+ ALOGD("the caller gets sessionid=%" PRId64 "", sessionid);
+ }
+ return sessionid;
+ }
+
+ virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, nsecs_t ts)
+ {
+ return getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
+ }
+
+ virtual List<sp<MediaAnalyticsItem>> *getMediaAnalyticsItemList(bool finished, nsecs_t ts, MediaAnalyticsItem::Key key)
+ {
+ Parcel data, reply;
+ status_t err;
+
+ data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+ data.writeInt32(finished);
+ data.writeInt64(ts);
+ const char *str = key.c_str();
+ if (key.empty()) {
+ str = MediaAnalyticsItem::kKeyNone.c_str();
+ }
+ data.writeCString(str);
+ err = remote()->transact(GET_ITEM_LIST, data, &reply);
+ if (err != NO_ERROR) {
+ return NULL;
+ }
+
+ // read a count
+ int32_t count = reply.readInt32();
+ List<sp<MediaAnalyticsItem>> *list = NULL;
+
+ if (count > 0) {
+ list = new List<sp<MediaAnalyticsItem>>();
+ for (int i=0;i<count;i++) {
+ sp<MediaAnalyticsItem> item = new MediaAnalyticsItem;
+ // XXX: watch for failures here
+ item->readFromParcel(reply);
+ list->push_back(item);
+ }
+ }
+
+ return list;
+ }
+};
+
+IMPLEMENT_META_INTERFACE(MediaAnalyticsService, "android.media.IMediaAnalyticsService");
+
+// ----------------------------------------------------------------------
+
+status_t BnMediaAnalyticsService::onTransact(
+ uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+
+
+ // get calling pid/tid
+ IPCThreadState *ipc = IPCThreadState::self();
+ int clientPid = ipc->getCallingPid();
+ // permission checking
+
+ if(DEBUGGING_FLOW) {
+ ALOGD("running in service, code %d, pid %d; called from pid %d",
+ code, getpid(), clientPid);
+ }
+
+ switch (code) {
+
+ case GENERATE_UNIQUE_SESSIONID: {
+ CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+
+ MediaAnalyticsItem::SessionID_t sessionid = generateUniqueSessionID();
+ reply->writeInt64(sessionid);
+
+ return NO_ERROR;
+ } break;
+
+ case SUBMIT_ITEM: {
+ CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+
+ bool forcenew;
+ sp<MediaAnalyticsItem> item = new MediaAnalyticsItem;
+
+ data.readBool(&forcenew);
+ item->readFromParcel(data);
+
+ item->setPid(clientPid);
+
+ // submit() takes ownership of / responsibility for the item
+ MediaAnalyticsItem::SessionID_t sessionid = submit(item, forcenew);
+ reply->writeInt64(sessionid);
+
+ return NO_ERROR;
+ } break;
+
+ case GET_ITEM_LIST: {
+ CHECK_INTERFACE(IMediaPlayerService, data, reply);
+ // get the parameters
+ bool finished = data.readInt32();
+ nsecs_t ts = data.readInt64();
+ MediaAnalyticsItem::Key key = data.readCString();
+
+ // find the (0 or more) items
+ List<sp<MediaAnalyticsItem>> *list = getMediaAnalyticsItemList(finished, ts, key);
+ // encapsulate/serialize them
+ reply->writeInt32(list->size());
+ if (list->size() > 0) {
+ for (List<sp<MediaAnalyticsItem>>::iterator it = list->begin();
+ it != list->end(); it++) {
+ (*it)->writeToParcel(reply);
+ }
+
+
+ }
+
+ // avoid leakiness; organized discarding of list and its contents
+ list->clear();
+ delete list;
+
+ return NO_ERROR;
+ } break;
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+// ----------------------------------------------------------------------------
+
+} // namespace android
diff --git a/media/libmedia/MediaAnalyticsItem.cpp b/media/libmedia/MediaAnalyticsItem.cpp
new file mode 100644
index 0000000..5f05d5a
--- /dev/null
+++ b/media/libmedia/MediaAnalyticsItem.cpp
@@ -0,0 +1,703 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "MediaAnalyticsItem"
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/Mutex.h>
+#include <utils/RefBase.h>
+#include <utils/SortedVector.h>
+#include <utils/threads.h>
+
+#include <media/stagefright/foundation/AString.h>
+
+#include <binder/IServiceManager.h>
+#include <media/IMediaAnalyticsService.h>
+#include <media/MediaAnalyticsItem.h>
+
+namespace android {
+
+#define DEBUG_SERVICEACCESS 0
+
+// the few universal keys we have
+const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyAny = "any";
+const MediaAnalyticsItem::Key MediaAnalyticsItem::kKeyNone = "none";
+
+const char * const MediaAnalyticsItem::EnabledProperty = "media.analytics.enabled";
+const char * const MediaAnalyticsItem::EnabledPropertyPersist = "persist.media.analytics.enabled";
+const int MediaAnalyticsItem::EnabledProperty_default = 0;
+
+
+// access functions for the class
+MediaAnalyticsItem::MediaAnalyticsItem()
+ : RefBase(),
+ mPid(0),
+ mUid(0),
+ mSessionID(MediaAnalyticsItem::SessionIDNone),
+ mTimestamp(0),
+ mFinalized(0) {
+ mKey = MediaAnalyticsItem::kKeyNone;
+}
+
+MediaAnalyticsItem::MediaAnalyticsItem(MediaAnalyticsItem::Key key)
+ : RefBase(),
+ mPid(0),
+ mUid(0),
+ mSessionID(MediaAnalyticsItem::SessionIDNone),
+ mTimestamp(0),
+ mFinalized(0) {
+ mKey = key;
+}
+
+MediaAnalyticsItem::~MediaAnalyticsItem() {
+ clear();
+}
+
+// so clients can send intermediate values to be overlaid later
+MediaAnalyticsItem &MediaAnalyticsItem::setFinalized(bool value) {
+ mFinalized = value;
+ return *this;
+}
+
+bool MediaAnalyticsItem::getFinalized() const {
+ return mFinalized;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setSessionID(MediaAnalyticsItem::SessionID_t id) {
+ mSessionID = id;
+ return *this;
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::getSessionID() const {
+ return mSessionID;
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsItem::generateSessionID() {
+ MediaAnalyticsItem::SessionID_t newid = SessionIDNone;
+ ALOGD("generateSessionID()");
+
+ if (mSessionID == SessionIDNone) {
+ // get one from the server
+ sp<IMediaAnalyticsService> svc = getInstance();
+ if (svc != NULL) {
+ newid = svc->generateUniqueSessionID();
+ }
+ mSessionID = newid;
+ }
+
+ return mSessionID;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::clearSessionID() {
+ mSessionID = MediaAnalyticsItem::SessionIDNone;
+ return *this;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setTimestamp(nsecs_t ts) {
+ mTimestamp = ts;
+ return *this;
+}
+
+nsecs_t MediaAnalyticsItem::getTimestamp() const {
+ return mTimestamp;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setPid(pid_t pid) {
+ mPid = pid;
+ return *this;
+}
+
+pid_t MediaAnalyticsItem::getPid() const {
+ return mPid;
+}
+
+MediaAnalyticsItem &MediaAnalyticsItem::setUid(uid_t uid) {
+ mUid = uid;
+ return *this;
+}
+
+uid_t MediaAnalyticsItem::getUid() const {
+ return mUid;
+}
+
+void MediaAnalyticsItem::clear() {
+
+ mKey.clear();
+
+#if 0
+ // not sure that I need to (or should) be doing this...
+ // seeing some strangeness in some records
+ int count = mItems.size();
+ for (int i = 0 ; i < count; i++ ) {
+ MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+ const sp<Item> value = mItems.valueAt(i);
+ value->clear();
+ attr.clear();
+ }
+ mItems.clear();
+#endif
+
+ return;
+}
+
+// this key is for the overall record -- "vid" or "aud"
+// assuming for the moment we use int32_t like the
+// media frameworks MetaData.cpp
+MediaAnalyticsItem &MediaAnalyticsItem::setKey(MediaAnalyticsItem::Key key) {
+ // XXX: possible validation of legal keys.
+ mKey = key;
+ return *this;
+}
+
+MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
+ return mKey;
+}
+
+// number of keys we have in our dictionary
+// we won't upload empty records
+int32_t MediaAnalyticsItem::count() const {
+ return mItems.size();
+}
+
+// set the values
+bool MediaAnalyticsItem::setInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ item->mType = MediaAnalyticsItem::Item::kTypeInt32;
+ item->u.int32Value = value;
+ return overwrote;
+}
+
+bool MediaAnalyticsItem::setInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ item->mType = MediaAnalyticsItem::Item::kTypeInt64;
+ item->u.int64Value = value;
+ return overwrote;
+}
+
+bool MediaAnalyticsItem::setDouble(MediaAnalyticsItem::Attr attr, double value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ item->mType = MediaAnalyticsItem::Item::kTypeDouble;
+ item->u.doubleValue = value;
+ return overwrote;
+}
+
+bool MediaAnalyticsItem::setCString(MediaAnalyticsItem::Attr attr, const char *value) {
+ bool overwrote = true;
+ if (value == NULL) return false;
+ // we store our own copy of the supplied string
+ char *nvalue = strdup(value);
+ if (nvalue == NULL) {
+ return false;
+ }
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ if (item->mType == MediaAnalyticsItem::Item::kTypeCString
+ && item->u.CStringValue != NULL) {
+ free(item->u.CStringValue);
+ item->u.CStringValue = NULL;
+ }
+ item->mType = MediaAnalyticsItem::Item::kTypeCString;
+ item->u.CStringValue = nvalue;
+ return true;
+}
+
+// find/add/set fused into a single operation
+bool MediaAnalyticsItem::addInt32(MediaAnalyticsItem::Attr attr, int32_t value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ if (overwrote
+ && item->mType == MediaAnalyticsItem::Item::kTypeInt32) {
+ item->u.int32Value += value;
+ } else {
+ // start clean if there was a type mismatch
+ item->u.int32Value = value;
+ }
+ item->mType = MediaAnalyticsItem::Item::kTypeInt32;
+ return overwrote;
+}
+
+bool MediaAnalyticsItem::addInt64(MediaAnalyticsItem::Attr attr, int64_t value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ if (overwrote
+ && item->mType == MediaAnalyticsItem::Item::kTypeInt64) {
+ item->u.int64Value += value;
+ } else {
+ // start clean if there was a type mismatch
+ item->u.int64Value = value;
+ }
+ item->mType = MediaAnalyticsItem::Item::kTypeInt64;
+ return overwrote;
+}
+
+bool MediaAnalyticsItem::addDouble(MediaAnalyticsItem::Attr attr, double value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ bool overwrote = true;
+ if (i<0) {
+ sp<Item> item = new Item();
+ i = mItems.add(attr, item);
+ overwrote = false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ if (overwrote
+ && item->mType == MediaAnalyticsItem::Item::kTypeDouble) {
+ item->u.doubleValue += value;
+ } else {
+ // start clean if there was a type mismatch
+ item->u.doubleValue = value;
+ }
+ item->mType = MediaAnalyticsItem::Item::kTypeDouble;
+ return overwrote;
+}
+
+// find & extract values
+bool MediaAnalyticsItem::getInt32(MediaAnalyticsItem::Attr attr, int32_t *value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i < 0) {
+ return false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ *value = item->u.int32Value;
+ return true;
+}
+bool MediaAnalyticsItem::getInt64(MediaAnalyticsItem::Attr attr, int64_t *value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i < 0) {
+ return false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ *value = item->u.int64Value;
+ return true;
+}
+bool MediaAnalyticsItem::getDouble(MediaAnalyticsItem::Attr attr, double *value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i < 0) {
+ return false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ *value = item->u.doubleValue;
+ return true;
+}
+
+// caller responsible for the returned string
+bool MediaAnalyticsItem::getCString(MediaAnalyticsItem::Attr attr, char **value) {
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i < 0) {
+ return false;
+ }
+ sp<Item> &item = mItems.editValueAt(i);
+ char *p = strdup(item->u.CStringValue);
+ *value = p;
+ return true;
+}
+
+// remove indicated keys and their values
+// return value is # keys removed
+int32_t MediaAnalyticsItem::filter(int n, MediaAnalyticsItem::Attr attrs[]) {
+ int zapped = 0;
+ if (attrs == NULL) {
+ return -1;
+ }
+ if (n <= 0) {
+ return -1;
+ }
+ for (ssize_t i = 0 ; i < n ; i++) {
+ ssize_t j = mItems.indexOfKey(attrs[i]);
+ if (j >= 0) {
+ mItems.removeItemsAt(j);
+ zapped++;
+ }
+ }
+ return zapped;
+}
+
+// remove any keys NOT in the provided list
+// return value is # keys removed
+int32_t MediaAnalyticsItem::filterNot(int n, MediaAnalyticsItem::Attr attrs[]) {
+ int zapped = 0;
+ if (attrs == NULL) {
+ return -1;
+ }
+ if (n <= 0) {
+ return -1;
+ }
+ for (ssize_t i = mItems.size()-1 ; i >=0 ; i--) {
+ const MediaAnalyticsItem::Attr& lattr = mItems.keyAt(i);
+ ssize_t j;
+ for (j= 0; j < n ; j++) {
+ if (lattr == attrs[j]) {
+ mItems.removeItemsAt(i);
+ zapped++;
+ break;
+ }
+ }
+ }
+ return zapped;
+}
+
+// remove a single key
+// return value is 0 (not found) or 1 (found and removed)
+int32_t MediaAnalyticsItem::filter(MediaAnalyticsItem::Attr attr) {
+ if (attr == 0) return -1;
+ ssize_t i = mItems.indexOfKey(attr);
+ if (i < 0) {
+ return 0;
+ }
+ mItems.removeItemsAt(i);
+ return 1;
+}
+
+
+// handle individual items/properties stored within the class
+//
+MediaAnalyticsItem::Item::Item()
+ : mType(kTypeNone)
+{
+}
+
+MediaAnalyticsItem::Item::~Item()
+{
+ clear();
+}
+
+void MediaAnalyticsItem::Item::clear()
+{
+ if (mType == kTypeCString && u.CStringValue != NULL) {
+ free(u.CStringValue);
+ u.CStringValue = NULL;
+ }
+ mType = kTypeNone;
+}
+
+// Parcel / serialize things for binder calls
+//
+
+int32_t MediaAnalyticsItem::readFromParcel(const Parcel& data) {
+ // into 'this' object
+ // .. we make a copy of the string to put away.
+ mKey = data.readCString();
+ mSessionID = data.readInt64();
+ mFinalized = data.readInt32();
+ mTimestamp = data.readInt64();
+
+ int count = data.readInt32();
+ for (int i = 0; i < count ; i++) {
+ MediaAnalyticsItem::Attr attr = data.readCString();
+ int32_t ztype = data.readInt32();
+ switch (ztype) {
+ case MediaAnalyticsItem::Item::kTypeInt32:
+ setInt32(attr, data.readInt32());
+ break;
+ case MediaAnalyticsItem::Item::kTypeInt64:
+ setInt64(attr, data.readInt64());
+ break;
+ case MediaAnalyticsItem::Item::kTypeDouble:
+ setDouble(attr, data.readDouble());
+ break;
+ case MediaAnalyticsItem::Item::kTypeCString:
+ setCString(attr, data.readCString());
+ break;
+ default:
+ ALOGE("reading bad item type: %d, idx %d",
+ ztype, i);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+int32_t MediaAnalyticsItem::writeToParcel(Parcel *data) {
+ if (data == NULL) return -1;
+
+
+ data->writeCString(mKey.c_str());
+ data->writeInt64(mSessionID);
+ data->writeInt32(mFinalized);
+ data->writeInt64(mTimestamp);
+
+ // set of items
+ int count = mItems.size();
+ data->writeInt32(count);
+ for (int i = 0 ; i < count; i++ ) {
+ MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+ sp<Item> value = mItems.valueAt(i);
+ {
+ data->writeCString(attr.c_str());
+ data->writeInt32(value->mType);
+ switch (value->mType) {
+ case MediaAnalyticsItem::Item::kTypeInt32:
+ data->writeInt32(value->u.int32Value);
+ break;
+ case MediaAnalyticsItem::Item::kTypeInt64:
+ data->writeInt64(value->u.int64Value);
+ break;
+ case MediaAnalyticsItem::Item::kTypeDouble:
+ data->writeDouble(value->u.doubleValue);
+ break;
+ case MediaAnalyticsItem::Item::kTypeCString:
+ data->writeCString(value->u.CStringValue);
+ break;
+ default:
+ ALOGE("found bad item type: %d, idx %d",
+ value->mType, i);
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+
+AString MediaAnalyticsItem::toString() {
+
+ AString result = "(";
+ char buffer[256];
+
+ // same order as we spill into the parcel, although not required
+ // key+session are our primary matching criteria
+ result.append(mKey.c_str());
+ result.append(":");
+ snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mSessionID);
+ result.append(buffer);
+
+ // we need these internally, but don't want to upload them
+ snprintf(buffer, sizeof(buffer), "%d:%d", mUid, mPid);
+ result.append(buffer);
+
+ snprintf(buffer, sizeof(buffer), "%d:", mFinalized);
+ result.append(buffer);
+ snprintf(buffer, sizeof(buffer), "%" PRId64 ":", mTimestamp);
+ result.append(buffer);
+
+ // set of items
+ int count = mItems.size();
+ snprintf(buffer, sizeof(buffer), "%d:", count);
+ result.append(buffer);
+ for (int i = 0 ; i < count; i++ ) {
+ const MediaAnalyticsItem::Attr attr = mItems.keyAt(i);
+ const sp<Item> value = mItems.valueAt(i);
+ switch (value->mType) {
+ case MediaAnalyticsItem::Item::kTypeInt32:
+ snprintf(buffer,sizeof(buffer),
+ "%s=%d:", attr.c_str(), value->u.int32Value);
+ break;
+ case MediaAnalyticsItem::Item::kTypeInt64:
+ snprintf(buffer,sizeof(buffer),
+ "%s=%" PRId64 ":", attr.c_str(), value->u.int64Value);
+ break;
+ case MediaAnalyticsItem::Item::kTypeDouble:
+ snprintf(buffer,sizeof(buffer),
+ "%s=%e:", attr.c_str(), value->u.doubleValue);
+ break;
+ case MediaAnalyticsItem::Item::kTypeCString:
+ // XXX: worry about escape chars
+ // XXX: worry about overflowing buffer
+ snprintf(buffer,sizeof(buffer), "%s=", attr.c_str());
+ result.append(buffer);
+ result.append(value->u.CStringValue);
+ buffer[0] = ':';
+ buffer[1] = '\0';
+ break;
+ default:
+ ALOGE("to_String bad item type: %d",
+ value->mType);
+ break;
+ }
+ result.append(buffer);
+ }
+
+ result.append(")");
+
+ return result;
+}
+
+// for the lazy, we offer methods that finds the service and
+// calls the appropriate daemon
+bool MediaAnalyticsItem::selfrecord() {
+ return selfrecord(false);
+}
+
+bool MediaAnalyticsItem::selfrecord(bool forcenew) {
+
+ AString p = this->toString();
+ ALOGD("selfrecord of: %s [forcenew=%d]", p.c_str(), forcenew);
+
+ sp<IMediaAnalyticsService> svc = getInstance();
+
+ if (svc != NULL) {
+ svc->submit(this, forcenew);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// get a connection we can reuse for most of our lifetime
+// static
+sp<IMediaAnalyticsService> MediaAnalyticsItem::sAnalyticsService;
+static Mutex sInitMutex;
+
+//static
+bool MediaAnalyticsItem::isEnabled() {
+ int enabled = property_get_int32(MediaAnalyticsItem::EnabledProperty, -1);
+
+ if (enabled == -1) {
+ enabled = property_get_int32(MediaAnalyticsItem::EnabledPropertyPersist, -1);
+ }
+ if (enabled == -1) {
+ enabled = MediaAnalyticsItem::EnabledProperty_default;
+ }
+ if (enabled <= 0) {
+ return false;
+ }
+ return true;
+}
+
+//static
+sp<IMediaAnalyticsService> MediaAnalyticsItem::getInstance() {
+ static const char *servicename = "media.analytics";
+ int enabled = isEnabled();
+
+ if (enabled == false) {
+ if (DEBUG_SERVICEACCESS) {
+ ALOGD("disabled");
+ }
+ return NULL;
+ }
+
+ {
+ Mutex::Autolock _l(sInitMutex);
+ const char *badness = "";
+
+
+ if (sAnalyticsService == NULL) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm != NULL) {
+ sp<IBinder> binder = sm->getService(String16(servicename));
+ if (binder != NULL) {
+ sAnalyticsService = interface_cast<IMediaAnalyticsService>(binder);
+ } else {
+ badness = "did not find service";
+ }
+ } else {
+ badness = "No Service Manager access";
+ }
+ // always
+ if (1 || DEBUG_SERVICEACCESS) {
+ if (sAnalyticsService == NULL) {
+ ALOGD("Unable to bind to service %s: %s", servicename, badness);
+ }
+ }
+ }
+ return sAnalyticsService;
+ }
+}
+
+
+// merge the info from 'incoming' into this record.
+// we finish with a union of this+incoming and special handling for collisions
+bool MediaAnalyticsItem::merge(sp<MediaAnalyticsItem> incoming) {
+
+ // if I don't have key or session id, take them from incoming
+ // 'this' should never be missing both of them...
+ if (mKey.empty()) {
+ mKey = incoming->mKey;
+ } else if (mSessionID == 0) {
+ mSessionID = incoming->mSessionID;
+ }
+
+ // we always take the more recent 'finalized' value
+ setFinalized(incoming->getFinalized());
+
+ // for each attribute from 'incoming', resolve appropriately
+ int nattr = incoming->mItems.size();
+ for (int i = 0 ; i < nattr; i++ ) {
+ const MediaAnalyticsItem::Attr attr = incoming->mItems.keyAt(i);
+ const sp<Item> value = incoming->mItems.valueAt(i);
+
+ const char *p = attr.c_str();
+ char semantic = p[strlen(p)-1];
+
+ switch (semantic) {
+ default: // default operation is keep new
+ case '>': // last aka keep new
+ mItems.replaceValueFor(attr, value);
+ break;
+
+ case '<': /* first aka keep first*/
+ /* nop */
+ break;
+
+ case '+': /* sum */
+ // XXX validate numeric types, sum in place
+ break;
+
+ }
+ }
+
+ // not sure when we'd return false...
+ return true;
+}
+
+} // namespace android
+
diff --git a/media/libmediaanalyticsservice/Android.mk b/media/libmediaanalyticsservice/Android.mk
new file mode 100644
index 0000000..dd59651
--- /dev/null
+++ b/media/libmediaanalyticsservice/Android.mk
@@ -0,0 +1,44 @@
+LOCAL_PATH:= $(call my-dir)
+
+#
+# libmediaanalyticsservice
+#
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ MediaAnalyticsService.cpp \
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libcutils \
+ liblog \
+ libdl \
+ libgui \
+ libmedia \
+ libmediautils \
+ libstagefright_foundation \
+ libutils
+
+LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := libmedia
+
+LOCAL_C_INCLUDES := \
+ $(TOP)/frameworks/av/media/libstagefright/include \
+ $(TOP)/frameworks/av/media/libstagefright/rtsp \
+ $(TOP)/frameworks/av/media/libstagefright/wifi-display \
+ $(TOP)/frameworks/av/media/libstagefright/webm \
+ $(TOP)/frameworks/av/include/media \
+ $(TOP)/frameworks/av/include/camera \
+ $(TOP)/frameworks/native/include/media/openmax \
+ $(TOP)/frameworks/native/include/media/hardware \
+ $(TOP)/external/tremolo/Tremolo \
+ libcore/include \
+
+LOCAL_CFLAGS += -Werror -Wno-error=deprecated-declarations -Wall
+LOCAL_CLANG := true
+
+LOCAL_MODULE:= libmediaanalyticsservice
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/media/libmediaanalyticsservice/MediaAnalyticsService.cpp b/media/libmediaanalyticsservice/MediaAnalyticsService.cpp
new file mode 100644
index 0000000..a039c6c
--- /dev/null
+++ b/media/libmediaanalyticsservice/MediaAnalyticsService.cpp
@@ -0,0 +1,459 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+// Proxy for media player implementations
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaAnalyticsService"
+#include <utils/Log.h>
+
+#include <inttypes.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <dirent.h>
+#include <unistd.h>
+
+#include <string.h>
+
+#include <cutils/atomic.h>
+#include <cutils/properties.h> // for property_get
+
+#include <utils/misc.h>
+
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryBase.h>
+#include <gui/Surface.h>
+#include <utils/Errors.h> // for status_t
+#include <utils/List.h>
+#include <utils/String8.h>
+#include <utils/SystemClock.h>
+#include <utils/Timers.h>
+#include <utils/Vector.h>
+
+#include <media/AudioPolicyHelper.h>
+#include <media/IMediaHTTPService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+#include <media/MediaPlayerInterface.h>
+#include <media/mediarecorder.h>
+#include <media/MediaMetadataRetrieverInterface.h>
+#include <media/Metadata.h>
+#include <media/AudioTrack.h>
+#include <media/MemoryLeakTrackUtil.h>
+#include <media/stagefright/MediaCodecList.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/ALooperRoster.h>
+#include <mediautils/BatteryNotifier.h>
+
+//#include <memunreachable/memunreachable.h>
+#include <system/audio.h>
+
+#include <private/android_filesystem_config.h>
+
+#include "MediaAnalyticsService.h"
+
+
+namespace android {
+
+
+static int trackqueue = 0;
+
+//using android::status_t;
+//using android::OK;
+//using android::BAD_VALUE;
+//using android::NOT_ENOUGH_DATA;
+//using android::Parcel;
+
+
+void MediaAnalyticsService::instantiate() {
+ defaultServiceManager()->addService(
+ String16("media.analytics"), new MediaAnalyticsService());
+}
+
+// XXX: add dynamic controls for mMaxRecords
+MediaAnalyticsService::MediaAnalyticsService()
+ : mMaxRecords(100) {
+
+ ALOGD("MediaAnalyticsService created");
+ // clear our queues
+ mOpen = new List<sp<MediaAnalyticsItem>>();
+ mFinalized = new List<sp<MediaAnalyticsItem>>();
+
+ mItemsSubmitted = 0;
+ mItemsFinalized = 0;
+ mItemsDiscarded = 0;
+
+ mLastSessionID = 0;
+ // recover any persistency we set up
+ // etc
+}
+
+MediaAnalyticsService::~MediaAnalyticsService() {
+ ALOGD("MediaAnalyticsService destroyed");
+
+ // XXX: clean out mOpen and mFinalized
+}
+
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
+ // generate a new sessionid
+
+ Mutex::Autolock _l(mLock_ids);
+ return (++mLastSessionID);
+}
+
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(sp<MediaAnalyticsItem> item, bool forcenew) {
+
+ MediaAnalyticsItem::SessionID_t id = MediaAnalyticsItem::SessionIDInvalid;
+
+ // we control these, not using whatever the user might have sent
+ nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ item->setTimestamp(now);
+ int pid = IPCThreadState::self()->getCallingPid();
+ item->setPid(pid);
+ int uid = IPCThreadState::self()->getCallingUid();
+ item->setUid(uid);
+
+ mItemsSubmitted++;
+
+ // validate the record; we discard if we don't like it
+ if (contentValid(item) == false) {
+ return MediaAnalyticsItem::SessionIDInvalid;
+ }
+
+
+ // if we have a sesisonid in the new record, look to make
+ // sure it doesn't appear in the finalized list.
+ // XXX: this is for security / DOS prevention.
+ // may also require that we persist the unique sessionIDs
+ // across boots [instead of within a single boot]
+
+
+ // match this new record up against records in the open
+ // list...
+ // if there's a match, merge them together
+ // deal with moving the old / merged record into the finalized que
+
+ bool finalizing = item->getFinalized();
+
+ // if finalizing, we'll remove it
+ sp<MediaAnalyticsItem> oitem = findItem(mOpen, item, finalizing | forcenew);
+ if (oitem != NULL) {
+ if (forcenew) {
+ // old one gets finalized, then we insert the new one
+ // so we'll have 2 records at the end of this.
+ // but don't finalize an empty record
+ if (oitem->count() != 0) {
+ oitem->setFinalized(true);
+ saveItem(mFinalized, oitem, 0);
+ }
+ // new record could itself be marked finalized...
+ if (finalizing) {
+ saveItem(mFinalized, item, 0);
+ mItemsFinalized++;
+ } else {
+ saveItem(mOpen, item, 1);
+ }
+ id = item->getSessionID();
+ } else {
+ // combine the records, send it to finalized if appropriate
+ oitem->merge(item);
+ if (finalizing) {
+ saveItem(mFinalized, oitem, 0);
+ mItemsFinalized++;
+ }
+ id = oitem->getSessionID();
+ }
+ } else {
+ // nothing to merge, save the new record
+ if (finalizing) {
+ if (item->count() != 0) {
+ // drop empty records
+ saveItem(mFinalized, item, 0);
+ mItemsFinalized++;
+ }
+ } else {
+ saveItem(mOpen, item, 1);
+ }
+ id = item->getSessionID();
+ }
+
+ return id;
+}
+
+List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
+ // this might never get called; the binder interface maps to the full parm list
+ // on the client side before making the binder call.
+ // but this lets us be sure...
+ List<sp<MediaAnalyticsItem>> *list;
+ list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
+ return list;
+}
+
+List<sp<MediaAnalyticsItem>> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
+
+ // XXX: implement the get-item-list semantics
+
+ List<sp<MediaAnalyticsItem>> *list = NULL;
+ // set up our query on the persistent data
+ // slurp in all of the pieces
+ // return that
+ return list;
+}
+
+// ignoring 2nd argument, name removed to keep compiler happy
+// XXX: arguments to parse:
+// -- a timestamp (either since X or last X seconds) to bound search
+status_t MediaAnalyticsService::dump(int fd, const Vector<String16>&)
+{
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ String8 result;
+
+ if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
+ snprintf(buffer, SIZE, "Permission Denial: "
+ "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
+ IPCThreadState::self()->getCallingPid(),
+ IPCThreadState::self()->getCallingUid());
+ result.append(buffer);
+ } else {
+
+ // crack parameters
+
+
+ Mutex::Autolock _l(mLock);
+
+ snprintf(buffer, SIZE, "Dump of the mediaanalytics process:\n");
+ result.append(buffer);
+
+ int enabled = MediaAnalyticsItem::isEnabled();
+ if (enabled) {
+ snprintf(buffer, SIZE, "Analytics gathering: enabled\n");
+ } else {
+ snprintf(buffer, SIZE, "Analytics gathering: DISABLED via property\n");
+ }
+ result.append(buffer);
+
+ snprintf(buffer, SIZE,
+ "Since Boot: Submissions: %" PRId64
+ " Finalizations: %" PRId64
+ " Discarded: %" PRId64 "\n",
+ mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
+ result.append(buffer);
+
+ // show the recently recorded records
+ snprintf(buffer, sizeof(buffer), "\nFinalized Analytics (oldest first):\n");
+ result.append(buffer);
+ result.append(this->dumpQueue(mFinalized));
+
+ snprintf(buffer, sizeof(buffer), "\nIn-Progress Analytics (newest first):\n");
+ result.append(buffer);
+ result.append(this->dumpQueue(mOpen));
+
+ // show who is connected and injecting records?
+ // talk about # records fed to the 'readers'
+ // talk about # records we discarded, perhaps "discarded w/o reading" too
+
+ }
+ write(fd, result.string(), result.size());
+ return NO_ERROR;
+}
+
+// caller has locked mLock...
+String8 MediaAnalyticsService::dumpQueue(List<sp<MediaAnalyticsItem>> *theList) {
+ const size_t SIZE = 256;
+ char buffer[SIZE];
+ String8 result;
+ int slot = 0;
+
+ if (theList->empty()) {
+ result.append("empty\n");
+ } else {
+ List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
+ for (; it != theList->end(); it++, slot++) {
+ AString entry = (*it)->toString();
+ snprintf(buffer, sizeof(buffer), "%4d: %s\n",
+ slot, entry.c_str());
+ result.append(buffer);
+ }
+ }
+
+ return result;
+}
+
+//
+// Our Cheap in-core, non-persistent records management.
+// XXX: rewrite this to manage persistence, etc.
+
+// insert appropriately into queue
+void MediaAnalyticsService::saveItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item, int front) {
+
+ Mutex::Autolock _l(mLock);
+
+ if (false)
+ ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
+ item->getSessionID(), item->getTimestamp());
+
+ if (trackqueue) {
+ String8 before = dumpQueue(l);
+ ALOGD("Q before insert: %s", before.string());
+ }
+
+ // adding at back of queue (fifo order)
+ if (front) {
+ l->push_front(item);
+ } else {
+ l->push_back(item);
+ }
+
+ if (trackqueue) {
+ String8 after = dumpQueue(l);
+ ALOGD("Q after insert: %s", after.string());
+ }
+
+ // keep removing old records the front until we're in-bounds
+ if (mMaxRecords > 0) {
+ while (l->size() > (size_t) mMaxRecords) {
+ sp<MediaAnalyticsItem> oitem = *(l->begin());
+ if (trackqueue) {
+ ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
+ oitem->getKey().c_str(), oitem->getSessionID(),
+ oitem->getTimestamp());
+ }
+ l->erase(l->begin());
+ mItemsDiscarded++;
+ }
+ }
+
+ if (trackqueue) {
+ String8 after = dumpQueue(l);
+ ALOGD("Q after cleanup: %s", after.string());
+ }
+}
+
+// are they alike enough that nitem can be folded into oitem?
+static bool compatibleItems(sp<MediaAnalyticsItem> oitem, sp<MediaAnalyticsItem> nitem) {
+
+ if (0) {
+ ALOGD("Compare: o %s n %s",
+ oitem->toString().c_str(), nitem->toString().c_str());
+ }
+
+ // general safety
+ if (nitem->getUid() != oitem->getUid()) {
+ return false;
+ }
+ if (nitem->getPid() != oitem->getPid()) {
+ return false;
+ }
+
+ // key -- needs to match
+ if (nitem->getKey() == oitem->getKey()) {
+ // still in the game.
+ } else {
+ return false;
+ }
+
+ // session id -- empty field in new is allowed
+ MediaAnalyticsItem::SessionID_t osession = oitem->getSessionID();
+ MediaAnalyticsItem::SessionID_t nsession = nitem->getSessionID();
+ if (nsession != osession) {
+ // incoming '0' matches value in osession
+ if (nsession != 0) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+// find the incomplete record that this will overlay
+sp<MediaAnalyticsItem> MediaAnalyticsService::findItem(List<sp<MediaAnalyticsItem>> *theList, sp<MediaAnalyticsItem> nitem, bool removeit) {
+ sp<MediaAnalyticsItem> item;
+
+ if (nitem == NULL) {
+ return NULL;
+ }
+
+ Mutex::Autolock _l(mLock);
+
+ for (List<sp<MediaAnalyticsItem>>::iterator it = theList->begin();
+ it != theList->end(); it++) {
+ sp<MediaAnalyticsItem> tmp = (*it);
+
+ if (!compatibleItems(tmp, nitem)) {
+ continue;
+ }
+
+ // we match! this is the one I want.
+ if (removeit) {
+ theList->erase(it);
+ }
+ item = tmp;
+ break;
+ }
+ return item;
+}
+
+
+// delete the indicated record
+void MediaAnalyticsService::deleteItem(List<sp<MediaAnalyticsItem>> *l, sp<MediaAnalyticsItem> item) {
+
+ Mutex::Autolock _l(mLock);
+
+ if(trackqueue) {
+ String8 before = dumpQueue(l);
+ ALOGD("Q before delete: %s", before.string());
+ }
+
+ for (List<sp<MediaAnalyticsItem>>::iterator it = l->begin();
+ it != l->end(); it++) {
+ if ((*it)->getSessionID() != item->getSessionID())
+ continue;
+
+ ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
+ l->erase(it);
+ break;
+ }
+
+ if (trackqueue) {
+ String8 after = dumpQueue(l);
+ ALOGD("Q after delete: %s", after.string());
+ }
+}
+
+// are the contents good
+bool MediaAnalyticsService::contentValid(sp<MediaAnalyticsItem>) {
+
+ // certain keys require certain uids
+ // internal consistency
+
+ return true;
+}
+
+// are we rate limited, normally false
+bool MediaAnalyticsService::rateLimited(sp<MediaAnalyticsItem>) {
+
+ return false;
+}
+
+
+} // namespace android
diff --git a/media/libmediaanalyticsservice/MediaAnalyticsService.h b/media/libmediaanalyticsservice/MediaAnalyticsService.h
new file mode 100644
index 0000000..f9afeb2
--- /dev/null
+++ b/media/libmediaanalyticsservice/MediaAnalyticsService.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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_MEDIAANALYTICSSERVICE_H
+#define ANDROID_MEDIAANALYTICSSERVICE_H
+
+#include <arpa/inet.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+
+namespace android {
+
+class MediaAnalyticsService : public BnMediaAnalyticsService
+{
+
+ public:
+
+ virtual int64_t submit(sp<MediaAnalyticsItem> item, bool forcenew);
+
+ virtual List<sp<MediaAnalyticsItem>>
+ *getMediaAnalyticsItemList(bool finished, int64_t ts);
+ virtual List<sp<MediaAnalyticsItem>>
+ *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key);
+
+
+ static void instantiate();
+ virtual status_t dump(int fd, const Vector<String16>& args);
+
+ MediaAnalyticsService();
+ virtual ~MediaAnalyticsService();
+
+ private:
+ MediaAnalyticsItem::SessionID_t generateUniqueSessionID();
+
+ // statistics about our analytics
+ int64_t mItemsSubmitted;
+ int64_t mItemsFinalized;
+ int64_t mItemsDiscarded;
+ MediaAnalyticsItem::SessionID_t mLastSessionID;
+
+ // partitioned a bit so we don't over serialize
+ mutable Mutex mLock;
+ mutable Mutex mLock_ids;
+
+ // the most we hold in memory
+ // up to this many in each queue (open, finalized)
+ int32_t mMaxRecords;
+
+ // input validation after arrival from client
+ bool contentValid(sp<MediaAnalyticsItem>);
+ bool rateLimited(sp<MediaAnalyticsItem>);
+
+ // the ones that are still open
+ // (newest at front) since we keep looking for them
+ List<sp<MediaAnalyticsItem>> *mOpen;
+ // the ones we've finalized
+ // (oldest at front) so it prints nicely for dumpsys
+ List<sp<MediaAnalyticsItem>> *mFinalized;
+ // searching within these queues: queue, key
+ sp<MediaAnalyticsItem> findItem(List<sp<MediaAnalyticsItem>> *,
+ sp<MediaAnalyticsItem>, bool removeit);
+
+ void saveItem(sp<MediaAnalyticsItem>);
+ void saveItem(List<sp<MediaAnalyticsItem>>*, sp<MediaAnalyticsItem>, int);
+ void deleteItem(List<sp<MediaAnalyticsItem>>*, sp<MediaAnalyticsItem>);
+
+ String8 dumpQueue(List<sp<MediaAnalyticsItem>> *);
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_MEDIAANALYTICSSERVICE_H
diff --git a/media/libstagefright/include/MPEG2TSExtractor.h b/media/libstagefright/include/MPEG2TSExtractor.h
index 93e9a4b..ef55620 100644
--- a/media/libstagefright/include/MPEG2TSExtractor.h
+++ b/media/libstagefright/include/MPEG2TSExtractor.h
@@ -89,6 +89,8 @@
// Add a SynPoint derived from |event|.
void addSyncPoint_l(const ATSParser::SyncEvent &event);
+ status_t estimateDurationsFromTimesUsAtEnd();
+
DISALLOW_EVIL_CONSTRUCTORS(MPEG2TSExtractor);
};
diff --git a/media/libstagefright/mpeg2ts/ATSParser.cpp b/media/libstagefright/mpeg2ts/ATSParser.cpp
index 844479e..4975d9a 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.cpp
+++ b/media/libstagefright/mpeg2ts/ATSParser.cpp
@@ -90,6 +90,10 @@
return mParser->mFlags;
}
+ uint64_t firstPTS() const {
+ return mFirstPTS;
+ }
+
private:
struct StreamInfo {
unsigned mType;
@@ -135,6 +139,7 @@
void signalEOS(status_t finalResult);
+ SourceType getSourceType();
sp<MediaSource> getSource(SourceType type);
bool isAudio() const;
@@ -208,11 +213,12 @@
: mHasReturnedData(false), mOffset(offset), mTimeUs(0) {}
void ATSParser::SyncEvent::init(off64_t offset, const sp<MediaSource> &source,
- int64_t timeUs) {
+ int64_t timeUs, SourceType type) {
mHasReturnedData = true;
mOffset = offset;
mMediaSource = source;
mTimeUs = timeUs;
+ mType = type;
}
void ATSParser::SyncEvent::reset() {
@@ -1121,13 +1127,24 @@
int64_t timeUs;
if (accessUnit->meta()->findInt64("timeUs", &timeUs)) {
found = true;
- event->init(pesStartOffset, mSource, timeUs);
+ event->init(pesStartOffset, mSource, timeUs, getSourceType());
}
}
}
}
}
+ATSParser::SourceType ATSParser::Stream::getSourceType() {
+ if (isVideo()) {
+ return VIDEO;
+ } else if (isAudio()) {
+ return AUDIO;
+ } else if (isMeta()) {
+ return META;
+ }
+ return NUM_SOURCE_TYPES;
+}
+
sp<MediaSource> ATSParser::Stream::getSource(SourceType type) {
switch (type) {
case VIDEO:
@@ -1565,6 +1582,16 @@
return mPrograms.editItemAt(0)->PTSTimeDeltaEstablished();
}
+int64_t ATSParser::getFirstPTSTimeUs() {
+ for (size_t i = 0; i < mPrograms.size(); ++i) {
+ sp<ATSParser::Program> program = mPrograms.itemAt(i);
+ if (program->PTSTimeDeltaEstablished()) {
+ return (program->firstPTS() * 100) / 9;
+ }
+ }
+ return -1;
+}
+
__attribute__((no_sanitize("integer")))
void ATSParser::updatePCR(
unsigned /* PID */, uint64_t PCR, uint64_t byteOffsetFromStart) {
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 2b166f0..faae6c9 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -62,18 +62,26 @@
ALIGNED_VIDEO_DATA = 2,
};
+ enum SourceType {
+ VIDEO = 0,
+ AUDIO = 1,
+ META = 2,
+ NUM_SOURCE_TYPES = 3
+ };
+
// Event is used to signal sync point event at feedTSPacket().
struct SyncEvent {
explicit SyncEvent(off64_t offset);
void init(off64_t offset, const sp<MediaSource> &source,
- int64_t timeUs);
+ int64_t timeUs, SourceType type);
bool hasReturnedData() const { return mHasReturnedData; }
void reset();
off64_t getOffset() const { return mOffset; }
const sp<MediaSource> &getMediaSource() const { return mMediaSource; }
int64_t getTimeUs() const { return mTimeUs; }
+ SourceType getType() const { return mType; }
private:
bool mHasReturnedData;
@@ -87,6 +95,7 @@
sp<MediaSource> mMediaSource;
/* The timestamp of the sync frame. */
int64_t mTimeUs;
+ SourceType mType;
};
explicit ATSParser(uint32_t flags = 0);
@@ -107,17 +116,13 @@
void signalEOS(status_t finalResult);
- enum SourceType {
- VIDEO = 0,
- AUDIO = 1,
- META = 2,
- NUM_SOURCE_TYPES = 3
- };
sp<MediaSource> getSource(SourceType type);
bool hasSource(SourceType type) const;
bool PTSTimeDeltaEstablished();
+ int64_t getFirstPTSTimeUs();
+
enum {
// From ISO/IEC 13818-1: 2000 (E), Table 2-29
STREAMTYPE_RESERVED = 0x00,
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
index 4fcf7b5..548f44e 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.cpp
@@ -44,6 +44,7 @@
mEnabled(true),
mFormat(NULL),
mLastQueuedTimeUs(0),
+ mEstimatedBufferDurationUs(-1),
mEOSResult(OK),
mLatestEnqueuedMeta(NULL),
mLatestDequeuedMeta(NULL) {
@@ -309,6 +310,8 @@
mFormat = NULL;
mLatestEnqueuedMeta = NULL;
+
+ mEstimatedBufferDurationUs = -1;
}
void AnotherPacketSource::queueDiscontinuity(
@@ -431,6 +434,31 @@
return durationUs;
}
+int64_t AnotherPacketSource::getEstimatedBufferDurationUs() {
+ Mutex::Autolock autoLock(mLock);
+ if (mEstimatedBufferDurationUs >= 0) {
+ return mEstimatedBufferDurationUs;
+ }
+
+ SortedVector<int64_t> maxTimesUs;
+ List<sp<ABuffer> >::iterator it;
+ int64_t t1 = 0, t2 = 0;
+ for (it = mBuffers.begin(); it != mBuffers.end(); ++it) {
+ int64_t timeUs = 0;
+ const sp<ABuffer> &buffer = *it;
+ if (!buffer->meta()->findInt64("timeUs", &timeUs)) {
+ continue;
+ }
+ maxTimesUs.add(timeUs);
+ while (maxTimesUs.size() > 2) {
+ maxTimesUs.removeAt(0);
+ t1 = maxTimesUs.itemAt(0);
+ t2 = maxTimesUs.itemAt(1);
+ }
+ }
+ return mEstimatedBufferDurationUs = t2 - t1;
+}
+
status_t AnotherPacketSource::nextBufferTime(int64_t *timeUs) {
*timeUs = 0;
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index dd6849e..b0890d7 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -57,6 +57,9 @@
// presentation timestamps since the last discontinuity (if any).
int64_t getBufferedDurationUs(status_t *finalResult);
+ // Returns the difference between the two largest timestamps queued
+ int64_t getEstimatedBufferDurationUs();
+
status_t nextBufferTime(int64_t *timeUs);
void queueAccessUnit(const sp<ABuffer> &buffer);
@@ -113,6 +116,7 @@
bool mEnabled;
sp<MetaData> mFormat;
int64_t mLastQueuedTimeUs;
+ int64_t mEstimatedBufferDurationUs;
List<sp<ABuffer> > mBuffers;
status_t mEOSResult;
sp<AMessage> mLatestEnqueuedMeta;
diff --git a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
index 116a5bc..bde33dc 100644
--- a/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
+++ b/media/libstagefright/mpeg2ts/MPEG2TSExtractor.cpp
@@ -26,6 +26,7 @@
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/DataSource.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
@@ -40,6 +41,8 @@
namespace android {
static const size_t kTSPacketSize = 188;
+static const int kMaxDurationReadSize = 250000LL;
+static const int kMaxDurationRetry = 6;
struct MPEG2TSSource : public MediaSource {
MPEG2TSSource(
@@ -243,6 +246,8 @@
const sp<MetaData> meta = impl->getFormat();
meta->setInt64(kKeyDuration, durationUs);
impl->setFormat(meta);
+ } else {
+ estimateDurationsFromTimesUsAtEnd();
}
}
@@ -301,6 +306,106 @@
}
}
+status_t MPEG2TSExtractor::estimateDurationsFromTimesUsAtEnd() {
+ if (!(mDataSource->flags() & DataSource::kIsLocalFileSource)) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ off64_t size = 0;
+ status_t err = mDataSource->getSize(&size);
+ if (err != OK) {
+ return err;
+ }
+
+ uint8_t packet[kTSPacketSize];
+ const off64_t zero = 0;
+ off64_t offset = max(zero, size - kMaxDurationReadSize);
+ if (mDataSource->readAt(offset, &packet, 0) < 0) {
+ return ERROR_IO;
+ }
+
+ int retry = 0;
+ bool allDurationsFound = false;
+ int64_t timeAnchorUs = mParser->getFirstPTSTimeUs();
+ do {
+ int bytesRead = 0;
+ sp<ATSParser> parser = new ATSParser(ATSParser::TS_TIMESTAMPS_ARE_ABSOLUTE);
+ ATSParser::SyncEvent ev(0);
+ offset = max(zero, size - (kMaxDurationReadSize << retry));
+ offset = (offset / kTSPacketSize) * kTSPacketSize;
+ for (;;) {
+ if (bytesRead >= kMaxDurationReadSize << max(0, retry - 1)) {
+ break;
+ }
+
+ ssize_t n = mDataSource->readAt(offset, packet, kTSPacketSize);
+ if (n < 0) {
+ return n;
+ } else if (n < (ssize_t)kTSPacketSize) {
+ break;
+ }
+
+ offset += kTSPacketSize;
+ bytesRead += kTSPacketSize;
+ err = parser->feedTSPacket(packet, kTSPacketSize, &ev);
+ if (err != OK) {
+ return err;
+ }
+
+ if (ev.hasReturnedData()) {
+ int64_t durationUs = ev.getTimeUs();
+ ATSParser::SourceType type = ev.getType();
+ ev.reset();
+
+ int64_t firstTimeUs;
+ sp<AnotherPacketSource> src =
+ (AnotherPacketSource *)mParser->getSource(type).get();
+ if (src == NULL || src->nextBufferTime(&firstTimeUs) != OK) {
+ continue;
+ }
+ durationUs += src->getEstimatedBufferDurationUs();
+ durationUs -= timeAnchorUs;
+ durationUs -= firstTimeUs;
+ if (durationUs > 0) {
+ int64_t origDurationUs, lastDurationUs;
+ const sp<MetaData> meta = src->getFormat();
+ const uint32_t kKeyLastDuration = 'ldur';
+ // Require two consecutive duration calculations to be within 1 sec before
+ // updating; use MetaData to store previous duration estimate in per-stream
+ // context.
+ if (!meta->findInt64(kKeyDuration, &origDurationUs)
+ || !meta->findInt64(kKeyLastDuration, &lastDurationUs)
+ || (origDurationUs < durationUs
+ && abs(durationUs - lastDurationUs) < 60000000)) {
+ meta->setInt64(kKeyDuration, durationUs);
+ }
+ meta->setInt64(kKeyLastDuration, durationUs);
+ }
+ }
+ }
+
+ if (!allDurationsFound) {
+ allDurationsFound = true;
+ for (auto t: {ATSParser::VIDEO, ATSParser::AUDIO}) {
+ sp<AnotherPacketSource> src = (AnotherPacketSource *)mParser->getSource(t).get();
+ if (src == NULL) {
+ continue;
+ }
+ int64_t durationUs;
+ const sp<MetaData> meta = src->getFormat();
+ if (!meta->findInt64(kKeyDuration, &durationUs)) {
+ allDurationsFound = false;
+ break;
+ }
+ }
+ }
+
+ ++retry;
+ } while(!allDurationsFound && offset > 0 && retry <= kMaxDurationRetry);
+
+ return allDurationsFound? OK : ERROR_UNSUPPORTED;
+}
+
uint32_t MPEG2TSExtractor::flags() const {
return CAN_PAUSE | CAN_SEEK_BACKWARD | CAN_SEEK_FORWARD;
}
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index afc2d22..c3bf1f9 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -19,6 +19,7 @@
#define ANDROID_AUDIO_FLINGER_H
#include "Configuration.h"
+#include <deque>
#include <stdint.h>
#include <sys/types.h>
#include <limits.h>
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index 47cf7b1..d745121 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -25,10 +25,11 @@
// state changes or resource modifications. Always respect the following order
// if multiple mutexes must be acquired to avoid cross deadlock:
// AudioFlinger -> ThreadBase -> EffectChain -> EffectModule
+// AudioHandle -> ThreadBase -> EffectChain -> EffectModule
// In addition, methods that lock the AudioPolicyService mutex (getOutputForEffect(),
-// startOutput()...) should never be called with AudioFlinger or Threadbase mutex locked
-// to avoid cross deadlock with other clients calling AudioPolicyService methods that in turn
-// call AudioFlinger thus locking the same mutexes in the reverse order.
+// startOutput(), getInputForAttr(), releaseInput()...) should never be called with AudioFlinger or
+// Threadbase mutex locked to avoid cross deadlock with other clients calling AudioPolicyService
+// methods that in turn call AudioFlinger thus locking the same mutexes in the reverse order.
// The EffectModule class is a wrapper object controlling the effect engine implementation
// in the effect library. It prevents concurrent calls to process() and command() functions
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index bc774a3..9966eeb 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1623,6 +1623,7 @@
dumpInternals(fd, args);
dumpTracks(fd, args);
dumpEffectChains(fd, args);
+ mLocalLog.dump(fd, args, " " /* prefix */);
}
void AudioFlinger::PlaybackThread::dumpTracks(int fd, const Vector<String16>& args __unused)
@@ -2093,6 +2094,10 @@
chain->incActiveTrackCnt();
}
+ char buffer[256];
+ track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+ mLocalLog.log("addTrack_l (%p) %s", track.get(), buffer + 4); // log for analysis
+
status = NO_ERROR;
}
@@ -2118,6 +2123,11 @@
void AudioFlinger::PlaybackThread::removeTrack_l(const sp<Track>& track)
{
track->triggerEvents(AudioSystem::SYNC_EVENT_PRESENTATION_COMPLETE);
+
+ char buffer[256];
+ track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+ mLocalLog.log("removeTrack_l (%p) %s", track.get(), buffer + 4); // log for analysis
+
mTracks.remove(track);
deleteTrackName_l(track->name());
// redundant as track is about to be destroyed, for dumpsys only
@@ -3285,6 +3295,10 @@
}
if (track->isTerminated()) {
removeTrack_l(track);
+ } else { // inactive but not terminated
+ char buffer[256];
+ track->dump(buffer, ARRAY_SIZE(buffer), false /* active */);
+ mLocalLog.log("removeTracks_l(%p) %s", track.get(), buffer + 4);
}
}
}
@@ -3731,6 +3745,15 @@
FastMixerStateQueue *sq = mFastMixer->sq();
FastMixerState *state = sq->begin();
if (!(state->mCommand & FastMixerState::IDLE)) {
+ // Report any frames trapped in the Monopipe
+ MonoPipe *monoPipe = (MonoPipe *)mPipeSink.get();
+ const long long pipeFrames = monoPipe->maxFrames() - monoPipe->availableToWrite();
+ mLocalLog.log("threadLoop_standby: framesWritten:%lld suspendedFrames:%lld "
+ "monoPipeWritten:%lld monoPipeLeft:%lld",
+ (long long)mFramesWritten, (long long)mSuspendedFrames,
+ (long long)mPipeSink->framesWritten(), pipeFrames);
+ mLocalLog.log("threadLoop_standby: %s", mTimestamp.toString().c_str());
+
state->mCommand = FastMixerState::COLD_IDLE;
state->mColdFutexAddr = &mFastMixerFutex;
state->mColdGen++;
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index eb4ac26..d261ea5 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -864,6 +864,78 @@
uint32_t mScreenState; // cached copy of gScreenState
static const size_t kFastMixerLogSize = 4 * 1024;
sp<NBLog::Writer> mFastMixerNBLogWriter;
+
+ // Do not call from a sched_fifo thread as it uses a system time call
+ // and obtains a local mutex.
+ class LocalLog {
+ public:
+ void log(const char *fmt, ...) {
+ va_list val;
+ va_start(val, fmt);
+
+ // format to buffer
+ char buffer[512];
+ int length = vsnprintf(buffer, sizeof(buffer), fmt, val);
+ if (length >= (signed)sizeof(buffer)) {
+ length = sizeof(buffer) - 1;
+ }
+
+ // strip out trailing newline
+ while (length > 0 && buffer[length - 1] == '\n') {
+ buffer[--length] = 0;
+ }
+
+ // store in circular array
+ AutoMutex _l(mLock);
+ mLog.emplace_back(
+ std::make_pair(systemTime(SYSTEM_TIME_REALTIME), std::string(buffer)));
+ if (mLog.size() > kLogSize) {
+ mLog.pop_front();
+ }
+
+ va_end(val);
+ }
+
+ void dump(int fd, const Vector<String16>& args, const char *prefix = "") {
+ if (!AudioFlinger::dumpTryLock(mLock)) return; // a local lock, shouldn't happen
+ if (mLog.size() > 0) {
+ bool dumpAll = false;
+ for (const auto &arg : args) {
+ if (arg == String16("--locallog")) {
+ dumpAll = true;
+ }
+ }
+
+ dprintf(fd, "Local Log:\n");
+ auto it = mLog.begin();
+ if (!dumpAll && mLog.size() > kLogPrint) {
+ it += (mLog.size() - kLogPrint);
+ }
+ for (; it != mLog.end(); ++it) {
+ const int64_t ns = it->first;
+ const int ns_per_sec = 1000000000;
+ const time_t sec = ns / ns_per_sec;
+ struct tm tm;
+ localtime_r(&sec, &tm);
+
+ dprintf(fd, "%s%02d-%02d %02d:%02d:%02d.%03d %s\n",
+ prefix,
+ tm.tm_mon + 1, // localtime_r uses months in 0 - 11 range
+ tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ (int)(ns % ns_per_sec / 1000000),
+ it->second.c_str());
+ }
+ }
+ mLock.unlock();
+ }
+
+ private:
+ Mutex mLock;
+ static const size_t kLogSize = 256; // full history
+ static const size_t kLogPrint = 32; // default print history
+ std::deque<std::pair<int64_t, std::string>> mLog;
+ } mLocalLog;
+
public:
virtual bool hasFastMixer() const = 0;
virtual FastTrackUnderruns getFastTrackUnderruns(size_t fastIndex __unused) const
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 8f134c1..e8e27e4 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -459,7 +459,7 @@
/*static*/ void AudioFlinger::PlaybackThread::Track::appendDumpHeader(String8& result)
{
result.append(" Name Active Client Type Fmt Chn mask Session fCount S F SRate "
- "L dB R dB Server Main buf Aux Buf Flags UndFrmCnt\n");
+ "L dB R dB Server Main buf Aux buf Flags UndFrmCnt Flushed\n");
}
void AudioFlinger::PlaybackThread::Track::dump(char* buffer, size_t size, bool active)
@@ -526,7 +526,7 @@
break;
}
snprintf(&buffer[8], size-8, " %6s %6u %4u %08X %08X %7u %6zu %1c %1d %5u %5.2g %5.2g "
- "%08X %p %p 0x%03X %9u%c\n",
+ "%08X %08zX %08zX 0x%03X %9u%c %7u\n",
active ? "yes" : "no",
(mClient == 0) ? getpid_cached : mClient->pid(),
mStreamType,
@@ -540,11 +540,12 @@
20.0 * log10(float_from_gain(gain_minifloat_unpack_left(vlr))),
20.0 * log10(float_from_gain(gain_minifloat_unpack_right(vlr))),
mCblk->mServer,
- mMainBuffer,
- mAuxBuffer,
+ (size_t)mMainBuffer, // use %zX as %p appends 0x
+ (size_t)mAuxBuffer, // use %zX as %p appends 0x
mCblk->mFlags,
mAudioTrackServerProxy->getUnderrunFrames(),
- nowInUnderrun);
+ nowInUnderrun,
+ (unsigned)mAudioTrackServerProxy->framesFlushed() % 10000000); // 7 digits
}
uint32_t AudioFlinger::PlaybackThread::Track::sampleRate() const {
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
new file mode 100644
index 0000000..76a5c1c
--- /dev/null
+++ b/services/mediaanalytics/Android.mk
@@ -0,0 +1,32 @@
+# Media Statistics service
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ main_mediaanalytics.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libcutils \
+ liblog \
+ libmedia \
+ libmediaanalyticsservice \
+ libutils \
+ libbinder \
+ libicuuc
+
+LOCAL_STATIC_LIBRARIES := \
+ libicuandroid_utils \
+ libregistermsext
+
+LOCAL_C_INCLUDES := \
+ frameworks/av/media/libmediaanalyticsservice
+
+LOCAL_MODULE:= mediaanalytics
+
+LOCAL_INIT_RC := mediaanalytics.rc
+
+LOCAL_CFLAGS := -Werror -Wall
+
+include $(BUILD_EXECUTABLE)
diff --git a/services/mediaanalytics/main_mediaanalytics.cpp b/services/mediaanalytics/main_mediaanalytics.cpp
new file mode 100644
index 0000000..ba601ee
--- /dev/null
+++ b/services/mediaanalytics/main_mediaanalytics.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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_TAG "mediaanalytics"
+//#define LOG_NDEBUG 0
+
+#include <binder/IPCThreadState.h>
+#include <binder/ProcessState.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+//#include "RegisterExtensions.h"
+
+// from LOCAL_C_INCLUDES
+#include "IcuUtils.h"
+#include "MediaAnalyticsService.h"
+
+using namespace android;
+
+int main(int argc __unused, char **argv __unused)
+{
+ signal(SIGPIPE, SIG_IGN);
+
+ sp<ProcessState> proc(ProcessState::self());
+ sp<IServiceManager> sm(defaultServiceManager());
+ ALOGI("ServiceManager: %p", sm.get());
+
+ InitializeIcuOrDie();
+ MediaAnalyticsService::instantiate();
+ ProcessState::self()->startThreadPool();
+ IPCThreadState::self()->joinThreadPool();
+}
diff --git a/services/mediaanalytics/mediaanalytics.rc b/services/mediaanalytics/mediaanalytics.rc
new file mode 100644
index 0000000..0af69f5
--- /dev/null
+++ b/services/mediaanalytics/mediaanalytics.rc
@@ -0,0 +1,5 @@
+service mediaanalytics /system/bin/mediaanalytics
+ class main
+ user media
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks