Merge "[audiopolicy] fix -Wreorder-init-list"
diff --git a/drm/libmediadrm/DrmHal.cpp b/drm/libmediadrm/DrmHal.cpp
index bd4b521..37609eb 100644
--- a/drm/libmediadrm/DrmHal.cpp
+++ b/drm/libmediadrm/DrmHal.cpp
@@ -899,9 +899,8 @@
status_t DrmHal::provideKeyResponse(Vector<uint8_t> const &sessionId,
Vector<uint8_t> const &response, Vector<uint8_t> &keySetId) {
Mutex::Autolock autoLock(mLock);
- EventTimer<status_t> keyResponseTimer(&mMetrics.mProvideKeyResponseTimeUs);
-
INIT_CHECK();
+ EventTimer<status_t> keyResponseTimer(&mMetrics.mProvideKeyResponseTimeUs);
DrmSessionManager::Instance()->useSession(sessionId);
diff --git a/media/audioserver/audioserver.rc b/media/audioserver/audioserver.rc
index 3f3ef69..5484613 100644
--- a/media/audioserver/audioserver.rc
+++ b/media/audioserver/audioserver.rc
@@ -6,10 +6,10 @@
capabilities BLOCK_SUSPEND
ioprio rt 4
writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
- onrestart restart vendor.audio-hal-2-0
+ onrestart restart vendor.audio-hal
onrestart restart vendor.audio-hal-4-0-msd
- # Keep the original service name for backward compatibility when upgrading
- # O-MR1 devices with framework-only.
+ # Keep the original service names for backward compatibility
+ onrestart restart vendor.audio-hal-2-0
onrestart restart audio-hal-2-0
on property:vts.native_server.on=1
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 2ece474..6614e5e 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -653,7 +653,7 @@
// Should we block?
if (timeoutNanoseconds == 0) {
break; // don't block
- } else if (framesLeft > 0) {
+ } else if (wakeTimeNanos != 0) {
if (!mAudioEndpoint.isFreeRunning()) {
// If there is software on the other end of the FIFO then it may get delayed.
// So wake up just a little after we expect it to be ready.
@@ -712,37 +712,38 @@
aaudio_result_t AudioStreamInternal::setBufferSize(int32_t requestedFrames) {
int32_t adjustedFrames = requestedFrames;
- int32_t actualFrames = 0;
- int32_t maximumSize = getBufferCapacity();
+ const int32_t maximumSize = getBufferCapacity() - mFramesPerBurst;
+ // The buffer size can be set to zero.
+ // This means that the callback may be called when the internal buffer becomes empty.
+ // This will be fine on some devices in ideal circumstances and will result in the
+ // lowest possible latency.
+ // If there are glitches then they should be detected as XRuns and the size can be increased.
+ static const int32_t minimumSize = 0;
// Clip to minimum size so that rounding up will work better.
- if (adjustedFrames < 1) {
- adjustedFrames = 1;
- }
+ adjustedFrames = std::max(minimumSize, adjustedFrames);
- if (adjustedFrames > maximumSize) {
- // Clip to maximum size.
+ // Prevent arithmetic overflow by clipping before we round.
+ if (adjustedFrames >= maximumSize) {
adjustedFrames = maximumSize;
} else {
// Round to the next highest burst size.
int32_t numBursts = (adjustedFrames + mFramesPerBurst - 1) / mFramesPerBurst;
adjustedFrames = numBursts * mFramesPerBurst;
- // Rounding may have gone above maximum.
- if (adjustedFrames > maximumSize) {
- adjustedFrames = maximumSize;
- }
}
- aaudio_result_t result = mAudioEndpoint.setBufferSizeInFrames(adjustedFrames, &actualFrames);
- if (result < 0) {
- return result;
- } else {
- return (aaudio_result_t) actualFrames;
- }
+ // Clip against the actual size from the endpoint.
+ int32_t actualFrames = 0;
+ mAudioEndpoint.setBufferSizeInFrames(maximumSize, &actualFrames);
+ // actualFrames should be <= maximumSize
+ adjustedFrames = std::min(actualFrames, adjustedFrames);
+
+ mBufferSizeInFrames = adjustedFrames;
+ return (aaudio_result_t) adjustedFrames;
}
int32_t AudioStreamInternal::getBufferSize() const {
- return mAudioEndpoint.getBufferSizeInFrames();
+ return mBufferSizeInFrames;
}
int32_t AudioStreamInternal::getBufferCapacity() const {
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 9395416..596d37f 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -204,6 +204,9 @@
// Sometimes the hardware is operating with a different channel count from the app.
// Then we require conversion in AAudio.
int32_t mDeviceChannelCount = 0;
+
+ int32_t mBufferSizeInFrames = 0; // local threshold to control latency
+
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
index b8ef247..dc9f48c 100644
--- a/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternalPlay.cpp
@@ -167,8 +167,10 @@
ATRACE_INT("aaWrote", framesWritten);
}
+ // Sleep if there is too much data in the buffer.
// Calculate an ideal time to wake up.
- if (wakeTimePtr != nullptr && framesWritten >= 0) {
+ if (wakeTimePtr != nullptr
+ && (mAudioEndpoint.getFullFramesAvailable() >= getBufferSize())) {
// By default wake up a few milliseconds from now. // TODO review
int64_t wakeTime = currentNanoTime + (1 * AAUDIO_NANOS_PER_MILLISECOND);
aaudio_stream_state_t state = getState();
@@ -184,14 +186,10 @@
break;
case AAUDIO_STREAM_STATE_STARTED:
{
- // When do we expect the next read burst to occur?
-
- // Calculate frame position based off of the writeCounter because
- // the readCounter might have just advanced in the background,
- // causing us to sleep until a later burst.
- int64_t nextPosition = mAudioEndpoint.getDataWriteCounter() + mFramesPerBurst
- - mAudioEndpoint.getBufferSizeInFrames();
- wakeTime = mClockModel.convertPositionToTime(nextPosition);
+ // Sleep until the readCounter catches up and we only have
+ // the getBufferSize() frames of data sitting in the buffer.
+ int64_t nextReadPosition = mAudioEndpoint.getDataWriteCounter() - getBufferSize();
+ wakeTime = mClockModel.convertPositionToTime(nextReadPosition);
}
break;
default:
diff --git a/media/libaudiofoundation/Android.bp b/media/libaudiofoundation/Android.bp
index 10f3e67..e830e56 100644
--- a/media/libaudiofoundation/Android.bp
+++ b/media/libaudiofoundation/Android.bp
@@ -2,9 +2,19 @@
name: "libaudiofoundation_headers",
vendor_available: true,
export_include_dirs: ["include"],
+ header_libs: [
+ "libaudio_system_headers",
+ "libaudioclient_headers",
+ "libmedia_headers",
+ ],
+ export_header_lib_headers: [
+ "libaudio_system_headers",
+ "libaudioclient_headers",
+ "libmedia_headers",
+ ],
}
-cc_library_shared {
+cc_library {
name: "libaudiofoundation",
vendor_available: true,
@@ -26,12 +36,12 @@
],
header_libs: [
- "libaudio_system_headers",
- "libaudioclient_headers",
"libaudiofoundation_headers",
],
- export_header_lib_headers: ["libaudiofoundation_headers"],
+ export_header_lib_headers: [
+ "libaudiofoundation_headers",
+ ],
cflags: [
"-Werror",
diff --git a/media/libaudiofoundation/TEST_MAPPING b/media/libaudiofoundation/TEST_MAPPING
new file mode 100644
index 0000000..f6d249a
--- /dev/null
+++ b/media/libaudiofoundation/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "audiofoundation_parcelable_test"
+ }
+ ]
+}
diff --git a/media/libaudiofoundation/tests/Android.bp b/media/libaudiofoundation/tests/Android.bp
new file mode 100644
index 0000000..f258b14
--- /dev/null
+++ b/media/libaudiofoundation/tests/Android.bp
@@ -0,0 +1,25 @@
+cc_test {
+ name: "audiofoundation_parcelable_test",
+
+ shared_libs: [
+ "libaudiofoundation",
+ "libbinder",
+ "liblog",
+ "libutils",
+ ],
+
+ header_libs: [
+ "libaudio_system_headers",
+ ],
+
+ srcs: [
+ "audiofoundation_parcelable_test.cpp",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ test_suites: ["device-tests"],
+}
diff --git a/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp b/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp
new file mode 100644
index 0000000..5baa072
--- /dev/null
+++ b/media/libaudiofoundation/tests/audiofoundation_parcelable_test.cpp
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2019 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 "audiofoundation_parcelable_test"
+
+#include <gtest/gtest.h>
+
+#include <binder/IServiceManager.h>
+#include <binder/Parcelable.h>
+#include <binder/ProcessState.h>
+#include <media/AudioGain.h>
+#include <media/AudioPort.h>
+#include <media/AudioProfile.h>
+#include <media/DeviceDescriptorBase.h>
+#include <utils/Log.h>
+#include <utils/String16.h>
+
+namespace android {
+
+static const audio_port_config TEST_AUDIO_PORT_CONFIG = {
+ .id = 0,
+ .role = AUDIO_PORT_ROLE_SINK,
+ .type = AUDIO_PORT_TYPE_DEVICE,
+ .config_mask = AUDIO_PORT_CONFIG_SAMPLE_RATE | AUDIO_PORT_CONFIG_CHANNEL_MASK |
+ AUDIO_PORT_CONFIG_FORMAT | AUDIO_PORT_CONFIG_GAIN,
+ .sample_rate = 48000,
+ .channel_mask = AUDIO_CHANNEL_OUT_STEREO,
+ .format = AUDIO_FORMAT_PCM_16_BIT,
+ .gain = {
+ .index = 0,
+ .mode = AUDIO_GAIN_MODE_JOINT,
+ .channel_mask = AUDIO_CHANNEL_OUT_STEREO,
+ }
+};
+
+class AudioPortConfigTestStub : public AudioPortConfig {
+public:
+ sp<AudioPort> getAudioPort() const override { return nullptr; }
+};
+
+AudioGains getAudioGainsForTest() {
+ AudioGains audioGains;
+ sp<AudioGain> audioGain = new AudioGain(0 /*index*/, false /*useInChannelMask*/);
+ audioGain->setMode(AUDIO_GAIN_MODE_JOINT);
+ audioGain->setChannelMask(AUDIO_CHANNEL_OUT_STEREO);
+ audioGain->setMinValueInMb(-3200);
+ audioGain->setMaxValueInMb(600);
+ audioGain->setDefaultValueInMb(0);
+ audioGain->setStepValueInMb(100);
+ audioGain->setMinRampInMs(100);
+ audioGain->setMaxRampInMs(500);
+ audioGains.push_back(audioGain);
+ return audioGains;
+}
+
+AudioProfileVector getAudioProfileVectorForTest() {
+ AudioProfileVector audioProfiles;
+ sp<AudioProfile> audioProfile = AudioProfile::createFullDynamic();
+ audioProfile->setChannels({AUDIO_CHANNEL_OUT_MONO, AUDIO_CHANNEL_OUT_STEREO});
+ audioProfile->setSampleRates({48000});
+ audioProfiles.add(audioProfile);
+ return audioProfiles;
+}
+
+TEST(AudioFoundationParcelableTest, ParcelingAudioGain) {
+ Parcel data;
+ AudioGains audioGains = getAudioGainsForTest();
+
+ ASSERT_EQ(data.writeParcelable(audioGains), NO_ERROR);
+ data.setDataPosition(0);
+ AudioGains audioGainsFromParcel;
+ ASSERT_EQ(data.readParcelable(&audioGainsFromParcel), NO_ERROR);
+ ASSERT_TRUE(audioGainsFromParcel.equals(audioGains));
+}
+
+TEST(AudioFoundationParcelableTest, ParcelingAudioProfileVector) {
+ Parcel data;
+ AudioProfileVector audioProfiles = getAudioProfileVectorForTest();
+
+ ASSERT_EQ(data.writeParcelable(audioProfiles), NO_ERROR);
+ data.setDataPosition(0);
+ AudioProfileVector audioProfilesFromParcel;
+ ASSERT_EQ(data.readParcelable(&audioProfilesFromParcel), NO_ERROR);
+ ASSERT_TRUE(audioProfilesFromParcel.equals(audioProfiles));
+}
+
+TEST(AudioFoundationParcelableTest, ParcelingAudioPort) {
+ Parcel data;
+ sp<AudioPort> audioPort = new AudioPort(
+ "AudioPortName", AUDIO_PORT_TYPE_DEVICE, AUDIO_PORT_ROLE_SINK);
+ audioPort->setGains(getAudioGainsForTest());
+ audioPort->setAudioProfiles(getAudioProfileVectorForTest());
+
+ ASSERT_EQ(data.writeParcelable(*audioPort), NO_ERROR);
+ data.setDataPosition(0);
+ sp<AudioPort> audioPortFromParcel = new AudioPort(
+ "", AUDIO_PORT_TYPE_NONE, AUDIO_PORT_ROLE_NONE);
+ ASSERT_EQ(data.readParcelable(audioPortFromParcel.get()), NO_ERROR);
+ ASSERT_TRUE(audioPortFromParcel->equals(audioPort));
+}
+
+TEST(AudioFoundationParcelableTest, ParcelingAudioPortConfig) {
+ Parcel data;
+ sp<AudioPortConfig> audioPortConfig = new AudioPortConfigTestStub();
+ audioPortConfig->applyAudioPortConfig(&TEST_AUDIO_PORT_CONFIG);
+
+ ASSERT_EQ(data.writeParcelable(*audioPortConfig), NO_ERROR);
+ data.setDataPosition(0);
+ sp<AudioPortConfig> audioPortConfigFromParcel = new AudioPortConfigTestStub();
+ ASSERT_EQ(data.readParcelable(audioPortConfigFromParcel.get()), NO_ERROR);
+ ASSERT_TRUE(audioPortConfigFromParcel->equals(audioPortConfig));
+}
+
+TEST(AudioFoundationParcelableTest, ParcelingDeviceDescriptorBase) {
+ Parcel data;
+ sp<DeviceDescriptorBase> desc = new DeviceDescriptorBase(AUDIO_DEVICE_OUT_SPEAKER);
+ desc->setGains(getAudioGainsForTest());
+ desc->setAudioProfiles(getAudioProfileVectorForTest());
+ desc->applyAudioPortConfig(&TEST_AUDIO_PORT_CONFIG);
+ desc->setAddress("DeviceDescriptorBaseTestAddress");
+
+ ASSERT_EQ(data.writeParcelable(*desc), NO_ERROR);
+ data.setDataPosition(0);
+ sp<DeviceDescriptorBase> descFromParcel = new DeviceDescriptorBase(AUDIO_DEVICE_NONE);
+ ASSERT_EQ(data.readParcelable(descFromParcel.get()), NO_ERROR);
+ ASSERT_TRUE(descFromParcel->equals(desc));
+}
+
+} // namespace android
diff --git a/media/libaudiohal/Android.bp b/media/libaudiohal/Android.bp
index 5837fcf..74b48f3 100644
--- a/media/libaudiohal/Android.bp
+++ b/media/libaudiohal/Android.bp
@@ -16,6 +16,7 @@
"libaudiohal@2.0",
"libaudiohal@4.0",
"libaudiohal@5.0",
+ "libaudiohal@6.0",
"libutils",
],
diff --git a/media/libaudiohal/impl/Android.bp b/media/libaudiohal/impl/Android.bp
index a23d945..8669e2a 100644
--- a/media/libaudiohal/impl/Android.bp
+++ b/media/libaudiohal/impl/Android.bp
@@ -99,3 +99,20 @@
"-include common/all-versions/VersionMacro.h",
]
}
+
+cc_library_shared {
+ name: "libaudiohal@6.0",
+ defaults: ["libaudiohal_default"],
+ shared_libs: [
+ "android.hardware.audio.common@6.0",
+ "android.hardware.audio.common@6.0-util",
+ "android.hardware.audio.effect@6.0",
+ "android.hardware.audio@6.0",
+ ],
+ cflags: [
+ "-DMAJOR_VERSION=6",
+ "-DMINOR_VERSION=0",
+ "-include common/all-versions/VersionMacro.h",
+ ]
+}
+
diff --git a/media/libaudiohal/impl/include/libaudiohal/FactoryHalHidl.h b/media/libaudiohal/impl/include/libaudiohal/FactoryHalHidl.h
index 829f99c..271bafc 100644
--- a/media/libaudiohal/impl/include/libaudiohal/FactoryHalHidl.h
+++ b/media/libaudiohal/impl/include/libaudiohal/FactoryHalHidl.h
@@ -34,6 +34,7 @@
* the preferred available impl.
*/
enum class AudioHALVersion {
+ V6_0,
V5_0,
V4_0,
V2_0,
diff --git a/media/libmedia/Android.bp b/media/libmedia/Android.bp
index 4e99cb2..fdab14a 100644
--- a/media/libmedia/Android.bp
+++ b/media/libmedia/Android.bp
@@ -286,7 +286,6 @@
"IMediaMetadataRetriever.cpp",
"mediametadataretriever.cpp",
"MidiDeviceInfo.cpp",
- "JetPlayer.cpp",
"MediaScanner.cpp",
"MediaScannerClient.cpp",
"CharacterEncodingDetector.cpp",
@@ -346,7 +345,6 @@
static_libs: [
"libc_malloc_debug_backtrace", // for memory heap analysis
- "libmedia_midiiowrapper",
],
export_include_dirs: [
diff --git a/media/libmedia/JetPlayer.cpp b/media/libmedia/JetPlayer.cpp
deleted file mode 100644
index 0d3c1ba..0000000
--- a/media/libmedia/JetPlayer.cpp
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright (C) 2008 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 "JetPlayer-C"
-
-#include <utils/Log.h>
-#include <media/JetPlayer.h>
-
-
-namespace android
-{
-
-static const int MIX_NUM_BUFFERS = 4;
-static const S_EAS_LIB_CONFIG* pLibConfig = NULL;
-
-//-------------------------------------------------------------------------------------------------
-JetPlayer::JetPlayer(void *javaJetPlayer, int maxTracks, int trackBufferSize) :
- mEventCallback(NULL),
- mJavaJetPlayerRef(javaJetPlayer),
- mTid(-1),
- mRender(false),
- mPaused(false),
- mMaxTracks(maxTracks),
- mEasData(NULL),
- mIoWrapper(NULL),
- mTrackBufferSize(trackBufferSize)
-{
- ALOGV("JetPlayer constructor");
- mPreviousJetStatus.currentUserID = -1;
- mPreviousJetStatus.segmentRepeatCount = -1;
- mPreviousJetStatus.numQueuedSegments = -1;
- mPreviousJetStatus.paused = true;
-}
-
-//-------------------------------------------------------------------------------------------------
-JetPlayer::~JetPlayer()
-{
- ALOGV("~JetPlayer");
- release();
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::init()
-{
- //Mutex::Autolock lock(&mMutex);
-
- EAS_RESULT result;
-
- // retrieve the EAS library settings
- if (pLibConfig == NULL)
- pLibConfig = EAS_Config();
- if (pLibConfig == NULL) {
- ALOGE("JetPlayer::init(): EAS library configuration could not be retrieved, aborting.");
- return EAS_FAILURE;
- }
-
- // init the EAS library
- result = EAS_Init(&mEasData);
- if (result != EAS_SUCCESS) {
- ALOGE("JetPlayer::init(): Error initializing Sonivox EAS library, aborting.");
- mState = EAS_STATE_ERROR;
- return result;
- }
- // init the JET library with the default app event controller range
- result = JET_Init(mEasData, NULL, sizeof(S_JET_CONFIG));
- if (result != EAS_SUCCESS) {
- ALOGE("JetPlayer::init(): Error initializing JET library, aborting.");
- mState = EAS_STATE_ERROR;
- return result;
- }
-
- // create the output AudioTrack
- mAudioTrack = new AudioTrack();
- status_t status = mAudioTrack->set(AUDIO_STREAM_MUSIC, //TODO parameterize this
- pLibConfig->sampleRate,
- AUDIO_FORMAT_PCM_16_BIT,
- audio_channel_out_mask_from_count(pLibConfig->numChannels),
- (size_t) mTrackBufferSize,
- AUDIO_OUTPUT_FLAG_NONE);
- if (status != OK) {
- ALOGE("JetPlayer::init(): Error initializing JET library; AudioTrack error %d", status);
- mAudioTrack.clear();
- mState = EAS_STATE_ERROR;
- return EAS_FAILURE;
- }
-
- // create render and playback thread
- {
- Mutex::Autolock l(mMutex);
- ALOGV("JetPlayer::init(): trying to start render thread");
- mThread = new JetPlayerThread(this);
- mThread->run("jetRenderThread", ANDROID_PRIORITY_AUDIO);
- mCondition.wait(mMutex);
- }
- if (mTid > 0) {
- // render thread started, we're ready
- ALOGV("JetPlayer::init(): render thread(%d) successfully started.", mTid);
- mState = EAS_STATE_READY;
- } else {
- ALOGE("JetPlayer::init(): failed to start render thread.");
- mState = EAS_STATE_ERROR;
- return EAS_FAILURE;
- }
-
- return EAS_SUCCESS;
-}
-
-void JetPlayer::setEventCallback(jetevent_callback eventCallback)
-{
- Mutex::Autolock l(mMutex);
- mEventCallback = eventCallback;
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::release()
-{
- ALOGV("JetPlayer::release()");
- Mutex::Autolock lock(mMutex);
- mPaused = true;
- mRender = false;
- if (mEasData) {
- JET_Pause(mEasData);
- JET_CloseFile(mEasData);
- JET_Shutdown(mEasData);
- EAS_Shutdown(mEasData);
- }
- delete mIoWrapper;
- mIoWrapper = NULL;
- if (mAudioTrack != 0) {
- mAudioTrack->stop();
- mAudioTrack->flush();
- mAudioTrack.clear();
- }
- if (mAudioBuffer) {
- delete mAudioBuffer;
- mAudioBuffer = NULL;
- }
- mEasData = NULL;
-
- return EAS_SUCCESS;
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::render() {
- EAS_RESULT result = EAS_FAILURE;
- EAS_I32 count;
- int temp;
- bool audioStarted = false;
-
- ALOGV("JetPlayer::render(): entering");
-
- // allocate render buffer
- mAudioBuffer =
- new EAS_PCM[pLibConfig->mixBufferSize * pLibConfig->numChannels * MIX_NUM_BUFFERS];
-
- // signal main thread that we started
- {
- Mutex::Autolock l(mMutex);
- mTid = gettid();
- ALOGV("JetPlayer::render(): render thread(%d) signal", mTid);
- mCondition.signal();
- }
-
- while (1) {
-
- mMutex.lock(); // [[[[[[[[ LOCK ---------------------------------------
-
- if (mEasData == NULL) {
- mMutex.unlock();
- ALOGV("JetPlayer::render(): NULL EAS data, exiting render.");
- goto threadExit;
- }
-
- // nothing to render, wait for client thread to wake us up
- while (!mRender)
- {
- ALOGV("JetPlayer::render(): signal wait");
- if (audioStarted) {
- mAudioTrack->pause();
- // we have to restart the playback once we start rendering again
- audioStarted = false;
- }
- mCondition.wait(mMutex);
- ALOGV("JetPlayer::render(): signal rx'd");
- }
-
- // render midi data into the input buffer
- int num_output = 0;
- EAS_PCM* p = mAudioBuffer;
- for (int i = 0; i < MIX_NUM_BUFFERS; i++) {
- result = EAS_Render(mEasData, p, pLibConfig->mixBufferSize, &count);
- if (result != EAS_SUCCESS) {
- ALOGE("JetPlayer::render(): EAS_Render returned error %ld", result);
- }
- p += count * pLibConfig->numChannels;
- num_output += count * pLibConfig->numChannels * sizeof(EAS_PCM);
-
- // send events that were generated (if any) to the event callback
- fireEventsFromJetQueue();
- }
-
- // update playback state
- //ALOGV("JetPlayer::render(): updating state");
- JET_Status(mEasData, &mJetStatus);
- fireUpdateOnStatusChange();
- mPaused = mJetStatus.paused;
-
- mMutex.unlock(); // UNLOCK ]]]]]]]] -----------------------------------
-
- // check audio output track
- if (mAudioTrack == NULL) {
- ALOGE("JetPlayer::render(): output AudioTrack was not created");
- goto threadExit;
- }
-
- // Write data to the audio hardware
- //ALOGV("JetPlayer::render(): writing to audio output");
- if ((temp = mAudioTrack->write(mAudioBuffer, num_output)) < 0) {
- ALOGE("JetPlayer::render(): Error in writing:%d",temp);
- return temp;
- }
-
- // start audio output if necessary
- if (!audioStarted) {
- ALOGV("JetPlayer::render(): starting audio playback");
- mAudioTrack->start();
- audioStarted = true;
- }
-
- }//while (1)
-
-threadExit:
- if (mAudioTrack != NULL) {
- mAudioTrack->stop();
- mAudioTrack->flush();
- }
- delete [] mAudioBuffer;
- mAudioBuffer = NULL;
- mMutex.lock();
- mTid = -1;
- mCondition.signal();
- mMutex.unlock();
- return result;
-}
-
-
-//-------------------------------------------------------------------------------------------------
-// fire up an update if any of the status fields has changed
-// precondition: mMutex locked
-void JetPlayer::fireUpdateOnStatusChange()
-{
- if ( (mJetStatus.currentUserID != mPreviousJetStatus.currentUserID)
- ||(mJetStatus.segmentRepeatCount != mPreviousJetStatus.segmentRepeatCount) ) {
- if (mEventCallback) {
- mEventCallback(
- JetPlayer::JET_USERID_UPDATE,
- mJetStatus.currentUserID,
- mJetStatus.segmentRepeatCount,
- mJavaJetPlayerRef);
- }
- mPreviousJetStatus.currentUserID = mJetStatus.currentUserID;
- mPreviousJetStatus.segmentRepeatCount = mJetStatus.segmentRepeatCount;
- }
-
- if (mJetStatus.numQueuedSegments != mPreviousJetStatus.numQueuedSegments) {
- if (mEventCallback) {
- mEventCallback(
- JetPlayer::JET_NUMQUEUEDSEGMENT_UPDATE,
- mJetStatus.numQueuedSegments,
- -1,
- mJavaJetPlayerRef);
- }
- mPreviousJetStatus.numQueuedSegments = mJetStatus.numQueuedSegments;
- }
-
- if (mJetStatus.paused != mPreviousJetStatus.paused) {
- if (mEventCallback) {
- mEventCallback(JetPlayer::JET_PAUSE_UPDATE,
- mJetStatus.paused,
- -1,
- mJavaJetPlayerRef);
- }
- mPreviousJetStatus.paused = mJetStatus.paused;
- }
-
-}
-
-
-//-------------------------------------------------------------------------------------------------
-// fire up all the JET events in the JET engine queue (until the queue is empty)
-// precondition: mMutex locked
-void JetPlayer::fireEventsFromJetQueue()
-{
- if (!mEventCallback) {
- // no callback, just empty the event queue
- while (JET_GetEvent(mEasData, NULL, NULL)) { }
- return;
- }
-
- EAS_U32 rawEvent;
- while (JET_GetEvent(mEasData, &rawEvent, NULL)) {
- mEventCallback(
- JetPlayer::JET_EVENT,
- rawEvent,
- -1,
- mJavaJetPlayerRef);
- }
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::loadFromFile(const char* path)
-{
- ALOGV("JetPlayer::loadFromFile(): path=%s", path);
-
- Mutex::Autolock lock(mMutex);
-
- delete mIoWrapper;
- mIoWrapper = new MidiIoWrapper(path);
-
- EAS_RESULT result = JET_OpenFile(mEasData, mIoWrapper->getLocator());
- if (result != EAS_SUCCESS)
- mState = EAS_STATE_ERROR;
- else
- mState = EAS_STATE_OPEN;
- return( result );
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::loadFromFD(const int fd, const long long offset, const long long length)
-{
- ALOGV("JetPlayer::loadFromFD(): fd=%d offset=%lld length=%lld", fd, offset, length);
-
- Mutex::Autolock lock(mMutex);
-
- delete mIoWrapper;
- mIoWrapper = new MidiIoWrapper(fd, offset, length);
-
- EAS_RESULT result = JET_OpenFile(mEasData, mIoWrapper->getLocator());
- if (result != EAS_SUCCESS)
- mState = EAS_STATE_ERROR;
- else
- mState = EAS_STATE_OPEN;
- return( result );
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::closeFile()
-{
- Mutex::Autolock lock(mMutex);
- return JET_CloseFile(mEasData);
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::play()
-{
- ALOGV("JetPlayer::play(): entering");
- Mutex::Autolock lock(mMutex);
-
- EAS_RESULT result = JET_Play(mEasData);
-
- mPaused = false;
- mRender = true;
-
- JET_Status(mEasData, &mJetStatus);
- this->dumpJetStatus(&mJetStatus);
-
- fireUpdateOnStatusChange();
-
- // wake up render thread
- ALOGV("JetPlayer::play(): wakeup render thread");
- mCondition.signal();
-
- return result;
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::pause()
-{
- Mutex::Autolock lock(mMutex);
- mPaused = true;
- EAS_RESULT result = JET_Pause(mEasData);
-
- mRender = false;
-
- JET_Status(mEasData, &mJetStatus);
- this->dumpJetStatus(&mJetStatus);
- fireUpdateOnStatusChange();
-
-
- return result;
-}
-
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::queueSegment(int segmentNum, int libNum, int repeatCount, int transpose,
- EAS_U32 muteFlags, EAS_U8 userID)
-{
- ALOGV("JetPlayer::queueSegment segmentNum=%d, libNum=%d, repeatCount=%d, transpose=%d",
- segmentNum, libNum, repeatCount, transpose);
- Mutex::Autolock lock(mMutex);
- return JET_QueueSegment(mEasData, segmentNum, libNum, repeatCount, transpose, muteFlags,
- userID);
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::setMuteFlags(EAS_U32 muteFlags, bool sync)
-{
- Mutex::Autolock lock(mMutex);
- return JET_SetMuteFlags(mEasData, muteFlags, sync);
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::setMuteFlag(int trackNum, bool muteFlag, bool sync)
-{
- Mutex::Autolock lock(mMutex);
- return JET_SetMuteFlag(mEasData, trackNum, muteFlag, sync);
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::triggerClip(int clipId)
-{
- ALOGV("JetPlayer::triggerClip clipId=%d", clipId);
- Mutex::Autolock lock(mMutex);
- return JET_TriggerClip(mEasData, clipId);
-}
-
-//-------------------------------------------------------------------------------------------------
-int JetPlayer::clearQueue()
-{
- ALOGV("JetPlayer::clearQueue");
- Mutex::Autolock lock(mMutex);
- return JET_Clear_Queue(mEasData);
-}
-
-//-------------------------------------------------------------------------------------------------
-void JetPlayer::dump()
-{
-}
-
-void JetPlayer::dumpJetStatus(S_JET_STATUS* pJetStatus)
-{
- if (pJetStatus!=NULL)
- ALOGV(">> current JET player status: userID=%d segmentRepeatCount=%d numQueuedSegments=%d "
- "paused=%d",
- pJetStatus->currentUserID, pJetStatus->segmentRepeatCount,
- pJetStatus->numQueuedSegments, pJetStatus->paused);
- else
- ALOGE(">> JET player status is NULL");
-}
-
-
-} // end namespace android
diff --git a/media/libmedia/include/media/JetPlayer.h b/media/libmedia/include/media/JetPlayer.h
deleted file mode 100644
index bb569bc..0000000
--- a/media/libmedia/include/media/JetPlayer.h
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2008 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 JETPLAYER_H_
-#define JETPLAYER_H_
-
-#include <utils/threads.h>
-
-#include <libsonivox/jet.h>
-#include <libsonivox/eas_types.h>
-#include <media/AudioTrack.h>
-#include <media/MidiIoWrapper.h>
-
-
-namespace android {
-
-typedef void (*jetevent_callback)(int eventType, int val1, int val2, void *cookie);
-
-class JetPlayer {
-
-public:
-
- // to keep in sync with the JetPlayer class constants
- // defined in frameworks/base/media/java/android/media/JetPlayer.java
- static const int JET_EVENT = 1;
- static const int JET_USERID_UPDATE = 2;
- static const int JET_NUMQUEUEDSEGMENT_UPDATE = 3;
- static const int JET_PAUSE_UPDATE = 4;
-
- JetPlayer(void *javaJetPlayer,
- int maxTracks = 32,
- int trackBufferSize = 1200);
- ~JetPlayer();
- int init();
- int release();
-
- int loadFromFile(const char* url);
- int loadFromFD(const int fd, const long long offset, const long long length);
- int closeFile();
- int play();
- int pause();
- int queueSegment(int segmentNum, int libNum, int repeatCount, int transpose,
- EAS_U32 muteFlags, EAS_U8 userID);
- int setMuteFlags(EAS_U32 muteFlags, bool sync);
- int setMuteFlag(int trackNum, bool muteFlag, bool sync);
- int triggerClip(int clipId);
- int clearQueue();
-
- void setEventCallback(jetevent_callback callback);
-
- int getMaxTracks() { return mMaxTracks; };
-
-
-private:
- int render();
- void fireUpdateOnStatusChange();
- void fireEventsFromJetQueue();
-
- JetPlayer() {} // no default constructor
- void dump();
- void dumpJetStatus(S_JET_STATUS* pJetStatus);
-
- jetevent_callback mEventCallback;
-
- void* mJavaJetPlayerRef;
- Mutex mMutex; // mutex to sync the render and playback thread with the JET calls
- pid_t mTid;
- Condition mCondition;
- volatile bool mRender;
- bool mPaused;
-
- EAS_STATE mState;
- int* mMemFailedVar;
-
- int mMaxTracks; // max number of MIDI tracks, usually 32
- EAS_DATA_HANDLE mEasData;
- MidiIoWrapper* mIoWrapper;
- EAS_PCM* mAudioBuffer;// EAS renders the MIDI data into this buffer,
- sp<AudioTrack> mAudioTrack; // and we play it in this audio track
- int mTrackBufferSize;
- S_JET_STATUS mJetStatus;
- S_JET_STATUS mPreviousJetStatus;
-
- class JetPlayerThread : public Thread {
- public:
- JetPlayerThread(JetPlayer *player) : mPlayer(player) {
- }
-
- protected:
- virtual ~JetPlayerThread() {}
-
- private:
- JetPlayer *mPlayer;
-
- bool threadLoop() {
- int result;
- result = mPlayer->render();
- return false;
- }
-
- JetPlayerThread(const JetPlayerThread &);
- JetPlayerThread &operator=(const JetPlayerThread &);
- };
-
- sp<JetPlayerThread> mThread;
-
-}; // end class JetPlayer
-
-} // end namespace android
-
-
-
-#endif /*JETPLAYER_H_*/
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
index b7856a6..c8713bf 100644
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ b/media/libmediametrics/MediaAnalyticsItem.cpp
@@ -234,10 +234,6 @@
return *this;
}
-MediaAnalyticsItem::Key MediaAnalyticsItem::getKey() {
- return mKey;
-}
-
// number of attributes we have in this record
int32_t MediaAnalyticsItem::count() const {
return mPropCount;
@@ -795,11 +791,11 @@
return strdup(val.c_str());
}
-std::string MediaAnalyticsItem::toString() {
+std::string MediaAnalyticsItem::toString() const {
return toString(PROTO_LAST);
}
-std::string MediaAnalyticsItem::toString(int version) {
+std::string MediaAnalyticsItem::toString(int version) const {
// v0 : released with 'o'
// v1 : bug fix (missing pid/finalized separator),
diff --git a/media/libmediametrics/include/IMediaAnalyticsService.h b/media/libmediametrics/include/IMediaAnalyticsService.h
index f635e94..f2e7710 100644
--- a/media/libmediametrics/include/IMediaAnalyticsService.h
+++ b/media/libmediametrics/include/IMediaAnalyticsService.h
@@ -39,19 +39,25 @@
public:
DECLARE_META_INTERFACE(MediaAnalyticsService);
- // generate a unique sessionID to use across multiple requests
- // 'unique' is within this device, since last reboot
+ /**
+ * Returns 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.
- // caller continues to own the passed item
+ /**
+ * Submits the indicated record to the mediaanalytics service, where
+ * it will be merged (if appropriate) with incomplete records that
+ * share the same key and sessionID.
+ *
+ * \param item the item to submit.
+ * \param forcenew marks any matching incomplete record as complete before
+ * inserting this new record.
+ *
+ * \return the sessionID associated with that item or
+ * MediaAnalyticsItem::SessionIDInvalid on failure.
+ */
virtual MediaAnalyticsItem::SessionID_t submit(MediaAnalyticsItem *item, bool forcenew) = 0;
-
};
// ----------------------------------------------------------------------------
@@ -59,10 +65,10 @@
class BnMediaAnalyticsService: public BnInterface<IMediaAnalyticsService>
{
public:
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
+ status_t onTransact(uint32_t code,
+ const Parcel& data,
+ Parcel* reply,
+ uint32_t flags = 0) override;
};
}; // namespace android
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index 42a2f5b..3024c84 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -119,7 +119,7 @@
// set the key discriminator for the record.
// most often initialized as part of the constructor
MediaAnalyticsItem &setKey(MediaAnalyticsItem::Key);
- MediaAnalyticsItem::Key getKey();
+ const MediaAnalyticsItem::Key& getKey() const { return mKey; }
// # of attributes in the record
int32_t count() const;
@@ -191,8 +191,8 @@
// supports the stable interface
bool dumpAttributes(char **pbuffer, size_t *plength);
- std::string toString();
- std::string toString(int version);
+ std::string toString() const;
+ std::string toString(int version) const;
const char *toCString();
const char *toCString(int version);
diff --git a/media/tests/benchmark/MediaBenchmarkTest/Android.bp b/media/tests/benchmark/MediaBenchmarkTest/Android.bp
index 831944b..91b03f1 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/Android.bp
+++ b/media/tests/benchmark/MediaBenchmarkTest/Android.bp
@@ -43,4 +43,8 @@
srcs: ["src/main/**/*.java"],
sdk_version: "system_current",
+
+ static_libs: [
+ "androidx.test.core",
+ ],
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/res/values/strings.xml b/media/tests/benchmark/MediaBenchmarkTest/res/values/strings.xml
index b6ac7b5..24dbccc 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/res/values/strings.xml
+++ b/media/tests/benchmark/MediaBenchmarkTest/res/values/strings.xml
@@ -1,3 +1,4 @@
<resources>
<string name="input_file_path">/data/local/tmp/MediaBenchmark/res/</string>
-</resources>
\ No newline at end of file
+ <string name="output_file_path">/data/local/tmp/MediaBenchmark/output/</string>
+</resources>
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java
new file mode 100644
index 0000000..be2633d
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.media.benchmark.tests;
+
+import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.media.benchmark.R;
+import com.android.media.benchmark.library.CodecUtils;
+import com.android.media.benchmark.library.Decoder;
+import com.android.media.benchmark.library.Extractor;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public class DecoderTest {
+ private static final Context mContext =
+ InstrumentationRegistry.getInstrumentation().getTargetContext();
+ private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
+ private static final String mOutputFilePath = mContext.getString(R.string.output_file_path);
+ private static final String TAG = "DecoderTest";
+ private static final long PER_TEST_TIMEOUT_MS = 60000;
+ private static final boolean DEBUG = false;
+ private static final boolean WRITE_OUTPUT = false;
+ private String mInputFile;
+ private boolean mAsyncMode;
+
+ public DecoderTest(String inputFile, boolean asyncMode) {
+ this.mInputFile = inputFile;
+ this.mAsyncMode = asyncMode;
+ }
+
+ @Parameterized.Parameters
+ public static Collection<Object[]> input() {
+ return Arrays.asList(new Object[][]{
+ //Audio Sync Test
+ {"bbb_44100hz_2ch_128kbps_aac_30sec.mp4", false},
+ {"bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", false},
+ {"bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", false},
+ {"bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", false},
+ {"bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", false},
+ {"bbb_44100hz_2ch_600kbps_flac_30sec.mp4", false},
+ {"bbb_48000hz_2ch_100kbps_opus_30sec.webm", false},
+ // Audio Async Test
+ {"bbb_44100hz_2ch_128kbps_aac_30sec.mp4", true},
+ {"bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", true},
+ {"bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", true},
+ {"bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", true},
+ {"bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", true},
+ {"bbb_44100hz_2ch_600kbps_flac_30sec.mp4", true},
+ {"bbb_48000hz_2ch_100kbps_opus_30sec.webm", true},
+ // Video Sync Test
+ {"crowd_1920x1080_25fps_4000kbps_vp9.webm", false},
+ {"crowd_1920x1080_25fps_4000kbps_vp8.webm", false},
+ {"crowd_1920x1080_25fps_4000kbps_av1.webm", false},
+ {"crowd_1920x1080_25fps_7300kbps_mpeg2.mp4", false},
+ {"crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", false},
+ {"crowd_352x288_25fps_6000kbps_h263.3gp", false},
+ {"crowd_1920x1080_25fps_6700kbps_h264.ts", false},
+ {"crowd_1920x1080_25fps_4000kbps_h265.mkv", false},
+ // Video Async Test
+ {"crowd_1920x1080_25fps_4000kbps_vp9.webm", true},
+ {"crowd_1920x1080_25fps_4000kbps_vp8.webm", true},
+ {"crowd_1920x1080_25fps_4000kbps_av1.webm", true},
+ {"crowd_1920x1080_25fps_7300kbps_mpeg2.mp4", true},
+ {"crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", true},
+ {"crowd_352x288_25fps_6000kbps_h263.3gp", true},
+ {"crowd_1920x1080_25fps_6700kbps_h264.ts", true},
+ {"crowd_1920x1080_25fps_4000kbps_h265.mkv", true}});
+ }
+
+ @Test(timeout = PER_TEST_TIMEOUT_MS)
+ public void testDecoder() throws IOException {
+ File inputFile = new File(mInputFilePath + mInputFile);
+ if (inputFile.exists()) {
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
+ if (trackCount <= 0) {
+ Log.e(TAG, "Extraction failed. No tracks for file: " + mInputFile);
+ return;
+ }
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, false);
+ if (mediaCodecs.size() <= 0) {
+ Log.e(TAG,
+ "No suitable codecs found for file: " + mInputFile
+ + " track : " + currentTrack + " mime: " + mime);
+ continue;
+ }
+ // Get samples from extractor
+ int sampleSize;
+ do {
+ sampleSize = extractor.getFrameSample();
+ MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
+ MediaCodec.BufferInfo info = extractor.getBufferInfo();
+ ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
+ dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
+ bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
+ inputBuffer.add(dataBuffer);
+ frameInfo.add(bufInfo);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = "
+ + bufInfo.presentationTimeUs + " size = " + bufInfo.size);
+ }
+ } while (sampleSize > 0);
+ for (String codecName : mediaCodecs) {
+ FileOutputStream decodeOutputStream = null;
+ if (WRITE_OUTPUT) {
+ if (!Paths.get(mOutputFilePath).toFile().exists()) {
+ Files.createDirectories(Paths.get(mOutputFilePath));
+ }
+ File outFile = new File(mOutputFilePath + "decoder.out");
+ if (outFile.exists()) {
+ if (!outFile.delete()) {
+ Log.e(TAG, " Unable to delete existing file" + outFile.toString());
+ }
+ }
+ if (outFile.createNewFile()) {
+ decodeOutputStream = new FileOutputStream(outFile);
+ } else {
+ Log.e(TAG, "Unable to create file: " + outFile.toString());
+ }
+ }
+ Decoder decoder = new Decoder();
+ decoder.setupDecoder(decodeOutputStream);
+ int status =
+ decoder.decode(inputBuffer, frameInfo, mAsyncMode, format, codecName);
+ decoder.deInitCodec();
+ if (status == 0) {
+ decoder.dumpStatistics(
+ mInputFile + " " + codecName, extractor.getClipDuration());
+ Log.i(TAG,
+ "Decoding Successful for file: " + mInputFile
+ + " with codec: " + codecName);
+ } else {
+ Log.e(TAG,
+ "Decoder returned error " + status + " for file: " + mInputFile
+ + " with codec: " + codecName);
+ }
+ decoder.resetDecoder();
+ if (decodeOutputStream != null) {
+ decodeOutputStream.close();
+ }
+ }
+ extractor.unselectExtractorTrack(currentTrack);
+ inputBuffer.clear();
+ frameInfo.clear();
+ }
+ extractor.deinitExtractor();
+ fileInput.close();
+ } else {
+ Log.w(TAG,
+ "Warning: Test Skipped. Cannot find " + mInputFile + " in directory "
+ + mInputFilePath);
+ }
+ }
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/CodecUtils.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/CodecUtils.java
new file mode 100644
index 0000000..08035c9
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/CodecUtils.java
@@ -0,0 +1,39 @@
+package com.android.media.benchmark.library;
+
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.os.Build;
+
+import java.util.ArrayList;
+
+public class CodecUtils {
+ private CodecUtils() {}
+
+ /**
+ * Queries the MediaCodecList and returns codec names of supported codecs.
+ *
+ * @param mimeType Mime type of input
+ * @param isEncoder Specifies encoder or decoder
+ * @return ArrayList of codec names
+ */
+ public static ArrayList<String> selectCodecs(String mimeType, boolean isEncoder) {
+ MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+ MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+ ArrayList<String> supportedCodecs = new ArrayList<>();
+ for (MediaCodecInfo codecInfo : codecInfos) {
+ if (isEncoder != codecInfo.isEncoder()) {
+ continue;
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) {
+ continue;
+ }
+ String[] types = codecInfo.getSupportedTypes();
+ for (String type : types) {
+ if (type.equalsIgnoreCase(mimeType)) {
+ supportedCodecs.add(codecInfo.getName());
+ }
+ }
+ }
+ return supportedCodecs;
+ }
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java
new file mode 100644
index 0000000..2cd27c2
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+package com.android.media.benchmark.library;
+
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+public class Decoder {
+ private static final String TAG = "Decoder";
+ private static final boolean DEBUG = false;
+ private static final int kQueueDequeueTimeoutUs = 1000;
+
+ private final Object mLock = new Object();
+ private MediaCodec mCodec;
+ private ArrayList<BufferInfo> mInputBufferInfo;
+ private Stats mStats;
+
+ private boolean mSawInputEOS;
+ private boolean mSawOutputEOS;
+ private boolean mSignalledError;
+
+ private int mNumOutputFrame;
+ private int mIndex;
+
+ private ArrayList<ByteBuffer> mInputBuffer;
+ private FileOutputStream mOutputStream;
+
+ public Decoder() { mStats = new Stats(); }
+
+ /**
+ * Setup of decoder
+ *
+ * @param outputStream Will dump the output in this stream if not null.
+ */
+ public void setupDecoder(FileOutputStream outputStream) {
+ mSignalledError = false;
+ mOutputStream = outputStream;
+ }
+
+ private MediaCodec createCodec(String codecName, MediaFormat format) throws IOException {
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ try {
+ MediaCodec codec;
+ if (codecName.isEmpty()) {
+ Log.i(TAG, "File mime type: " + mime);
+ if (mime != null) {
+ codec = MediaCodec.createDecoderByType(mime);
+ Log.i(TAG, "Decoder created for mime type " + mime);
+ return codec;
+ } else {
+ Log.e(TAG, "Mime type is null, please specify a mime type to create decoder");
+ return null;
+ }
+ } else {
+ codec = MediaCodec.createByCodecName(codecName);
+ Log.i(TAG, "Decoder created with codec name: " + codecName + " mime: " + mime);
+ return codec;
+ }
+ } catch (IllegalArgumentException ex) {
+ ex.printStackTrace();
+ Log.e(TAG, "Failed to create decoder for " + codecName + " mime:" + mime);
+ return null;
+ }
+ }
+
+ /**
+ * Decodes the given input buffer,
+ * provided valid list of buffer info and format are passed as inputs.
+ *
+ * @param inputBuffer Decode the provided list of ByteBuffers
+ * @param inputBufferInfo List of buffer info corresponding to provided input buffers
+ * @param asyncMode Will run on async implementation if true
+ * @param format For creating the decoder if codec name is empty and configuring it
+ * @param codecName Will create the decoder with codecName
+ * @return 0 if decode was successful , -1 for fail, -2 for decoder not created
+ * @throws IOException if the codec cannot be created.
+ */
+ public int decode(@NonNull ArrayList<ByteBuffer> inputBuffer,
+ @NonNull ArrayList<BufferInfo> inputBufferInfo, final boolean asyncMode,
+ @NonNull MediaFormat format, String codecName) throws IOException {
+ mInputBuffer = new ArrayList<>(inputBuffer.size());
+ mInputBuffer.addAll(inputBuffer);
+ mInputBufferInfo = new ArrayList<>(inputBufferInfo.size());
+ mInputBufferInfo.addAll(inputBufferInfo);
+ mSawInputEOS = false;
+ mSawOutputEOS = false;
+ mNumOutputFrame = 0;
+ mIndex = 0;
+ long sTime = mStats.getCurTime();
+ mCodec = createCodec(codecName, format);
+ if (mCodec == null) {
+ return -2;
+ }
+ if (asyncMode) {
+ mCodec.setCallback(new MediaCodec.Callback() {
+ @Override
+ public void onInputBufferAvailable(
+ @NonNull MediaCodec mediaCodec, int inputBufferId) {
+ try {
+ mStats.addInputTime();
+ onInputAvailable(inputBufferId, mediaCodec);
+ } catch (Exception e) {
+ e.printStackTrace();
+ Log.e(TAG, e.toString());
+ }
+ }
+
+ @Override
+ public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec,
+ int outputBufferId, @NonNull MediaCodec.BufferInfo bufferInfo) {
+ mStats.addOutputTime();
+ onOutputAvailable(mediaCodec, outputBufferId, bufferInfo);
+ if (mSawOutputEOS) {
+ Log.i(TAG, "Saw output EOS");
+ synchronized (mLock) { mLock.notify(); }
+ }
+ }
+
+ @Override
+ public void onOutputFormatChanged(
+ @NonNull MediaCodec mediaCodec, @NonNull MediaFormat format) {
+ Log.i(TAG, "Output format changed. Format: " + format.toString());
+ }
+
+ @Override
+ public void onError(
+ @NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
+ mSignalledError = true;
+ Log.e(TAG, "Codec Error: " + e.toString());
+ e.printStackTrace();
+ synchronized (mLock) { mLock.notify(); }
+ }
+ });
+ }
+ int isEncoder = 0;
+ if (DEBUG) {
+ Log.d(TAG, "Media Format : " + format.toString());
+ }
+ mCodec.configure(format, null, null, isEncoder);
+ mCodec.start();
+ Log.i(TAG, "Codec started ");
+ long eTime = mStats.getCurTime();
+ mStats.setInitTime(mStats.getTimeDiff(sTime, eTime));
+ mStats.setStartTime();
+ if (asyncMode) {
+ try {
+ synchronized (mLock) { mLock.wait(); }
+ if (mSignalledError) {
+ return -1;
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ } else {
+ while (!mSawOutputEOS && !mSignalledError) {
+ /* Queue input data */
+ if (!mSawInputEOS) {
+ int inputBufferId = mCodec.dequeueInputBuffer(kQueueDequeueTimeoutUs);
+ if (inputBufferId < 0 && inputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ Log.e(TAG,
+ "MediaCodec.dequeueInputBuffer "
+ + " returned invalid index : " + inputBufferId);
+ return -1;
+ }
+ mStats.addInputTime();
+ onInputAvailable(inputBufferId, mCodec);
+ }
+ /* Dequeue output data */
+ BufferInfo outputBufferInfo = new BufferInfo();
+ int outputBufferId =
+ mCodec.dequeueOutputBuffer(outputBufferInfo, kQueueDequeueTimeoutUs);
+ if (outputBufferId < 0) {
+ if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+ MediaFormat outFormat = mCodec.getOutputFormat();
+ Log.i(TAG, "Output format changed. Format: " + outFormat.toString());
+ } else if (outputBufferId == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+ Log.i(TAG, "Ignoring deprecated flag: INFO_OUTPUT_BUFFERS_CHANGED");
+ } else if (outputBufferId != MediaCodec.INFO_TRY_AGAIN_LATER) {
+ Log.e(TAG,
+ "MediaCodec.dequeueOutputBuffer"
+ + " returned invalid index " + outputBufferId);
+ return -1;
+ }
+ } else {
+ mStats.addOutputTime();
+ if (DEBUG) {
+ Log.d(TAG, "Dequeue O/P buffer with BufferID " + outputBufferId);
+ }
+ onOutputAvailable(mCodec, outputBufferId, outputBufferInfo);
+ }
+ if (outputBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
+ Log.i(TAG, "Saw output EOS");
+ }
+ }
+ }
+ mInputBuffer.clear();
+ mInputBufferInfo.clear();
+ return 0;
+ }
+
+ /**
+ * Stops the codec and releases codec resources.
+ */
+ public void deInitCodec() {
+ long sTime = mStats.getCurTime();
+ if (mCodec != null) {
+ mCodec.stop();
+ mCodec.release();
+ mCodec = null;
+ }
+ long eTime = mStats.getCurTime();
+ mStats.setDeInitTime(mStats.getTimeDiff(sTime, eTime));
+ }
+
+ /**
+ * Prints out the statistics in the information log
+ *
+ * @param inputReference The operation being performed, in this case decode
+ * @param durationUs Duration of the clip in microseconds
+ */
+ public void dumpStatistics(String inputReference, long durationUs) {
+ String operation = "decode";
+ mStats.dumpStatistics(operation, inputReference, durationUs);
+ }
+
+ /**
+ * Resets the stats
+ */
+ public void resetDecoder() { mStats.reset(); }
+
+ private void onInputAvailable(int inputBufferId, MediaCodec mediaCodec) {
+ if ((inputBufferId >= 0) && !mSawInputEOS) {
+ ByteBuffer inputCodecBuffer = mediaCodec.getInputBuffer(inputBufferId);
+ BufferInfo bufInfo = mInputBufferInfo.get(mIndex);
+ inputCodecBuffer.put(mInputBuffer.get(mIndex).array());
+ mIndex++;
+ if (bufInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
+ mSawInputEOS = true;
+ Log.i(TAG, "Saw input EOS");
+ }
+ mStats.addFrameSize(bufInfo.size);
+ mediaCodec.queueInputBuffer(inputBufferId, bufInfo.offset, bufInfo.size,
+ bufInfo.presentationTimeUs, bufInfo.flags);
+ if (DEBUG) {
+ Log.d(TAG,
+ "Codec Input: "
+ + "flag = " + bufInfo.flags + " timestamp = "
+ + bufInfo.presentationTimeUs + " size = " + bufInfo.size);
+ }
+ }
+ }
+
+ private void onOutputAvailable(
+ MediaCodec mediaCodec, int outputBufferId, BufferInfo outputBufferInfo) {
+ if (mSawOutputEOS || outputBufferId < 0) {
+ return;
+ }
+ mNumOutputFrame++;
+ if (DEBUG) {
+ Log.d(TAG,
+ "In OutputBufferAvailable ,"
+ + " output frame number = " + mNumOutputFrame);
+ }
+ if (mOutputStream != null) {
+ try {
+ ByteBuffer outputBuffer = mediaCodec.getOutputBuffer(outputBufferId);
+ byte[] bytesOutput = new byte[outputBuffer.remaining()];
+ outputBuffer.get(bytesOutput);
+ mOutputStream.write(bytesOutput);
+ } catch (IOException e) {
+ e.printStackTrace();
+ Log.d(TAG, "Error Dumping File: Exception " + e.toString());
+ }
+ }
+ mediaCodec.releaseOutputBuffer(outputBufferId, false);
+ mSawOutputEOS = (outputBufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+ }
+}
diff --git a/media/tests/benchmark/README.md b/media/tests/benchmark/README.md
index 5dd83dd..d1354b3 100644
--- a/media/tests/benchmark/README.md
+++ b/media/tests/benchmark/README.md
@@ -96,3 +96,10 @@
```
adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.ExtractorTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
```
+
+## Decoder
+
+The test decodes input stream and benchmarks the decoders available in SDK.
+```
+adb shell am instrument -w -r -e class 'com.android.media.benchmark.tests.DecoderTest' com.android.media.benchmark/androidx.test.runner.AndroidJUnitRunner
+```
diff --git a/media/tests/benchmark/tests/DecoderTest.cpp b/media/tests/benchmark/tests/DecoderTest.cpp
index 6cb42d6..242178f 100644
--- a/media/tests/benchmark/tests/DecoderTest.cpp
+++ b/media/tests/benchmark/tests/DecoderTest.cpp
@@ -72,24 +72,7 @@
vector<AMediaCodecBufferInfo> frameInfo;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
- int32_t idx = 0;
- // Get CSD data
- while (1) {
- void *csdBuffer = extractor->getCSDSample(info, idx);
- if (!csdBuffer || !info.size) break;
-
- // copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
- memcpy(inputBuffer + inputBufferOffset, csdBuffer, info.size);
- frameInfo.push_back(info);
- inputBufferOffset += info.size;
- idx++;
- }
// Get frame data
while (1) {
status = extractor->getFrameSample(info);
@@ -135,6 +118,7 @@
make_tuple("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "", false),
make_tuple("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "", false),
make_tuple("bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", "", false),
+ make_tuple("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "", false),
make_tuple("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "", false)));
INSTANTIATE_TEST_SUITE_P(
@@ -144,6 +128,7 @@
make_tuple("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "", true),
make_tuple("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "", true),
make_tuple("bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", "", true),
+ make_tuple("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "", true),
make_tuple("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "", true)));
INSTANTIATE_TEST_SUITE_P(VideDecoderSyncTest, DecoderTest,
diff --git a/media/tests/benchmark/tests/EncoderTest.cpp b/media/tests/benchmark/tests/EncoderTest.cpp
index 574083d..9f42c64 100644
--- a/media/tests/benchmark/tests/EncoderTest.cpp
+++ b/media/tests/benchmark/tests/EncoderTest.cpp
@@ -72,24 +72,6 @@
vector<AMediaCodecBufferInfo> frameInfo;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
- int32_t idx = 0;
-
- // Get CSD data
- while (1) {
- void *csdBuffer = extractor->getCSDSample(info, idx);
- if (!csdBuffer || !info.size) break;
-
- // copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
- memcpy(inputBuffer + inputBufferOffset, csdBuffer, info.size);
- frameInfo.push_back(info);
- inputBufferOffset += info.size;
- idx++;
- }
// Get frame data
while (1) {
@@ -189,6 +171,7 @@
::testing::Values(make_tuple("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "", false),
make_tuple("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "", false),
make_tuple("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "", false),
+ make_tuple("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "", false),
make_tuple("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "", false)));
INSTANTIATE_TEST_SUITE_P(
@@ -196,6 +179,7 @@
::testing::Values(make_tuple("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "", true),
make_tuple("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "", true),
make_tuple("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "", true),
+ make_tuple("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "", true),
make_tuple("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "", true)));
INSTANTIATE_TEST_SUITE_P(VideEncoderSyncTest, EncoderTest,
diff --git a/services/audiopolicy/service/AudioPolicyService.cpp b/services/audiopolicy/service/AudioPolicyService.cpp
index 7dfc205..2319838 100644
--- a/services/audiopolicy/service/AudioPolicyService.cpp
+++ b/services/audiopolicy/service/AudioPolicyService.cpp
@@ -422,12 +422,18 @@
// AND is on TOP
// AND the source is VOICE_RECOGNITION or HOTWORD
// OR the client source is virtual (remote submix, call audio TX or RX...)
+// OR the client source is HOTWORD
+// AND is on TOP
+// OR all active clients are using HOTWORD source
+// AND no call is active
+// OR client has CAPTURE_AUDIO_OUTPUT privileged permission
// OR Any client
// AND The assistant is not on TOP
// AND is on TOP or latest started
// AND there is no active privacy sensitive capture or call
// OR client has CAPTURE_AUDIO_OUTPUT privileged permission
+
sp<AudioRecordClient> topActive;
sp<AudioRecordClient> latestActive;
sp<AudioRecordClient> latestSensitiveActive;
@@ -442,6 +448,7 @@
bool rttCallActive =
(mPhoneState == AUDIO_MODE_IN_CALL || mPhoneState == AUDIO_MODE_IN_COMMUNICATION)
&& mUidPolicy->isRttEnabled();
+ bool onlyHotwordActive = true;
// if Sensor Privacy is enabled then all recordings should be silenced.
if (mSensorPrivacyPolicy->isSensorPrivacyEnabled()) {
@@ -473,12 +480,11 @@
isAssistantOnTop = true;
}
}
- // Assistant capturing for HOTWORD or Accessibility services not considered
+ // Client capturing for HOTWORD or Accessibility services not considered
// for latest active to avoid masking regular clients started before
if (current->startTimeNs > latestStartNs
- && !((current->attributes.source == AUDIO_SOURCE_HOTWORD
- || isA11yOnTop || rttCallActive)
- && isAssistant)
+ && !(current->attributes.source == AUDIO_SOURCE_HOTWORD
+ || ((isA11yOnTop || rttCallActive) && isAssistant))
&& !isAccessibility) {
latestActive = current;
latestStartNs = current->startTimeNs;
@@ -490,6 +496,9 @@
}
isSensitiveActive = true;
}
+ if (current->attributes.source != AUDIO_SOURCE_HOTWORD) {
+ onlyHotwordActive = false;
+ }
}
// if no active client with UI on Top, consider latest active as top
@@ -556,6 +565,14 @@
allowCapture = true;
}
}
+ } else if (source == AUDIO_SOURCE_HOTWORD) {
+ // For HOTWORD source allow capture when not on TOP if:
+ // All active clients are using HOTWORD source
+ // AND no call is active
+ // OR client has CAPTURE_AUDIO_OUTPUT privileged permission
+ if (onlyHotwordActive && !(isInCall && !current->canCaptureOutput)) {
+ allowCapture = true;
+ }
}
setAppState_l(current->portId,
allowCapture ? apmStatFromAmState(mUidPolicy->getUidState(current->uid)) :
diff --git a/services/mediaanalytics/Android.bp b/services/mediaanalytics/Android.bp
index c27aced..dc72064 100644
--- a/services/mediaanalytics/Android.bp
+++ b/services/mediaanalytics/Android.bp
@@ -6,8 +6,32 @@
srcs: [
"main_mediametrics.cpp",
- "MediaAnalyticsService.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libmediaanalyticsservice",
+ "libutils",
+ ],
+
+ init_rc: [
+ "mediametrics.rc",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+}
+
+cc_library_shared {
+ name: "libmediaanalyticsservice",
+
+ srcs: [
"iface_statsd.cpp",
+ "MediaAnalyticsService.cpp",
"statsd_audiopolicy.cpp",
"statsd_audiorecord.cpp",
"statsd_audiothread.cpp",
@@ -24,45 +48,25 @@
},
shared_libs: [
- "libcutils",
- "liblog",
- "libmedia",
- "libutils",
"libbinder",
- "libdl",
- "libgui",
- "libmedia",
- "libmediautils",
+ "liblog",
"libmediametrics",
- "libstagefright_foundation",
+ "libprotobuf-cpp-lite",
"libstatslog",
"libutils",
- "libprotobuf-cpp-lite",
],
static_libs: [
"libplatformprotos",
- "libregistermsext",
],
include_dirs: [
- "frameworks/av/media/libstagefright/include",
- "frameworks/av/media/libstagefright/rtsp",
- "frameworks/av/media/libstagefright/webm",
- "frameworks/av/include/media",
- "frameworks/av/camera/include/camera",
- "frameworks/native/include/media/openmax",
- "frameworks/native/include/media/hardware",
- "external/tremolo/Tremolo",
+ "system/media/audio_utils/include",
],
- init_rc: ["mediametrics.rc"],
-
cflags: [
- "-Werror",
"-Wall",
- "-Wno-error=deprecated-declarations",
+ "-Werror",
+ "-Wextra",
],
- clang: true,
-
}
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 988c06b..f0cb9c1 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -14,66 +14,20 @@
* limitations under the License.
*/
-// Proxy for media player implementations
-
//#define LOG_NDEBUG 0
#define LOG_TAG "MediaAnalyticsService"
#include <utils/Log.h>
-#include <stdint.h>
-#include <inttypes.h>
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <sys/time.h>
-#include <dirent.h>
-#include <pthread.h>
-#include <unistd.h>
-
-#include <string.h>
-#include <pwd.h>
-
-#include <cutils/atomic.h>
-#include <cutils/properties.h> // for property_get
-
-#include <utils/misc.h>
-
-#include <android/content/pm/IPackageManagerNative.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/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"
+#include <pwd.h> //getpwuid
+
+#include <audio_utils/clock.h> // clock conversions
+#include <android/content/pm/IPackageManagerNative.h> // package info
+#include <binder/IPCThreadState.h> // get calling uid
+#include <cutils/properties.h> // for property_get
+#include <private/android_filesystem_config.h> // UID
+
namespace android {
// individual records kept in memory: age or count
@@ -81,72 +35,45 @@
// count: hard limit of # records
// (0 for either of these disables that threshold)
//
-static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * (1000*1000*1000ll);
+static constexpr nsecs_t kMaxRecordAgeNs = 28 * 3600 * NANOS_PER_SECOND;
// 2019/6: average daily per device is currently 375-ish;
// setting this to 2000 is large enough to catch most devices
// we'll lose some data on very very media-active devices, but only for
// the gms collection; statsd will have already covered those for us.
// This also retains enough information to help with bugreports
-static constexpr int kMaxRecords = 2000;
+static constexpr size_t kMaxRecords = 2000;
// max we expire in a single call, to constrain how long we hold the
// mutex, which also constrains how long a client might wait.
-static constexpr int kMaxExpiredAtOnce = 50;
+static constexpr size_t kMaxExpiredAtOnce = 50;
// TODO: need to look at tuning kMaxRecords and friends for low-memory devices
-static const char *kServiceName = "media.metrics";
-
-void MediaAnalyticsService::instantiate() {
- defaultServiceManager()->addService(
- String16(kServiceName), new MediaAnalyticsService());
-}
-
MediaAnalyticsService::MediaAnalyticsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
mMaxRecordsExpiredAtOnce(kMaxExpiredAtOnce),
- mDumpProto(MediaAnalyticsItem::PROTO_V1),
- mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1) {
-
- ALOGD("MediaAnalyticsService created");
-
- mItemsSubmitted = 0;
- mItemsFinalized = 0;
- mItemsDiscarded = 0;
- mItemsDiscardedExpire = 0;
- mItemsDiscardedCount = 0;
-
- mLastSessionID = 0;
- // recover any persistency we set up
- // etc
+ mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1)
+{
+ ALOGD("%s", __func__);
}
-MediaAnalyticsService::~MediaAnalyticsService() {
- ALOGD("MediaAnalyticsService destroyed");
-
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedCount++;
- }
+MediaAnalyticsService::~MediaAnalyticsService()
+{
+ ALOGD("%s", __func__);
+ // the class destructor clears anyhow, but we enforce clearing items first.
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
}
-
MediaAnalyticsItem::SessionID_t MediaAnalyticsService::generateUniqueSessionID() {
- // generate a new sessionid
-
- Mutex::Autolock _l(mLock_ids);
- return (++mLastSessionID);
+ return ++mLastSessionID;
}
-// caller surrenders ownership of 'item'
-MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(MediaAnalyticsItem *item, bool forcenew)
+// TODO: consider removing forcenew.
+MediaAnalyticsItem::SessionID_t MediaAnalyticsService::submit(
+ MediaAnalyticsItem *item, bool forcenew __unused)
{
- UNUSED(forcenew);
-
// fill in a sessionID if we do not yet have one
if (item->getSessionID() <= MediaAnalyticsItem::SessionIDNone) {
item->setSessionID(generateUniqueSessionID());
@@ -155,61 +82,60 @@
// we control these, generally not trusting user input
nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
// round nsecs to seconds
- now = ((now + 500000000) / 1000000000) * 1000000000;
+ now = (now + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
+ // TODO: if we convert to boot time, do we need to round timestamp?
item->setTimestamp(now);
- int pid = IPCThreadState::self()->getCallingPid();
- int uid = IPCThreadState::self()->getCallingUid();
- int uid_given = item->getUid();
- int pid_given = item->getPid();
+ const int pid = IPCThreadState::self()->getCallingPid();
+ const int uid = IPCThreadState::self()->getCallingUid();
+ const int uid_given = item->getUid();
+ const int pid_given = item->getPid();
- // although we do make exceptions for some trusted client uids
- bool isTrusted = false;
-
- ALOGV("caller has uid=%d, embedded uid=%d", uid, uid_given);
-
- switch (uid) {
- case AID_DRM:
- case AID_MEDIA:
- case AID_MEDIA_CODEC:
- case AID_MEDIA_EX:
- case AID_MEDIA_DRM:
- // trusted source, only override default values
- isTrusted = true;
- if (uid_given == (-1)) {
- item->setUid(uid);
- }
- if (pid_given == (-1)) {
- item->setPid(pid);
- }
- break;
- default:
- isTrusted = false;
- item->setPid(pid);
+ ALOGV("%s: caller has uid=%d, embedded uid=%d", __func__, uid, uid_given);
+ bool isTrusted;
+ switch (uid) {
+ case AID_DRM:
+ case AID_MEDIA:
+ case AID_MEDIA_CODEC:
+ case AID_MEDIA_EX:
+ case AID_MEDIA_DRM:
+ // trusted source, only override default values
+ isTrusted = true;
+ if (uid_given == -1) {
item->setUid(uid);
- break;
+ }
+ if (pid_given == -1) {
+ item->setPid(pid);
+ }
+ break;
+ default:
+ isTrusted = false;
+ item->setPid(pid);
+ item->setUid(uid);
+ break;
}
// Overwrite package name and version if the caller was untrusted.
if (!isTrusted) {
- setPkgInfo(item, item->getUid(), true, true);
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
} else if (item->getPkgName().empty()) {
- // empty, so fill out both parts
- setPkgInfo(item, item->getUid(), true, true);
+ // empty, so fill out both parts
+ mUidInfo.setPkgInfo(item, item->getUid(), true, true);
} else {
- // trusted, provided a package, do nothing
+ // trusted, provided a package, do nothing
}
- ALOGV("given uid %d; sanitized uid: %d sanitized pkg: %s "
- "sanitized pkg version: %" PRId64,
+ ALOGV("%s: given uid %d; sanitized uid: %d sanitized pkg: %s "
+ "sanitized pkg version: %lld",
+ __func__,
uid_given, item->getUid(),
item->getPkgName().c_str(),
- item->getPkgVersionCode());
+ (long long)item->getPkgVersionCode());
mItemsSubmitted++;
// validate the record; we discard if we don't like it
- if (contentValid(item, isTrusted) == false) {
+ if (isContentValid(item, isTrusted) == false) {
delete item;
return MediaAnalyticsItem::SessionIDInvalid;
}
@@ -218,56 +144,46 @@
// sure it doesn't appear in the finalized list.
if (item->count() == 0) {
- ALOGV("dropping empty record...");
+ ALOGV("%s: dropping empty record...", __func__);
delete item;
- item = NULL;
return MediaAnalyticsItem::SessionIDInvalid;
}
- // save the new record
- //
- // send a copy to statsd
- dump2Statsd(item);
+ // send to statsd
+ extern bool dump2Statsd(MediaAnalyticsItem *item); // extern hook
+ (void)dump2Statsd(item); // failure should be logged in function.
- // and keep our copy for dumpsys
- MediaAnalyticsItem::SessionID_t id = item->getSessionID();
+ // keep our copy
+ const MediaAnalyticsItem::SessionID_t id = item->getSessionID();
saveItem(item);
- mItemsFinalized++;
-
return id;
}
-
status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
String8 result;
if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
- snprintf(buffer, SIZE, "Permission Denial: "
+ result.appendFormat("Permission Denial: "
"can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
IPCThreadState::self()->getCallingPid(),
IPCThreadState::self()->getCallingUid());
- result.append(buffer);
write(fd, result.string(), result.size());
return NO_ERROR;
}
// crack any parameters
- String16 protoOption("-proto");
+ const String16 protoOption("-proto");
int chosenProto = mDumpProtoDefault;
- String16 clearOption("-clear");
+ const String16 clearOption("-clear");
bool clear = false;
- String16 sinceOption("-since");
+ const String16 sinceOption("-since");
nsecs_t ts_since = 0;
- String16 helpOption("-help");
- String16 onlyOption("-only");
+ const String16 helpOption("-help");
+ const String16 onlyOption("-only");
std::string only;
- int n = args.size();
-
+ const int n = args.size();
for (int i = 0; i < n; i++) {
- String8 myarg(args[i]);
if (args[i] == clearOption) {
clear = true;
} else if (args[i] == protoOption) {
@@ -305,7 +221,7 @@
ts_since = 0;
}
// command line is milliseconds; internal units are nano-seconds
- ts_since *= 1000*1000;
+ ts_since *= NANOS_PER_MILLISECOND;
} else if (args[i] == onlyOption) {
i++;
if (i < n) {
@@ -313,6 +229,10 @@
only = value.string();
}
} else if (args[i] == helpOption) {
+ // TODO: consider function area dumping.
+ // dumpsys media.metrics audiotrack,codec
+ // or dumpsys media.metrics audiotrack codec
+
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
result.append("-proto # dump using protocol #");
@@ -325,31 +245,18 @@
}
}
- Mutex::Autolock _l(mLock);
- // mutex between insertion and dumping the contents
+ {
+ std::lock_guard _l(mLock);
- mDumpProto = chosenProto;
+ result.appendFormat("Dump of the %s process:\n", kServiceName);
+ dumpHeaders_l(result, chosenProto, ts_since);
+ dumpRecent_l(result, chosenProto, ts_since, only.c_str());
- // we ALWAYS dump this piece
- snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
- result.append(buffer);
-
- dumpHeaders(result, ts_since);
-
- dumpRecent(result, ts_since, only.c_str());
-
-
- if (clear) {
- // remove everything from the finalized queue
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
+ if (clear) {
+ mItemsDiscarded += mItems.size();
+ mItems.clear();
+ // shall we clear the summary data too?
}
-
- // shall we clear the summary data too?
-
}
write(fd, result.string(), result.size());
@@ -357,275 +264,207 @@
}
// dump headers
-void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since)
+void MediaAnalyticsService::dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
-
- snprintf(buffer, SIZE, "Protocol Version: %d\n", mDumpProto);
- result.append(buffer);
-
- int enabled = MediaAnalyticsItem::isEnabled();
- if (enabled) {
- snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
+ result.appendFormat("Protocol Version: %d\n", dumpProto);
+ if (MediaAnalyticsItem::isEnabled()) {
+ result.append("Metrics gathering: enabled\n");
} else {
- snprintf(buffer, SIZE, "Metrics gathering: DISABLED via property\n");
+ result.append("Metrics gathering: DISABLED via property\n");
}
- result.append(buffer);
-
- snprintf(buffer, SIZE,
- "Since Boot: Submissions: %8" PRId64
- " Accepted: %8" PRId64 "\n",
- mItemsSubmitted, mItemsFinalized);
- result.append(buffer);
- snprintf(buffer, SIZE,
- "Records Discarded: %8" PRId64
- " (by Count: %" PRId64 " by Expiration: %" PRId64 ")\n",
- mItemsDiscarded, mItemsDiscardedCount, mItemsDiscardedExpire);
- result.append(buffer);
+ result.appendFormat(
+ "Since Boot: Submissions: %lld Accepted: %lld\n",
+ (long long)mItemsSubmitted.load(), (long long)mItemsFinalized);
+ result.appendFormat(
+ "Records Discarded: %lld (by Count: %lld by Expiration: %lld)\n",
+ (long long)mItemsDiscarded, (long long)mItemsDiscardedCount,
+ (long long)mItemsDiscardedExpire);
if (ts_since != 0) {
- snprintf(buffer, SIZE,
- "Emitting Queue entries more recent than: %" PRId64 "\n",
- (int64_t) ts_since);
- result.append(buffer);
+ result.appendFormat(
+ "Emitting Queue entries more recent than: %lld\n",
+ (long long)ts_since);
}
}
-// the recent, detailed queues
-void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only)
+void MediaAnalyticsService::dumpRecent_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only)
{
- const size_t SIZE = 512;
- char buffer[SIZE];
-
- if (only != NULL && *only == '\0') {
- only = NULL;
+ if (only != nullptr && *only == '\0') {
+ only = nullptr;
}
-
- // show the recently recorded records
- snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
- result.append(buffer);
- result.append(this->dumpQueue(ts_since, only));
+ result.append("\nFinalized Metrics (oldest first):\n");
+ dumpQueue_l(result, dumpProto, ts_since, only);
// 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
}
-// caller has locked mLock...
-String8 MediaAnalyticsService::dumpQueue() {
- return dumpQueue((nsecs_t) 0, NULL);
+void MediaAnalyticsService::dumpQueue_l(String8 &result, int dumpProto) {
+ dumpQueue_l(result, dumpProto, (nsecs_t) 0, nullptr /* only */);
}
-String8 MediaAnalyticsService::dumpQueue(nsecs_t ts_since, const char * only) {
- String8 result;
+void MediaAnalyticsService::dumpQueue_l(
+ String8 &result, int dumpProto, nsecs_t ts_since, const char * only) {
int slot = 0;
if (mItems.empty()) {
- result.append("empty\n");
+ result.append("empty\n");
} else {
- List<MediaAnalyticsItem *>::iterator it = mItems.begin();
- for (; it != mItems.end(); it++) {
- nsecs_t when = (*it)->getTimestamp();
+ for (const auto &item : mItems) {
+ nsecs_t when = item->getTimestamp();
if (when < ts_since) {
continue;
}
- if (only != NULL &&
- strcmp(only, (*it)->getKey().c_str()) != 0) {
- ALOGV("Omit '%s', it's not '%s'", (*it)->getKey().c_str(), only);
+ // TODO: Only should be a set<string>
+ if (only != nullptr &&
+ item->getKey() /* std::string */ != only) {
+ ALOGV("%s: omit '%s', it's not '%s'",
+ __func__, item->getKey().c_str(), only);
continue;
}
- std::string entry = (*it)->toString(mDumpProto);
- result.appendFormat("%5d: %s\n", slot, entry.c_str());
+ result.appendFormat("%5d: %s\n",
+ slot, item->toString(dumpProto).c_str());
slot++;
}
}
-
- return result;
}
//
// Our Cheap in-core, non-persistent records management.
-
-// we hold mLock when we get here
// if item != NULL, it's the item we just inserted
// true == more items eligible to be recovered
bool MediaAnalyticsService::expirations_l(MediaAnalyticsItem *item)
{
bool more = false;
- int handled = 0;
- // keep removing old records the front until we're in-bounds (count)
- // since we invoke this with each insertion, it should be 0/1 iterations.
- if (mMaxRecords > 0) {
- while (mItems.size() > (size_t) mMaxRecords) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
- if (oitem == item) {
- break;
- }
- if (handled >= mMaxRecordsExpiredAtOnce) {
- // unlikely in this loop
- more = true;
- break;
- }
- handled++;
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedCount++;
+ // check queue size
+ size_t overlimit = 0;
+ if (mMaxRecords > 0 && mItems.size() > mMaxRecords) {
+ overlimit = mItems.size() - mMaxRecords;
+ if (overlimit > mMaxRecordsExpiredAtOnce) {
+ more = true;
+ overlimit = mMaxRecordsExpiredAtOnce;
}
}
- // keep removing old records the front until we're in-bounds (age)
- // limited to mMaxRecordsExpiredAtOnce items per invocation.
- if (mMaxRecordAgeNs > 0) {
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- while (mItems.size() > 0) {
- MediaAnalyticsItem * oitem = *(mItems.begin());
+ // check queue times
+ size_t expired = 0;
+ if (!more && mMaxRecordAgeNs > 0) {
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ // we check one at a time, skip search would be more efficient.
+ size_t i = overlimit;
+ for (; i < mItems.size(); ++i) {
+ auto &oitem = mItems[i];
nsecs_t when = oitem->getTimestamp();
- if (oitem == item) {
+ if (oitem.get() == item) {
break;
}
- // careful about timejumps too
- if ((now > when) && (now-when) <= mMaxRecordAgeNs) {
- // this (and the rest) are recent enough to keep
- break;
+ if (now > when && (now - when) <= mMaxRecordAgeNs) {
+ break; // TODO: if we use BOOTTIME, should be monotonic.
}
- if (handled >= mMaxRecordsExpiredAtOnce) {
+ if (i >= mMaxRecordsExpiredAtOnce) {
// this represents "one too many"; tell caller there are
// more to be reclaimed.
more = true;
break;
}
- handled++;
- mItems.erase(mItems.begin());
- delete oitem;
- mItemsDiscarded++;
- mItemsDiscardedExpire++;
}
+ expired = i - overlimit;
}
- // we only indicate whether there's more to clean;
- // caller chooses whether to schedule further cleanup.
+ if (const size_t toErase = overlimit + expired;
+ toErase > 0) {
+ mItemsDiscardedCount += overlimit;
+ mItemsDiscardedExpire += expired;
+ mItemsDiscarded += toErase;
+ mItems.erase(mItems.begin(), mItems.begin() + toErase); // erase from front
+ }
return more;
}
-// process expirations in bite sized chunks, allowing new insertions through
-// runs in a pthread specifically started for this (which then exits)
-bool MediaAnalyticsService::processExpirations()
+void MediaAnalyticsService::processExpirations()
{
bool more;
do {
sleep(1);
- {
- Mutex::Autolock _l(mLock);
- more = expirations_l(NULL);
- if (!more) {
- break;
- }
- }
+ std::lock_guard _l(mLock);
+ more = expirations_l(nullptr);
} while (more);
- return true; // value is for std::future thread synchronization
}
-// insert appropriately into queue
-void MediaAnalyticsService::saveItem(MediaAnalyticsItem * item)
+void MediaAnalyticsService::saveItem(MediaAnalyticsItem *item)
{
-
- Mutex::Autolock _l(mLock);
- // mutex between insertion and dumping the contents
-
- // we want to dump 'in FIFO order', so insert at the end
- mItems.push_back(item);
-
- // clean old stuff from the queue
- bool more = expirations_l(item);
-
- // consider scheduling some asynchronous cleaning, if not running
- if (more) {
- if (!mExpireFuture.valid()
- || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) {
-
- mExpireFuture = std::async(std::launch::async, [this]()
- {return this->processExpirations();});
- }
+ std::lock_guard _l(mLock);
+ // we assume the items are roughly in time order.
+ mItems.emplace_back(item);
+ ++mItemsFinalized;
+ if (expirations_l(item)
+ && (!mExpireFuture.valid()
+ || mExpireFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready)) {
+ mExpireFuture = std::async(std::launch::async, [this] { processExpirations(); });
}
}
-static std::string allowedKeys[] =
+/* static */
+bool MediaAnalyticsService::isContentValid(const MediaAnalyticsItem *item, bool isTrusted)
{
- "audiopolicy",
- "audiorecord",
- "audiothread",
- "audiotrack",
- "codec",
- "extractor",
- "nuplayer",
-};
-
-static const int nAllowedKeys = sizeof(allowedKeys) / sizeof(allowedKeys[0]);
-
-// are the contents good
-bool MediaAnalyticsService::contentValid(MediaAnalyticsItem *item, bool isTrusted) {
-
+ if (isTrusted) return true;
// untrusted uids can only send us a limited set of keys
- if (isTrusted == false) {
- // restrict to a specific set of keys
- std::string key = item->getKey();
-
- size_t i;
- for(i = 0; i < nAllowedKeys; i++) {
- if (key == allowedKeys[i]) {
- break;
- }
- }
- if (i == nAllowedKeys) {
- ALOGD("Ignoring (key): %s", item->toString().c_str());
- return false;
+ const std::string &key = item->getKey();
+ for (const char *allowedKey : {
+ "audiopolicy",
+ "audiorecord",
+ "audiothread",
+ "audiotrack",
+ "codec",
+ "extractor",
+ "nuplayer",
+ }) {
+ if (key == allowedKey) {
+ return true;
}
}
-
- // internal consistency
-
- return true;
-}
-
-// are we rate limited, normally false
-bool MediaAnalyticsService::rateLimited(MediaAnalyticsItem *) {
-
+ ALOGD("%s: invalid key: %s", __func__, item->toString().c_str());
return false;
}
-// how long we hold package info before we re-fetch it
-#define PKG_EXPIRATION_NS (30*60*1000000000ll) // 30 minutes, in nsecs
+// are we rate limited, normally false
+bool MediaAnalyticsService::isRateLimited(MediaAnalyticsItem *) const
+{
+ return false;
+}
+
+// How long we hold package info before we re-fetch it
+constexpr nsecs_t PKG_EXPIRATION_NS = 30 * 60 * NANOS_PER_SECOND; // 30 minutes
// give me the package name, perhaps going to find it
// manages its own mutex operations internally
-void MediaAnalyticsService::setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
+void MediaAnalyticsService::UidInfo::setPkgInfo(
+ MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
{
- ALOGV("asking for packagename to go with uid=%d", uid);
+ ALOGV("%s: uid=%d", __func__, uid);
if (!setName && !setVersion) {
- // setting nothing? strange
- return;
+ return; // setting nothing? strange
}
- nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- struct UidToPkgMap mapping;
- mapping.uid = (uid_t)(-1);
-
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ struct UidToPkgInfo mapping;
{
- Mutex::Autolock _l(mLock_mappings);
- int i = mPkgMappings.indexOfKey(uid);
- if (i >= 0) {
- mapping = mPkgMappings.valueAt(i);
- ALOGV("Expiration? uid %d expiration %" PRId64 " now %" PRId64,
- uid, mapping.expiration, now);
+ std::lock_guard _l(mUidInfoLock);
+ auto it = mPkgMappings.find(uid);
+ if (it != mPkgMappings.end()) {
+ mapping = it->second;
+ ALOGV("%s: uid %d expiration %lld now %lld",
+ __func__, uid, (long long)mapping.expiration, (long long)now);
if (mapping.expiration <= now) {
// purge the stale entry and fall into re-fetching
- ALOGV("entry for uid %d expired, now= %" PRId64 "", uid, now);
- mPkgMappings.removeItemsAt(i);
- mapping.uid = (uid_t)(-1);
+ ALOGV("%s: entry for uid %d expired, now %lld",
+ __func__, uid, (long long)now);
+ mPkgMappings.erase(it);
+ mapping.uid = (uid_t)-1; // this is always fully overwritten
}
}
}
@@ -633,115 +472,103 @@
// if we did not find it
if (mapping.uid == (uid_t)(-1)) {
std::string pkg;
- std::string installer = "";
+ std::string installer;
int64_t versionCode = 0;
- struct passwd *pw = getpwuid(uid);
+ const struct passwd *pw = getpwuid(uid);
if (pw) {
pkg = pw->pw_name;
}
- // find the proper value
-
- sp<IBinder> binder = NULL;
sp<IServiceManager> sm = defaultServiceManager();
- if (sm == NULL) {
- ALOGE("defaultServiceManager failed");
+ sp<content::pm::IPackageManagerNative> package_mgr;
+ if (sm.get() == nullptr) {
+ ALOGE("%s: Cannot find service manager", __func__);
} else {
- binder = sm->getService(String16("package_native"));
- if (binder == NULL) {
- ALOGE("getService package_native failed");
+ sp<IBinder> binder = sm->getService(String16("package_native"));
+ if (binder.get() == nullptr) {
+ ALOGE("%s: Cannot find package_native", __func__);
+ } else {
+ package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
}
}
- if (binder != NULL) {
- sp<content::pm::IPackageManagerNative> package_mgr =
- interface_cast<content::pm::IPackageManagerNative>(binder);
- binder::Status status;
-
+ if (package_mgr != nullptr) {
std::vector<int> uids;
std::vector<std::string> names;
-
uids.push_back(uid);
-
- status = package_mgr->getNamesForUids(uids, &names);
+ binder::Status status = package_mgr->getNamesForUids(uids, &names);
if (!status.isOk()) {
- ALOGE("package_native::getNamesForUids failed: %s",
- status.exceptionMessage().c_str());
- } else {
- if (!names[0].empty()) {
- pkg = names[0].c_str();
- }
+ ALOGE("%s: getNamesForUids failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+ if (!names[0].empty()) {
+ pkg = names[0].c_str();
+ }
+ }
+
+ // strip any leading "shared:" strings that came back
+ if (pkg.compare(0, 7, "shared:") == 0) {
+ pkg.erase(0, 7);
+ }
+ // determine how pkg was installed and the versionCode
+ if (pkg.empty()) {
+ pkg = std::to_string(uid); // no name for us to manage
+ } else if (strchr(pkg.c_str(), '.') == NULL) {
+ // not of form 'com.whatever...'; assume internal and ok
+ } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
+ // android.* packages are assumed fine
+ } else if (package_mgr.get() != nullptr) {
+ String16 pkgName16(pkg.c_str());
+ binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ if (!status.isOk()) {
+ ALOGE("%s: getInstallerForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
}
- // strip any leading "shared:" strings that came back
- if (pkg.compare(0, 7, "shared:") == 0) {
- pkg.erase(0, 7);
- }
-
- // determine how pkg was installed and the versionCode
- //
- if (pkg.empty()) {
- // no name for us to manage
- } else if (strchr(pkg.c_str(), '.') == NULL) {
- // not of form 'com.whatever...'; assume internal and ok
- } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
- // android.* packages are assumed fine
- } else {
- String16 pkgName16(pkg.c_str());
- status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ // skip if we didn't get an installer
+ if (status.isOk()) {
+ status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
if (!status.isOk()) {
- ALOGE("package_native::getInstallerForPackage failed: %s",
- status.exceptionMessage().c_str());
- }
-
- // skip if we didn't get an installer
- if (status.isOk()) {
- status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
- if (!status.isOk()) {
- ALOGE("package_native::getVersionCodeForPackage failed: %s",
- status.exceptionMessage().c_str());
- }
- }
-
-
- ALOGV("package '%s' installed by '%s' versioncode %" PRId64 " / %" PRIx64,
- pkg.c_str(), installer.c_str(), versionCode, versionCode);
-
- if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
- // from play store, we keep info
- } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
- // some google source, we keep info
- } else if (strcmp(installer.c_str(), "preload") == 0) {
- // preloads, we keep the info
- } else if (installer.c_str()[0] == '\0') {
- // sideload (no installer); do not report
- pkg = "";
- versionCode = 0;
- } else {
- // unknown installer; do not report
- pkg = "";
- versionCode = 0;
+ ALOGE("%s: getVersionCodeForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
}
}
+
+ ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
+ __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
+
+ if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
+ // from play store, we keep info
+ } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
+ // some google source, we keep info
+ } else if (strcmp(installer.c_str(), "preload") == 0) {
+ // preloads, we keep the info
+ } else if (installer.c_str()[0] == '\0') {
+ // sideload (no installer); report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ } else {
+ // unknown installer; report UID only
+ pkg = std::to_string(uid);
+ versionCode = 0;
+ }
+ } else {
+ // unvalidated by package_mgr just send uid.
+ pkg = std::to_string(uid);
}
// add it to the map, to save a subsequent lookup
- if (!pkg.empty()) {
- Mutex::Autolock _l(mLock_mappings);
- ALOGV("Adding uid %d pkg '%s'", uid, pkg.c_str());
- ssize_t i = mPkgMappings.indexOfKey(uid);
- if (i < 0) {
- mapping.uid = uid;
- mapping.pkg = pkg;
- mapping.installer = installer.c_str();
- mapping.versionCode = versionCode;
- mapping.expiration = now + PKG_EXPIRATION_NS;
- ALOGV("expiration for uid %d set to %" PRId64 "", uid, mapping.expiration);
-
- mPkgMappings.add(uid, mapping);
- }
- }
+ std::lock_guard _l(mUidInfoLock);
+ // always overwrite
+ mapping.uid = uid;
+ mapping.pkg = std::move(pkg);
+ mapping.installer = std::move(installer);
+ mapping.versionCode = versionCode;
+ mapping.expiration = now + PKG_EXPIRATION_NS;
+ ALOGV("%s: adding uid %d pkg '%s' expiration: %lld",
+ __func__, uid, mapping.pkg.c_str(), (long long)mapping.expiration);
+ mPkgMappings[uid] = mapping;
}
if (mapping.uid != (uid_t)(-1)) {
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index 6c9cbaa..ed7b7b1 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -14,109 +14,115 @@
* limitations under the License.
*/
+#pragma once
-#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 <atomic>
+#include <deque>
#include <future>
+#include <mutex>
+#include <unordered_map>
+// IMediaAnalyticsService must include Vector, String16, Errors
#include <media/IMediaAnalyticsService.h>
+#include <utils/String8.h>
namespace android {
class MediaAnalyticsService : public BnMediaAnalyticsService
{
+public:
+ MediaAnalyticsService();
+ ~MediaAnalyticsService() override;
- public:
+ /**
+ * Submits the indicated record to the mediaanalytics service, where
+ * it will be merged (if appropriate) with incomplete records that
+ * share the same key and sessionid.
+ *
+ * \param item the item to submit.
+ * \param forcenew marks any matching incomplete record as complete before
+ * inserting this new record.
+ *
+ * \return the sessionID associated with that item or
+ * MediaAnalyticsItem::SessionIDInvalid on failure.
+ *
+ * BEWARE: When called directly on the service (not from the binder interface),
+ * the caller surrenders ownership of item, MediaAnalyticsService will delete
+ * even on error. The binder interface does not take ownership.
+ * TODO: fix this inconsistency with the binder RPC interface.
+ */
+ MediaAnalyticsItem::SessionID_t submit(MediaAnalyticsItem *item, bool forcenew) override;
- // on this side, caller surrenders ownership
- virtual int64_t submit(MediaAnalyticsItem *item, bool forcenew);
+ status_t dump(int fd, const Vector<String16>& args) override;
- static void instantiate();
- virtual status_t dump(int fd, const Vector<String16>& args);
+ static constexpr const char * const kServiceName = "media.metrics";
- MediaAnalyticsService();
- virtual ~MediaAnalyticsService();
-
- bool processExpirations();
-
- private:
+private:
+ void processExpirations();
MediaAnalyticsItem::SessionID_t generateUniqueSessionID();
+ // input validation after arrival from client
+ static bool isContentValid(const MediaAnalyticsItem *item, bool isTrusted);
+ bool isRateLimited(MediaAnalyticsItem *) const;
+ void saveItem(MediaAnalyticsItem *);
- // statistics about our analytics
- int64_t mItemsSubmitted;
- int64_t mItemsFinalized;
- int64_t mItemsDiscarded;
- int64_t mItemsDiscardedExpire;
- int64_t mItemsDiscardedCount;
- MediaAnalyticsItem::SessionID_t mLastSessionID;
+ // The following methods are GUARDED_BY(mLock)
+ bool expirations_l(MediaAnalyticsItem *);
- // partitioned a bit so we don't over serialize
- mutable Mutex mLock;
- mutable Mutex mLock_ids;
- mutable Mutex mLock_mappings;
+ // support for generating output
+ void dumpQueue_l(String8 &result, int dumpProto);
+ void dumpQueue_l(String8 &result, int dumpProto, nsecs_t, const char *only);
+ void dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since);
+ void dumpSummaries_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+ void dumpRecent_l(String8 &result, int dumpProto, nsecs_t ts_since, const char * only);
+
+ // The following variables accessed without mLock
// limit how many records we'll retain
// by count (in each queue (open, finalized))
- int32_t mMaxRecords;
- // by time (none older than this long agan
- nsecs_t mMaxRecordAgeNs;
+ const size_t mMaxRecords;
+ // by time (none older than this)
+ const nsecs_t mMaxRecordAgeNs;
// max to expire per expirations_l() invocation
- int32_t mMaxRecordsExpiredAtOnce;
- //
- // # of sets of summaries
- int32_t mMaxRecordSets;
- // nsecs until we start a new record set
- nsecs_t mNewSetInterval;
+ const size_t mMaxRecordsExpiredAtOnce;
+ const int mDumpProtoDefault;
- // input validation after arrival from client
- bool contentValid(MediaAnalyticsItem *item, bool isTrusted);
- bool rateLimited(MediaAnalyticsItem *);
+ std::atomic<MediaAnalyticsItem::SessionID_t> mLastSessionID{};
- // (oldest at front) so it prints nicely for dumpsys
- List<MediaAnalyticsItem *> mItems;
- void saveItem(MediaAnalyticsItem *);
+ class UidInfo {
+ public:
+ void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
- bool expirations_l(MediaAnalyticsItem *);
- std::future<bool> mExpireFuture;
+ private:
+ std::mutex mUidInfoLock;
- // support for generating output
- int mDumpProto;
- int mDumpProtoDefault;
- String8 dumpQueue();
- String8 dumpQueue(nsecs_t, const char *only);
+ struct UidToPkgInfo {
+ uid_t uid = -1;
+ std::string pkg;
+ std::string installer;
+ int64_t versionCode = 0;
+ nsecs_t expiration = 0; // TODO: remove expiration.
+ };
- void dumpHeaders(String8 &result, nsecs_t ts_since);
- void dumpSummaries(String8 &result, nsecs_t ts_since, const char * only);
- void dumpRecent(String8 &result, nsecs_t ts_since, const char * only);
+ // TODO: use concurrent hashmap with striped lock.
+ std::unordered_map<uid_t, struct UidToPkgInfo> mPkgMappings; // GUARDED_BY(mUidInfoLock)
+ } mUidInfo; // mUidInfo can be accessed without lock (locked internally)
- // mapping uids to package names
- struct UidToPkgMap {
- uid_t uid;
- std::string pkg;
- std::string installer;
- int64_t versionCode;
- nsecs_t expiration;
- };
+ std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
- KeyedVector<uid_t,struct UidToPkgMap> mPkgMappings;
- void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
+ std::mutex mLock;
+ // statistics about our analytics
+ int64_t mItemsFinalized = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscarded = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedExpire = 0; // GUARDED_BY(mLock)
+ int64_t mItemsDiscardedCount = 0; // GUARDED_BY(mLock)
+ // If we have a worker thread to garbage collect
+ std::future<void> mExpireFuture; // GUARDED_BY(mLock)
+
+ // Our item queue, generally (oldest at front)
+ // TODO: Make separate class, use segmented queue, write lock only end.
+ // Note: Another analytics module might have ownership of an item longer than the log.
+ std::deque<std::shared_ptr<const MediaAnalyticsItem>> mItems; // GUARDED_BY(mLock)
};
-// hook to send things off to the statsd subsystem
-extern bool dump2Statsd(MediaAnalyticsItem *item);
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
-
-#endif // ANDROID_MEDIAANALYTICSSERVICE_H
+} // namespace android
diff --git a/services/mediaanalytics/OWNERS b/services/mediaanalytics/OWNERS
index 9af258b..e37a1f8 100644
--- a/services/mediaanalytics/OWNERS
+++ b/services/mediaanalytics/OWNERS
@@ -1 +1,2 @@
essick@google.com
+hunga@google.com
diff --git a/services/mediaanalytics/iface_statsd.cpp b/services/mediaanalytics/iface_statsd.cpp
index 6fd4415..e02c9cf 100644
--- a/services/mediaanalytics/iface_statsd.cpp
+++ b/services/mediaanalytics/iface_statsd.cpp
@@ -52,7 +52,7 @@
};
// keep this sorted, so we can do binary searches
-struct statsd_hooks statsd_handlers[] =
+static constexpr struct statsd_hooks statsd_handlers[] =
{
{ "audiopolicy", statsd_audiopolicy },
{ "audiorecord", statsd_audiorecord },
@@ -68,7 +68,6 @@
{ "recorder", statsd_recorder },
};
-
// give me a record, i'll look at the type and upload appropriately
bool dump2Statsd(MediaAnalyticsItem *item) {
if (item == NULL) return false;
@@ -81,10 +80,9 @@
return false;
}
- int i;
- for(i = 0;i < sizeof(statsd_handlers) / sizeof(statsd_handlers[0]) ; i++) {
- if (key == statsd_handlers[i].key) {
- return (*statsd_handlers[i].handler)(item);
+ for (const auto &statsd_handler : statsd_handlers) {
+ if (key == statsd_handler.key) {
+ return statsd_handler.handler(item);
}
}
return false;
diff --git a/services/mediaanalytics/main_mediametrics.cpp b/services/mediaanalytics/main_mediametrics.cpp
index 8020a03..6833fe2 100644
--- a/services/mediaanalytics/main_mediametrics.cpp
+++ b/services/mediaanalytics/main_mediametrics.cpp
@@ -16,33 +16,33 @@
#define LOG_TAG "mediametrics"
//#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 "MediaAnalyticsService.h"
-using namespace android;
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
+
int main(int argc __unused, char **argv __unused)
{
+ using namespace android;
+
signal(SIGPIPE, SIG_IGN);
// to match the service name
// we're replacing "/system/bin/mediametrics" with "media.metrics"
// we add a ".", but discard the path components: we finish with a shorter string
- strcpy(argv[0], "media.metrics");
+ strcpy(argv[0], MediaAnalyticsService::kServiceName);
- sp<ProcessState> proc(ProcessState::self());
- sp<IServiceManager> sm(defaultServiceManager());
- ALOGI("ServiceManager: %p", sm.get());
+ defaultServiceManager()->addService(
+ String16(MediaAnalyticsService::kServiceName), new MediaAnalyticsService());
- MediaAnalyticsService::instantiate();
-
- ProcessState::self()->startThreadPool();
+ sp<ProcessState> processState(ProcessState::self());
+ // processState->setThreadPoolMaxThreadCount(8);
+ processState->startThreadPool();
IPCThreadState::self()->joinThreadPool();
+
+ return EXIT_SUCCESS;
}
diff --git a/services/mediaanalytics/tests/Android.bp b/services/mediaanalytics/tests/Android.bp
new file mode 100644
index 0000000..7bca1c4
--- /dev/null
+++ b/services/mediaanalytics/tests/Android.bp
@@ -0,0 +1,25 @@
+cc_test {
+ name: "mediametrics_tests",
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+
+ include_dirs: [
+ "frameworks/av/services/mediaanalytics",
+ ],
+
+ shared_libs: [
+ "libbinder",
+ "liblog",
+ "libmediaanalyticsservice",
+ "libmediametrics",
+ "libutils",
+ ],
+
+ srcs: [
+ "mediametrics_tests.cpp",
+ ],
+}
diff --git a/services/mediaanalytics/tests/build_and_run_all_unit_tests.sh b/services/mediaanalytics/tests/build_and_run_all_unit_tests.sh
new file mode 100755
index 0000000..2511c30
--- /dev/null
+++ b/services/mediaanalytics/tests/build_and_run_all_unit_tests.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Run tests in this directory.
+#
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+ echo "Android build environment not set"
+ exit -1
+fi
+
+# ensure we have mm
+. $ANDROID_BUILD_TOP/build/envsetup.sh
+
+mm
+
+echo "waiting for device"
+
+adb root && adb wait-for-device remount
+
+echo "========================================"
+
+echo "testing mediametrics"
+adb push $OUT/data/nativetest/mediametrics_tests/mediametrics_tests /system/bin
+adb shell /system/bin/mediametrics_tests
diff --git a/services/mediaanalytics/tests/mediametrics_tests.cpp b/services/mediaanalytics/tests/mediametrics_tests.cpp
new file mode 100644
index 0000000..794b2f0
--- /dev/null
+++ b/services/mediaanalytics/tests/mediametrics_tests.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2019 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 "mediametrics_tests"
+#include <utils/Log.h>
+
+#include "MediaAnalyticsService.h"
+
+#include <stdio.h>
+
+#include <gtest/gtest.h>
+#include <media/MediaAnalyticsItem.h>
+
+using namespace android;
+
+TEST(mediametrics_tests, instantiate) {
+ sp mediaMetrics = new MediaAnalyticsService();
+ status_t status;
+
+ // NOTE: submission of items to MediaMetrics releases ownership, even on error.
+
+ // random keys ignored when empty
+ status = mediaMetrics->submit(MediaAnalyticsItem::create("random_key"), false);
+ ASSERT_EQ(MediaAnalyticsItem::SessionIDInvalid, status);
+
+ // random keys ignored with data
+ auto random_key = MediaAnalyticsItem::create("random_key");
+ random_key->setInt32("foo", 10);
+ status = mediaMetrics->submit(random_key, false);
+ ASSERT_EQ(MediaAnalyticsItem::SessionIDInvalid, status);
+
+ // known keys ignored if empty
+ status = mediaMetrics->submit(MediaAnalyticsItem::create("audiotrack"), false);
+ ASSERT_EQ(MediaAnalyticsItem::SessionIDInvalid, status);
+
+ auto audiotrack = MediaAnalyticsItem::create("audiotrack");
+ audiotrack->addInt32("foo", 10);
+ status = mediaMetrics->submit(audiotrack, false);
+ ASSERT_GT(status, MediaAnalyticsItem::SessionIDNone);
+
+ mediaMetrics->dump(fileno(stdout), {} /* args */);
+}