Merge "Camera: Don't advertize zoom in case max digital zoom is 1" into oc-dev am: 0ba7cd8501
am: 01612a5621
Change-Id: I6893eac824dcfc277a4a047240281a78a7d765ab
diff --git a/media/libaaudio/examples/input_monitor/Android.mk b/media/libaaudio/examples/input_monitor/Android.mk
new file mode 100644
index 0000000..b56328b
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/Android.mk
@@ -0,0 +1,6 @@
+# include $(call all-subdir-makefiles)
+
+# Just include static/ for now.
+LOCAL_PATH := $(call my-dir)
+#include $(LOCAL_PATH)/jni/Android.mk
+include $(LOCAL_PATH)/static/Android.mk
diff --git a/media/libaaudio/examples/input_monitor/README.md b/media/libaaudio/examples/input_monitor/README.md
new file mode 100644
index 0000000..3e54ef0
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/README.md
@@ -0,0 +1 @@
+Monitor input level and print value.
diff --git a/media/libaaudio/examples/input_monitor/jni/Android.mk b/media/libaaudio/examples/input_monitor/jni/Android.mk
new file mode 100644
index 0000000..51a5a85
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/jni/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+ libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/liboboe/include
+
+LOCAL_SRC_FILES:= frameworks/av/media/liboboe/src/write_sine_threaded.cpp
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia libtinyalsa \
+ libbinder libcutils libutils
+LOCAL_STATIC_LIBRARIES := libsndfile
+LOCAL_MODULE := write_sine_threaded_ndk
+LOCAL_SHARED_LIBRARIES += liboboe_prebuilt
+include $(BUILD_EXECUTABLE)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := liboboe_prebuilt
+LOCAL_SRC_FILES := liboboe.so
+LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
+include $(PREBUILT_SHARED_LIBRARY)
diff --git a/media/libaaudio/examples/input_monitor/jni/Application.mk b/media/libaaudio/examples/input_monitor/jni/Application.mk
new file mode 100644
index 0000000..e74475c
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/jni/Application.mk
@@ -0,0 +1,3 @@
+# TODO remove then when we support other architectures
+APP_ABI := arm64-v8a
+APP_CPPFLAGS += -std=c++11
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp
new file mode 100644
index 0000000..545496f
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/src/input_monitor.cpp
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Record input using AAudio and display the peak amplitudes.
+
+#include <new>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
+
+#define SAMPLE_RATE 48000
+#define NUM_SECONDS 10
+#define NANOS_PER_MICROSECOND ((int64_t)1000)
+#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
+#define NANOS_PER_SECOND (NANOS_PER_MILLISECOND * 1000)
+
+#define DECAY_FACTOR 0.999
+#define MIN_FRAMES_TO_READ 48 /* arbitrary, 1 msec at 48000 Hz */
+
+static const char *getSharingModeText(aaudio_sharing_mode_t mode) {
+ const char *modeText = "unknown";
+ switch (mode) {
+ case AAUDIO_SHARING_MODE_EXCLUSIVE:
+ modeText = "EXCLUSIVE";
+ break;
+ case AAUDIO_SHARING_MODE_SHARED:
+ modeText = "SHARED";
+ break;
+ default:
+ break;
+ }
+ return modeText;
+}
+
+int main(int argc, char **argv)
+{
+ (void)argc; // unused
+
+ aaudio_result_t result;
+
+ int actualSamplesPerFrame;
+ int actualSampleRate;
+ const aaudio_audio_format_t requestedDataFormat = AAUDIO_FORMAT_PCM_I16;
+ aaudio_audio_format_t actualDataFormat;
+
+ const aaudio_sharing_mode_t requestedSharingMode = AAUDIO_SHARING_MODE_SHARED;
+ aaudio_sharing_mode_t actualSharingMode;
+
+ AAudioStreamBuilder *aaudioBuilder = nullptr;
+ AAudioStream *aaudioStream = nullptr;
+ aaudio_stream_state_t state;
+ int32_t framesPerBurst = 0;
+ int32_t framesPerRead = 0;
+ int32_t framesToRecord = 0;
+ int32_t framesLeft = 0;
+ int32_t xRunCount = 0;
+ int16_t *data = nullptr;
+ float peakLevel = 0.0;
+ int loopCounter = 0;
+
+ // Make printf print immediately so that debug info is not stuck
+ // in a buffer if we hang or crash.
+ setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+
+ printf("%s - Monitor input level using AAudio\n", argv[0]);
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ result = AAudio_createStreamBuilder(&aaudioBuilder);
+ if (result != AAUDIO_OK) {
+ goto finish;
+ }
+
+ // Request stream properties.
+ AAudioStreamBuilder_setDirection(aaudioBuilder, AAUDIO_DIRECTION_INPUT);
+ AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat);
+ AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode);
+
+ // Create an AAudioStream using the Builder.
+ result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
+ if (result != AAUDIO_OK) {
+ goto finish;
+ }
+
+ actualSamplesPerFrame = AAudioStream_getSamplesPerFrame(aaudioStream);
+ printf("SamplesPerFrame = %d\n", actualSamplesPerFrame);
+ actualSampleRate = AAudioStream_getSampleRate(aaudioStream);
+ printf("SamplesPerFrame = %d\n", actualSampleRate);
+
+ actualSharingMode = AAudioStream_getSharingMode(aaudioStream);
+ printf("SharingMode: requested = %s, actual = %s\n",
+ getSharingModeText(requestedSharingMode),
+ getSharingModeText(actualSharingMode));
+
+ // This is the number of frames that are written in one chunk by a DMA controller
+ // or a DSP.
+ framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream);
+ printf("DataFormat: framesPerBurst = %d\n",framesPerBurst);
+
+ // Some DMA might use very short bursts of 16 frames. We don't need to read such small
+ // buffers. But it helps to use a multiple of the burst size for predictable scheduling.
+ framesPerRead = framesPerBurst;
+ while (framesPerRead < MIN_FRAMES_TO_READ) {
+ framesPerRead *= 2;
+ }
+ printf("DataFormat: framesPerRead = %d\n",framesPerRead);
+
+ actualDataFormat = AAudioStream_getFormat(aaudioStream);
+ printf("DataFormat: requested = %d, actual = %d\n", requestedDataFormat, actualDataFormat);
+ // TODO handle other data formats
+ assert(actualDataFormat == AAUDIO_FORMAT_PCM_I16);
+
+ // Allocate a buffer for the audio data.
+ data = new(std::nothrow) int16_t[framesPerRead * actualSamplesPerFrame];
+ if (data == nullptr) {
+ fprintf(stderr, "ERROR - could not allocate data buffer\n");
+ result = AAUDIO_ERROR_NO_MEMORY;
+ goto finish;
+ }
+
+ // Start the stream.
+ printf("call AAudioStream_requestStart()\n");
+ result = AAudioStream_requestStart(aaudioStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d\n", result);
+ goto finish;
+ }
+
+ state = AAudioStream_getState(aaudioStream);
+ printf("after start, state = %s\n", AAudio_convertStreamStateToText(state));
+
+ // Play for a while.
+ framesToRecord = actualSampleRate * NUM_SECONDS;
+ framesLeft = framesToRecord;
+ while (framesLeft > 0) {
+ // Read audio data from the stream.
+ int64_t timeoutNanos = 100 * NANOS_PER_MILLISECOND;
+ int minFrames = (framesToRecord < framesPerRead) ? framesToRecord : framesPerRead;
+ int actual = AAudioStream_read(aaudioStream, data, minFrames, timeoutNanos);
+ if (actual < 0) {
+ fprintf(stderr, "ERROR - AAudioStream_read() returned %zd\n", actual);
+ goto finish;
+ } else if (actual == 0) {
+ fprintf(stderr, "WARNING - AAudioStream_read() returned %zd\n", actual);
+ goto finish;
+ }
+ framesLeft -= actual;
+
+ // Peak follower.
+ for (int frameIndex = 0; frameIndex < actual; frameIndex++) {
+ float sample = data[frameIndex * actualSamplesPerFrame] * (1.0/32768);
+ peakLevel *= DECAY_FACTOR;
+ if (sample > peakLevel) {
+ peakLevel = sample;
+ }
+ }
+
+ // Display level as stars, eg. "******".
+ if ((loopCounter++ % 10) == 0) {
+ printf("%5.3f ", peakLevel);
+ int numStars = (int)(peakLevel * 50);
+ for (int i = 0; i < numStars; i++) {
+ printf("*");
+ }
+ printf("\n");
+ }
+ }
+
+ xRunCount = AAudioStream_getXRunCount(aaudioStream);
+ printf("AAudioStream_getXRunCount %d\n", xRunCount);
+
+finish:
+ delete[] data;
+ AAudioStream_close(aaudioStream);
+ AAudioStreamBuilder_delete(aaudioBuilder);
+ printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+ return (result != AAUDIO_OK) ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+
diff --git a/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
new file mode 100644
index 0000000..8d40d94
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/src/input_monitor_callback.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Record input using AAudio and display the peak amplitudes.
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
+
+#define NUM_SECONDS 10
+#define NANOS_PER_MICROSECOND ((int64_t)1000)
+#define NANOS_PER_MILLISECOND (NANOS_PER_MICROSECOND * 1000)
+#define NANOS_PER_SECOND (NANOS_PER_MILLISECOND * 1000)
+
+//#define SHARING_MODE AAUDIO_SHARING_MODE_EXCLUSIVE
+#define SHARING_MODE AAUDIO_SHARING_MODE_SHARED
+
+/**
+ * Simple wrapper for AAudio that opens a default stream and then calls
+ * a callback function to fill the output buffers.
+ */
+class SimpleAAudioPlayer {
+public:
+ SimpleAAudioPlayer() {}
+ ~SimpleAAudioPlayer() {
+ close();
+ };
+
+ /**
+ * Call this before calling open().
+ * @param requestedSharingMode
+ */
+ void setSharingMode(aaudio_sharing_mode_t requestedSharingMode) {
+ mRequestedSharingMode = requestedSharingMode;
+ }
+
+ /**
+ * Also known as "sample rate"
+ * Only call this after open() has been called.
+ */
+ int32_t getFramesPerSecond() {
+ if (mStream == nullptr) {
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+ return AAudioStream_getSampleRate(mStream);;
+ }
+
+ /**
+ * Only call this after open() has been called.
+ */
+ int32_t getSamplesPerFrame() {
+ if (mStream == nullptr) {
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+ return AAudioStream_getSamplesPerFrame(mStream);;
+ }
+
+ /**
+ * Open a stream
+ */
+ aaudio_result_t open(AAudioStream_dataCallback proc, void *userContext) {
+ aaudio_result_t result = AAUDIO_OK;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ result = AAudio_createStreamBuilder(&mBuilder);
+ if (result != AAUDIO_OK) return result;
+
+ AAudioStreamBuilder_setDirection(mBuilder, AAUDIO_DIRECTION_INPUT);
+ AAudioStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
+ AAudioStreamBuilder_setDataCallback(mBuilder, proc, userContext);
+ AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_I16);
+
+ // Open an AAudioStream using the Builder.
+ result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStreamBuilder_openStream() returned %d %s\n",
+ result, AAudio_convertResultToText(result));
+ goto finish1;
+ }
+
+ printf("AAudioStream_getFramesPerBurst() = %d\n",
+ AAudioStream_getFramesPerBurst(mStream));
+ printf("AAudioStream_getBufferSizeInFrames() = %d\n",
+ AAudioStream_getBufferSizeInFrames(mStream));
+ printf("AAudioStream_getBufferCapacityInFrames() = %d\n",
+ AAudioStream_getBufferCapacityInFrames(mStream));
+ return result;
+
+ finish1:
+ AAudioStreamBuilder_delete(mBuilder);
+ mBuilder = nullptr;
+ return result;
+ }
+
+ aaudio_result_t close() {
+ if (mStream != nullptr) {
+ printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout);
+ AAudioStream_close(mStream);
+ mStream = nullptr;
+ AAudioStreamBuilder_delete(mBuilder);
+ mBuilder = nullptr;
+ }
+ return AAUDIO_OK;
+ }
+
+ // Write zero data to fill up the buffer and prevent underruns.
+ // Assume format is PCM_I16. TODO use floats.
+ aaudio_result_t prime() {
+ int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(mStream);
+ const int numFrames = 32; // arbitrary
+ int16_t zeros[numFrames * samplesPerFrame];
+ memset(zeros, 0, sizeof(zeros));
+ aaudio_result_t result = numFrames;
+ while (result == numFrames) {
+ result = AAudioStream_write(mStream, zeros, numFrames, 0);
+ }
+ return result;
+ }
+
+ // Start the stream. AAudio will start calling your callback function.
+ aaudio_result_t start() {
+ aaudio_result_t result = AAudioStream_requestStart(mStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d %s\n",
+ result, AAudio_convertResultToText(result));
+ }
+ return result;
+ }
+
+ // Stop the stream. AAudio will stop calling your callback function.
+ aaudio_result_t stop() {
+ aaudio_result_t result = AAudioStream_requestStop(mStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_requestStop() returned %d %s\n",
+ result, AAudio_convertResultToText(result));
+ }
+ int32_t xRunCount = AAudioStream_getXRunCount(mStream);
+ printf("AAudioStream_getXRunCount %d\n", xRunCount);
+ return result;
+ }
+
+private:
+ AAudioStreamBuilder *mBuilder = nullptr;
+ AAudioStream *mStream = nullptr;
+ aaudio_sharing_mode_t mRequestedSharingMode = SHARING_MODE;
+};
+
+// Application data that gets passed to the callback.
+typedef struct PeakTrackerData {
+ float peakLevel;
+} PeakTrackerData_t;
+
+#define DECAY_FACTOR 0.999
+
+// Callback function that fills the audio output buffer.
+aaudio_data_callback_result_t MyDataCallbackProc(
+ AAudioStream *stream,
+ void *userData,
+ void *audioData,
+ int32_t numFrames
+ ) {
+
+ PeakTrackerData_t *data = (PeakTrackerData_t *) userData;
+ // printf("MyCallbackProc(): frameCount = %d\n", numFrames);
+ int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(stream);
+ float sample;
+ // This code assume mono or stereo.
+ switch (AAudioStream_getFormat(stream)) {
+ case AAUDIO_FORMAT_PCM_I16: {
+ int16_t *audioBuffer = (int16_t *) audioData;
+ // Peak follower
+ for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+ sample = audioBuffer[frameIndex * samplesPerFrame] * (1.0/32768);
+ data->peakLevel *= DECAY_FACTOR;
+ if (sample > data->peakLevel) {
+ data->peakLevel = sample;
+ }
+ }
+ }
+ break;
+ case AAUDIO_FORMAT_PCM_FLOAT: {
+ float *audioBuffer = (float *) audioData;
+ // Peak follower
+ for (int frameIndex = 0; frameIndex < numFrames; frameIndex++) {
+ sample = audioBuffer[frameIndex * samplesPerFrame];
+ data->peakLevel *= DECAY_FACTOR;
+ if (sample > data->peakLevel) {
+ data->peakLevel = sample;
+ }
+ }
+ }
+ break;
+ default:
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+void displayPeakLevel(float peakLevel) {
+ printf("%5.3f ", peakLevel);
+ const int maxStars = 50; // arbitrary, fits on one line
+ int numStars = (int) (peakLevel * maxStars);
+ for (int i = 0; i < numStars; i++) {
+ printf("*");
+ }
+ printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+ (void)argc; // unused
+ SimpleAAudioPlayer player;
+ PeakTrackerData_t myData = {0.0};
+ aaudio_result_t result;
+ const int displayRateHz = 20; // arbitrary
+ const int loopsNeeded = NUM_SECONDS * displayRateHz;
+
+ // Make printf print immediately so that debug info is not stuck
+ // in a buffer if we hang or crash.
+ setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+ printf("%s - Display audio input using an AAudio callback\n", argv[0]);
+
+ player.setSharingMode(SHARING_MODE);
+
+ result = player.open(MyDataCallbackProc, &myData);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - player.open() returned %d\n", result);
+ goto error;
+ }
+ printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
+ printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+
+ result = player.start();
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - player.start() returned %d\n", result);
+ goto error;
+ }
+
+ printf("Sleep for %d seconds while audio plays in a callback thread.\n", NUM_SECONDS);
+ for (int i = 0; i < loopsNeeded; i++)
+ {
+ const struct timespec request = { .tv_sec = 0,
+ .tv_nsec = NANOS_PER_SECOND / displayRateHz };
+ (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+ displayPeakLevel(myData.peakLevel);
+ }
+ printf("Woke up now.\n");
+
+ result = player.stop();
+ if (result != AAUDIO_OK) {
+ goto error;
+ }
+ result = player.close();
+ if (result != AAUDIO_OK) {
+ goto error;
+ }
+
+ printf("SUCCESS\n");
+ return EXIT_SUCCESS;
+error:
+ player.close();
+ printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+ return EXIT_FAILURE;
+}
+
diff --git a/media/libaaudio/examples/input_monitor/static/Android.mk b/media/libaaudio/examples/input_monitor/static/Android.mk
new file mode 100644
index 0000000..e83f179
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/static/Android.mk
@@ -0,0 +1,35 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := examples
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/libaaudio/include
+
+# TODO reorganize folders to avoid using ../
+LOCAL_SRC_FILES:= ../src/input_monitor.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+ libbinder libcutils libutils \
+ libaudioclient liblog libtinyalsa
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := input_monitor
+include $(BUILD_EXECUTABLE)
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/libaaudio/include
+
+LOCAL_SRC_FILES:= ../src/input_monitor_callback.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+ libbinder libcutils libutils \
+ libaudioclient liblog
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := input_monitor_callback
+include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/examples/input_monitor/static/README.md b/media/libaaudio/examples/input_monitor/static/README.md
new file mode 100644
index 0000000..6e26d7b
--- /dev/null
+++ b/media/libaaudio/examples/input_monitor/static/README.md
@@ -0,0 +1,2 @@
+Makefile for building simple command line examples.
+They link with AAudio as a static library.
diff --git a/media/libaaudio/examples/write_sine/src/SineGenerator.h b/media/libaaudio/examples/write_sine/src/SineGenerator.h
index ade7527..64b772d 100644
--- a/media/libaaudio/examples/write_sine/src/SineGenerator.h
+++ b/media/libaaudio/examples/write_sine/src/SineGenerator.h
@@ -79,7 +79,7 @@
}
}
- double mAmplitude = 0.01;
+ double mAmplitude = 0.05; // unitless scaler
double mPhase = 0.0;
double mPhaseIncrement = 440 * M_PI * 2 / 48000;
double mFrameRate = 48000;
diff --git a/media/libaaudio/examples/write_sine/src/write_sine.cpp b/media/libaaudio/examples/write_sine/src/write_sine.cpp
index 80b6252..d8e5ec1 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine.cpp
@@ -19,7 +19,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
-#include <aaudio/AAudioDefinitions.h>
#include <aaudio/AAudio.h>
#include "SineGenerator.h"
@@ -44,6 +43,7 @@
return modeText;
}
+// TODO move to a common utility library
static int64_t getNanoseconds(clockid_t clockId = CLOCK_MONOTONIC) {
struct timespec time;
int result = clock_gettime(clockId, &time);
@@ -74,6 +74,8 @@
AAudioStream *aaudioStream = nullptr;
aaudio_stream_state_t state = AAUDIO_STREAM_STATE_UNINITIALIZED;
int32_t framesPerBurst = 0;
+ int32_t framesPerWrite = 0;
+ int32_t bufferCapacity = 0;
int32_t framesToPlay = 0;
int32_t framesLeft = 0;
int32_t xRunCount = 0;
@@ -100,7 +102,6 @@
AAudioStreamBuilder_setFormat(aaudioBuilder, requestedDataFormat);
AAudioStreamBuilder_setSharingMode(aaudioBuilder, requestedSharingMode);
-
// Create an AAudioStream using the Builder.
result = AAudioStreamBuilder_openStream(aaudioBuilder, &aaudioStream);
if (result != AAUDIO_OK) {
@@ -129,21 +130,25 @@
// This is the number of frames that are read in one chunk by a DMA controller
// or a DSP or a mixer.
framesPerBurst = AAudioStream_getFramesPerBurst(aaudioStream);
- printf("DataFormat: original framesPerBurst = %d\n",framesPerBurst);
+ printf("DataFormat: framesPerBurst = %d\n",framesPerBurst);
+ bufferCapacity = AAudioStream_getBufferCapacityInFrames(aaudioStream);
+ printf("DataFormat: bufferCapacity = %d, remainder = %d\n",
+ bufferCapacity, bufferCapacity % framesPerBurst);
// Some DMA might use very short bursts of 16 frames. We don't need to write such small
// buffers. But it helps to use a multiple of the burst size for predictable scheduling.
- while (framesPerBurst < 48) {
- framesPerBurst *= 2;
+ framesPerWrite = framesPerBurst;
+ while (framesPerWrite < 48) {
+ framesPerWrite *= 2;
}
- printf("DataFormat: final framesPerBurst = %d\n",framesPerBurst);
+ printf("DataFormat: framesPerWrite = %d\n",framesPerWrite);
actualDataFormat = AAudioStream_getFormat(aaudioStream);
printf("DataFormat: requested = %d, actual = %d\n", requestedDataFormat, actualDataFormat);
// TODO handle other data formats
// Allocate a buffer for the audio data.
- data = new int16_t[framesPerBurst * actualSamplesPerFrame];
+ data = new int16_t[framesPerWrite * actualSamplesPerFrame];
if (data == nullptr) {
fprintf(stderr, "ERROR - could not allocate data buffer\n");
result = AAUDIO_ERROR_NO_MEMORY;
@@ -166,14 +171,14 @@
framesLeft = framesToPlay;
while (framesLeft > 0) {
// Render sine waves to left and right channels.
- sineOsc1.render(&data[0], actualSamplesPerFrame, framesPerBurst);
+ sineOsc1.render(&data[0], actualSamplesPerFrame, framesPerWrite);
if (actualSamplesPerFrame > 1) {
- sineOsc2.render(&data[1], actualSamplesPerFrame, framesPerBurst);
+ sineOsc2.render(&data[1], actualSamplesPerFrame, framesPerWrite);
}
// Write audio data to the stream.
int64_t timeoutNanos = 100 * NANOS_PER_MILLISECOND;
- int minFrames = (framesToPlay < framesPerBurst) ? framesToPlay : framesPerBurst;
+ int minFrames = (framesToPlay < framesPerWrite) ? framesToPlay : framesPerWrite;
int actual = AAudioStream_write(aaudioStream, data, minFrames, timeoutNanos);
if (actual < 0) {
fprintf(stderr, "ERROR - AAudioStream_write() returned %zd\n", actual);
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
new file mode 100644
index 0000000..9414236
--- /dev/null
+++ b/media/libaaudio/examples/write_sine/src/write_sine_callback.cpp
@@ -0,0 +1,320 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+// Play sine waves using an AAudio callback.
+
+#include <assert.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sched.h>
+#include <stdio.h>
+#include <math.h>
+#include <time.h>
+#include <aaudio/AAudio.h>
+#include "SineGenerator.h"
+
+#define NUM_SECONDS 5
+
+//#define SHARING_MODE AAUDIO_SHARING_MODE_EXCLUSIVE
+#define SHARING_MODE AAUDIO_SHARING_MODE_SHARED
+
+#define CALLBACK_SIZE_FRAMES 128
+
+// TODO refactor common code into a single SimpleAAudio class
+/**
+ * Simple wrapper for AAudio that opens a default stream and then calls
+ * a callback function to fill the output buffers.
+ */
+class SimpleAAudioPlayer {
+public:
+ SimpleAAudioPlayer() {}
+ ~SimpleAAudioPlayer() {
+ close();
+ };
+
+ /**
+ * Call this before calling open().
+ * @param requestedSharingMode
+ */
+ void setSharingMode(aaudio_sharing_mode_t requestedSharingMode) {
+ mRequestedSharingMode = requestedSharingMode;
+ }
+
+ /**
+ * Also known as "sample rate"
+ * Only call this after open() has been called.
+ */
+ int32_t getFramesPerSecond() {
+ if (mStream == nullptr) {
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+ return AAudioStream_getSampleRate(mStream);;
+ }
+
+ /**
+ * Only call this after open() has been called.
+ */
+ int32_t getSamplesPerFrame() {
+ if (mStream == nullptr) {
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+ return AAudioStream_getSamplesPerFrame(mStream);;
+ }
+
+ /**
+ * Open a stream
+ */
+ aaudio_result_t open(AAudioStream_dataCallback dataProc, void *userContext) {
+ aaudio_result_t result = AAUDIO_OK;
+
+ // Use an AAudioStreamBuilder to contain requested parameters.
+ result = AAudio_createStreamBuilder(&mBuilder);
+ if (result != AAUDIO_OK) return result;
+
+ AAudioStreamBuilder_setSharingMode(mBuilder, mRequestedSharingMode);
+ AAudioStreamBuilder_setDataCallback(mBuilder, dataProc, userContext);
+ AAudioStreamBuilder_setFormat(mBuilder, AAUDIO_FORMAT_PCM_FLOAT);
+ AAudioStreamBuilder_setFramesPerDataCallback(mBuilder, CALLBACK_SIZE_FRAMES);
+ // AAudioStreamBuilder_setBufferCapacityInFrames(mBuilder, CALLBACK_SIZE_FRAMES * 4);
+
+ // Open an AAudioStream using the Builder.
+ result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
+ if (result != AAUDIO_OK) goto finish1;
+
+ printf("AAudioStream_getFramesPerBurst() = %d\n",
+ AAudioStream_getFramesPerBurst(mStream));
+ printf("AAudioStream_getBufferSizeInFrames() = %d\n",
+ AAudioStream_getBufferSizeInFrames(mStream));
+ printf("AAudioStream_getBufferCapacityInFrames() = %d\n",
+ AAudioStream_getBufferCapacityInFrames(mStream));
+ return result;
+
+ finish1:
+ AAudioStreamBuilder_delete(mBuilder);
+ mBuilder = nullptr;
+ return result;
+ }
+
+ aaudio_result_t close() {
+ if (mStream != nullptr) {
+ printf("call AAudioStream_close(%p)\n", mStream); fflush(stdout);
+ AAudioStream_close(mStream);
+ mStream = nullptr;
+ AAudioStreamBuilder_delete(mBuilder);
+ mBuilder = nullptr;
+ }
+ return AAUDIO_OK;
+ }
+
+ // Write zero data to fill up the buffer and prevent underruns.
+ aaudio_result_t prime() {
+ int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(mStream);
+ const int numFrames = 32;
+ float zeros[numFrames * samplesPerFrame];
+ memset(zeros, 0, sizeof(zeros));
+ aaudio_result_t result = numFrames;
+ while (result == numFrames) {
+ result = AAudioStream_write(mStream, zeros, numFrames, 0);
+ }
+ return result;
+ }
+
+ // Start the stream. AAudio will start calling your callback function.
+ aaudio_result_t start() {
+ aaudio_result_t result = AAudioStream_requestStart(mStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_requestStart() returned %d %s\n",
+ result, AAudio_convertResultToText(result));
+ }
+ return result;
+ }
+
+ // Stop the stream. AAudio will stop calling your callback function.
+ aaudio_result_t stop() {
+ aaudio_result_t result = AAudioStream_requestStop(mStream);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_requestStop() returned %d %s\n",
+ result, AAudio_convertResultToText(result));
+ }
+ int32_t xRunCount = AAudioStream_getXRunCount(mStream);
+ printf("AAudioStream_getXRunCount %d\n", xRunCount);
+ return result;
+ }
+
+ AAudioStream *getStream() const {
+ return mStream;
+ }
+
+private:
+ AAudioStreamBuilder *mBuilder = nullptr;
+ AAudioStream *mStream = nullptr;
+ aaudio_sharing_mode_t mRequestedSharingMode = SHARING_MODE;
+};
+
+// Application data that gets passed to the callback.
+#define MAX_FRAME_COUNT_RECORDS 256
+typedef struct SineThreadedData_s {
+ SineGenerator sineOsc1;
+ SineGenerator sineOsc2;
+ // Remove these variables used for testing.
+ int32_t numFrameCounts;
+ int32_t frameCounts[MAX_FRAME_COUNT_RECORDS];
+ int scheduler;
+ bool schedulerChecked;
+} SineThreadedData_t;
+
+// Callback function that fills the audio output buffer.
+aaudio_data_callback_result_t MyDataCallbackProc(
+ AAudioStream *stream,
+ void *userData,
+ void *audioData,
+ int32_t numFrames
+ ) {
+
+ SineThreadedData_t *sineData = (SineThreadedData_t *) userData;
+
+ if (sineData->numFrameCounts < MAX_FRAME_COUNT_RECORDS) {
+ sineData->frameCounts[sineData->numFrameCounts++] = numFrames;
+ }
+
+ if (!sineData->schedulerChecked) {
+ sineData->scheduler = sched_getscheduler(gettid());
+ sineData->schedulerChecked = true;
+ }
+
+ int32_t samplesPerFrame = AAudioStream_getSamplesPerFrame(stream);
+ // This code only plays on the first one or two channels.
+ // TODO Support arbitrary number of channels.
+ switch (AAudioStream_getFormat(stream)) {
+ case AAUDIO_FORMAT_PCM_I16: {
+ int16_t *audioBuffer = (int16_t *) audioData;
+ // Render sine waves as shorts to first channel.
+ sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
+ // Render sine waves to second channel if there is one.
+ if (samplesPerFrame > 1) {
+ sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
+ }
+ }
+ break;
+ case AAUDIO_FORMAT_PCM_FLOAT: {
+ float *audioBuffer = (float *) audioData;
+ // Render sine waves as floats to first channel.
+ sineData->sineOsc1.render(&audioBuffer[0], samplesPerFrame, numFrames);
+ // Render sine waves to second channel if there is one.
+ if (samplesPerFrame > 1) {
+ sineData->sineOsc2.render(&audioBuffer[1], samplesPerFrame, numFrames);
+ }
+ }
+ break;
+ default:
+ return AAUDIO_CALLBACK_RESULT_STOP;
+ }
+
+ return AAUDIO_CALLBACK_RESULT_CONTINUE;
+}
+
+int main(int argc, char **argv)
+{
+ (void)argc; // unused
+ SimpleAAudioPlayer player;
+ SineThreadedData_t myData;
+ aaudio_result_t result;
+
+ // Make printf print immediately so that debug info is not stuck
+ // in a buffer if we hang or crash.
+ setvbuf(stdout, nullptr, _IONBF, (size_t) 0);
+ printf("%s - Play a sine sweep using an AAudio callback\n", argv[0]);
+
+ player.setSharingMode(SHARING_MODE);
+
+ myData.numFrameCounts = 0;
+ myData.schedulerChecked = false;
+
+ result = player.open(MyDataCallbackProc, &myData);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - player.open() returned %d\n", result);
+ goto error;
+ }
+ printf("player.getFramesPerSecond() = %d\n", player.getFramesPerSecond());
+ printf("player.getSamplesPerFrame() = %d\n", player.getSamplesPerFrame());
+ myData.sineOsc1.setup(440.0, 48000);
+ myData.sineOsc1.setSweep(300.0, 600.0, 5.0);
+ myData.sineOsc2.setup(660.0, 48000);
+ myData.sineOsc2.setSweep(350.0, 900.0, 7.0);
+
+#if 0
+ result = player.prime(); // FIXME crashes AudioTrack.cpp
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - player.prime() returned %d\n", result);
+ goto error;
+ }
+#endif
+
+ result = player.start();
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - player.start() returned %d\n", result);
+ goto error;
+ }
+
+ printf("Sleep for %d seconds while audio plays in a callback thread.\n", NUM_SECONDS);
+ for (int second = 0; second < NUM_SECONDS; second++)
+ {
+ const struct timespec request = { .tv_sec = 1, .tv_nsec = 0 };
+ (void) clock_nanosleep(CLOCK_MONOTONIC, 0 /*flags*/, &request, NULL /*remain*/);
+
+ aaudio_stream_state_t state;
+ result = AAudioStream_waitForStateChange(player.getStream(),
+ AAUDIO_STREAM_STATE_CLOSED,
+ &state,
+ 0);
+ if (result != AAUDIO_OK) {
+ fprintf(stderr, "ERROR - AAudioStream_waitForStateChange() returned %d\n", result);
+ goto error;
+ }
+ if (state != AAUDIO_STREAM_STATE_STARTING && state != AAUDIO_STREAM_STATE_STARTED) {
+ printf("Stream state is %d %s!\n", state, AAudio_convertStreamStateToText(state));
+ break;
+ }
+ }
+ printf("Woke up now.\n");
+
+ result = player.stop();
+ if (result != AAUDIO_OK) {
+ goto error;
+ }
+ result = player.close();
+ if (result != AAUDIO_OK) {
+ goto error;
+ }
+
+ // Report data gathered in the callback.
+ for (int i = 0; i < myData.numFrameCounts; i++) {
+ printf("numFrames[%4d] = %4d\n", i, myData.frameCounts[i]);
+ }
+ if (myData.schedulerChecked) {
+ printf("scheduler = 0x%08x, SCHED_FIFO = 0x%08X\n",
+ myData.scheduler,
+ SCHED_FIFO);
+ }
+
+ printf("SUCCESS\n");
+ return EXIT_SUCCESS;
+error:
+ player.close();
+ printf("exiting - AAudio result = %d = %s\n", result, AAudio_convertResultToText(result));
+ return EXIT_FAILURE;
+}
+
diff --git a/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp b/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
index 40e5016..8065c48 100644
--- a/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
+++ b/media/libaaudio/examples/write_sine/src/write_sine_threaded.cpp
@@ -22,7 +22,6 @@
#include <stdio.h>
#include <math.h>
#include <time.h>
-#include <aaudio/AAudioDefinitions.h>
#include <aaudio/AAudio.h>
#include "SineGenerator.h"
@@ -49,7 +48,7 @@
class SimpleAAudioPlayer {
public:
SimpleAAudioPlayer() {}
- virtual ~SimpleAAudioPlayer() {
+ ~SimpleAAudioPlayer() {
close();
};
@@ -83,7 +82,7 @@
// Open an AAudioStream using the Builder.
result = AAudioStreamBuilder_openStream(mBuilder, &mStream);
- if (result != AAUDIO_OK) goto finish1;
+ if (result != AAUDIO_OK) goto error;
// Check to see what kind of stream we actually got.
mFramesPerSecond = AAudioStream_getSampleRate(mStream);
@@ -126,7 +125,7 @@
}
return result;
- finish1:
+ error:
AAudioStreamBuilder_delete(mBuilder);
mBuilder = nullptr;
return result;
diff --git a/media/libaaudio/examples/write_sine/static/Android.mk b/media/libaaudio/examples/write_sine/static/Android.mk
index 139b70a..aeccb4a 100644
--- a/media/libaaudio/examples/write_sine/static/Android.mk
+++ b/media/libaaudio/examples/write_sine/static/Android.mk
@@ -17,6 +17,8 @@
LOCAL_MODULE := write_sine
include $(BUILD_EXECUTABLE)
+
+
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := tests
LOCAL_C_INCLUDES := \
@@ -27,8 +29,26 @@
LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
libbinder libcutils libutils \
- libaudioclient liblog libtinyalsa
+ libaudioclient liblog
LOCAL_STATIC_LIBRARIES := libaaudio
LOCAL_MODULE := write_sine_threaded
include $(BUILD_EXECUTABLE)
+
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := tests
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/libaaudio/include
+
+LOCAL_SRC_FILES:= ../src/write_sine_callback.cpp
+
+LOCAL_SHARED_LIBRARIES := libaudioutils libmedia \
+ libbinder libcutils libutils \
+ libaudioclient liblog
+LOCAL_STATIC_LIBRARIES := libaaudio
+
+LOCAL_MODULE := write_sine_callback
+include $(BUILD_EXECUTABLE)
diff --git a/media/libaaudio/include/aaudio/AAudio.h b/media/libaaudio/include/aaudio/AAudio.h
index 551dcc9..25ad5f8 100644
--- a/media/libaaudio/include/aaudio/AAudio.h
+++ b/media/libaaudio/include/aaudio/AAudio.h
@@ -89,7 +89,8 @@
* Request an audio device identified device using an ID.
* On Android, for example, the ID could be obtained from the Java AudioManager.
*
- * By default, the primary device will be used.
+ * The default, if you do not call this function, is AAUDIO_DEVICE_UNSPECIFIED,
+ * in which case the primary device will be used.
*
* @param builder reference provided by AAudio_createStreamBuilder()
* @param deviceId device identifier or AAUDIO_DEVICE_UNSPECIFIED
@@ -98,52 +99,71 @@
int32_t deviceId);
/**
- * Request a sample rate in Hz.
+ * Request a sample rate in Hertz.
+ *
* The stream may be opened with a different sample rate.
* So the application should query for the actual rate after the stream is opened.
*
* Technically, this should be called the "frame rate" or "frames per second",
* because it refers to the number of complete frames transferred per second.
- * But it is traditionally called "sample rate". Se we use that term.
+ * But it is traditionally called "sample rate". So we use that term.
*
- * Default is AAUDIO_UNSPECIFIED.
-
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param sampleRate frames per second. Common rates include 44100 and 48000 Hz.
*/
AAUDIO_API void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder* builder,
int32_t sampleRate);
/**
* Request a number of samples per frame.
+ *
* The stream may be opened with a different value.
* So the application should query for the actual value after the stream is opened.
*
- * Default is AAUDIO_UNSPECIFIED.
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
*
* Note, this quantity is sometimes referred to as "channel count".
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param samplesPerFrame Number of samples in one frame, ie. numChannels.
*/
AAUDIO_API void AAudioStreamBuilder_setSamplesPerFrame(AAudioStreamBuilder* builder,
int32_t samplesPerFrame);
/**
* Request a sample data format, for example AAUDIO_FORMAT_PCM_I16.
- * The application should query for the actual format after the stream is opened.
+ *
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * The stream may be opened with a different value.
+ * So the application should query for the actual value after the stream is opened.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param format Most common formats are AAUDIO_FORMAT_PCM_FLOAT and AAUDIO_FORMAT_PCM_I16.
*/
AAUDIO_API void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder,
aaudio_audio_format_t format);
/**
* Request a mode for sharing the device.
+ *
+ * The default, if you do not call this function, is AAUDIO_SHARING_MODE_SHARED.
+ *
* The requested sharing mode may not be available.
- * So the application should query for the actual mode after the stream is opened.
+ * The application can query for the actual mode after the stream is opened.
*
* @param builder reference provided by AAudio_createStreamBuilder()
- * @param sharingMode AAUDIO_SHARING_MODE_LEGACY or AAUDIO_SHARING_MODE_EXCLUSIVE
+ * @param sharingMode AAUDIO_SHARING_MODE_SHARED or AAUDIO_SHARING_MODE_EXCLUSIVE
*/
AAUDIO_API void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder* builder,
aaudio_sharing_mode_t sharingMode);
/**
- * Request the direction for a stream. The default is AAUDIO_DIRECTION_OUTPUT.
+ * Request the direction for a stream.
+ *
+ * The default, if you do not call this function, is AAUDIO_DIRECTION_OUTPUT.
*
* @param builder reference provided by AAudio_createStreamBuilder()
* @param direction AAUDIO_DIRECTION_OUTPUT or AAUDIO_DIRECTION_INPUT
@@ -152,16 +172,162 @@
aaudio_direction_t direction);
/**
- * Set the requested maximum buffer capacity in frames.
+ * Set the requested buffer capacity in frames.
* The final AAudioStream capacity may differ, but will probably be at least this big.
*
- * Default is AAUDIO_UNSPECIFIED.
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
*
* @param builder reference provided by AAudio_createStreamBuilder()
- * @param frames the desired buffer capacity in frames or AAUDIO_UNSPECIFIED
+ * @param numFrames the desired buffer capacity in frames or AAUDIO_UNSPECIFIED
*/
AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
- int32_t frames);
+ int32_t numFrames);
+/**
+ * Return one of these values from the data callback function.
+ */
+enum {
+
+ /**
+ * Continue calling the callback.
+ */
+ AAUDIO_CALLBACK_RESULT_CONTINUE = 0,
+
+ /**
+ * Stop calling the callback.
+ *
+ * The application will still need to call AAudioStream_requestPause()
+ * or AAudioStream_requestStop().
+ */
+ AAUDIO_CALLBACK_RESULT_STOP,
+
+};
+typedef int32_t aaudio_data_callback_result_t;
+
+/**
+ * Prototype for the data function that is passed to AAudioStreamBuilder_setDataCallback().
+ *
+ * For an output stream, this function should render and write numFrames of data
+ * in the streams current data format to the audioData buffer.
+ *
+ * For an input stream, this function should read and process numFrames of data
+ * from the audioData buffer.
+ *
+ * Note that this callback function should be considered a "real-time" function.
+ * It must not do anything that could cause an unbounded delay because that can cause the
+ * audio to glitch or pop.
+ *
+ * These are things the function should NOT do:
+ * <ul>
+ * <li>allocate memory using, for example, malloc() or new</li>
+ * <li>any file operations such as opening, closing, reading or writing</li>
+ * <li>any network operations such as streaming</li>
+ * <li>use any mutexes or other synchronization primitives</li>
+ * <li>sleep</li>
+ * </ul>
+ *
+ * If you need to move data, eg. MIDI commands, in or out of the callback function then
+ * we recommend the use of non-blocking techniques such as an atomic FIFO.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @param userData the same address that was passed to AAudioStreamBuilder_setCallback()
+ * @param audioData a pointer to the audio data
+ * @param numFrames the number of frames to be processed
+ * @return AAUDIO_CALLBACK_RESULT_*
+ */
+typedef aaudio_data_callback_result_t (*AAudioStream_dataCallback)(
+ AAudioStream *stream,
+ void *userData,
+ void *audioData,
+ int32_t numFrames);
+
+/**
+ * Request that AAudio call this functions when the stream is running.
+ *
+ * Note that when using this callback, the audio data will be passed in or out
+ * of the function as an argument.
+ * So you cannot call AAudioStream_write() or AAudioStream_read() on the same stream
+ * that has an active data callback.
+ *
+ * The callback function will start being called after AAudioStream_requestStart() is called.
+ * It will stop being called after AAudioStream_requestPause() or
+ * AAudioStream_requestStop() is called.
+ *
+ * This callback function will be called on a real-time thread owned by AAudio. See
+ * {@link aaudio_data_callback_proc_t} for more information.
+ *
+ * Note that the AAudio callbacks will never be called simultaneously from multiple threads.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param callback pointer to a function that will process audio data.
+ * @param userData pointer to an application data structure that will be passed
+ * to the callback functions.
+ */
+AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
+ AAudioStream_dataCallback callback,
+ void *userData);
+
+/**
+ * Set the requested data callback buffer size in frames.
+ * See {@link AAudioStream_dataCallback}.
+ *
+ * The default, if you do not call this function, is AAUDIO_UNSPECIFIED.
+ *
+ * For the lowest possible latency, do not call this function. AAudio will then
+ * call the dataProc callback function with whatever size is optimal.
+ * That size may vary from one callback to another.
+ *
+ * Only use this function if the application requires a specific number of frames for processing.
+ * The application might, for example, be using an FFT that requires
+ * a specific power-of-two sized buffer.
+ *
+ * AAudio may need to add additional buffering in order to adapt between the internal
+ * buffer size and the requested buffer size.
+ *
+ * If you do call this function then the requested size should be less than
+ * half the buffer capacity, to allow double buffering.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param numFrames the desired buffer size in frames or AAUDIO_UNSPECIFIED
+ */
+AAUDIO_API void AAudioStreamBuilder_setFramesPerDataCallback(AAudioStreamBuilder* builder,
+ int32_t numFrames);
+
+/**
+ * Prototype for the callback function that is passed to
+ * AAudioStreamBuilder_setErrorCallback().
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @param userData the same address that was passed to AAudioStreamBuilder_setErrorCallback()
+ * @param error an AAUDIO_ERROR_* value.
+ */
+typedef void (*AAudioStream_errorCallback)(
+ AAudioStream *stream,
+ void *userData,
+ aaudio_result_t error);
+
+/**
+ * Request that AAudio call this functions if any error occurs on a callback thread.
+ *
+ * It will be called, for example, if a headset or a USB device is unplugged causing the stream's
+ * device to be unavailable.
+ * In response, this function could signal or launch another thread to reopen a
+ * stream on another device. Do not reopen the stream in this callback.
+ *
+ * This will not be called because of actions by the application, such as stopping
+ * or closing a stream.
+ *
+ * Another possible cause of error would be a timeout or an unanticipated internal error.
+ *
+ * Note that the AAudio callbacks will never be called simultaneously from multiple threads.
+ *
+ * @param builder reference provided by AAudio_createStreamBuilder()
+ * @param callback pointer to a function that will be called if an error occurs.
+ * @param userData pointer to an application data structure that will be passed
+ * to the callback functions.
+ */
+AAUDIO_API void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder* builder,
+ AAudioStream_errorCallback callback,
+ void *userData);
/**
* Open a stream based on the options in the StreamBuilder.
@@ -333,9 +499,14 @@
// High priority audio threads
// ============================================================
+/**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ */
typedef void *(*aaudio_audio_thread_proc_t)(void *);
/**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ *
* Create a thread associated with a stream. The thread has special properties for
* low latency audio performance. This thread can be used to implement a callback API.
*
@@ -360,6 +531,8 @@
void *arg);
/**
+ * @deprecated Use AudioStreamBuilder_setCallback()
+ *
* Wait until the thread exits or an error occurs.
*
* @param stream A stream created using AAudioStreamBuilder_openStream().
@@ -388,11 +561,11 @@
* Call AAudioStream_getBufferSizeInFrames() to see what the actual final size is.
*
* @param stream reference provided by AAudioStreamBuilder_openStream()
- * @param requestedFrames requested number of frames that can be filled without blocking
+ * @param numFrames requested number of frames that can be filled without blocking
* @return actual buffer size in frames or a negative error
*/
AAUDIO_API aaudio_result_t AAudioStream_setBufferSizeInFrames(AAudioStream* stream,
- int32_t requestedFrames);
+ int32_t numFrames);
/**
* Query the maximum number of frames that can be filled without blocking.
@@ -421,11 +594,32 @@
* Query maximum buffer capacity in frames.
*
* @param stream reference provided by AAudioStreamBuilder_openStream()
- * @return the buffer capacity in frames
+ * @return buffer capacity in frames
*/
AAUDIO_API int32_t AAudioStream_getBufferCapacityInFrames(AAudioStream* stream);
/**
+ * Query the size of the buffer that will be passed to the dataProc callback
+ * in the numFrames parameter.
+ *
+ * This call can be used if the application needs to know the value of numFrames before
+ * the stream is started. This is not normally necessary.
+ *
+ * If a specific size was requested by calling AAudioStreamBuilder_setCallbackSizeInFrames()
+ * then this will be the same size.
+ *
+ * If AAudioStreamBuilder_setCallbackSizeInFrames() was not called then this will
+ * return the size chosen by AAudio, or AAUDIO_UNSPECIFIED.
+ *
+ * AAUDIO_UNSPECIFIED indicates that the callback buffer size for this stream
+ * may vary from one dataProc callback to the next.
+ *
+ * @param stream reference provided by AAudioStreamBuilder_openStream()
+ * @return callback buffer size in frames or AAUDIO_UNSPECIFIED
+ */
+AAUDIO_API int32_t AAudioStream_getFramesPerDataCallback(AAudioStream* stream);
+
+/**
* An XRun is an Underrun or an Overrun.
* During playing, an underrun will occur if the stream is not written in time
* and the system runs out of valid data.
diff --git a/media/libaaudio/libaaudio.map.txt b/media/libaaudio/libaaudio.map.txt
index a9e9109..f22fdfe 100644
--- a/media/libaaudio/libaaudio.map.txt
+++ b/media/libaaudio/libaaudio.map.txt
@@ -4,6 +4,9 @@
AAudio_convertStreamStateToText;
AAudio_createStreamBuilder;
AAudioStreamBuilder_setDeviceId;
+ AAudioStreamBuilder_setDataCallback;
+ AAudioStreamBuilder_setErrorCallback;
+ AAudioStreamBuilder_setFramesPerDataCallback;
AAudioStreamBuilder_setSampleRate;
AAudioStreamBuilder_setSamplesPerFrame;
AAudioStreamBuilder_setFormat;
@@ -25,6 +28,7 @@
AAudioStream_joinThread;
AAudioStream_setBufferSizeInFrames;
AAudioStream_getBufferSizeInFrames;
+ AAudioStream_getFramesPerDataCallback;
AAudioStream_getFramesPerBurst;
AAudioStream_getBufferCapacityInFrames;
AAudioStream_getXRunCount;
diff --git a/media/libaaudio/src/Android.mk b/media/libaaudio/src/Android.mk
index a016b49..1ee73bf 100644
--- a/media/libaaudio/src/Android.mk
+++ b/media/libaaudio/src/Android.mk
@@ -30,10 +30,14 @@
core/AudioStream.cpp \
core/AudioStreamBuilder.cpp \
core/AAudioAudio.cpp \
+ legacy/AudioStreamLegacy.cpp \
legacy/AudioStreamRecord.cpp \
legacy/AudioStreamTrack.cpp \
utility/HandleTracker.cpp \
utility/AAudioUtilities.cpp \
+ utility/FixedBlockAdapter.cpp \
+ utility/FixedBlockReader.cpp \
+ utility/FixedBlockWriter.cpp \
fifo/FifoBuffer.cpp \
fifo/FifoControllerBase.cpp \
client/AudioEndpoint.cpp \
@@ -79,10 +83,14 @@
LOCAL_SRC_FILES = core/AudioStream.cpp \
core/AudioStreamBuilder.cpp \
core/AAudioAudio.cpp \
+ legacy/AudioStreamLegacy.cpp \
legacy/AudioStreamRecord.cpp \
legacy/AudioStreamTrack.cpp \
utility/HandleTracker.cpp \
utility/AAudioUtilities.cpp \
+ utility/FixedBlockAdapter.cpp \
+ utility/FixedBlockReader.cpp \
+ utility/FixedBlockWriter.cpp \
fifo/FifoBuffer.cpp \
fifo/FifoControllerBase.cpp \
client/AudioEndpoint.cpp \
diff --git a/media/libaaudio/src/client/AudioEndpoint.cpp b/media/libaaudio/src/client/AudioEndpoint.cpp
index 47c4774..90c619c 100644
--- a/media/libaaudio/src/client/AudioEndpoint.cpp
+++ b/media/libaaudio/src/client/AudioEndpoint.cpp
@@ -19,7 +19,7 @@
#include <utils/Log.h>
#include <cassert>
-#include <aaudio/AAudioDefinitions.h>
+#include <aaudio/AAudio.h>
#include "AudioEndpointParcelable.h"
#include "AudioEndpoint.h"
diff --git a/media/libaaudio/src/client/AudioStreamInternal.cpp b/media/libaaudio/src/client/AudioStreamInternal.cpp
index 54f4870..1f9ce4f 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.cpp
+++ b/media/libaaudio/src/client/AudioStreamInternal.cpp
@@ -18,23 +18,19 @@
//#define LOG_NDEBUG 0
#include <utils/Log.h>
-#include <stdint.h>
#include <assert.h>
#include <binder/IServiceManager.h>
#include <utils/Mutex.h>
#include <aaudio/AAudio.h>
+#include <utils/String16.h>
-#include "AudioClock.h"
-#include "AudioEndpointParcelable.h"
-#include "binding/AAudioStreamRequest.h"
-#include "binding/AAudioStreamConfiguration.h"
-#include "binding/IAAudioService.h"
+#include "utility/AudioClock.h"
+#include "AudioStreamInternal.h"
#include "binding/AAudioServiceMessage.h"
#include "core/AudioStreamBuilder.h"
-#include "AudioStreamInternal.h"
#define LOG_TIMESTAMPS 0
@@ -51,6 +47,11 @@
#define AAUDIO_SERVICE_NAME "AAudioService"
+#define MIN_TIMEOUT_NANOS (1000 * AAUDIO_NANOS_PER_MILLISECOND)
+
+// Wait at least this many times longer than the operation should take.
+#define MIN_TIMEOUT_OPERATIONS 4
+
// Helper function to get access to the "AAudioService" service.
// This code was modeled after frameworks/av/media/libaudioclient/AudioSystem.cpp
static const sp<IAAudioService> getAAudioService() {
@@ -151,6 +152,29 @@
mClockModel.setSampleRate(getSampleRate());
mClockModel.setFramesPerBurst(mFramesPerBurst);
+ if (getDataCallbackProc()) {
+ mCallbackFrames = builder.getFramesPerDataCallback();
+ if (mCallbackFrames > getBufferCapacity() / 2) {
+ ALOGE("AudioStreamInternal.open(): framesPerCallback too large");
+ service->closeStream(mServiceStreamHandle);
+ return AAUDIO_ERROR_OUT_OF_RANGE;
+
+ } else if (mCallbackFrames < 0) {
+ ALOGE("AudioStreamInternal.open(): framesPerCallback negative");
+ service->closeStream(mServiceStreamHandle);
+ return AAUDIO_ERROR_OUT_OF_RANGE;
+
+ }
+ if (mCallbackFrames == AAUDIO_UNSPECIFIED) {
+ mCallbackFrames = mFramesPerBurst;
+ }
+
+ int32_t bytesPerFrame = getSamplesPerFrame()
+ * AAudioConvert_formatToSizeInBytes(getFormat());
+ int32_t callbackBufferSize = mCallbackFrames * bytesPerFrame;
+ mCallbackBuffer = new uint8_t[callbackBufferSize];
+ }
+
setState(AAUDIO_STREAM_STATE_OPEN);
}
return result;
@@ -164,12 +188,69 @@
const sp<IAAudioService>& aaudioService = getAAudioService();
if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
aaudioService->closeStream(serviceStreamHandle);
+ delete[] mCallbackBuffer;
return AAUDIO_OK;
} else {
return AAUDIO_ERROR_INVALID_HANDLE;
}
}
+// Render audio in the application callback and then write the data to the stream.
+void *AudioStreamInternal::callbackLoop() {
+ aaudio_result_t result = AAUDIO_OK;
+ aaudio_data_callback_result_t callbackResult = AAUDIO_CALLBACK_RESULT_CONTINUE;
+ int32_t framesWritten = 0;
+ AAudioStream_dataCallback appCallback = getDataCallbackProc();
+ if (appCallback == nullptr) return NULL;
+
+ while (mCallbackEnabled.load() && isPlaying() && (result >= 0)) { // result might be a frame count
+ // Call application using the AAudio callback interface.
+ callbackResult = (*appCallback)(
+ (AAudioStream *) this,
+ getDataCallbackUserData(),
+ mCallbackBuffer,
+ mCallbackFrames);
+
+ if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
+ // Write audio data to stream
+ int64_t timeoutNanos = calculateReasonableTimeout(mCallbackFrames);
+ result = write(mCallbackBuffer, mCallbackFrames, timeoutNanos);
+ if (result == AAUDIO_ERROR_DISCONNECTED) {
+ if (getErrorCallbackProc() != nullptr) {
+ ALOGD("AudioStreamAAudio(): callbackLoop() stream disconnected");
+ (*getErrorCallbackProc())(
+ (AAudioStream *) this,
+ getErrorCallbackUserData(),
+ AAUDIO_OK);
+ }
+ break;
+ } else if (result != mCallbackFrames) {
+ ALOGE("AudioStreamAAudio(): callbackLoop() wrote %d / %d",
+ framesWritten, mCallbackFrames);
+ break;
+ }
+ } else if (callbackResult == AAUDIO_CALLBACK_RESULT_STOP) {
+ ALOGD("AudioStreamAAudio(): callback returned AAUDIO_CALLBACK_RESULT_STOP");
+ break;
+ }
+ }
+
+ ALOGD("AudioStreamAAudio(): callbackLoop() exiting, result = %d, isPlaying() = %d",
+ result, (int) isPlaying());
+ return NULL; // TODO review
+}
+
+static void *aaudio_callback_thread_proc(void *context)
+{
+ AudioStreamInternal *stream = (AudioStreamInternal *)context;
+ //LOGD("AudioStreamAAudio(): oboe_callback_thread, stream = %p", stream);
+ if (stream != NULL) {
+ return stream->callbackLoop();
+ } else {
+ return NULL;
+ }
+}
+
aaudio_result_t AudioStreamInternal::requestStart()
{
int64_t startTime;
@@ -178,35 +259,81 @@
return AAUDIO_ERROR_INVALID_STATE;
}
const sp<IAAudioService>& aaudioService = getAAudioService();
- if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
+ if (aaudioService == 0) {
+ return AAUDIO_ERROR_NO_SERVICE;
+ }
startTime = AudioClock::getNanoseconds();
mClockModel.start(startTime);
processTimestamp(0, startTime);
setState(AAUDIO_STREAM_STATE_STARTING);
- return aaudioService->startStream(mServiceStreamHandle);
+ aaudio_result_t result = aaudioService->startStream(mServiceStreamHandle);
+
+ if (result == AAUDIO_OK && getDataCallbackProc() != nullptr) {
+ // Launch the callback loop thread.
+ int64_t periodNanos = mCallbackFrames
+ * AAUDIO_NANOS_PER_SECOND
+ / getSampleRate();
+ mCallbackEnabled.store(true);
+ result = createThread(periodNanos, aaudio_callback_thread_proc, this);
+ }
+ return result;
}
-aaudio_result_t AudioStreamInternal::requestPause()
+int64_t AudioStreamInternal::calculateReasonableTimeout(int32_t framesPerOperation) {
+
+ // Wait for at least a second or some number of callbacks to join the thread.
+ int64_t timeoutNanoseconds = (MIN_TIMEOUT_OPERATIONS * framesPerOperation * AAUDIO_NANOS_PER_SECOND)
+ / getSampleRate();
+ if (timeoutNanoseconds < MIN_TIMEOUT_NANOS) { // arbitrary number of seconds
+ timeoutNanoseconds = MIN_TIMEOUT_NANOS;
+ }
+ return timeoutNanoseconds;
+}
+
+aaudio_result_t AudioStreamInternal::stopCallback()
+{
+ if (isDataCallbackActive()) {
+ mCallbackEnabled.store(false);
+ return joinThread(NULL, calculateReasonableTimeout(mCallbackFrames));
+ } else {
+ return AAUDIO_OK;
+ }
+}
+
+aaudio_result_t AudioStreamInternal::requestPauseInternal()
{
ALOGD("AudioStreamInternal(): pause()");
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
return AAUDIO_ERROR_INVALID_STATE;
}
const sp<IAAudioService>& aaudioService = getAAudioService();
- if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
+ if (aaudioService == 0) {
+ return AAUDIO_ERROR_NO_SERVICE;
+ }
mClockModel.stop(AudioClock::getNanoseconds());
setState(AAUDIO_STREAM_STATE_PAUSING);
return aaudioService->pauseStream(mServiceStreamHandle);
}
+aaudio_result_t AudioStreamInternal::requestPause()
+{
+ aaudio_result_t result = stopCallback();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+ return requestPauseInternal();
+}
+
aaudio_result_t AudioStreamInternal::requestFlush() {
ALOGD("AudioStreamInternal(): flush()");
if (mServiceStreamHandle == AAUDIO_HANDLE_INVALID) {
return AAUDIO_ERROR_INVALID_STATE;
}
const sp<IAAudioService>& aaudioService = getAAudioService();
- if (aaudioService == 0) return AAUDIO_ERROR_NO_SERVICE;
-setState(AAUDIO_STREAM_STATE_FLUSHING);
+ if (aaudioService == 0) {
+ return AAUDIO_ERROR_NO_SERVICE;
+ }
+ setState(AAUDIO_STREAM_STATE_FLUSHING);
return aaudioService->flushStream(mServiceStreamHandle);
}
@@ -260,18 +387,20 @@
return aaudioService->unregisterAudioThread(mServiceStreamHandle, gettid());
}
-// TODO use aaudio_clockid_t all the way down to AudioClock
aaudio_result_t AudioStreamInternal::getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) {
-// TODO implement using real HAL
+ // TODO implement using real HAL
int64_t time = AudioClock::getNanoseconds();
*framePosition = mClockModel.convertTimeToPosition(time);
*timeNanoseconds = time + (10 * AAUDIO_NANOS_PER_MILLISECOND); // Fake hardware delay
return AAUDIO_OK;
}
-aaudio_result_t AudioStreamInternal::updateState() {
+aaudio_result_t AudioStreamInternal::updateStateWhileWaiting() {
+ if (isDataCallbackActive()) {
+ return AAUDIO_OK; // state is getting updated by the callback thread read/write call
+ }
return processCommands();
}
@@ -485,43 +614,6 @@
return framesWritten;
}
-aaudio_result_t AudioStreamInternal::waitForStateChange(aaudio_stream_state_t currentState,
- aaudio_stream_state_t *nextState,
- int64_t timeoutNanoseconds)
-
-{
- aaudio_result_t result = processCommands();
-// ALOGD("AudioStreamInternal::waitForStateChange() - processCommands() returned %d", result);
- if (result != AAUDIO_OK) {
- return result;
- }
- // TODO replace this polling with a timed sleep on a futex on the message queue
- int32_t durationNanos = 5 * AAUDIO_NANOS_PER_MILLISECOND;
- aaudio_stream_state_t state = getState();
-// ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
- while (state == currentState && timeoutNanoseconds > 0) {
- // TODO use futex from service message queue
- if (durationNanos > timeoutNanoseconds) {
- durationNanos = timeoutNanoseconds;
- }
- AudioClock::sleepForNanos(durationNanos);
- timeoutNanoseconds -= durationNanos;
-
- result = processCommands();
- if (result != AAUDIO_OK) {
- return result;
- }
-
- state = getState();
-// ALOGD("AudioStreamInternal::waitForStateChange() - state = %d", state);
- }
- if (nextState != nullptr) {
- *nextState = state;
- }
- return (state == currentState) ? AAUDIO_ERROR_TIMEOUT : AAUDIO_OK;
-}
-
-
void AudioStreamInternal::processTimestamp(uint64_t position, int64_t time) {
mClockModel.processTimestamp( position, time);
}
diff --git a/media/libaaudio/src/client/AudioStreamInternal.h b/media/libaaudio/src/client/AudioStreamInternal.h
index 6f3a7ac..9a15a9b 100644
--- a/media/libaaudio/src/client/AudioStreamInternal.h
+++ b/media/libaaudio/src/client/AudioStreamInternal.h
@@ -53,7 +53,7 @@
int64_t *timeNanoseconds) override;
- virtual aaudio_result_t updateState() override;
+ virtual aaudio_result_t updateStateWhileWaiting() override;
// =========== End ABSTRACT methods ===========================
virtual aaudio_result_t open(const AudioStreamBuilder &builder) override;
@@ -64,10 +64,6 @@
int32_t numFrames,
int64_t timeoutNanoseconds) override;
- virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState,
- aaudio_stream_state_t *nextState,
- int64_t timeoutNanoseconds) override;
-
virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
virtual int32_t getBufferSize() const override;
@@ -86,10 +82,17 @@
virtual aaudio_result_t unregisterThread() override;
+ // Called internally from 'C'
+ void *callbackLoop();
+
protected:
aaudio_result_t processCommands();
+ aaudio_result_t requestPauseInternal();
+
+ aaudio_result_t stopCallback();
+
/**
* Low level write that will not block. It will just write as much as it can.
*
@@ -108,17 +111,22 @@
aaudio_result_t onTimestampFromServer(AAudioServiceMessage *message);
+ // Calculate timeout for an operation involving framesPerOperation.
+ int64_t calculateReasonableTimeout(int32_t framesPerOperation);
+
private:
IsochronousClockModel mClockModel;
AudioEndpoint mAudioEndpoint;
aaudio_handle_t mServiceStreamHandle;
EndpointDescriptor mEndpointDescriptor;
+ uint8_t *mCallbackBuffer = nullptr;
+ int32_t mCallbackFrames = 0;
+
// Offset from underlying frame position.
int64_t mFramesOffsetFromService = 0;
int64_t mLastFramesRead = 0;
int32_t mFramesPerBurst;
int32_t mXRunCount = 0;
-
void processTimestamp(uint64_t position, int64_t time);
};
diff --git a/media/libaaudio/src/client/IsochronousClockModel.cpp b/media/libaaudio/src/client/IsochronousClockModel.cpp
index 4c8aabc..c278c8b 100644
--- a/media/libaaudio/src/client/IsochronousClockModel.cpp
+++ b/media/libaaudio/src/client/IsochronousClockModel.cpp
@@ -19,7 +19,6 @@
#include <utils/Log.h>
#include <stdint.h>
-#include <aaudio/AAudioDefinitions.h>
#include "utility/AudioClock.h"
#include "IsochronousClockModel.h"
diff --git a/media/libaaudio/src/client/IsochronousClockModel.h b/media/libaaudio/src/client/IsochronousClockModel.h
index 524c286..205c341 100644
--- a/media/libaaudio/src/client/IsochronousClockModel.h
+++ b/media/libaaudio/src/client/IsochronousClockModel.h
@@ -14,11 +14,10 @@
* limitations under the License.
*/
-#ifndef AAUDIO_ISOCHRONOUSCLOCKMODEL_H
-#define AAUDIO_ISOCHRONOUSCLOCKMODEL_H
+#ifndef AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
+#define AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
#include <stdint.h>
-#include <aaudio/AAudio.h>
namespace aaudio {
@@ -107,4 +106,4 @@
} /* namespace aaudio */
-#endif //AAUDIO_ISOCHRONOUSCLOCKMODEL_H
+#endif //AAUDIO_ISOCHRONOUS_CLOCK_MODEL_H
diff --git a/media/libaaudio/src/core/AAudioAudio.cpp b/media/libaaudio/src/core/AAudioAudio.cpp
index 52bad70..bc2f281 100644
--- a/media/libaaudio/src/core/AAudioAudio.cpp
+++ b/media/libaaudio/src/core/AAudioAudio.cpp
@@ -114,53 +114,79 @@
AAUDIO_API void AAudioStreamBuilder_setDeviceId(AAudioStreamBuilder* builder,
int32_t deviceId)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setDeviceId(deviceId);
}
AAUDIO_API void AAudioStreamBuilder_setSampleRate(AAudioStreamBuilder* builder,
int32_t sampleRate)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setSampleRate(sampleRate);
}
AAUDIO_API void AAudioStreamBuilder_setSamplesPerFrame(AAudioStreamBuilder* builder,
int32_t samplesPerFrame)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setSamplesPerFrame(samplesPerFrame);
}
AAUDIO_API void AAudioStreamBuilder_setDirection(AAudioStreamBuilder* builder,
aaudio_direction_t direction)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setDirection(direction);
}
-
AAUDIO_API void AAudioStreamBuilder_setFormat(AAudioStreamBuilder* builder,
aaudio_audio_format_t format)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setFormat(format);
}
AAUDIO_API void AAudioStreamBuilder_setSharingMode(AAudioStreamBuilder* builder,
aaudio_sharing_mode_t sharingMode)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setSharingMode(sharingMode);
}
AAUDIO_API void AAudioStreamBuilder_setBufferCapacityInFrames(AAudioStreamBuilder* builder,
int32_t frames)
{
- AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);;
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
streamBuilder->setBufferCapacity(frames);
}
+AAUDIO_API void AAudioStreamBuilder_setDataCallback(AAudioStreamBuilder* builder,
+ AAudioStream_dataCallback callback,
+ void *userData)
+{
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+ ALOGD("AAudioStreamBuilder_setCallback(): userData = %p", userData);
+ streamBuilder->setDataCallbackProc(callback);
+ streamBuilder->setDataCallbackUserData(userData);
+}
+AAUDIO_API void AAudioStreamBuilder_setErrorCallback(AAudioStreamBuilder* builder,
+ AAudioStream_errorCallback callback,
+ void *userData)
+{
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+ ALOGD("AAudioStreamBuilder_setCallback(): userData = %p", userData);
+ streamBuilder->setErrorCallbackProc(callback);
+ streamBuilder->setErrorCallbackUserData(userData);
+}
+
+AAUDIO_API void AAudioStreamBuilder_setFramesPerDataCallback(AAudioStreamBuilder* builder,
+ int32_t frames)
+{
+ AudioStreamBuilder *streamBuilder = convertAAudioBuilderToStreamBuilder(builder);
+ ALOGD("%s: frames = %d", __func__, frames);
+ streamBuilder->setFramesPerDataCallback(frames);
+}
+
static aaudio_result_t AAudioInternal_openStream(AudioStreamBuilder *streamBuilder,
AAudioStream** streamPtr)
{
@@ -276,6 +302,13 @@
if (buffer == nullptr) {
return AAUDIO_ERROR_NULL;
}
+
+ // Don't allow writes when playing with a callback.
+ if (audioStream->getDataCallbackProc() != nullptr && audioStream->isPlaying()) {
+ ALOGE("Cannot write to a callback stream when running.");
+ return AAUDIO_ERROR_INVALID_STATE;
+ }
+
if (numFrames < 0) {
return AAUDIO_ERROR_ILLEGAL_ARGUMENT;
} else if (numFrames == 0) {
@@ -297,6 +330,9 @@
aaudio_audio_thread_proc_t threadProc, void *arg)
{
AudioStream *audioStream = convertAAudioStreamToAudioStream(stream);
+ if (audioStream->getDataCallbackProc() != nullptr) {
+ return AAUDIO_ERROR_INCOMPATIBLE;
+ }
return audioStream->createThread(periodNanoseconds, threadProc, arg);
}
diff --git a/media/libaaudio/src/core/AudioStream.cpp b/media/libaaudio/src/core/AudioStream.cpp
index b054d94..68579fd 100644
--- a/media/libaaudio/src/core/AudioStream.cpp
+++ b/media/libaaudio/src/core/AudioStream.cpp
@@ -28,7 +28,9 @@
using namespace aaudio;
-AudioStream::AudioStream() {
+AudioStream::AudioStream()
+ : mCallbackEnabled(false)
+{
// mThread is a pthread_t of unknown size so we need memset.
memset(&mThread, 0, sizeof(mThread));
setPeriodNanoseconds(0);
@@ -36,13 +38,30 @@
aaudio_result_t AudioStream::open(const AudioStreamBuilder& builder)
{
- // TODO validate parameters.
+
// Copy parameters from the Builder because the Builder may be deleted after this call.
mSamplesPerFrame = builder.getSamplesPerFrame();
mSampleRate = builder.getSampleRate();
mDeviceId = builder.getDeviceId();
mFormat = builder.getFormat();
- mSharingMode = builder.getSharingMode();
+ mDirection = builder.getDirection();
+
+ // callbacks
+ mFramesPerDataCallback = builder.getFramesPerDataCallback();
+ mDataCallbackProc = builder.getDataCallbackProc();
+ mErrorCallbackProc = builder.getErrorCallbackProc();
+ mDataCallbackUserData = builder.getDataCallbackUserData();
+
+ // TODO validate more parameters.
+ if (mErrorCallbackProc != nullptr && mDataCallbackProc == nullptr) {
+ ALOGE("AudioStream::open(): disconnect callback cannot be used without a data callback.");
+ return AAUDIO_ERROR_UNEXPECTED_VALUE;
+ }
+ if (mDirection != AAUDIO_DIRECTION_INPUT && mDirection != AAUDIO_DIRECTION_OUTPUT) {
+ ALOGE("AudioStream::open(): illegal direction %d", mDirection);
+ return AAUDIO_ERROR_UNEXPECTED_VALUE;
+ }
+
return AAUDIO_OK;
}
@@ -75,8 +94,13 @@
aaudio_stream_state_t *nextState,
int64_t timeoutNanoseconds)
{
+ aaudio_result_t result = updateStateWhileWaiting();
+ if (result != AAUDIO_OK) {
+ return result;
+ }
+
// TODO replace this when similar functionality added to AudioTrack.cpp
- int64_t durationNanos = 20 * AAUDIO_NANOS_PER_MILLISECOND;
+ int64_t durationNanos = 20 * AAUDIO_NANOS_PER_MILLISECOND; // arbitrary
aaudio_stream_state_t state = getState();
while (state == currentState && timeoutNanoseconds > 0) {
if (durationNanos > timeoutNanoseconds) {
@@ -85,7 +109,7 @@
AudioClock::sleepForNanos(durationNanos);
timeoutNanoseconds -= durationNanos;
- aaudio_result_t result = updateState();
+ aaudio_result_t result = updateStateWhileWaiting();
if (result != AAUDIO_OK) {
return result;
}
diff --git a/media/libaaudio/src/core/AudioStream.h b/media/libaaudio/src/core/AudioStream.h
index 6ac8554..1485d20 100644
--- a/media/libaaudio/src/core/AudioStream.h
+++ b/media/libaaudio/src/core/AudioStream.h
@@ -18,8 +18,8 @@
#define AAUDIO_AUDIOSTREAM_H
#include <atomic>
+#include <mutex>
#include <stdint.h>
-#include <aaudio/AAudioDefinitions.h>
#include <aaudio/AAudio.h>
#include "AAudioUtilities.h"
@@ -55,14 +55,18 @@
int64_t *timeNanoseconds) = 0;
- virtual aaudio_result_t updateState() = 0;
+ /**
+ * Update state while in the middle of waitForStateChange()
+ * @return
+ */
+ virtual aaudio_result_t updateStateWhileWaiting() = 0;
// =========== End ABSTRACT methods ===========================
virtual aaudio_result_t waitForStateChange(aaudio_stream_state_t currentState,
- aaudio_stream_state_t *nextState,
- int64_t timeoutNanoseconds);
+ aaudio_stream_state_t *nextState,
+ int64_t timeoutNanoseconds);
/**
* Open the stream using the parameters in the builder.
@@ -152,10 +156,16 @@
return mDirection;
}
+ /**
+ * This is only valid after setSamplesPerFrame() and setFormat() have been called.
+ */
int32_t getBytesPerFrame() const {
return mSamplesPerFrame * getBytesPerSample();
}
+ /**
+ * This is only valid after setFormat() has been called.
+ */
int32_t getBytesPerSample() const {
return AAudioConvert_formatToSizeInBytes(mFormat);
}
@@ -168,6 +178,27 @@
return mFramesRead.get();
}
+ AAudioStream_dataCallback getDataCallbackProc() const {
+ return mDataCallbackProc;
+ }
+ AAudioStream_errorCallback getErrorCallbackProc() const {
+ return mErrorCallbackProc;
+ }
+
+ void *getDataCallbackUserData() const {
+ return mDataCallbackUserData;
+ }
+ void *getErrorCallbackUserData() const {
+ return mErrorCallbackUserData;
+ }
+
+ int32_t getFramesPerDataCallback() const {
+ return mFramesPerDataCallback;
+ }
+
+ bool isDataCallbackActive() {
+ return (mDataCallbackProc != nullptr) && isPlaying();
+ }
// ============== I/O ===========================
// A Stream will only implement read() or write() depending on its direction.
@@ -235,6 +266,9 @@
mState = state;
}
+ std::mutex mStreamMutex;
+
+ std::atomic<bool> mCallbackEnabled;
protected:
@@ -259,6 +293,15 @@
aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT;
aaudio_stream_state_t mState = AAUDIO_STREAM_STATE_UNINITIALIZED;
+ // callback ----------------------------------
+
+ AAudioStream_dataCallback mDataCallbackProc = nullptr; // external callback functions
+ void *mDataCallbackUserData = nullptr;
+ int32_t mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames
+
+ AAudioStream_errorCallback mErrorCallbackProc = nullptr;
+ void *mErrorCallbackUserData = nullptr;
+
// background thread ----------------------------------
bool mHasThread = false;
pthread_t mThread; // initialized in constructor
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.cpp b/media/libaaudio/src/core/AudioStreamBuilder.cpp
index 5a54e62..858ae80 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.cpp
+++ b/media/libaaudio/src/core/AudioStreamBuilder.cpp
@@ -44,6 +44,7 @@
aaudio_result_t AudioStreamBuilder::build(AudioStream** streamPtr) {
AudioStream* audioStream = nullptr;
const aaudio_sharing_mode_t sharingMode = getSharingMode();
+ ALOGE("AudioStreamBuilder.build() sharingMode = %d", sharingMode);
switch (getDirection()) {
case AAUDIO_DIRECTION_INPUT:
switch (sharingMode) {
diff --git a/media/libaaudio/src/core/AudioStreamBuilder.h b/media/libaaudio/src/core/AudioStreamBuilder.h
index 7b5f35c..93ca7f5 100644
--- a/media/libaaudio/src/core/AudioStreamBuilder.h
+++ b/media/libaaudio/src/core/AudioStreamBuilder.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef AAUDIO_AUDIOSTREAMBUILDER_H
-#define AAUDIO_AUDIOSTREAMBUILDER_H
+#ifndef AAUDIO_AUDIO_STREAM_BUILDER_H
+#define AAUDIO_AUDIO_STREAM_BUILDER_H
#include <stdint.h>
@@ -101,6 +101,52 @@
return this;
}
+ AAudioStream_dataCallback getDataCallbackProc() const {
+ return mDataCallbackProc;
+ }
+
+ AudioStreamBuilder* setDataCallbackProc(AAudioStream_dataCallback proc) {
+ mDataCallbackProc = proc;
+ return this;
+ }
+
+
+ void *getDataCallbackUserData() const {
+ return mDataCallbackUserData;
+ }
+
+ AudioStreamBuilder* setDataCallbackUserData(void *userData) {
+ mDataCallbackUserData = userData;
+ return this;
+ }
+
+ AAudioStream_errorCallback getErrorCallbackProc() const {
+ return mErrorCallbackProc;
+ }
+
+ AudioStreamBuilder* setErrorCallbackProc(AAudioStream_errorCallback proc) {
+ mErrorCallbackProc = proc;
+ return this;
+ }
+
+ AudioStreamBuilder* setErrorCallbackUserData(void *userData) {
+ mErrorCallbackUserData = userData;
+ return this;
+ }
+
+ void *getErrorCallbackUserData() const {
+ return mErrorCallbackUserData;
+ }
+
+ int32_t getFramesPerDataCallback() const {
+ return mFramesPerDataCallback;
+ }
+
+ AudioStreamBuilder* setFramesPerDataCallback(int32_t sizeInFrames) {
+ mFramesPerDataCallback = sizeInFrames;
+ return this;
+ }
+
aaudio_result_t build(AudioStream **streamPtr);
private:
@@ -111,8 +157,15 @@
aaudio_audio_format_t mFormat = AAUDIO_FORMAT_UNSPECIFIED;
aaudio_direction_t mDirection = AAUDIO_DIRECTION_OUTPUT;
int32_t mBufferCapacity = AAUDIO_UNSPECIFIED;
+
+ AAudioStream_dataCallback mDataCallbackProc = nullptr; // external callback functions
+ void *mDataCallbackUserData = nullptr;
+ int32_t mFramesPerDataCallback = AAUDIO_UNSPECIFIED; // frames
+
+ AAudioStream_errorCallback mErrorCallbackProc = nullptr;
+ void *mErrorCallbackUserData = nullptr;
};
} /* namespace aaudio */
-#endif /* AAUDIO_AUDIOSTREAMBUILDER_H */
+#endif //AAUDIO_AUDIO_STREAM_BUILDER_H
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.cpp b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
new file mode 100644
index 0000000..baa24c9
--- /dev/null
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.cpp
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2017 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 "AudioStreamLegacy"
+//#define LOG_NDEBUG 0
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <utils/String16.h>
+#include <media/AudioTrack.h>
+#include <aaudio/AAudio.h>
+
+#include "core/AudioStream.h"
+#include "legacy/AudioStreamLegacy.h"
+
+using namespace android;
+using namespace aaudio;
+
+AudioStreamLegacy::AudioStreamLegacy()
+ : AudioStream() {
+}
+
+AudioStreamLegacy::~AudioStreamLegacy() {
+}
+
+// Called from AudioTrack.cpp or AudioRecord.cpp
+static void AudioStreamLegacy_callback(int event, void* userData, void *info) {
+ AudioStreamLegacy *streamLegacy = (AudioStreamLegacy *) userData;
+ streamLegacy->processCallback(event, info);
+}
+
+aaudio_legacy_callback_t AudioStreamLegacy::getLegacyCallback() {
+ return AudioStreamLegacy_callback;
+}
+
+// Implement FixedBlockProcessor
+int32_t AudioStreamLegacy::onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) {
+ int32_t frameCount = numBytes / getBytesPerFrame();
+ // Call using the AAudio callback interface.
+ AAudioStream_dataCallback appCallback = getDataCallbackProc();
+ return (*appCallback)(
+ (AAudioStream *) this,
+ getDataCallbackUserData(),
+ buffer,
+ frameCount);
+}
+
+void AudioStreamLegacy::processCallbackCommon(aaudio_callback_operation_t opcode, void *info) {
+ aaudio_data_callback_result_t callbackResult;
+ switch (opcode) {
+ case AAUDIO_CALLBACK_OPERATION_PROCESS_DATA: {
+ // Note that this code assumes an AudioTrack::Buffer is the same as AudioRecord::Buffer
+ // TODO define our own AudioBuffer and pass it from the subclasses.
+ AudioTrack::Buffer *audioBuffer = static_cast<AudioTrack::Buffer *>(info);
+ if (audioBuffer->frameCount == 0) return;
+
+ // If the caller specified an exact size then use a block size adapter.
+ if (mBlockAdapter != nullptr) {
+ int32_t byteCount = audioBuffer->frameCount * getBytesPerFrame();
+ callbackResult = mBlockAdapter->processVariableBlock((uint8_t *) audioBuffer->raw,
+ byteCount);
+ } else {
+ // Call using the AAudio callback interface.
+ callbackResult = (*getDataCallbackProc())(
+ (AAudioStream *) this,
+ getDataCallbackUserData(),
+ audioBuffer->raw,
+ audioBuffer->frameCount
+ );
+ }
+ if (callbackResult == AAUDIO_CALLBACK_RESULT_CONTINUE) {
+ audioBuffer->size = audioBuffer->frameCount * getBytesPerFrame();
+ } else {
+ audioBuffer->size = 0;
+ }
+ }
+ break;
+
+ // Stream got rerouted so we disconnect.
+ case AAUDIO_CALLBACK_OPERATION_DISCONNECTED: {
+ ALOGD("AudioStreamAAudio(): callbackLoop() stream disconnected");
+ if (getErrorCallbackProc() != nullptr) {
+ (*getErrorCallbackProc())(
+ (AAudioStream *) this,
+ getErrorCallbackUserData(),
+ AAUDIO_OK
+ );
+ }
+ mCallbackEnabled.store(false);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
diff --git a/media/libaaudio/src/legacy/AudioStreamLegacy.h b/media/libaaudio/src/legacy/AudioStreamLegacy.h
new file mode 100644
index 0000000..c109ee7
--- /dev/null
+++ b/media/libaaudio/src/legacy/AudioStreamLegacy.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LEGACY_AUDIO_STREAM_LEGACY_H
+#define LEGACY_AUDIO_STREAM_LEGACY_H
+
+
+#include <aaudio/AAudio.h>
+
+#include "AudioStream.h"
+#include "AAudioLegacy.h"
+#include "utility/FixedBlockAdapter.h"
+
+namespace aaudio {
+
+
+typedef void (*aaudio_legacy_callback_t)(int event, void* user, void *info);
+
+enum {
+ /**
+ * Request that the callback function should fill the data buffer of an output stream,
+ * or process the data of an input stream.
+ * The address parameter passed to the callback function will point to a data buffer.
+ * For an input stream, the data is read-only.
+ * The value1 parameter will be the number of frames.
+ * The value2 parameter is reserved and will be set to zero.
+ * The callback should return AAUDIO_CALLBACK_RESULT_CONTINUE or AAUDIO_CALLBACK_RESULT_STOP.
+ */
+ AAUDIO_CALLBACK_OPERATION_PROCESS_DATA,
+
+ /**
+ * Inform the callback function that the stream was disconnected.
+ * The address parameter passed to the callback function will be NULL.
+ * The value1 will be an error code or AAUDIO_OK.
+ * The value2 parameter is reserved and will be set to zero.
+ * The callback return value will be ignored.
+ */
+ AAUDIO_CALLBACK_OPERATION_DISCONNECTED,
+};
+typedef int32_t aaudio_callback_operation_t;
+
+
+class AudioStreamLegacy : public AudioStream, public FixedBlockProcessor {
+public:
+ AudioStreamLegacy();
+
+ virtual ~AudioStreamLegacy();
+
+ aaudio_legacy_callback_t getLegacyCallback();
+
+ // This is public so it can be called from the C callback function.
+ // This is called from the AudioTrack/AudioRecord client.
+ virtual void processCallback(int event, void *info) = 0;
+
+ void processCallbackCommon(aaudio_callback_operation_t opcode, void *info);
+
+ // Implement FixedBlockProcessor
+ int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override;
+
+protected:
+ FixedBlockAdapter *mBlockAdapter = nullptr;
+ aaudio_wrapping_frames_t mPositionWhenStarting = 0;
+ int32_t mCallbackBufferSize = 0;
+};
+
+} /* namespace aaudio */
+
+#endif //LEGACY_AUDIO_STREAM_LEGACY_H
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.cpp b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
index d380eb8..f0a6ceb 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.cpp
@@ -24,14 +24,16 @@
#include <aaudio/AAudio.h>
#include "AudioClock.h"
-#include "AudioStreamRecord.h"
-#include "utility/AAudioUtilities.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "legacy/AudioStreamRecord.h"
+#include "utility/FixedBlockWriter.h"
using namespace android;
using namespace aaudio;
AudioStreamRecord::AudioStreamRecord()
- : AudioStream()
+ : AudioStreamLegacy()
+ , mFixedBlockWriter(*this)
{
}
@@ -58,7 +60,6 @@
? 2 : getSamplesPerFrame();
audio_channel_mask_t channelMask = audio_channel_in_mask_from_count(samplesPerFrame);
- AudioRecord::callback_t callback = nullptr;
audio_input_flags_t flags = (audio_input_flags_t) AUDIO_INPUT_FLAG_NONE;
size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
@@ -68,6 +69,17 @@
? AUDIO_FORMAT_PCM_FLOAT
: AAudioConvert_aaudioToAndroidDataFormat(getFormat());
+ // Setup the callback if there is one.
+ AudioRecord::callback_t callback = nullptr;
+ void *callbackData = nullptr;
+ AudioRecord::transfer_type streamTransferType = AudioRecord::transfer_type::TRANSFER_SYNC;
+ if (builder.getDataCallbackProc() != nullptr) {
+ streamTransferType = AudioRecord::transfer_type::TRANSFER_CALLBACK;
+ callback = getLegacyCallback();
+ callbackData = this;
+ }
+ mCallbackBufferSize = builder.getFramesPerDataCallback();
+
mAudioRecord = new AudioRecord(
AUDIO_SOURCE_DEFAULT,
getSampleRate(),
@@ -76,10 +88,10 @@
mOpPackageName, // const String16& opPackageName TODO does not compile
frameCount,
callback,
- nullptr, // void* user = nullptr,
+ callbackData,
0, // uint32_t notificationFrames = 0,
AUDIO_SESSION_ALLOCATE,
- AudioRecord::TRANSFER_DEFAULT,
+ streamTransferType,
flags
// int uid = -1,
// pid_t pid = -1,
@@ -99,6 +111,15 @@
setSamplesPerFrame(mAudioRecord->channelCount());
setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioRecord->format()));
+ // We may need to pass the data through a block size adapter to guarantee constant size.
+ if (mCallbackBufferSize != AAUDIO_UNSPECIFIED) {
+ int callbackSizeBytes = getBytesPerFrame() * mCallbackBufferSize;
+ mFixedBlockWriter.open(callbackSizeBytes);
+ mBlockAdapter = &mFixedBlockWriter;
+ } else {
+ mBlockAdapter = nullptr;
+ }
+
setState(AAUDIO_STREAM_STATE_OPEN);
return AAUDIO_OK;
@@ -111,9 +132,29 @@
mAudioRecord.clear();
setState(AAUDIO_STREAM_STATE_CLOSED);
}
+ mFixedBlockWriter.close();
return AAUDIO_OK;
}
+void AudioStreamRecord::processCallback(int event, void *info) {
+
+ ALOGD("AudioStreamRecord::processCallback(), event %d", event);
+ switch (event) {
+ case AudioRecord::EVENT_MORE_DATA:
+ processCallbackCommon(AAUDIO_CALLBACK_OPERATION_PROCESS_DATA, info);
+ break;
+
+ // Stream got rerouted so we disconnect.
+ case AudioRecord::EVENT_NEW_IAUDIORECORD:
+ processCallbackCommon(AAUDIO_CALLBACK_OPERATION_DISCONNECTED, info);
+ break;
+
+ default:
+ break;
+ }
+ return;
+}
+
aaudio_result_t AudioStreamRecord::requestStart()
{
if (mAudioRecord.get() == nullptr) {
@@ -124,6 +165,7 @@
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
}
+
err = mAudioRecord->start();
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
@@ -151,7 +193,7 @@
return AAUDIO_OK;
}
-aaudio_result_t AudioStreamRecord::updateState()
+aaudio_result_t AudioStreamRecord::updateStateWhileWaiting()
{
aaudio_result_t result = AAUDIO_OK;
aaudio_wrapping_frames_t position;
@@ -222,7 +264,7 @@
int32_t AudioStreamRecord::getFramesPerBurst() const
{
- return 192; // TODO add query to AudioRecord.cpp
+ return static_cast<int32_t>(mAudioRecord->getNotificationPeriodInFrames());
}
aaudio_result_t AudioStreamRecord::getTimestamp(clockid_t clockId,
diff --git a/media/libaaudio/src/legacy/AudioStreamRecord.h b/media/libaaudio/src/legacy/AudioStreamRecord.h
index 4667f05..897a5b3 100644
--- a/media/libaaudio/src/legacy/AudioStreamRecord.h
+++ b/media/libaaudio/src/legacy/AudioStreamRecord.h
@@ -23,51 +23,58 @@
#include "AudioStreamBuilder.h"
#include "AudioStream.h"
#include "AAudioLegacy.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "utility/FixedBlockWriter.h"
namespace aaudio {
/**
* Internal stream that uses the legacy AudioTrack path.
*/
-class AudioStreamRecord : public AudioStream {
+class AudioStreamRecord : public AudioStreamLegacy {
public:
AudioStreamRecord();
virtual ~AudioStreamRecord();
- virtual aaudio_result_t open(const AudioStreamBuilder & builder) override;
- virtual aaudio_result_t close() override;
+ aaudio_result_t open(const AudioStreamBuilder & builder) override;
+ aaudio_result_t close() override;
- virtual aaudio_result_t requestStart() override;
- virtual aaudio_result_t requestPause() override;
- virtual aaudio_result_t requestFlush() override;
- virtual aaudio_result_t requestStop() override;
+ aaudio_result_t requestStart() override;
+ aaudio_result_t requestPause() override;
+ aaudio_result_t requestFlush() override;
+ aaudio_result_t requestStop() override;
virtual aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
- virtual aaudio_result_t read(void *buffer,
+ aaudio_result_t read(void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
- virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
+ aaudio_result_t setBufferSize(int32_t requestedFrames) override;
- virtual int32_t getBufferSize() const override;
+ int32_t getBufferSize() const override;
- virtual int32_t getBufferCapacity() const override;
+ int32_t getBufferCapacity() const override;
- virtual int32_t getXRunCount() const override;
+ int32_t getXRunCount() const override;
- virtual int32_t getFramesPerBurst() const override;
+ int32_t getFramesPerBurst() const override;
- virtual aaudio_result_t updateState() override;
+ aaudio_result_t updateStateWhileWaiting() override;
+
+ // This is public so it can be called from the C callback function.
+ void processCallback(int event, void *info) override;
private:
android::sp<android::AudioRecord> mAudioRecord;
+ // adapts between variable sized blocks and fixed size blocks
+ FixedBlockWriter mFixedBlockWriter;
+
// TODO add 64-bit position reporting to AudioRecord and use it.
- aaudio_wrapping_frames_t mPositionWhenStarting = 0;
- android::String16 mOpPackageName;
+ android::String16 mOpPackageName;
};
} /* namespace aaudio */
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.cpp b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
index 8bb6aee..ff87c28 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.cpp
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.cpp
@@ -20,20 +20,25 @@
#include <stdint.h>
#include <media/AudioTrack.h>
-#include <aaudio/AAudio.h>
-#include "utility/AudioClock.h"
-#include "AudioStreamTrack.h"
-#include "utility/AAudioUtilities.h"
+#include <aaudio/AAudio.h>
+#include "AudioClock.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "legacy/AudioStreamTrack.h"
+#include "utility/FixedBlockReader.h"
using namespace android;
using namespace aaudio;
+// Arbitrary and somewhat generous number of bursts.
+#define DEFAULT_BURSTS_PER_BUFFER_CAPACITY 8
+
/*
* Create a stream that uses the AudioTrack.
*/
AudioStreamTrack::AudioStreamTrack()
- : AudioStream()
+ : AudioStreamLegacy()
+ , mFixedBlockReader(*this)
{
}
@@ -53,6 +58,8 @@
return result;
}
+ ALOGD("AudioStreamTrack::open = %p", this);
+
// Try to create an AudioTrack
// TODO Support UNSPECIFIED in AudioTrack. For now, use stereo if unspecified.
int32_t samplesPerFrame = (getSamplesPerFrame() == AAUDIO_UNSPECIFIED)
@@ -61,16 +68,40 @@
ALOGD("AudioStreamTrack::open(), samplesPerFrame = %d, channelMask = 0x%08x",
samplesPerFrame, channelMask);
- AudioTrack::callback_t callback = nullptr;
// TODO add more performance options
audio_output_flags_t flags = (audio_output_flags_t) AUDIO_OUTPUT_FLAG_FAST;
- size_t frameCount = (builder.getBufferCapacity() == AAUDIO_UNSPECIFIED) ? 0
- : builder.getBufferCapacity();
+
+ int32_t frameCount = builder.getBufferCapacity();
+ ALOGD("AudioStreamTrack::open(), requested buffer capacity %d", frameCount);
+
+ int32_t notificationFrames = 0;
+
// TODO implement an unspecified AudioTrack format then use that.
- audio_format_t format = (getFormat() == AAUDIO_UNSPECIFIED)
+ audio_format_t format = (getFormat() == AAUDIO_FORMAT_UNSPECIFIED)
? AUDIO_FORMAT_PCM_FLOAT
: AAudioConvert_aaudioToAndroidDataFormat(getFormat());
+ // Setup the callback if there is one.
+ AudioTrack::callback_t callback = nullptr;
+ void *callbackData = nullptr;
+ // Note that TRANSFER_SYNC does not allow FAST track
+ AudioTrack::transfer_type streamTransferType = AudioTrack::transfer_type::TRANSFER_SYNC;
+ if (builder.getDataCallbackProc() != nullptr) {
+ streamTransferType = AudioTrack::transfer_type::TRANSFER_CALLBACK;
+ callback = getLegacyCallback();
+ callbackData = this;
+
+ notificationFrames = builder.getFramesPerDataCallback();
+ // If the total buffer size is unspecified then base the size on the burst size.
+ if (frameCount == AAUDIO_UNSPECIFIED) {
+ // Take advantage of a special trick that allows us to create a buffer
+ // that is some multiple of the burst size.
+ notificationFrames = 0 - DEFAULT_BURSTS_PER_BUFFER_CAPACITY;
+ }
+ }
+ mCallbackBufferSize = builder.getFramesPerDataCallback();
+
+ ALOGD("AudioStreamTrack::open(), notificationFrames = %d", notificationFrames);
mAudioTrack = new AudioTrack(
(audio_stream_type_t) AUDIO_STREAM_MUSIC,
getSampleRate(),
@@ -79,10 +110,10 @@
frameCount,
flags,
callback,
- nullptr, // user callback data
- 0, // notificationFrames
+ callbackData,
+ notificationFrames,
AUDIO_SESSION_ALLOCATE,
- AudioTrack::transfer_type::TRANSFER_SYNC // TODO - this does not allow FAST
+ streamTransferType
);
// Did we get a valid track?
@@ -97,7 +128,18 @@
// Get the actual values from the AudioTrack.
setSamplesPerFrame(mAudioTrack->channelCount());
setSampleRate(mAudioTrack->getSampleRate());
- setFormat(AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format()));
+ aaudio_audio_format_t aaudioFormat =
+ AAudioConvert_androidToAAudioDataFormat(mAudioTrack->format());
+ setFormat(aaudioFormat);
+
+ // We may need to pass the data through a block size adapter to guarantee constant size.
+ if (mCallbackBufferSize != AAUDIO_UNSPECIFIED) {
+ int callbackSizeBytes = getBytesPerFrame() * mCallbackBufferSize;
+ mFixedBlockReader.open(callbackSizeBytes);
+ mBlockAdapter = &mFixedBlockReader;
+ } else {
+ mBlockAdapter = nullptr;
+ }
setState(AAUDIO_STREAM_STATE_OPEN);
@@ -111,11 +153,32 @@
mAudioTrack.clear(); // TODO is this right?
setState(AAUDIO_STREAM_STATE_CLOSED);
}
+ mFixedBlockReader.close();
return AAUDIO_OK;
}
+void AudioStreamTrack::processCallback(int event, void *info) {
+
+ switch (event) {
+ case AudioTrack::EVENT_MORE_DATA:
+ processCallbackCommon(AAUDIO_CALLBACK_OPERATION_PROCESS_DATA, info);
+ break;
+
+ // Stream got rerouted so we disconnect.
+ case AudioTrack::EVENT_NEW_IAUDIOTRACK:
+ processCallbackCommon(AAUDIO_CALLBACK_OPERATION_DISCONNECTED, info);
+ break;
+
+ default:
+ break;
+ }
+ return;
+}
+
aaudio_result_t AudioStreamTrack::requestStart()
{
+ std::lock_guard<std::mutex> lock(mStreamMutex);
+
if (mAudioTrack.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
}
@@ -124,6 +187,7 @@
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
}
+
err = mAudioTrack->start();
if (err != OK) {
return AAudioConvert_androidToAAudioResult(err);
@@ -135,11 +199,14 @@
aaudio_result_t AudioStreamTrack::requestPause()
{
+ std::lock_guard<std::mutex> lock(mStreamMutex);
+
if (mAudioTrack.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
} else if (getState() != AAUDIO_STREAM_STATE_STARTING
&& getState() != AAUDIO_STREAM_STATE_STARTED) {
- ALOGE("requestPause(), called when state is %s", AAudio_convertStreamStateToText(getState()));
+ ALOGE("requestPause(), called when state is %s",
+ AAudio_convertStreamStateToText(getState()));
return AAUDIO_ERROR_INVALID_STATE;
}
setState(AAUDIO_STREAM_STATE_PAUSING);
@@ -152,6 +219,8 @@
}
aaudio_result_t AudioStreamTrack::requestFlush() {
+ std::lock_guard<std::mutex> lock(mStreamMutex);
+
if (mAudioTrack.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
} else if (getState() != AAUDIO_STREAM_STATE_PAUSED) {
@@ -165,6 +234,8 @@
}
aaudio_result_t AudioStreamTrack::requestStop() {
+ std::lock_guard<std::mutex> lock(mStreamMutex);
+
if (mAudioTrack.get() == nullptr) {
return AAUDIO_ERROR_INVALID_STATE;
}
@@ -175,7 +246,7 @@
return AAUDIO_OK;
}
-aaudio_result_t AudioStreamTrack::updateState()
+aaudio_result_t AudioStreamTrack::updateStateWhileWaiting()
{
status_t err;
aaudio_wrapping_frames_t position;
@@ -272,7 +343,7 @@
int32_t AudioStreamTrack::getFramesPerBurst() const
{
- return 192; // TODO add query to AudioTrack.cpp
+ return static_cast<int32_t>(mAudioTrack->getNotificationPeriodInFrames());
}
int64_t AudioStreamTrack::getFramesRead() {
@@ -303,7 +374,7 @@
}
// TODO Merge common code into AudioStreamLegacy after rebasing.
int timebase;
- switch(clockId) {
+ switch (clockId) {
case CLOCK_BOOTTIME:
timebase = ExtendedTimestamp::TIMEBASE_BOOTTIME;
break;
diff --git a/media/libaaudio/src/legacy/AudioStreamTrack.h b/media/libaaudio/src/legacy/AudioStreamTrack.h
index 7a53022..29f5d15 100644
--- a/media/libaaudio/src/legacy/AudioStreamTrack.h
+++ b/media/libaaudio/src/legacy/AudioStreamTrack.h
@@ -17,54 +17,63 @@
#ifndef LEGACY_AUDIO_STREAM_TRACK_H
#define LEGACY_AUDIO_STREAM_TRACK_H
+#include <math.h>
#include <media/AudioTrack.h>
#include <aaudio/AAudio.h>
#include "AudioStreamBuilder.h"
#include "AudioStream.h"
-#include "AAudioLegacy.h"
+#include "legacy/AAudioLegacy.h"
+#include "legacy/AudioStreamLegacy.h"
+#include "utility/FixedBlockReader.h"
namespace aaudio {
-
/**
* Internal stream that uses the legacy AudioTrack path.
*/
-class AudioStreamTrack : public AudioStream {
+class AudioStreamTrack : public AudioStreamLegacy {
public:
AudioStreamTrack();
virtual ~AudioStreamTrack();
- virtual aaudio_result_t open(const AudioStreamBuilder & builder) override;
- virtual aaudio_result_t close() override;
+ aaudio_result_t open(const AudioStreamBuilder & builder) override;
+ aaudio_result_t close() override;
- virtual aaudio_result_t requestStart() override;
- virtual aaudio_result_t requestPause() override;
- virtual aaudio_result_t requestFlush() override;
- virtual aaudio_result_t requestStop() override;
+ aaudio_result_t requestStart() override;
+ aaudio_result_t requestPause() override;
+ aaudio_result_t requestFlush() override;
+ aaudio_result_t requestStop() override;
- virtual aaudio_result_t getTimestamp(clockid_t clockId,
+ aaudio_result_t getTimestamp(clockid_t clockId,
int64_t *framePosition,
int64_t *timeNanoseconds) override;
- virtual aaudio_result_t write(const void *buffer,
+ aaudio_result_t write(const void *buffer,
int32_t numFrames,
int64_t timeoutNanoseconds) override;
- virtual aaudio_result_t setBufferSize(int32_t requestedFrames) override;
- virtual int32_t getBufferSize() const override;
- virtual int32_t getBufferCapacity() const override;
- virtual int32_t getFramesPerBurst()const override;
- virtual int32_t getXRunCount() const override;
+ aaudio_result_t setBufferSize(int32_t requestedFrames) override;
+ int32_t getBufferSize() const override;
+ int32_t getBufferCapacity() const override;
+ int32_t getFramesPerBurst()const override;
+ int32_t getXRunCount() const override;
- virtual int64_t getFramesRead() override;
+ int64_t getFramesRead() override;
- virtual aaudio_result_t updateState() override;
+ aaudio_result_t updateStateWhileWaiting() override;
+
+ // This is public so it can be called from the C callback function.
+ void processCallback(int event, void *info) override;
private:
+
android::sp<android::AudioTrack> mAudioTrack;
+ // adapts between variable sized blocks and fixed size blocks
+ FixedBlockReader mFixedBlockReader;
+
// TODO add 64-bit position reporting to AudioRecord and use it.
aaudio_wrapping_frames_t mPositionWhenStarting = 0;
aaudio_wrapping_frames_t mPositionWhenPausing = 0;
diff --git a/media/libaaudio/src/utility/FixedBlockAdapter.cpp b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
new file mode 100644
index 0000000..f4666af
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockAdapter.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+FixedBlockAdapter::~FixedBlockAdapter() {
+ close();
+}
+
+int32_t FixedBlockAdapter::open(int32_t bytesPerFixedBlock)
+{
+ mSize = bytesPerFixedBlock;
+ mStorage = new uint8_t[bytesPerFixedBlock]; // TODO use std::nothrow
+ mPosition = 0;
+ return 0;
+}
+
+int32_t FixedBlockAdapter::close()
+{
+ delete[] mStorage;
+ mStorage = nullptr;
+ mSize = 0;
+ mPosition = 0;
+ return 0;
+}
diff --git a/media/libaaudio/src/utility/FixedBlockAdapter.h b/media/libaaudio/src/utility/FixedBlockAdapter.h
new file mode 100644
index 0000000..7008b25
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockAdapter.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 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 AAUDIO_FIXED_BLOCK_ADAPTER_H
+#define AAUDIO_FIXED_BLOCK_ADAPTER_H
+
+#include <stdio.h>
+
+/**
+ * Interface for a class that needs fixed-size blocks.
+ */
+class FixedBlockProcessor {
+public:
+ virtual ~FixedBlockProcessor() = default;
+ virtual int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) = 0;
+};
+
+/**
+ * Base class for a variable-to-fixed-size block adapter.
+ */
+class FixedBlockAdapter
+{
+public:
+ FixedBlockAdapter(FixedBlockProcessor &fixedBlockProcessor)
+ : mFixedBlockProcessor(fixedBlockProcessor) {}
+
+ virtual ~FixedBlockAdapter();
+
+ /**
+ * Allocate internal resources needed for buffering data.
+ */
+ virtual int32_t open(int32_t bytesPerFixedBlock);
+
+ /**
+ * Note that if the fixed-sized blocks must be aligned, then the variable-sized blocks
+ * must have the same alignment.
+ * For example, if the fixed-size blocks must be a multiple of 8, then the variable-sized
+ * blocks must also be a multiple of 8.
+ *
+ * @param buffer
+ * @param numBytes
+ * @return zero if OK or a non-zero code
+ */
+ virtual int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) = 0;
+
+ /**
+ * Free internal resources.
+ */
+ int32_t close();
+
+protected:
+ FixedBlockProcessor &mFixedBlockProcessor;
+ uint8_t *mStorage = nullptr; // Store data here while assembling buffers.
+ int32_t mSize = 0; // Size in bytes of the fixed size buffer.
+ int32_t mPosition = 0; // Offset of the last byte read or written.
+};
+
+#endif /* AAUDIO_FIXED_BLOCK_ADAPTER_H */
diff --git a/media/libaaudio/src/utility/FixedBlockReader.cpp b/media/libaaudio/src/utility/FixedBlockReader.cpp
new file mode 100644
index 0000000..21ea70e
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockReader.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <memory.h>
+
+#include "FixedBlockAdapter.h"
+
+#include "FixedBlockReader.h"
+
+
+FixedBlockReader::FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor)
+ : FixedBlockAdapter(fixedBlockProcessor) {
+ mPosition = mSize;
+}
+
+int32_t FixedBlockReader::open(int32_t bytesPerFixedBlock) {
+ int32_t result = FixedBlockAdapter::open(bytesPerFixedBlock);
+ mPosition = mSize; // Indicate no data in storage.
+ return result;
+}
+
+int32_t FixedBlockReader::readFromStorage(uint8_t *buffer, int32_t numBytes) {
+ int32_t bytesToRead = numBytes;
+ int32_t dataAvailable = mSize - mPosition;
+ if (bytesToRead > dataAvailable) {
+ bytesToRead = dataAvailable;
+ }
+ memcpy(buffer, mStorage + mPosition, bytesToRead);
+ mPosition += bytesToRead;
+ return bytesToRead;
+}
+
+int32_t FixedBlockReader::processVariableBlock(uint8_t *buffer, int32_t numBytes) {
+ int32_t result = 0;
+ int32_t bytesLeft = numBytes;
+ while(bytesLeft > 0 && result == 0) {
+ if (mPosition < mSize) {
+ // Use up bytes currently in storage.
+ int32_t bytesRead = readFromStorage(buffer, bytesLeft);
+ buffer += bytesRead;
+ bytesLeft -= bytesRead;
+ } else if (bytesLeft >= mSize) {
+ // Read through if enough for a complete block.
+ result = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
+ buffer += mSize;
+ bytesLeft -= mSize;
+ } else {
+ // Just need a partial block so we have to use storage.
+ result = mFixedBlockProcessor.onProcessFixedBlock(mStorage, mSize);
+ mPosition = 0;
+ }
+ }
+ return result;
+}
+
diff --git a/media/libaaudio/src/utility/FixedBlockReader.h b/media/libaaudio/src/utility/FixedBlockReader.h
new file mode 100644
index 0000000..128dd52
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockReader.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 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 AAUDIO_FIXED_BLOCK_READER_H
+#define AAUDIO_FIXED_BLOCK_READER_H
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+/**
+ * Read from a fixed-size block to a variable sized block.
+ *
+ * This can be used to convert a pull data flow from fixed sized buffers to variable sized buffers.
+ * An example would be an audio output callback that reads from the app.
+ */
+class FixedBlockReader : public FixedBlockAdapter
+{
+public:
+ FixedBlockReader(FixedBlockProcessor &fixedBlockProcessor);
+
+ virtual ~FixedBlockReader() = default;
+
+ int32_t open(int32_t bytesPerFixedBlock) override;
+
+ int32_t readFromStorage(uint8_t *buffer, int32_t numBytes);
+
+ /**
+ * Read into a variable sized block.
+ */
+ int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) override;
+};
+
+
+#endif /* AAUDIO_FIXED_BLOCK_READER_H */
diff --git a/media/libaaudio/src/utility/FixedBlockWriter.cpp b/media/libaaudio/src/utility/FixedBlockWriter.cpp
new file mode 100644
index 0000000..2ce8046
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockWriter.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+#include <memory.h>
+
+#include "FixedBlockAdapter.h"
+#include "FixedBlockWriter.h"
+
+FixedBlockWriter::FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor)
+ : FixedBlockAdapter(fixedBlockProcessor) {}
+
+
+int32_t FixedBlockWriter::writeToStorage(uint8_t *buffer, int32_t numBytes) {
+ int32_t bytesToStore = numBytes;
+ int32_t roomAvailable = mSize - mPosition;
+ if (bytesToStore > roomAvailable) {
+ bytesToStore = roomAvailable;
+ }
+ memcpy(mStorage + mPosition, buffer, bytesToStore);
+ mPosition += bytesToStore;
+ return bytesToStore;
+}
+
+int32_t FixedBlockWriter::processVariableBlock(uint8_t *buffer, int32_t numBytes) {
+ int32_t result = 0;
+ int32_t bytesLeft = numBytes;
+
+ // If we already have data in storage then add to it.
+ if (mPosition > 0) {
+ int32_t bytesWritten = writeToStorage(buffer, bytesLeft);
+ buffer += bytesWritten;
+ bytesLeft -= bytesWritten;
+ // If storage full then flush it out
+ if (mPosition == mSize) {
+ result = mFixedBlockProcessor.onProcessFixedBlock(mStorage, mSize);
+ mPosition = 0;
+ }
+ }
+
+ // Write through if enough for a complete block.
+ while(bytesLeft > mSize && result == 0) {
+ result = mFixedBlockProcessor.onProcessFixedBlock(buffer, mSize);
+ buffer += mSize;
+ bytesLeft -= mSize;
+ }
+
+ // Save any remaining partial block for next time.
+ if (bytesLeft > 0) {
+ writeToStorage(buffer, bytesLeft);
+ }
+
+ return result;
+}
diff --git a/media/libaaudio/src/utility/FixedBlockWriter.h b/media/libaaudio/src/utility/FixedBlockWriter.h
new file mode 100644
index 0000000..f1d917c
--- /dev/null
+++ b/media/libaaudio/src/utility/FixedBlockWriter.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2017 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 AAUDIO_FIXED_BLOCK_WRITER_H
+#define AAUDIO_FIXED_BLOCK_WRITER_H
+
+#include <stdint.h>
+
+#include "FixedBlockAdapter.h"
+
+/**
+ * This can be used to convert a push data flow from variable sized buffers to fixed sized buffers.
+ * An example would be an audio input callback.
+ */
+class FixedBlockWriter : public FixedBlockAdapter
+{
+public:
+ FixedBlockWriter(FixedBlockProcessor &fixedBlockProcessor);
+
+ virtual ~FixedBlockWriter() = default;
+
+ int32_t writeToStorage(uint8_t *buffer, int32_t numBytes);
+
+ /**
+ * Write from a variable sized block.
+ */
+ int32_t processVariableBlock(uint8_t *buffer, int32_t numBytes) override;
+};
+
+#endif /* AAUDIO_FIXED_BLOCK_WRITER_H */
diff --git a/media/libaaudio/tests/Android.mk b/media/libaaudio/tests/Android.mk
index 7899cf5..06c9364 100644
--- a/media/libaaudio/tests/Android.mk
+++ b/media/libaaudio/tests/Android.mk
@@ -4,8 +4,7 @@
LOCAL_C_INCLUDES := \
$(call include-path-for, audio-utils) \
frameworks/av/media/libaaudio/include \
- frameworks/av/media/libaaudio/src/core \
- frameworks/av/media/libaaudio/src/utility
+ frameworks/av/media/libaaudio/src
LOCAL_SRC_FILES:= test_handle_tracker.cpp
LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
libcutils liblog libmedia libutils
@@ -17,13 +16,22 @@
LOCAL_C_INCLUDES := \
$(call include-path-for, audio-utils) \
frameworks/av/media/libaaudio/include \
- frameworks/av/media/libaaudio/src \
- frameworks/av/media/libaaudio/src/core \
- frameworks/av/media/libaaudio/src/fifo \
- frameworks/av/media/libaaudio/src/utility
+ frameworks/av/media/libaaudio/src
LOCAL_SRC_FILES:= test_marshalling.cpp
LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
libcutils liblog libmedia libutils
LOCAL_STATIC_LIBRARIES := libaaudio
-LOCAL_MODULE := test_marshalling
+LOCAL_MODULE := test_aaudio_marshalling
+include $(BUILD_NATIVE_TEST)
+
+include $(CLEAR_VARS)
+LOCAL_C_INCLUDES := \
+ $(call include-path-for, audio-utils) \
+ frameworks/av/media/libaaudio/include \
+ frameworks/av/media/libaaudio/src
+LOCAL_SRC_FILES:= test_block_adapter.cpp
+LOCAL_SHARED_LIBRARIES := libaudioclient libaudioutils libbinder \
+ libcutils liblog libmedia libutils
+LOCAL_STATIC_LIBRARIES := libaaudio
+LOCAL_MODULE := test_block_adapter
include $(BUILD_NATIVE_TEST)
diff --git a/media/libaaudio/tests/test_block_adapter.cpp b/media/libaaudio/tests/test_block_adapter.cpp
new file mode 100644
index 0000000..a22abb9
--- /dev/null
+++ b/media/libaaudio/tests/test_block_adapter.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <iostream>
+
+#include <gtest/gtest.h>
+
+#include "utility/FixedBlockAdapter.h"
+#include "utility/FixedBlockWriter.h"
+#include "utility/FixedBlockReader.h"
+
+#define FIXED_BLOCK_SIZE 47
+#define TEST_BUFFER_SIZE 103
+
+// Pass varying sized blocks.
+// Frames contain a sequential index, which are easily checked.
+class TestBlockAdapter {
+public:
+ TestBlockAdapter()
+ : mTestIndex(0), mLastIndex(0) {
+ }
+
+ ~TestBlockAdapter() = default;
+
+ void fillSequence(int32_t *indexBuffer, int32_t frameCount) {
+ ASSERT_LE(frameCount, TEST_BUFFER_SIZE);
+ for (int i = 0; i < frameCount; i++) {
+ indexBuffer[i] = mLastIndex++;
+ }
+ }
+
+ int checkSequence(const int32_t *indexBuffer, int32_t frameCount) {
+ // This is equivalent to calling an output callback.
+ for (int i = 0; i < frameCount; i++) {
+ int32_t expected = mTestIndex++;
+ int32_t actual = indexBuffer[i];
+ EXPECT_EQ(expected, actual);
+ if (actual != expected) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ int32_t mTestBuffer[TEST_BUFFER_SIZE];
+ int32_t mTestIndex;
+ int32_t mLastIndex;
+};
+
+class TestBlockWriter : public TestBlockAdapter, FixedBlockProcessor {
+public:
+ TestBlockWriter()
+ : mFixedBlockWriter(*this) {
+ mFixedBlockWriter.open(sizeof(int32_t) * FIXED_BLOCK_SIZE);
+ }
+
+ ~TestBlockWriter() {
+ mFixedBlockWriter.close();
+ }
+
+ int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override {
+ int32_t frameCount = numBytes / sizeof(int32_t);
+ return checkSequence((int32_t *) buffer, frameCount);
+ }
+
+ // Simulate audio input from a variable sized callback.
+ int32_t testInputWrite(int32_t variableCount) {
+ fillSequence(mTestBuffer, variableCount);
+ int32_t sizeBytes = variableCount * sizeof(int32_t);
+ return mFixedBlockWriter.processVariableBlock((uint8_t *) mTestBuffer, sizeBytes);
+ }
+
+private:
+ FixedBlockWriter mFixedBlockWriter;
+};
+
+class TestBlockReader : public TestBlockAdapter, FixedBlockProcessor {
+public:
+ TestBlockReader()
+ : mFixedBlockReader(*this) {
+ mFixedBlockReader.open(sizeof(int32_t) * FIXED_BLOCK_SIZE);
+ }
+
+ ~TestBlockReader() {
+ mFixedBlockReader.close();
+ }
+
+ int32_t onProcessFixedBlock(uint8_t *buffer, int32_t numBytes) override {
+ int32_t frameCount = numBytes / sizeof(int32_t);
+ fillSequence((int32_t *) buffer, frameCount);
+ return 0;
+ }
+
+ // Simulate audio output from a variable sized callback.
+ int32_t testOutputRead(int32_t variableCount) {
+ int32_t sizeBytes = variableCount * sizeof(int32_t);
+ int32_t result = mFixedBlockReader.processVariableBlock((uint8_t *) mTestBuffer, sizeBytes);
+ if (result >= 0) {
+ result = checkSequence((int32_t *)mTestBuffer, variableCount);
+ }
+ return result;
+ }
+
+private:
+ FixedBlockReader mFixedBlockReader;
+};
+
+
+TEST(test_block_adapter, block_adapter_write) {
+ TestBlockWriter tester;
+ int result = 0;
+ const int numLoops = 1000;
+
+ for (int i = 0; i<numLoops && result == 0; i++) {
+ long r = random();
+ int32_t size = (r % TEST_BUFFER_SIZE);
+ ASSERT_LE(size, TEST_BUFFER_SIZE);
+ ASSERT_GE(size, 0);
+ result = tester.testInputWrite(size);
+ }
+ ASSERT_EQ(0, result);
+}
+
+TEST(test_block_adapter, block_adapter_read) {
+ TestBlockReader tester;
+ int result = 0;
+ const int numLoops = 1000;
+
+ for (int i = 0; i < numLoops && result == 0; i++) {
+ long r = random();
+ int32_t size = (r % TEST_BUFFER_SIZE);
+ ASSERT_LE(size, TEST_BUFFER_SIZE);
+ ASSERT_GE(size, 0);
+ result = tester.testOutputRead(size);
+ }
+ ASSERT_EQ(0, result);
+};
+
diff --git a/media/libaaudio/tests/test_handle_tracker.cpp b/media/libaaudio/tests/test_handle_tracker.cpp
index e51c39c..e1cb676 100644
--- a/media/libaaudio/tests/test_handle_tracker.cpp
+++ b/media/libaaudio/tests/test_handle_tracker.cpp
@@ -22,7 +22,7 @@
#include <gtest/gtest.h>
#include <aaudio/AAudioDefinitions.h>
-#include "HandleTracker.h"
+#include "utility/HandleTracker.h"
// Test adding one address.
TEST(test_handle_tracker, aaudio_handle_tracker) {
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index 6c7cdde..5c54bb2 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -645,10 +645,10 @@
mAwaitBoost = false;
if (mFlags & AUDIO_INPUT_FLAG_FAST) {
if (flags & AUDIO_INPUT_FLAG_FAST) {
- ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu", frameCount);
+ ALOGI("AUDIO_INPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp);
mAwaitBoost = true;
} else {
- ALOGW("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu", frameCount);
+ ALOGW("AUDIO_INPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount, temp);
mFlags = (audio_input_flags_t) (mFlags & ~(AUDIO_INPUT_FLAG_FAST |
AUDIO_INPUT_FLAG_RAW));
continue; // retry
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index d590cb7..3a0ce5e 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -1479,12 +1479,13 @@
mAwaitBoost = false;
if (mFlags & AUDIO_OUTPUT_FLAG_FAST) {
if (flags & AUDIO_OUTPUT_FLAG_FAST) {
- ALOGV("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu", frameCount);
+ ALOGI("AUDIO_OUTPUT_FLAG_FAST successful; frameCount %zu -> %zu", frameCount, temp);
if (!mThreadCanCallJava) {
mAwaitBoost = true;
}
} else {
- ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu", frameCount);
+ ALOGW("AUDIO_OUTPUT_FLAG_FAST denied by server; frameCount %zu -> %zu", frameCount,
+ temp);
}
}
mFlags = flags;
diff --git a/media/libaudioclient/AudioTrackShared.cpp b/media/libaudioclient/AudioTrackShared.cpp
index 846f8b8..2ce6c63 100644
--- a/media/libaudioclient/AudioTrackShared.cpp
+++ b/media/libaudioclient/AudioTrackShared.cpp
@@ -696,7 +696,8 @@
ssize_t filled = rear - front;
// pipe should not already be overfull
if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
- ALOGE("Shared memory control block is corrupt (filled=%zd); shutting down", filled);
+ ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
+ filled, mFrameCount);
mIsShutdown = true;
}
if (mIsShutdown) {
@@ -820,7 +821,8 @@
ssize_t filled = rear - cblk->u.mStreaming.mFront;
// pipe should not already be overfull
if (!(0 <= filled && (size_t) filled <= mFrameCount)) {
- ALOGE("Shared memory control block is corrupt (filled=%zd); shutting down", filled);
+ ALOGE("Shared memory control block is corrupt (filled=%zd, mFrameCount=%zu); shutting down",
+ filled, mFrameCount);
mIsShutdown = true;
return 0;
}
diff --git a/media/libaudioclient/include/AudioRecord.h b/media/libaudioclient/include/AudioRecord.h
index 1c8746f..1b034b5 100644
--- a/media/libaudioclient/include/AudioRecord.h
+++ b/media/libaudioclient/include/AudioRecord.h
@@ -243,6 +243,13 @@
size_t frameSize() const { return mFrameSize; }
audio_source_t inputSource() const { return mAttributes.source; }
+ /*
+ * Return the period of the notification callback in frames.
+ * This value is set when the AudioRecord is constructed.
+ * It can be modified if the AudioRecord is rerouted.
+ */
+ uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
+
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
* If event is not AudioSystem::SYNC_EVENT_NONE, the capture start will be delayed until
diff --git a/media/libaudioclient/include/AudioTrack.h b/media/libaudioclient/include/AudioTrack.h
index 0358363..16eb225 100644
--- a/media/libaudioclient/include/AudioTrack.h
+++ b/media/libaudioclient/include/AudioTrack.h
@@ -348,7 +348,12 @@
uint32_t channelCount() const { return mChannelCount; }
size_t frameCount() const { return mFrameCount; }
- // TODO consider notificationFrames() if needed
+ /*
+ * Return the period of the notification callback in frames.
+ * This value is set when the AudioTrack is constructed.
+ * It can be modified if the AudioTrack is rerouted.
+ */
+ uint32_t getNotificationPeriodInFrames() const { return mNotificationFramesAct; }
/* Return effective size of audio buffer that an application writes to
* or a negative error if the track is uninitialized.
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
index f050e7f..dc501b2 100644
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ b/media/libmediametrics/include/MediaAnalyticsItem.h
@@ -41,6 +41,7 @@
friend class MediaAnalyticsService;
friend class IMediaAnalyticsService;
friend class MediaMetricsJNI;
+ friend class MetricsSummarizer;
public:
@@ -231,7 +232,6 @@
size_t mPropCount;
size_t mPropSize;
Prop *mProps;
-
};
} // namespace android
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index 95f378f..3998cf6 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -1999,10 +1999,12 @@
mCameraSourceTimeLapse = NULL;
}
- if (mVideoEncoderSource != NULL) {
- int64_t stopTimeUs = systemTime() / 1000;
- sp<MetaData> meta = new MetaData;
- err = mVideoEncoderSource->setStopStimeUs(stopTimeUs);
+ int64_t stopTimeUs = systemTime() / 1000;
+ for (const auto &source : { mAudioEncoderSource, mVideoEncoderSource }) {
+ if (source != nullptr && OK != source->setStopTimeUs(stopTimeUs)) {
+ ALOGW("Failed to set stopTime %lld us for %s",
+ (long long)stopTimeUs, source->isVideo() ? "Video" : "Audio");
+ }
}
if (mWriter != NULL) {
diff --git a/media/libnbaio/NBLog.cpp b/media/libnbaio/NBLog.cpp
index de38e7f..adbbb74 100644
--- a/media/libnbaio/NBLog.cpp
+++ b/media/libnbaio/NBLog.cpp
@@ -31,6 +31,7 @@
#include <utils/String8.h>
#include <queue>
+#include <utility>
namespace android {
@@ -51,12 +52,25 @@
// ---------------------------------------------------------------------------
-NBLog::FormatEntry::FormatEntry(const uint8_t *entry) : mEntry(entry) {
- ALOGW_IF(entry[offsetof(struct entry, type)] != EVENT_START_FMT,
- "Created format entry with invalid event type %d", entry[offsetof(struct entry, type)]);
+/*static*/
+std::unique_ptr<NBLog::AbstractEntry> NBLog::AbstractEntry::buildEntry(const uint8_t *ptr) {
+ uint8_t type = EntryIterator(ptr)->type;
+ switch (type) {
+ case EVENT_START_FMT:
+ return std::make_unique<FormatEntry>(FormatEntry(ptr));
+ case EVENT_HISTOGRAM_FLUSH:
+ case EVENT_HISTOGRAM_ENTRY_TS:
+ return std::make_unique<HistogramEntry>(HistogramEntry(ptr));
+ default:
+ ALOGW("Tried to create AbstractEntry of type %d", type);
+ return nullptr;
+ }
}
-NBLog::FormatEntry::FormatEntry(const NBLog::FormatEntry::iterator &it) : FormatEntry(it.ptr) {}
+NBLog::AbstractEntry::AbstractEntry(const uint8_t *entry) : mEntry(entry) {
+}
+
+// ---------------------------------------------------------------------------
const char *NBLog::FormatEntry::formatString() const {
return (const char*) mEntry + offsetof(entry, data);
@@ -66,12 +80,14 @@
return mEntry[offsetof(entry, length)];
}
-NBLog::FormatEntry::iterator NBLog::FormatEntry::args() const {
+NBLog::EntryIterator NBLog::FormatEntry::args() const {
auto it = begin();
// skip start fmt
++it;
// skip timestamp
++it;
+ // skip hash
+ ++it;
// Skip author if present
if (it->type == EVENT_AUTHOR) {
++it;
@@ -79,19 +95,33 @@
return it;
}
-timespec NBLog::FormatEntry::timestamp() const {
+int64_t NBLog::FormatEntry::timestamp() const {
auto it = begin();
// skip start fmt
++it;
- return it.payload<timespec>();
+ return it.payload<int64_t>();
}
-pid_t NBLog::FormatEntry::author() const {
+NBLog::log_hash_t NBLog::FormatEntry::hash() const {
auto it = begin();
// skip start fmt
++it;
// skip timestamp
++it;
+ // unaligned 64-bit read not supported
+ log_hash_t hash;
+ memcpy(&hash, it->data, sizeof(hash));
+ return hash;
+}
+
+int NBLog::FormatEntry::author() const {
+ auto it = begin();
+ // skip start fmt
+ ++it;
+ // skip timestamp
+ ++it;
+ // skip hash
+ ++it;
// if there is an author entry, return it, return -1 otherwise
if (it->type == EVENT_AUTHOR) {
return it.payload<int>();
@@ -99,13 +129,15 @@
return -1;
}
-NBLog::FormatEntry::iterator NBLog::FormatEntry::copyWithAuthor(
+NBLog::EntryIterator NBLog::FormatEntry::copyWithAuthor(
std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const {
auto it = begin();
// copy fmt start entry
it.copyTo(dst);
// copy timestamp
(++it).copyTo(dst);
+ // copy hash
+ (++it).copyTo(dst);
// insert author entry
size_t authorEntrySize = NBLog::Entry::kOverhead + sizeof(author);
uint8_t authorEntry[authorEntrySize];
@@ -124,71 +156,107 @@
return it;
}
-void NBLog::FormatEntry::iterator::copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const {
+void NBLog::EntryIterator::copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const {
size_t length = ptr[offsetof(entry, length)] + NBLog::Entry::kOverhead;
dst->write(ptr, length);
}
-void NBLog::FormatEntry::iterator::copyData(uint8_t *dst) const {
+void NBLog::EntryIterator::copyData(uint8_t *dst) const {
memcpy((void*) dst, ptr + offsetof(entry, data), ptr[offsetof(entry, length)]);
}
-NBLog::FormatEntry::iterator NBLog::FormatEntry::begin() const {
- return iterator(mEntry);
+NBLog::EntryIterator NBLog::FormatEntry::begin() const {
+ return EntryIterator(mEntry);
}
-NBLog::FormatEntry::iterator::iterator()
+NBLog::EntryIterator::EntryIterator()
: ptr(nullptr) {}
-NBLog::FormatEntry::iterator::iterator(const uint8_t *entry)
+NBLog::EntryIterator::EntryIterator(const uint8_t *entry)
: ptr(entry) {}
-NBLog::FormatEntry::iterator::iterator(const NBLog::FormatEntry::iterator &other)
+NBLog::EntryIterator::EntryIterator(const NBLog::EntryIterator &other)
: ptr(other.ptr) {}
-const NBLog::FormatEntry::entry& NBLog::FormatEntry::iterator::operator*() const {
+const NBLog::entry& NBLog::EntryIterator::operator*() const {
return *(entry*) ptr;
}
-const NBLog::FormatEntry::entry* NBLog::FormatEntry::iterator::operator->() const {
+const NBLog::entry* NBLog::EntryIterator::operator->() const {
return (entry*) ptr;
}
-NBLog::FormatEntry::iterator& NBLog::FormatEntry::iterator::operator++() {
+NBLog::EntryIterator& NBLog::EntryIterator::operator++() {
ptr += ptr[offsetof(entry, length)] + NBLog::Entry::kOverhead;
return *this;
}
-NBLog::FormatEntry::iterator& NBLog::FormatEntry::iterator::operator--() {
+NBLog::EntryIterator& NBLog::EntryIterator::operator--() {
ptr -= ptr[NBLog::Entry::kPreviousLengthOffset] + NBLog::Entry::kOverhead;
return *this;
}
-NBLog::FormatEntry::iterator NBLog::FormatEntry::iterator::next() const {
- iterator aux(*this);
+NBLog::EntryIterator NBLog::EntryIterator::next() const {
+ EntryIterator aux(*this);
return ++aux;
}
-NBLog::FormatEntry::iterator NBLog::FormatEntry::iterator::prev() const {
- iterator aux(*this);
+NBLog::EntryIterator NBLog::EntryIterator::prev() const {
+ EntryIterator aux(*this);
return --aux;
}
-int NBLog::FormatEntry::iterator::operator-(const NBLog::FormatEntry::iterator &other) const {
+int NBLog::EntryIterator::operator-(const NBLog::EntryIterator &other) const {
return ptr - other.ptr;
}
-bool NBLog::FormatEntry::iterator::operator!=(const iterator &other) const {
+bool NBLog::EntryIterator::operator!=(const EntryIterator &other) const {
return ptr != other.ptr;
}
-bool NBLog::FormatEntry::iterator::hasConsistentLength() const {
+bool NBLog::EntryIterator::hasConsistentLength() const {
return ptr[offsetof(entry, length)] == ptr[ptr[offsetof(entry, length)] +
NBLog::Entry::kOverhead + NBLog::Entry::kPreviousLengthOffset];
}
// ---------------------------------------------------------------------------
+int64_t NBLog::HistogramEntry::timestamp() const {
+ return EntryIterator(mEntry).payload<HistTsEntry>().ts;
+}
+
+NBLog::log_hash_t NBLog::HistogramEntry::hash() const {
+ return EntryIterator(mEntry).payload<HistTsEntry>().hash;
+}
+
+int NBLog::HistogramEntry::author() const {
+ EntryIterator it(mEntry);
+ if (it->length == sizeof(HistTsEntryWithAuthor)) {
+ return it.payload<HistTsEntryWithAuthor>().author;
+ } else {
+ return -1;
+ }
+}
+
+NBLog::EntryIterator NBLog::HistogramEntry::copyWithAuthor(
+ std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const {
+ // Current histogram entry has {type, length, struct HistTsEntry, length}.
+ // We now want {type, length, struct HistTsEntryWithAuthor, length}
+ uint8_t buffer[Entry::kOverhead + sizeof(HistTsEntryWithAuthor)];
+ // Copy content until the point we want to add the author
+ memcpy(buffer, mEntry, sizeof(entry) + sizeof(HistTsEntry));
+ // Copy the author
+ *(int*) (buffer + sizeof(entry) + sizeof(HistTsEntry)) = author;
+ // Update lengths
+ buffer[offsetof(entry, length)] = sizeof(HistTsEntryWithAuthor);
+ buffer[sizeof(buffer) + Entry::kPreviousLengthOffset] = sizeof(HistTsEntryWithAuthor);
+ // Write new buffer into FIFO
+ dst->write(buffer, sizeof(buffer));
+ return EntryIterator(mEntry).next();
+}
+
+// ---------------------------------------------------------------------------
+
#if 0 // FIXME see note in NBLog.h
NBLog::Timeline::Timeline(size_t size, void *shared)
: mSize(roundup(size)), mOwn(shared == NULL),
@@ -301,13 +369,15 @@
if (!mEnabled) {
return;
}
- struct timespec ts;
- if (!clock_gettime(CLOCK_MONOTONIC, &ts)) {
+ int64_t ts = get_monotonic_ns();
+ if (ts > 0) {
log(EVENT_TIMESTAMP, &ts, sizeof(ts));
+ } else {
+ ALOGE("Failed to get timestamp");
}
}
-void NBLog::Writer::logTimestamp(const struct timespec &ts)
+void NBLog::Writer::logTimestamp(const int64_t ts)
{
if (!mEnabled) {
return;
@@ -360,19 +430,57 @@
log(&entry, true);
}
-void NBLog::Writer::logFormat(const char *fmt, ...)
+void NBLog::Writer::logHash(log_hash_t hash)
+{
+ if (!mEnabled) {
+ return;
+ }
+ log(EVENT_HASH, &hash, sizeof(hash));
+}
+
+void NBLog::Writer::logHistTS(log_hash_t hash)
+{
+ if (!mEnabled) {
+ return;
+ }
+ HistTsEntry data;
+ data.hash = hash;
+ data.ts = get_monotonic_ns();
+ if (data.ts > 0) {
+ log(EVENT_HISTOGRAM_ENTRY_TS, &data, sizeof(data));
+ } else {
+ ALOGE("Failed to get timestamp");
+ }
+}
+
+void NBLog::Writer::logHistFlush(log_hash_t hash)
+{
+ if (!mEnabled) {
+ return;
+ }
+ HistTsEntry data;
+ data.hash = hash;
+ data.ts = get_monotonic_ns();
+ if (data.ts > 0) {
+ log(EVENT_HISTOGRAM_FLUSH, &data, sizeof(data));
+ } else {
+ ALOGE("Failed to get timestamp");
+ }
+}
+
+void NBLog::Writer::logFormat(const char *fmt, log_hash_t hash, ...)
{
if (!mEnabled) {
return;
}
va_list ap;
- va_start(ap, fmt);
- Writer::logVFormat(fmt, ap);
+ va_start(ap, hash);
+ Writer::logVFormat(fmt, hash, ap);
va_end(ap);
}
-void NBLog::Writer::logVFormat(const char *fmt, va_list argp)
+void NBLog::Writer::logVFormat(const char *fmt, log_hash_t hash, va_list argp)
{
if (!mEnabled) {
return;
@@ -381,8 +489,9 @@
int i;
double f;
char* s;
- struct timespec t;
+ int64_t t;
Writer::logTimestamp();
+ Writer::logHash(hash);
for (const char *p = fmt; *p != '\0'; p++) {
// TODO: implement more complex formatting such as %.3f
if (*p != '%') {
@@ -395,7 +504,7 @@
break;
case 't': // timestamp
- t = va_arg(argp, struct timespec);
+ t = va_arg(argp, int64_t);
Writer::logTimestamp(t);
break;
@@ -440,16 +549,8 @@
// a confusion for a programmer debugging their code.
return;
}
- switch (event) {
- case EVENT_STRING:
- case EVENT_TIMESTAMP:
- case EVENT_INTEGER:
- case EVENT_FLOAT:
- case EVENT_PID:
- case EVENT_START_FMT:
- break;
- case EVENT_RESERVED:
- default:
+ // Ignore if invalid event
+ if (event == EVENT_RESERVED || event >= EVENT_UPPER_BOUND) {
return;
}
Entry entry(event, data, length);
@@ -531,7 +632,7 @@
Writer::logTimestamp();
}
-void NBLog::LockedWriter::logTimestamp(const struct timespec &ts)
+void NBLog::LockedWriter::logTimestamp(const int64_t ts)
{
Mutex::Autolock _l(mLock);
Writer::logTimestamp(ts);
@@ -568,6 +669,12 @@
Writer::logEnd();
}
+void NBLog::LockedWriter::logHash(log_hash_t hash)
+{
+ Mutex::Autolock _l(mLock);
+ Writer::logHash(hash);
+}
+
bool NBLog::LockedWriter::isEnabled() const
{
Mutex::Autolock _l(mLock);
@@ -582,6 +689,11 @@
// ---------------------------------------------------------------------------
+const std::set<NBLog::Event> NBLog::Reader::startingTypes {NBLog::Event::EVENT_START_FMT,
+ NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS};
+const std::set<NBLog::Event> NBLog::Reader::endingTypes {NBLog::Event::EVENT_END_FMT,
+ NBLog::Event::EVENT_HISTOGRAM_ENTRY_TS,
+ NBLog::Event::EVENT_HISTOGRAM_FLUSH};
NBLog::Reader::Reader(const void *shared, size_t size)
: mShared((/*const*/ Shared *) shared), /*mIMemory*/
mFd(-1), mIndent(0),
@@ -604,16 +716,17 @@
delete mFifo;
}
-uint8_t *NBLog::Reader::findLastEntryOfType(uint8_t *front, uint8_t *back, uint8_t type) {
+const uint8_t *NBLog::Reader::findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
+ const std::set<Event> &types) {
while (back + Entry::kPreviousLengthOffset >= front) {
- uint8_t *prev = back - back[Entry::kPreviousLengthOffset] - Entry::kOverhead;
- if (prev < front || prev + prev[offsetof(FormatEntry::entry, length)] +
+ const uint8_t *prev = back - back[Entry::kPreviousLengthOffset] - Entry::kOverhead;
+ if (prev < front || prev + prev[offsetof(entry, length)] +
Entry::kOverhead != back) {
// prev points to an out of limits or inconsistent entry
return nullptr;
}
- if (prev[offsetof(FormatEntry::entry, type)] == type) {
+ if (types.find((const Event) prev[offsetof(entry, type)]) != types.end()) {
return prev;
}
back = prev;
@@ -652,21 +765,21 @@
// it ends in a complete entry (which is not an END_FMT). So is safe to traverse backwards.
// TODO: handle client corruption (in the middle of a buffer)
- uint8_t *back = snapshot->mData + availToRead;
- uint8_t *front = snapshot->mData;
+ const uint8_t *back = snapshot->mData + availToRead;
+ const uint8_t *front = snapshot->mData;
// Find last END_FMT. <back> is sitting on an entry which might be the middle of a FormatEntry.
// We go backwards until we find an EVENT_END_FMT.
- uint8_t *lastEnd = findLastEntryOfType(front, back, EVENT_END_FMT);
+ const uint8_t *lastEnd = findLastEntryOfTypes(front, back, endingTypes);
if (lastEnd == nullptr) {
- snapshot->mEnd = snapshot->mBegin = FormatEntry::iterator(front);
+ snapshot->mEnd = snapshot->mBegin = EntryIterator(front);
} else {
// end of snapshot points to after last END_FMT entry
- snapshot->mEnd = FormatEntry::iterator(lastEnd + Entry::kOverhead);
+ snapshot->mEnd = EntryIterator(lastEnd).next();
// find first START_FMT
- uint8_t *firstStart = nullptr;
- uint8_t *firstStartTmp = lastEnd;
- while ((firstStartTmp = findLastEntryOfType(front, firstStartTmp, EVENT_START_FMT))
+ const uint8_t *firstStart = nullptr;
+ const uint8_t *firstStartTmp = snapshot->mEnd;
+ while ((firstStartTmp = findLastEntryOfTypes(front, firstStartTmp, startingTypes))
!= nullptr) {
firstStart = firstStartTmp;
}
@@ -674,7 +787,7 @@
if (firstStart == nullptr) {
snapshot->mBegin = snapshot->mEnd;
} else {
- snapshot->mBegin = FormatEntry::iterator(firstStart);
+ snapshot->mBegin = EntryIterator(firstStart);
}
}
@@ -686,6 +799,10 @@
}
+inline static int deltaMs(int64_t t1, int64_t t2) {
+ return (t2 - t1) / (1000 * 1000);
+}
+
void NBLog::Reader::dump(int fd, size_t indent, NBLog::Reader::Snapshot &snapshot)
{
#if 0
@@ -712,7 +829,7 @@
mFd = fd;
mIndent = indent;
String8 timestamp, body;
- size_t lost = snapshot.lost() + (snapshot.begin() - FormatEntry::iterator(snapshot.data()));
+ size_t lost = snapshot.lost() + (snapshot.begin() - EntryIterator(snapshot.data()));
if (lost > 0) {
body.appendFormat("warning: lost %zu bytes worth of events", lost);
// TODO timestamp empty here, only other choice to wait for the first timestamp event in the
@@ -730,6 +847,7 @@
}
bool deferredTimestamp = false;
#endif
+
for (auto entry = snapshot.begin(); entry != snapshot.end();) {
switch (entry->type) {
#if 0
@@ -801,6 +919,44 @@
// right now, this is the only supported case
entry = handleFormat(FormatEntry(entry), ×tamp, &body);
break;
+ case EVENT_HISTOGRAM_ENTRY_TS: {
+ HistTsEntryWithAuthor *data = (HistTsEntryWithAuthor *) (entry->data);
+ // TODO This memcpies are here to avoid unaligned memory access crash.
+ // There's probably a more efficient way to do it
+ log_hash_t hash;
+ memcpy(&hash, &(data->hash), sizeof(hash));
+ int64_t ts;
+ memcpy(&ts, &data->ts, sizeof(ts));
+ const std::pair<log_hash_t, int> key(hash, data->author);
+ // TODO might want to filter excessively high outliers, which are usually caused
+ // by the thread being inactive.
+ mHists[key].push_back(ts);
+ ++entry;
+ break;
+ }
+ case EVENT_HISTOGRAM_FLUSH: {
+ HistogramEntry histEntry(entry);
+ // Log timestamp
+ int64_t ts = histEntry.timestamp();
+ timestamp.clear();
+ timestamp.appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+ (int) ((ts / (1000 * 1000)) % 1000));
+ // Log histograms
+ body.appendFormat("Histogram flush - ");
+ handleAuthor(histEntry, &body);
+ body.appendFormat("\n");
+ for (auto hist = mHists.begin(); hist != mHists.end();) {
+ if (hist->first.second == histEntry.author()) {
+ body.appendFormat("Histogram %X", (int)hist->first.first);
+ drawHistogram(&body, hist->second, true/*logScale*/, indent + timestamp.size());
+ hist = mHists.erase(hist);
+ } else {
+ ++hist;
+ }
+ }
+ ++entry;
+ break;
+ }
case EVENT_END_FMT:
body.appendFormat("warning: got to end format event");
++entry;
@@ -845,10 +1001,10 @@
}
void NBLog::appendTimestamp(String8 *body, const void *data) {
- struct timespec ts;
- memcpy(&ts, data, sizeof(struct timespec));
- body->appendFormat("[%d.%03d]", (int) ts.tv_sec,
- (int) (ts.tv_nsec / 1000000));
+ int64_t ts;
+ memcpy(&ts, data, sizeof(ts));
+ body->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+ (int) ((ts / (1000 * 1000)) % 1000));
}
void NBLog::appendInt(String8 *body, const void *data) {
@@ -868,20 +1024,42 @@
body->appendFormat("<PID: %d, name: %.*s>", id, (int) (length - sizeof(pid_t)), name);
}
-NBLog::FormatEntry::iterator NBLog::Reader::handleFormat(const FormatEntry &fmtEntry,
+String8 NBLog::bufferDump(const uint8_t *buffer, size_t size)
+{
+ String8 str;
+ str.append("[ ");
+ for(size_t i = 0; i < size; i++)
+ {
+ str.appendFormat("%d ", buffer[i]);
+ }
+ str.append("]");
+ return str;
+}
+
+String8 NBLog::bufferDump(const EntryIterator &it)
+{
+ return bufferDump(it, it->length + Entry::kOverhead);
+}
+
+NBLog::EntryIterator NBLog::Reader::handleFormat(const FormatEntry &fmtEntry,
String8 *timestamp,
String8 *body) {
// log timestamp
- struct timespec ts = fmtEntry.timestamp();
+ int64_t ts = fmtEntry.timestamp();
timestamp->clear();
- timestamp->appendFormat("[%d.%03d]", (int) ts.tv_sec,
- (int) (ts.tv_nsec / 1000000));
+ timestamp->appendFormat("[%d.%03d]", (int) (ts / (1000 * 1000 * 1000)),
+ (int) ((ts / (1000 * 1000)) % 1000));
+
+ // log unique hash
+ log_hash_t hash = fmtEntry.hash();
+ // print only lower 16bit of hash as hex and line as int to reduce spam in the log
+ body->appendFormat("%.4X-%d ", (int)(hash >> 16) & 0xFFFF, (int) hash & 0xFFFF);
// log author (if present)
handleAuthor(fmtEntry, body);
// log string
- NBLog::FormatEntry::iterator arg = fmtEntry.args();
+ NBLog::EntryIterator arg = fmtEntry.args();
const char* fmt = fmtEntry.formatString();
size_t fmt_length = fmtEntry.formatStringLength();
@@ -954,6 +1132,108 @@
return arg;
}
+static int widthOf(int x) {
+ int width = 0;
+ while (x > 0) {
+ ++width;
+ x /= 10;
+ }
+ return width;
+}
+
+static std::map<int, int> buildBuckets(const std::vector<int64_t> &samples) {
+ // TODO allow buckets of variable resolution
+ std::map<int, int> buckets;
+ for (size_t i = 1; i < samples.size(); ++i) {
+ ++buckets[deltaMs(samples[i - 1], samples[i])];
+ }
+ return buckets;
+}
+
+static inline uint32_t log2(uint32_t x) {
+ // This works for x > 0
+ return 31 - __builtin_clz(x);
+}
+
+// TODO put this function in separate file. Make it return a std::string instead of modifying body
+/*
+Example output:
+[54.234] Histogram flush - AudioOut_D:
+Histogram 33640BF1
+ [ 1][ 1][ 1][ 3][54][69][ 1][ 2][ 1]
+ 64| []
+ 32| [] []
+ 16| [] []
+ 8| [] []
+ 4| [] []
+ 2|______________[]__[]__[]______[]____
+ 4 5 6 8 9 10 11 13 15
+Notice that all values that fall in the same row have the same height (65 and 127 are displayed
+identically). That's why exact counts are added at the top.
+*/
+void NBLog::Reader::drawHistogram(String8 *body,
+ const std::vector<int64_t> &samples,
+ bool logScale,
+ int indent,
+ int maxHeight) {
+ if (samples.size() <= 1) {
+ return;
+ }
+ std::map<int, int> buckets = buildBuckets(samples);
+ // TODO consider changing all ints to uint32_t or uint64_t
+ static const char *underscores = "________________";
+ static const char *spaces = " ";
+
+ auto it = buckets.begin();
+ int maxLabel = it->first;
+ int maxVal = it->second;
+ // Compute maximum values
+ while (++it != buckets.end()) {
+ if (it->first > maxLabel) {
+ maxLabel = it->first;
+ }
+ if (it->second > maxVal) {
+ maxVal = it->second;
+ }
+ }
+ int height = (logScale) ? log2(maxVal) + 1 : maxVal; // maxVal > 0, safe to call log2
+ int leftPadding = widthOf(maxVal);
+ int colWidth = std::max(std::max(widthOf(maxLabel) + 1, 3), leftPadding + 2);
+ int scalingFactor = 1;
+ // scale data if it exceeds maximum height
+ if (height > maxHeight) {
+ scalingFactor = (height + maxHeight) / maxHeight;
+ height /= scalingFactor;
+ }
+ // write header line with bucket values
+ body->appendFormat("\n%*s", indent, " ");
+ body->appendFormat("%*s", leftPadding + 2, " ");
+ for (auto const &x : buckets)
+ {
+ body->appendFormat("[%*d]", colWidth - 2, x.second);
+ }
+ // write histogram ascii art
+ body->appendFormat("\n%*s", indent, " ");
+ for (int row = height * scalingFactor; row > 0; row -= scalingFactor)
+ {
+ int value = ((logScale) ? (1 << row) : row);
+ body->appendFormat("%*u|", leftPadding, value);
+ for (auto const &x : buckets) {
+ body->appendFormat("%.*s%s", colWidth - 2,
+ (row <= scalingFactor) ? underscores : spaces,
+ x.second < value ? ((row <= scalingFactor) ? "__" : " ") : "[]");
+ }
+ body->appendFormat("\n%*s", indent, " ");
+ }
+ // write footer with bucket labels
+ body->appendFormat("%*s", leftPadding + 1, " ");
+ for (auto const &x : buckets)
+ {
+ body->appendFormat("%*d", colWidth, x.first);
+ }
+ body->appendFormat("\n");
+}
+
// ---------------------------------------------------------------------------
NBLog::Merger::Merger(const void *shared, size_t size):
@@ -973,26 +1253,25 @@
// composed by a timestamp and the index of the snapshot where the timestamp came from
struct MergeItem
{
- struct timespec ts;
+ int64_t ts;
int index;
- MergeItem(struct timespec ts, int index): ts(ts), index(index) {}
+ MergeItem(int64_t ts, int index): ts(ts), index(index) {}
};
// operators needed for priority queue in merge
-bool operator>(const struct timespec &t1, const struct timespec &t2) {
- return t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec);
-}
+// bool operator>(const int64_t &t1, const int64_t &t2) {
+// return t1.tv_sec > t2.tv_sec || (t1.tv_sec == t2.tv_sec && t1.tv_nsec > t2.tv_nsec);
+// }
bool operator>(const struct MergeItem &i1, const struct MergeItem &i2) {
- return i1.ts > i2.ts ||
- (i1.ts.tv_sec == i2.ts.tv_sec && i1.ts.tv_nsec == i2.ts.tv_nsec && i1.index > i2.index);
+ return i1.ts > i2.ts || (i1.ts == i2.ts && i1.index > i2.index);
}
// Merge registered readers, sorted by timestamp
void NBLog::Merger::merge() {
int nLogs = mNamedReaders.size();
std::vector<std::unique_ptr<NBLog::Reader::Snapshot>> snapshots(nLogs);
- std::vector<NBLog::FormatEntry::iterator> offsets(nLogs);
+ std::vector<NBLog::EntryIterator> offsets(nLogs);
for (int i = 0; i < nLogs; ++i) {
snapshots[i] = mNamedReaders[i].reader()->getSnapshot();
offsets[i] = snapshots[i]->begin();
@@ -1004,7 +1283,7 @@
for (int i = 0; i < nLogs; ++i)
{
if (offsets[i] != snapshots[i]->end()) {
- timespec ts = FormatEntry(offsets[i]).timestamp();
+ int64_t ts = AbstractEntry::buildEntry(offsets[i])->timestamp();
timestamps.emplace(ts, i);
}
}
@@ -1013,11 +1292,12 @@
// find minimum timestamp
int index = timestamps.top().index;
// copy it to the log, increasing offset
- offsets[index] = FormatEntry(offsets[index]).copyWithAuthor(mFifoWriter, index);
+ offsets[index] = AbstractEntry::buildEntry(offsets[index])->copyWithAuthor(mFifoWriter,
+ index);
// update data structures
timestamps.pop();
if (offsets[index] != snapshots[index]->end()) {
- timespec ts = FormatEntry(offsets[index]).timestamp();
+ int64_t ts = AbstractEntry::buildEntry(offsets[index])->timestamp();
timestamps.emplace(ts, index);
}
}
@@ -1030,11 +1310,10 @@
NBLog::MergeReader::MergeReader(const void *shared, size_t size, Merger &merger)
: Reader(shared, size), mNamedReaders(merger.getNamedReaders()) {}
-size_t NBLog::MergeReader::handleAuthor(const NBLog::FormatEntry &fmtEntry, String8 *body) {
- int author = fmtEntry.author();
+void NBLog::MergeReader::handleAuthor(const NBLog::AbstractEntry &entry, String8 *body) {
+ int author = entry.author();
const char* name = (*mNamedReaders)[author].name();
body->appendFormat("%s: ", name);
- return NBLog::Entry::kOverhead + sizeof(author);
}
NBLog::MergeThread::MergeThread(NBLog::Merger &merger)
diff --git a/media/libnbaio/include/NBLog.h b/media/libnbaio/include/NBLog.h
index 59b77bd..2893dc9 100644
--- a/media/libnbaio/include/NBLog.h
+++ b/media/libnbaio/include/NBLog.h
@@ -24,6 +24,8 @@
#include <utils/Mutex.h>
#include <utils/threads.h>
+#include <map>
+#include <set>
#include <vector>
namespace android {
@@ -34,12 +36,14 @@
public:
+typedef uint64_t log_hash_t;
+
class Writer;
class Reader;
private:
-enum Event {
+enum Event : uint8_t {
EVENT_RESERVED,
EVENT_STRING, // ASCII string, not NUL-terminated
// TODO: make timestamp optional
@@ -50,7 +54,13 @@
EVENT_AUTHOR, // author index (present in merged logs) tracks entry's original log
EVENT_START_FMT, // logFormat start event: entry includes format string, following
// entries contain format arguments
+ EVENT_HASH, // unique HASH of log origin, originates from hash of file name
+ // and line number
+ EVENT_HISTOGRAM_ENTRY_TS, // single datum for timestamp histogram
+ EVENT_HISTOGRAM_FLUSH, // show histogram on log
EVENT_END_FMT, // end of logFormat argument list
+
+ EVENT_UPPER_BOUND, // to check for invalid events
};
@@ -60,93 +70,142 @@
// a formatted entry has the following structure:
// * START_FMT entry, containing the format string
// * TIMESTAMP entry
+// * HASH entry
// * author entry of the thread that generated it (optional, present in merged log)
// * format arg1
// * format arg2
// * ...
// * END_FMT entry
-class FormatEntry {
+// entry representation in memory
+struct entry {
+ const uint8_t type;
+ const uint8_t length;
+ const uint8_t data[0];
+};
+
+// entry tail representation (after data)
+struct ending {
+ uint8_t length;
+ uint8_t next[0];
+};
+
+// entry iterator
+class EntryIterator {
public:
- // build a Format Entry starting in the given pointer
- class iterator;
- explicit FormatEntry(const uint8_t *entry);
- explicit FormatEntry(const iterator &it);
+ EntryIterator();
+ explicit EntryIterator(const uint8_t *entry);
+ EntryIterator(const EntryIterator &other);
- // entry representation in memory
- struct entry {
- const uint8_t type;
- const uint8_t length;
- const uint8_t data[0];
- };
+ // dereference underlying entry
+ const entry& operator*() const;
+ const entry* operator->() const;
+ // advance to next entry
+ EntryIterator& operator++(); // ++i
+ // back to previous entry
+ EntryIterator& operator--(); // --i
+ EntryIterator next() const;
+ EntryIterator prev() const;
+ bool operator!=(const EntryIterator &other) const;
+ int operator-(const EntryIterator &other) const;
- // entry tail representation (after data)
- struct ending {
- uint8_t length;
- uint8_t next[0];
- };
+ bool hasConsistentLength() const;
+ void copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
+ void copyData(uint8_t *dst) const;
- // entry iterator
- class iterator {
- public:
- iterator();
- iterator(const uint8_t *entry);
- iterator(const iterator &other);
+ template<typename T>
+ inline const T& payload() {
+ return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
+ }
- // dereference underlying entry
- const entry& operator*() const;
- const entry* operator->() const;
- // advance to next entry
- iterator& operator++(); // ++i
- // back to previous entry
- iterator& operator--(); // --i
- iterator next() const;
- iterator prev() const;
- bool operator!=(const iterator &other) const;
- int operator-(const iterator &other) const;
+ inline operator const uint8_t*() const {
+ return ptr;
+ }
- bool hasConsistentLength() const;
- void copyTo(std::unique_ptr<audio_utils_fifo_writer> &dst) const;
- void copyData(uint8_t *dst) const;
+private:
+ const uint8_t *ptr;
+};
- template<typename T>
- inline const T& payload() {
- return *reinterpret_cast<const T *>(ptr + offsetof(entry, data));
- }
+class AbstractEntry {
+public:
- private:
- friend class FormatEntry;
- const uint8_t *ptr;
- };
+ // Entry starting in the given pointer
+ explicit AbstractEntry(const uint8_t *entry);
- // Entry's format string
- const char* formatString() const;
-
- // Enrty's format string length
- size_t formatStringLength() const;
-
- // Format arguments (excluding format string, timestamp and author)
- iterator args() const;
+ // build concrete entry of appropriate class from pointer
+ static std::unique_ptr<AbstractEntry> buildEntry(const uint8_t *ptr);
// get format entry timestamp
- timespec timestamp() const;
+ // TODO consider changing to uint64_t
+ virtual int64_t timestamp() const = 0;
+
+ // get format entry's unique id
+ virtual log_hash_t hash() const = 0;
// entry's author index (-1 if none present)
// a Merger has a vector of Readers, author simply points to the index of the
// Reader that originated the entry
- int author() const;
+ // TODO consider changing to uint32_t
+ virtual int author() const = 0;
- // copy entry, adding author before timestamp, returns size of original entry
- iterator copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst, int author) const;
+ // copy entry, adding author before timestamp, returns iterator to end of entry
+ virtual EntryIterator copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+ int author) const = 0;
- iterator begin() const;
-
-private:
+protected:
// copies ordinary entry from src to dst, and returns length of entry
// size_t copyEntry(audio_utils_fifo_writer *dst, const iterator &it);
const uint8_t *mEntry;
};
+class FormatEntry : public AbstractEntry {
+public:
+ // explicit FormatEntry(const EntryIterator &it);
+ explicit FormatEntry(const uint8_t *ptr) : AbstractEntry(ptr) {}
+
+ // Entry's format string
+ const char* formatString() const;
+
+ // Enrty's format string length
+ size_t formatStringLength() const;
+
+ // Format arguments (excluding format string, timestamp and author)
+ EntryIterator args() const;
+
+ // get format entry timestamp
+ virtual int64_t timestamp() const override;
+
+ // get format entry's unique id
+ virtual log_hash_t hash() const override;
+
+ // entry's author index (-1 if none present)
+ // a Merger has a vector of Readers, author simply points to the index of the
+ // Reader that originated the entry
+ virtual int author() const override;
+
+ // copy entry, adding author before timestamp, returns size of original entry
+ virtual EntryIterator copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+ int author) const override;
+
+ EntryIterator begin() const;
+};
+
+class HistogramEntry : public AbstractEntry {
+public:
+ explicit HistogramEntry(const uint8_t *ptr) : AbstractEntry(ptr) {
+ }
+
+ virtual int64_t timestamp() const override;
+
+ virtual log_hash_t hash() const override;
+
+ virtual int author() const override;
+
+ virtual EntryIterator copyWithAuthor(std::unique_ptr<audio_utils_fifo_writer> &dst,
+ int author) const override;
+
+};
+
// ---------------------------------------------------------------------------
// representation of a single log entry in private memory
@@ -165,12 +224,28 @@
static const size_t kMaxLength = 255;
public:
// mEvent, mLength, mData[...], duplicate mLength
- static const size_t kOverhead = sizeof(FormatEntry::entry) + sizeof(FormatEntry::ending);
+ static const size_t kOverhead = sizeof(entry) + sizeof(ending);
// endind length of previous entry
- static const size_t kPreviousLengthOffset = - sizeof(FormatEntry::ending) +
- offsetof(FormatEntry::ending, length);
+ static const size_t kPreviousLengthOffset = - sizeof(ending) +
+ offsetof(ending, length);
};
+struct HistTsEntry {
+ log_hash_t hash;
+ int64_t ts;
+}; //TODO __attribute__((packed));
+
+struct HistTsEntryWithAuthor {
+ log_hash_t hash;
+ int64_t ts;
+ int author;
+}; //TODO __attribute__((packed));
+
+struct HistIntEntry {
+ log_hash_t hash;
+ int value;
+}; //TODO __attribute__((packed));
+
// representation of a single log entry in shared memory
// byte[0] mEvent
// byte[1] mLength
@@ -187,7 +262,8 @@
static void appendPID(String8 *body, const void *data, size_t length);
static void appendTimestamp(String8 *body, const void *data);
static size_t fmtEntryLength(const uint8_t *data);
-
+ static String8 bufferDump(const uint8_t *buffer, size_t size);
+ static String8 bufferDump(const EntryIterator &it);
public:
// Located in shared memory, must be POD.
@@ -248,15 +324,17 @@
virtual void logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
virtual void logvf(const char *fmt, va_list ap);
virtual void logTimestamp();
- virtual void logTimestamp(const struct timespec &ts);
+ virtual void logTimestamp(const int64_t ts);
virtual void logInteger(const int x);
virtual void logFloat(const float x);
virtual void logPID();
- virtual void logFormat(const char *fmt, ...);
- virtual void logVFormat(const char *fmt, va_list ap);
+ virtual void logFormat(const char *fmt, log_hash_t hash, ...);
+ virtual void logVFormat(const char *fmt, log_hash_t hash, va_list ap);
virtual void logStart(const char *fmt);
virtual void logEnd();
-
+ virtual void logHash(log_hash_t hash);
+ virtual void logHistTS(log_hash_t hash);
+ virtual void logHistFlush(log_hash_t hash);
virtual bool isEnabled() const;
@@ -298,12 +376,13 @@
virtual void logf(const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
virtual void logvf(const char *fmt, va_list ap);
virtual void logTimestamp();
- virtual void logTimestamp(const struct timespec &ts);
+ virtual void logTimestamp(const int64_t ts);
virtual void logInteger(const int x);
virtual void logFloat(const float x);
virtual void logPID();
virtual void logStart(const char *fmt);
virtual void logEnd();
+ virtual void logHash(log_hash_t hash);
virtual bool isEnabled() const;
virtual bool setEnabled(bool enabled);
@@ -334,18 +413,18 @@
// iterator to beginning of readable segment of snapshot
// data between begin and end has valid entries
- FormatEntry::iterator begin() { return mBegin; }
+ EntryIterator begin() { return mBegin; }
// iterator to end of readable segment of snapshot
- FormatEntry::iterator end() { return mEnd; }
+ EntryIterator end() { return mEnd; }
private:
friend class Reader;
uint8_t *mData;
size_t mLost;
- FormatEntry::iterator mBegin;
- FormatEntry::iterator mEnd;
+ EntryIterator mBegin;
+ EntryIterator mEnd;
};
// Input parameter 'size' is the desired size of the timeline in byte units.
@@ -364,6 +443,8 @@
bool isIMemory(const sp<IMemory>& iMemory) const;
private:
+ static const std::set<Event> startingTypes;
+ static const std::set<Event> endingTypes;
/*const*/ Shared* const mShared; // raw pointer to shared memory, actually const but not
// declared as const because audio_utils_fifo() constructor
sp<IMemory> mIMemory; // ref-counted version, assigned only in constructor
@@ -374,17 +455,23 @@
audio_utils_fifo_reader * const mFifoReader; // used to read from FIFO,
// non-NULL unless constructor fails
+ std::map<std::pair<log_hash_t, int>, std::vector<int64_t>> mHists;
+
void dumpLine(const String8& timestamp, String8& body);
- FormatEntry::iterator handleFormat(const FormatEntry &fmtEntry,
+ EntryIterator handleFormat(const FormatEntry &fmtEntry,
String8 *timestamp,
String8 *body);
// dummy method for handling absent author entry
- virtual size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body) { return 0; }
+ virtual void handleAuthor(const AbstractEntry &fmtEntry, String8 *body) {}
+
+ static void drawHistogram(String8 *body, const std::vector<int64_t> &samples,
+ bool logScale, int indent = 0, int maxHeight = 10);
// Searches for the last entry of type <type> in the range [front, back)
// back has to be entry-aligned. Returns nullptr if none enconuntered.
- static uint8_t *findLastEntryOfType(uint8_t *front, uint8_t *back, uint8_t type);
+ static const uint8_t *findLastEntryOfTypes(const uint8_t *front, const uint8_t *back,
+ const std::set<Event> &types);
static const size_t kSquashTimestamp = 5; // squash this many or more adjacent timestamps
};
@@ -426,8 +513,6 @@
Shared * const mShared;
std::unique_ptr<audio_utils_fifo> mFifo;
std::unique_ptr<audio_utils_fifo_writer> mFifoWriter;
-
- static struct timespec getTimestamp(const uint8_t *data);
};
class MergeReader : public Reader {
@@ -437,7 +522,7 @@
const std::vector<NamedReader> *mNamedReaders;
// handle author entry by looking up the author's name and appending it to the body
// returns number of bytes read from fmtEntry
- size_t handleAuthor(const FormatEntry &fmtEntry, String8 *body);
+ void handleAuthor(const AbstractEntry &fmtEntry, String8 *body);
};
// MergeThread is a thread that contains a Merger. It works as a retriggerable one-shot:
@@ -479,6 +564,15 @@
}; // class NBLog
+// TODO put somewhere else
+static inline int64_t get_monotonic_ns() {
+ timespec ts;
+ if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+ return (uint64_t) ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
+ }
+ return 0; // should not happen.
+}
+
} // namespace android
#endif // ANDROID_MEDIA_NBLOG_H
diff --git a/media/libstagefright/AudioSource.cpp b/media/libstagefright/AudioSource.cpp
index 4ccd2d0..6a5a229 100644
--- a/media/libstagefright/AudioSource.cpp
+++ b/media/libstagefright/AudioSource.cpp
@@ -58,6 +58,8 @@
mOutSampleRate(outSampleRate > 0 ? outSampleRate : sampleRate),
mTrackMaxAmplitude(false),
mStartTimeUs(0),
+ mStopSystemTimeUs(-1),
+ mLastFrameTimestampUs(0),
mMaxAmplitude(0),
mPrevSampleTimeUs(0),
mInitialReadTimeUs(0),
@@ -175,6 +177,7 @@
}
mStarted = false;
+ mStopSystemTimeUs = -1;
mFrameAvailableCondition.signal();
mRecord->stop();
@@ -286,6 +289,21 @@
return OK;
}
+status_t AudioSource::setStopTimeUs(int64_t stopTimeUs) {
+ Mutex::Autolock autoLock(mLock);
+ ALOGV("Set stoptime: %lld us", (long long)stopTimeUs);
+
+ if (stopTimeUs < -1) {
+ ALOGE("Invalid stop time %lld us", (long long)stopTimeUs);
+ return BAD_VALUE;
+ } else if (stopTimeUs == -1) {
+ ALOGI("reset stopTime to be -1");
+ }
+
+ mStopSystemTimeUs = stopTimeUs;
+ return OK;
+}
+
void AudioSource::signalBufferReturned(MediaBuffer *buffer) {
ALOGV("signalBufferReturned: %p", buffer->data());
Mutex::Autolock autoLock(mLock);
@@ -338,6 +356,12 @@
return OK;
}
+ if (mStopSystemTimeUs != -1 && timeUs >= mStopSystemTimeUs) {
+ ALOGV("Drop Audio frame at %lld stop time: %lld us",
+ (long long)timeUs, (long long)mStopSystemTimeUs);
+ return OK;
+ }
+
if (mNumFramesReceived == 0 && mPrevSampleTimeUs == 0) {
mInitialReadTimeUs = timeUs;
// Initial delay
@@ -346,6 +370,7 @@
}
mPrevSampleTimeUs = mStartTimeUs;
}
+ mLastFrameTimestampUs = timeUs;
size_t numLostBytes = 0;
if (mNumFramesReceived > 0) { // Ignore earlier frame lost
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
index a569f5d..61a2b5f 100644
--- a/media/libstagefright/CameraSource.cpp
+++ b/media/libstagefright/CameraSource.cpp
@@ -220,6 +220,7 @@
mNumFramesEncoded(0),
mTimeBetweenFrameCaptureUs(0),
mFirstFrameTimeUs(0),
+ mStopSystemTimeUs(-1),
mNumFramesDropped(0),
mNumGlitches(0),
mGlitchDurationThresholdUs(200000),
@@ -879,6 +880,7 @@
{
Mutex::Autolock autoLock(mLock);
mStarted = false;
+ mStopSystemTimeUs = -1;
mFrameAvailableCondition.signal();
int64_t token;
@@ -1095,12 +1097,33 @@
return OK;
}
+status_t CameraSource::setStopTimeUs(int64_t stopTimeUs) {
+ Mutex::Autolock autoLock(mLock);
+ ALOGV("Set stoptime: %lld us", (long long)stopTimeUs);
+
+ if (stopTimeUs < -1) {
+ ALOGE("Invalid stop time %lld us", (long long)stopTimeUs);
+ return BAD_VALUE;
+ } else if (stopTimeUs == -1) {
+ ALOGI("reset stopTime to be -1");
+ }
+
+ mStopSystemTimeUs = stopTimeUs;
+ return OK;
+}
+
bool CameraSource::shouldSkipFrameLocked(int64_t timestampUs) {
if (!mStarted || (mNumFramesReceived == 0 && timestampUs < mStartTimeUs)) {
ALOGV("Drop frame at %lld/%lld us", (long long)timestampUs, (long long)mStartTimeUs);
return true;
}
+ if (mStopSystemTimeUs != -1 && timestampUs >= mStopSystemTimeUs) {
+ ALOGV("Drop Camera frame at %lld stop time: %lld us",
+ (long long)timestampUs, (long long)mStopSystemTimeUs);
+ return true;
+ }
+
// May need to skip frame or modify timestamp. Currently implemented
// by the subclass CameraSourceTimeLapse.
if (skipCurrentFrame(timestampUs)) {
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index cafedba..82aa04d 100755
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -2123,13 +2123,17 @@
if (mDone) {
return OK;
}
- mDone = true;
+
if (stopSource) {
ALOGD("%s track source stopping", getTrackType());
mSource->stop();
ALOGD("%s track source stopped", getTrackType());
}
+ // Set mDone to be true after sucessfully stop mSource as mSource may be still outputting
+ // buffers to the writer.
+ mDone = true;
+
void *dummy;
pthread_join(mThread, &dummy);
status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index bb20850..5424372 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -54,7 +54,7 @@
void stopSource();
void pause();
void resume();
-
+ status_t setStopTimeUs(int64_t stopTimeUs);
bool readBuffer(MediaBuffer **buffer);
protected:
@@ -66,6 +66,7 @@
kWhatStart = 'msta',
kWhatStop,
kWhatPull,
+ kWhatSetStopTimeUs,
};
sp<MediaSource> mSource;
@@ -161,6 +162,12 @@
return err;
}
+status_t MediaCodecSource::Puller::setStopTimeUs(int64_t stopTimeUs) {
+ sp<AMessage> msg = new AMessage(kWhatSetStopTimeUs, this);
+ msg->setInt64("stop-time-us", stopTimeUs);
+ return postSynchronouslyAndReturnError(msg);
+}
+
status_t MediaCodecSource::Puller::start(const sp<MetaData> &meta, const sp<AMessage> ¬ify) {
ALOGV("puller (%s) start", mIsAudio ? "audio" : "video");
mLooper->start(
@@ -250,6 +257,20 @@
break;
}
+ case kWhatSetStopTimeUs:
+ {
+ sp<AReplyToken> replyID;
+ CHECK(msg->senderAwaitsResponse(&replyID));
+ int64_t stopTimeUs;
+ CHECK(msg->findInt64("stop-time-us", &stopTimeUs));
+ status_t err = mSource->setStopTimeUs(stopTimeUs);
+
+ sp<AMessage> response = new AMessage;
+ response->setInt32("err", err);
+ response->postReply(replyID);
+ break;
+ }
+
case kWhatStop:
{
mSource->stop();
@@ -364,11 +385,8 @@
}
-status_t MediaCodecSource::setStopStimeUs(int64_t stopTimeUs) {
- if (!(mFlags & FLAG_USE_SURFACE_INPUT)) {
- return OK;
- }
- sp<AMessage> msg = new AMessage(kWhatSetStopTimeOffset, mReflector);
+status_t MediaCodecSource::setStopTimeUs(int64_t stopTimeUs) {
+ sp<AMessage> msg = new AMessage(kWhatSetStopTimeUs, mReflector);
msg->setInt64("stop-time-us", stopTimeUs);
return postSynchronouslyAndReturnError(msg);
}
@@ -1055,7 +1073,7 @@
response->postReply(replyID);
break;
}
- case kWhatSetStopTimeOffset:
+ case kWhatSetStopTimeUs:
{
sp<AReplyToken> replyID;
CHECK(msg->senderAwaitsResponse(&replyID));
@@ -1063,11 +1081,13 @@
int64_t stopTimeUs;
CHECK(msg->findInt64("stop-time-us", &stopTimeUs));
- // Propagate the timestamp offset to GraphicBufferSource.
+ // Propagate the stop time to GraphicBufferSource.
if (mFlags & FLAG_USE_SURFACE_INPUT) {
sp<AMessage> params = new AMessage;
params->setInt64("stop-time-us", stopTimeUs);
err = mEncoder->setParameters(params);
+ } else {
+ err = mPuller->setStopTimeUs(stopTimeUs);
}
sp<AMessage> response = new AMessage;
diff --git a/media/libstagefright/include/AudioSource.h b/media/libstagefright/include/AudioSource.h
index f20c2cd..07a51bf 100644
--- a/media/libstagefright/include/AudioSource.h
+++ b/media/libstagefright/include/AudioSource.h
@@ -53,6 +53,7 @@
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+ virtual status_t setStopTimeUs(int64_t stopTimeUs);
status_t dataCallback(const AudioRecord::Buffer& buffer);
virtual void signalBufferReturned(MediaBuffer *buffer);
@@ -85,6 +86,8 @@
bool mTrackMaxAmplitude;
int64_t mStartTimeUs;
+ int64_t mStopSystemTimeUs;
+ int64_t mLastFrameTimestampUs;
int16_t mMaxAmplitude;
int64_t mPrevSampleTimeUs;
int64_t mInitialReadTimeUs;
diff --git a/media/libstagefright/include/CameraSource.h b/media/libstagefright/include/CameraSource.h
index aa56d27..2aaa884 100644
--- a/media/libstagefright/include/CameraSource.h
+++ b/media/libstagefright/include/CameraSource.h
@@ -98,6 +98,7 @@
virtual status_t stop() { return reset(); }
virtual status_t read(
MediaBuffer **buffer, const ReadOptions *options = NULL);
+ virtual status_t setStopTimeUs(int64_t stopTimeUs);
/**
* Check whether a CameraSource object is properly initialized.
@@ -253,6 +254,7 @@
List<int64_t> mFrameTimes;
int64_t mFirstFrameTimeUs;
+ int64_t mStopSystemTimeUs;
int32_t mNumFramesDropped;
int32_t mNumGlitches;
int64_t mGlitchDurationThresholdUs;
diff --git a/media/libstagefright/include/MediaCodecSource.h b/media/libstagefright/include/MediaCodecSource.h
index 5e99b78..2259d05 100644
--- a/media/libstagefright/include/MediaCodecSource.h
+++ b/media/libstagefright/include/MediaCodecSource.h
@@ -59,6 +59,8 @@
virtual status_t read(
MediaBuffer **buffer,
const ReadOptions *options = NULL);
+ virtual status_t setStopTimeUs(int64_t stopTimeUs);
+
// MediaBufferObserver
virtual void signalBufferReturned(MediaBuffer *buffer);
@@ -66,11 +68,7 @@
// for AHandlerReflector
void onMessageReceived(const sp<AMessage> &msg);
- // Set GraphicBufferSource stop time. GraphicBufferSource will stop
- // after receiving a buffer with timestamp larger or equal than stopTimeUs.
- // All the buffers with timestamp larger or equal to stopTimeUs will be
- // discarded. stopTimeUs uses SYSTEM_TIME_MONOTONIC time base.
- status_t setStopStimeUs(int64_t stopTimeUs);
+
protected:
virtual ~MediaCodecSource();
@@ -85,7 +83,7 @@
kWhatStop,
kWhatPause,
kWhatSetInputBufferTimeOffset,
- kWhatSetStopTimeOffset,
+ kWhatSetStopTimeUs,
kWhatGetFirstSampleSystemTimeUs,
kWhatStopStalled,
};
diff --git a/media/libstagefright/include/MediaSource.h b/media/libstagefright/include/MediaSource.h
index 1bd3ed0..14adb05 100644
--- a/media/libstagefright/include/MediaSource.h
+++ b/media/libstagefright/include/MediaSource.h
@@ -75,6 +75,23 @@
return ERROR_UNSUPPORTED;
}
+ // The consumer of this media source requests the source stops sending
+ // buffers with timestamp larger than or equal to stopTimeUs. stopTimeUs
+ // must be in the same time base as the startTime passed in start(). If
+ // source does not support this request, ERROR_UNSUPPORTED will be returned.
+ // If stopTimeUs is invalid, BAD_VALUE will be returned. This could be
+ // called at any time even before source starts and it could be called
+ // multiple times. Setting stopTimeUs to be -1 will effectively cancel the stopTimeUs
+ // set previously. If stopTimeUs is larger than or equal to last buffer's timestamp,
+ // source will start to drop buffer when it gets a buffer with timestamp larger
+ // than or equal to stopTimeUs. If stopTimeUs is smaller than or equal to last
+ // buffer's timestamp, source will drop all the incoming buffers immediately.
+ // After setting stopTimeUs, source may still stop sending buffers with timestamp
+ // less than stopTimeUs if it is stopped by the consumer.
+ virtual status_t setStopTimeUs(int64_t /* stopTimeUs */) {
+ return ERROR_UNSUPPORTED;
+ }
+
protected:
virtual ~MediaSource();
diff --git a/media/mtp/tests/Android.mk b/media/mtp/tests/Android.mk
index ace0d40..884518c 100644
--- a/media/mtp/tests/Android.mk
+++ b/media/mtp/tests/Android.mk
@@ -4,6 +4,7 @@
LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk
LOCAL_MODULE := mtp_ffs_handle_test
+LOCAL_COMPATIBILITY_SUITE := device-tests
LOCAL_MODULE_TAGS := tests
diff --git a/media/mtp/tests/AndroidTest.xml b/media/mtp/tests/AndroidTest.xml
new file mode 100644
index 0000000..c1f4753
--- /dev/null
+++ b/media/mtp/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<configuration description="Config for mtp_ffs_handle_test">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push" value="mtp_ffs_handle_test->/data/local/tmp/mtp_ffs_handle_test" />
+ </target_preparer>
+ <option name="test-suite-tag" value="apct" />
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="mtp_ffs_handle_test" />
+ </test>
+</configuration>
\ No newline at end of file
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 7eb179a..4b2e643 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -2044,7 +2044,7 @@
// the first primary output opened designates the primary hw device
if ((mPrimaryHardwareDev == NULL) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
- ALOGI("Using module %d has the primary audio interface", module);
+ ALOGI("Using module %d as the primary audio interface", module);
mPrimaryHardwareDev = playbackThread->getOutput()->audioHwDev;
AutoMutex lock(mHardwareLock);
diff --git a/services/audioflinger/FastThread.cpp b/services/audioflinger/FastThread.cpp
index caf7905..cf9fce3 100644
--- a/services/audioflinger/FastThread.cpp
+++ b/services/audioflinger/FastThread.cpp
@@ -170,7 +170,7 @@
}
int policy = sched_getscheduler(0) & ~SCHED_RESET_ON_FORK;
if (!(policy == SCHED_FIFO || policy == SCHED_RR)) {
- ALOGE("did not receive expected priority boost");
+ ALOGE("did not receive expected priority boost on time");
}
// This may be overly conservative; there could be times that the normal mixer
// requests such a brief cold idle that it doesn't require resetting this flag.
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 3b1edec..6a75bb0 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -447,6 +447,8 @@
return "RECORD";
case OFFLOAD:
return "OFFLOAD";
+ case MMAP:
+ return "MMAP";
default:
return "unknown";
}
@@ -533,7 +535,7 @@
{
status_t status = initCheck();
if (status == NO_ERROR) {
- ALOGI("AudioFlinger's thread %p ready to run", this);
+ ALOGI("AudioFlinger's thread %p tid=%d ready to run", this, getTid());
} else {
ALOGE("No working audio driver found.");
}
@@ -809,14 +811,15 @@
char buffer[SIZE];
String8 result;
+ dprintf(fd, "\n%s thread %p, name %s, tid %d, type %d (%s):\n", isOutput() ? "Output" : "Input",
+ this, mThreadName, getTid(), type(), threadTypeToString(type()));
+
bool locked = AudioFlinger::dumpTryLock(mLock);
if (!locked) {
- dprintf(fd, "thread %p may be deadlocked\n", this);
+ dprintf(fd, " Thread may be deadlocked\n");
}
- dprintf(fd, " Thread name: %s\n", mThreadName);
dprintf(fd, " I/O handle: %d\n", mId);
- dprintf(fd, " TID: %d\n", getTid());
dprintf(fd, " Standby: %s\n", mStandby ? "yes" : "no");
dprintf(fd, " Sample rate: %u Hz\n", mSampleRate);
dprintf(fd, " HAL frame count: %zu\n", mFrameCount);
@@ -1778,8 +1781,6 @@
void AudioFlinger::PlaybackThread::dumpInternals(int fd, const Vector<String16>& args)
{
- dprintf(fd, "\nOutput thread %p type %d (%s):\n", this, type(), threadTypeToString(type()));
-
dumpBase(fd, args);
dprintf(fd, " Normal frame count: %zu\n", mNormalFrameCount);
@@ -4714,34 +4715,42 @@
dprintf(fd, " AudioMixer tracks: 0x%08x\n", mAudioMixer->trackNames());
dprintf(fd, " Master mono: %s\n", mMasterMono ? "on" : "off");
- // Make a non-atomic copy of fast mixer dump state so it won't change underneath us
- // while we are dumping it. It may be inconsistent, but it won't mutate!
- // This is a large object so we place it on the heap.
- // FIXME 25972958: Need an intelligent copy constructor that does not touch unused pages.
- const FastMixerDumpState *copy = new FastMixerDumpState(mFastMixerDumpState);
- copy->dump(fd);
- delete copy;
+ if (hasFastMixer()) {
+ dprintf(fd, " FastMixer thread %p tid=%d", mFastMixer.get(), mFastMixer->getTid());
+
+ // Make a non-atomic copy of fast mixer dump state so it won't change underneath us
+ // while we are dumping it. It may be inconsistent, but it won't mutate!
+ // This is a large object so we place it on the heap.
+ // FIXME 25972958: Need an intelligent copy constructor that does not touch unused pages.
+ const FastMixerDumpState *copy = new FastMixerDumpState(mFastMixerDumpState);
+ copy->dump(fd);
+ delete copy;
#ifdef STATE_QUEUE_DUMP
- // Similar for state queue
- StateQueueObserverDump observerCopy = mStateQueueObserverDump;
- observerCopy.dump(fd);
- StateQueueMutatorDump mutatorCopy = mStateQueueMutatorDump;
- mutatorCopy.dump(fd);
+ // Similar for state queue
+ StateQueueObserverDump observerCopy = mStateQueueObserverDump;
+ observerCopy.dump(fd);
+ StateQueueMutatorDump mutatorCopy = mStateQueueMutatorDump;
+ mutatorCopy.dump(fd);
#endif
+#ifdef AUDIO_WATCHDOG
+ if (mAudioWatchdog != 0) {
+ // Make a non-atomic copy of audio watchdog dump so it won't change underneath us
+ AudioWatchdogDump wdCopy = mAudioWatchdogDump;
+ wdCopy.dump(fd);
+ }
+#endif
+
+ } else {
+ dprintf(fd, " No FastMixer\n");
+ }
+
#ifdef TEE_SINK
// Write the tee output to a .wav file
dumpTee(fd, mTeeSource, mId);
#endif
-#ifdef AUDIO_WATCHDOG
- if (mAudioWatchdog != 0) {
- // Make a non-atomic copy of audio watchdog dump so it won't change underneath us
- AudioWatchdogDump wdCopy = mAudioWatchdogDump;
- wdCopy.dump(fd);
- }
-#endif
}
uint32_t AudioFlinger::MixerThread::idleSleepTimeUs() const
@@ -6872,8 +6881,6 @@
void AudioFlinger::RecordThread::dumpInternals(int fd, const Vector<String16>& args)
{
- dprintf(fd, "\nInput thread %p:\n", this);
-
dumpBase(fd, args);
AudioStreamIn *input = mInput;
@@ -8124,8 +8131,6 @@
void AudioFlinger::MmapThread::dumpInternals(int fd, const Vector<String16>& args)
{
- dprintf(fd, "\nMmap thread %p:\n", this);
-
dumpBase(fd, args);
dprintf(fd, " Attributes: content type %d usage %d source %d\n",
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index 8270e74..cc66cad 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -31,6 +31,7 @@
RECORD, // Thread class is RecordThread
OFFLOAD, // Thread class is OffloadThread
MMAP // control thread for MMAP stream
+ // If you add any values here, also update ThreadBase::threadTypeToString()
};
static const char *threadTypeToString(type_t type);
diff --git a/services/audioflinger/TypedLogger.h b/services/audioflinger/TypedLogger.h
index 0b23c7c..cc28095 100644
--- a/services/audioflinger/TypedLogger.h
+++ b/services/audioflinger/TypedLogger.h
@@ -19,7 +19,72 @@
#define ANDROID_TYPED_LOGGER_H
#include <media/nbaio/NBLog.h>
-#define LOGT(fmt, ...) logWriterTLS->logFormat(fmt, ##__VA_ARGS__) // TODO: check null pointer
+#include <algorithm>
+
+/*
+Fowler-Noll-Vo (FNV-1a) hash function for the file name.
+Hashes at compile time. FNV-1a iterative function:
+
+hash = offset_basis
+for each byte to be hashed
+ hash = hash xor byte
+ hash = hash * FNV_prime
+return hash
+
+offset_basis and FNV_prime values depend on the size of the hash output
+Following values are defined by FNV and should not be changed arbitrarily
+*/
+
+template<typename T>
+constexpr T offset_basis();
+
+template<typename T>
+constexpr T FNV_prime();
+
+template<>
+constexpr uint32_t offset_basis<uint32_t>() {
+ return 2166136261u;
+}
+
+template<>
+constexpr uint32_t FNV_prime<uint32_t>() {
+ return 16777619u;
+}
+
+template<>
+constexpr uint64_t offset_basis<uint64_t>() {
+ return 14695981039346656037ull;
+}
+
+template<>
+constexpr uint64_t FNV_prime<uint64_t>() {
+ return 1099511628211ull;
+}
+
+template <typename T, size_t n>
+constexpr T fnv1a(const char (&file)[n], int i = n - 1) {
+ return i == -1 ? offset_basis<T>() : (fnv1a<T>(file, i - 1) ^ file[i]) * FNV_prime<T>();
+}
+
+template <size_t n>
+constexpr uint64_t hash(const char (&file)[n], uint32_t line) {
+ // Line numbers over or equal to 2^16 are clamped to 2^16 - 1. This way increases collisions
+ // compared to wrapping around, but is easy to identify because it doesn't produce aliasing.
+ // It's a very unlikely case anyways.
+ return ((fnv1a<uint64_t>(file) << 16) ^ ((fnv1a<uint64_t>(file) >> 32) & 0xFFFF0000)) |
+ std::min(line, 0xFFFFu);
+}
+
+// Write formatted entry to log
+#define LOGT(fmt, ...) logWriterTLS->logFormat((fmt), \
+ hash(__FILE__, __LINE__), \
+ ##__VA_ARGS__) // TODO: check null pointer
+
+// Write histogram timestamp entry
+#define LOG_HIST_TS() logWriterTLS->logHistTS(hash(__FILE__, __LINE__))
+
+// flush all histogram
+#define LOG_HIST_FLUSH() logWriterTLS->logHistFlush(hash(__FILE__, __LINE__))
namespace android {
extern "C" {
diff --git a/services/mediaanalytics/Android.mk b/services/mediaanalytics/Android.mk
index f7197af..9e2813e 100644
--- a/services/mediaanalytics/Android.mk
+++ b/services/mediaanalytics/Android.mk
@@ -5,7 +5,12 @@
include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \
- main_mediametrics.cpp \
+ main_mediametrics.cpp \
+ MetricsSummarizerCodec.cpp \
+ MetricsSummarizerExtractor.cpp \
+ MetricsSummarizerPlayer.cpp \
+ MetricsSummarizerRecorder.cpp \
+ MetricsSummarizer.cpp \
MediaAnalyticsService.cpp
LOCAL_SHARED_LIBRARIES := \
diff --git a/services/mediaanalytics/MediaAnalyticsService.cpp b/services/mediaanalytics/MediaAnalyticsService.cpp
index 35c1f5b..876c685 100644
--- a/services/mediaanalytics/MediaAnalyticsService.cpp
+++ b/services/mediaanalytics/MediaAnalyticsService.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -20,6 +20,7 @@
#define LOG_TAG "MediaAnalyticsService"
#include <utils/Log.h>
+#include <stdint.h>
#include <inttypes.h>
#include <sys/types.h>
#include <sys/stat.h>
@@ -70,11 +71,28 @@
#include "MediaAnalyticsService.h"
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+#include "MetricsSummarizerExtractor.h"
+#include "MetricsSummarizerPlayer.h"
+#include "MetricsSummarizerRecorder.h"
+
namespace android {
-#define DEBUG_QUEUE 0
+
+// summarized records
+// up to 48 sets, each covering an hour -- at least 2 days of coverage
+// (will be longer if there are hours without any media action)
+static const nsecs_t kNewSetIntervalNs = 3600*(1000*1000*1000ll);
+static const int kMaxRecordSets = 48;
+// individual records kept in memory
+static const int kMaxRecords = 100;
+
+
+static const char *kServiceName = "media.metrics";
+
//using android::status_t;
//using android::OK;
@@ -85,18 +103,67 @@
void MediaAnalyticsService::instantiate() {
defaultServiceManager()->addService(
- String16("media.metrics"), new MediaAnalyticsService());
+ String16(kServiceName), new MediaAnalyticsService());
}
-// XXX: add dynamic controls for mMaxRecords
+// handle sets of summarizers
+MediaAnalyticsService::SummarizerSet::SummarizerSet() {
+ mSummarizers = new List<MetricsSummarizer *>();
+}
+MediaAnalyticsService::SummarizerSet::~SummarizerSet() {
+ // empty the list
+ List<MetricsSummarizer *> *l = mSummarizers;
+ while (l->size() > 0) {
+ MetricsSummarizer *summarizer = *(l->begin());
+ l->erase(l->begin());
+ delete summarizer;
+ }
+}
+
+void MediaAnalyticsService::newSummarizerSet() {
+ ALOGD("MediaAnalyticsService::newSummarizerSet");
+ MediaAnalyticsService::SummarizerSet *set = new MediaAnalyticsService::SummarizerSet();
+ nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ set->setStarted(now);
+
+ set->appendSummarizer(new MetricsSummarizerExtractor("extractor"));
+ set->appendSummarizer(new MetricsSummarizerCodec("codec"));
+ set->appendSummarizer(new MetricsSummarizerPlayer("nuplayer"));
+ set->appendSummarizer(new MetricsSummarizerRecorder("recorder"));
+
+ // ALWAYS at the end, since it catches everything
+ set->appendSummarizer(new MetricsSummarizer(NULL));
+
+ // inject this set at the BACK of the list.
+ mSummarizerSets->push_back(set);
+ mCurrentSet = set;
+
+ // limit the # that we have
+ if (mMaxRecordSets > 0) {
+ List<SummarizerSet *> *l = mSummarizerSets;
+ while (l->size() > (size_t) mMaxRecordSets) {
+ ALOGD("Deleting oldest record set....");
+ MediaAnalyticsService::SummarizerSet *oset = *(l->begin());
+ l->erase(l->begin());
+ delete oset;
+ mSetsDiscarded++;
+ }
+ }
+}
+
MediaAnalyticsService::MediaAnalyticsService()
- : mMaxRecords(100) {
+ : mMaxRecords(kMaxRecords),
+ mMaxRecordSets(kMaxRecordSets),
+ mNewSetInterval(kNewSetIntervalNs) {
ALOGD("MediaAnalyticsService created");
// clear our queues
mOpen = new List<MediaAnalyticsItem *>();
mFinalized = new List<MediaAnalyticsItem *>();
+ mSummarizerSets = new List<MediaAnalyticsService::SummarizerSet *>();
+ newSummarizerSet();
+
mItemsSubmitted = 0;
mItemsFinalized = 0;
mItemsDiscarded = 0;
@@ -109,7 +176,13 @@
MediaAnalyticsService::~MediaAnalyticsService() {
ALOGD("MediaAnalyticsService destroyed");
- // XXX: clean out mOpen and mFinalized
+ // clean out mOpen and mFinalized
+ delete mOpen;
+ mOpen = NULL;
+ delete mFinalized;
+ mFinalized = NULL;
+
+ // XXX: clean out the summaries
}
@@ -145,7 +218,7 @@
case AID_MEDIA_EX:
case AID_MEDIA_DRM:
// trusted source, only override default values
- isTrusted = true;
+ isTrusted = true;
if (uid_given == (-1)) {
item->setUid(uid);
}
@@ -197,10 +270,12 @@
oitem = NULL;
} else {
oitem->setFinalized(true);
+ summarize(oitem);
saveItem(mFinalized, oitem, 0);
}
// new record could itself be marked finalized...
if (finalizing) {
+ summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
} else {
@@ -211,6 +286,7 @@
// combine the records, send it to finalized if appropriate
oitem->merge(item);
if (finalizing) {
+ summarize(oitem);
saveItem(mFinalized, oitem, 0);
mItemsFinalized++;
}
@@ -229,6 +305,7 @@
delete item;
item = NULL;
} else {
+ summarize(item);
saveItem(mFinalized, item, 0);
mItemsFinalized++;
}
@@ -239,26 +316,6 @@
return id;
}
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool finished, nsecs_t ts) {
- // this might never get called; the binder interface maps to the full parm list
- // on the client side before making the binder call.
- // but this lets us be sure...
- List<MediaAnalyticsItem*> *list;
- list = getMediaAnalyticsItemList(finished, ts, MediaAnalyticsItem::kKeyAny);
- return list;
-}
-
-List<MediaAnalyticsItem *> *MediaAnalyticsService::getMediaAnalyticsItemList(bool , nsecs_t , MediaAnalyticsItem::Key ) {
-
- // XXX: implement the get-item-list semantics
-
- List<MediaAnalyticsItem *> *list = NULL;
- // set up our query on the persistent data
- // slurp in all of the pieces
- // return that
- return list;
-}
-
status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
{
const size_t SIZE = 512;
@@ -277,15 +334,21 @@
// crack any parameters
bool clear = false;
+ bool summary = false;
nsecs_t ts_since = 0;
+ String16 summaryOption("-summary");
String16 clearOption("-clear");
String16 sinceOption("-since");
String16 helpOption("-help");
+ String16 onlyOption("-only");
+ const char *only = NULL;
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] == summaryOption) {
+ summary = true;
} else if (args[i] == sinceOption) {
i++;
if (i < n) {
@@ -301,12 +364,27 @@
}
// command line is milliseconds; internal units are nano-seconds
ts_since *= 1000*1000;
+ } else if (args[i] == onlyOption) {
+ i++;
+ if (i < n) {
+ String8 value(args[i]);
+ const char *p = value.string();
+ char *q = strdup(p);
+ if (q != NULL) {
+ if (only != NULL) {
+ free((void*)only);
+ }
+ only = q;
+ }
+ }
} else if (args[i] == helpOption) {
result.append("Recognized parameters:\n");
result.append("-help this help message\n");
+ result.append("-summary show summary info\n");
result.append("-clear clears out saved records\n");
- result.append("-since XXX include records since XXX\n");
- result.append(" (XXX is milliseconds since the UNIX epoch)\n");
+ result.append("-only X process records for component X\n");
+ result.append("-since X include records since X\n");
+ result.append(" (X is milliseconds since the UNIX epoch)\n");
write(fd, result.string(), result.size());
return NO_ERROR;
}
@@ -314,9 +392,42 @@
Mutex::Autolock _l(mLock);
- snprintf(buffer, SIZE, "Dump of the mediametrics process:\n");
+ // we ALWAYS dump this piece
+ snprintf(buffer, SIZE, "Dump of the %s process:\n", kServiceName);
result.append(buffer);
+ dumpHeaders(result, ts_since);
+
+ // only want 1, to avoid confusing folks that parse the output
+ if (summary) {
+ dumpSummaries(result, ts_since, only);
+ } else {
+ dumpRecent(result, ts_since, only);
+ }
+
+
+ if (clear) {
+ // remove everything from the finalized queue
+ while (mFinalized->size() > 0) {
+ MediaAnalyticsItem * oitem = *(mFinalized->begin());
+ mFinalized->erase(mFinalized->begin());
+ delete oitem;
+ mItemsDiscarded++;
+ }
+
+ // shall we clear the summary data too?
+
+ }
+
+ write(fd, result.string(), result.size());
+ return NO_ERROR;
+}
+
+// dump headers
+void MediaAnalyticsService::dumpHeaders(String8 &result, nsecs_t ts_since) {
+ const size_t SIZE = 512;
+ char buffer[SIZE];
+
int enabled = MediaAnalyticsItem::isEnabled();
if (enabled) {
snprintf(buffer, SIZE, "Metrics gathering: enabled\n");
@@ -331,50 +442,71 @@
" Discarded: %" PRId64 "\n",
mItemsSubmitted, mItemsFinalized, mItemsDiscarded);
result.append(buffer);
+ snprintf(buffer, SIZE,
+ "Summary Sets Discarded: %" PRId64 "\n", mSetsDiscarded);
+ result.append(buffer);
if (ts_since != 0) {
snprintf(buffer, SIZE,
"Dumping Queue entries more recent than: %" PRId64 "\n",
(int64_t) ts_since);
result.append(buffer);
}
+}
+
+// dump summary info
+void MediaAnalyticsService::dumpSummaries(String8 &result, nsecs_t ts_since, const char *only) {
+ const size_t SIZE = 512;
+ char buffer[SIZE];
+ int slot = 0;
+
+ snprintf(buffer, SIZE, "\nSummarized Metrics:\n");
+ result.append(buffer);
+
+ // have each of the distillers dump records
+ if (mSummarizerSets != NULL) {
+ List<SummarizerSet *>::iterator itSet = mSummarizerSets->begin();
+ for (; itSet != mSummarizerSets->end(); itSet++) {
+ nsecs_t when = (*itSet)->getStarted();
+ if (when < ts_since) {
+ continue;
+ }
+ List<MetricsSummarizer *> *list = (*itSet)->getSummarizers();
+ List<MetricsSummarizer *>::iterator it = list->begin();
+ for (; it != list->end(); it++) {
+ if (only != NULL && strcmp(only, (*it)->getKey()) != 0) {
+ ALOGV("Told to omit '%s'", (*it)->getKey());
+ }
+ AString distilled = (*it)->dumpSummary(slot, only);
+ result.append(distilled.c_str());
+ }
+ }
+ }
+}
+
+// the recent, detailed queues
+void MediaAnalyticsService::dumpRecent(String8 &result, nsecs_t ts_since, const char * only) {
+ const size_t SIZE = 512;
+ char buffer[SIZE];
// show the recently recorded records
snprintf(buffer, sizeof(buffer), "\nFinalized Metrics (oldest first):\n");
result.append(buffer);
- result.append(this->dumpQueue(mFinalized, ts_since));
+ result.append(this->dumpQueue(mFinalized, ts_since, only));
snprintf(buffer, sizeof(buffer), "\nIn-Progress Metrics (newest first):\n");
result.append(buffer);
- result.append(this->dumpQueue(mOpen, ts_since));
+ result.append(this->dumpQueue(mOpen, 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
-
- if (clear) {
- // remove everything from the finalized queue
- while (mFinalized->size() > 0) {
- MediaAnalyticsItem * oitem = *(mFinalized->begin());
- if (DEBUG_QUEUE) {
- ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
- oitem->getKey().c_str(), oitem->getSessionID(),
- oitem->getTimestamp());
- }
- mFinalized->erase(mFinalized->begin());
- mItemsDiscarded++;
- }
- }
-
- write(fd, result.string(), result.size());
- return NO_ERROR;
}
-
// caller has locked mLock...
String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList) {
- return dumpQueue(theList, (nsecs_t) 0);
+ return dumpQueue(theList, (nsecs_t) 0, NULL);
}
-String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since) {
+String8 MediaAnalyticsService::dumpQueue(List<MediaAnalyticsItem *> *theList, nsecs_t ts_since, const char * only) {
String8 result;
int slot = 0;
@@ -387,6 +519,11 @@
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);
+ continue;
+ }
AString entry = (*it)->toString();
result.appendFormat("%5d: %s\n", slot, entry.c_str());
slot++;
@@ -405,13 +542,6 @@
Mutex::Autolock _l(mLock);
- if (DEBUG_QUEUE) {
- ALOGD("Inject a record: session %" PRId64 " ts %" PRId64 "",
- item->getSessionID(), item->getTimestamp());
- String8 before = dumpQueue(l);
- ALOGD("Q before insert: %s", before.string());
- }
-
// adding at back of queue (fifo order)
if (front) {
l->push_front(item);
@@ -419,30 +549,15 @@
l->push_back(item);
}
- if (DEBUG_QUEUE) {
- String8 after = dumpQueue(l);
- ALOGD("Q after insert: %s", after.string());
- }
-
// keep removing old records the front until we're in-bounds
if (mMaxRecords > 0) {
while (l->size() > (size_t) mMaxRecords) {
MediaAnalyticsItem * oitem = *(l->begin());
- if (DEBUG_QUEUE) {
- ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
- oitem->getKey().c_str(), oitem->getSessionID(),
- oitem->getTimestamp());
- }
l->erase(l->begin());
delete oitem;
mItemsDiscarded++;
}
}
-
- if (DEBUG_QUEUE) {
- String8 after = dumpQueue(l);
- ALOGD("Q after cleanup: %s", after.string());
- }
}
// are they alike enough that nitem can be folded into oitem?
@@ -515,29 +630,14 @@
Mutex::Autolock _l(mLock);
- if(DEBUG_QUEUE) {
- String8 before = dumpQueue(l);
- ALOGD("Q before delete: %s", before.string());
- }
-
for (List<MediaAnalyticsItem *>::iterator it = l->begin();
it != l->end(); it++) {
if ((*it)->getSessionID() != item->getSessionID())
continue;
-
- if (DEBUG_QUEUE) {
- ALOGD(" --- removing record for SessionID %" PRId64 "", item->getSessionID());
- ALOGD("drop record at %s:%d", __FILE__, __LINE__);
- }
delete *it;
l->erase(it);
break;
}
-
- if (DEBUG_QUEUE) {
- String8 after = dumpQueue(l);
- ALOGD("Q after delete: %s", after.string());
- }
}
static AString allowedKeys[] =
@@ -579,5 +679,43 @@
return false;
}
+// insert into the appropriate summarizer.
+// we make our own copy to save/summarize
+void MediaAnalyticsService::summarize(MediaAnalyticsItem *item) {
+
+ ALOGV("MediaAnalyticsService::summarize()");
+
+ if (item == NULL) {
+ return;
+ }
+
+ nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ if (mCurrentSet == NULL
+ || (mCurrentSet->getStarted() + mNewSetInterval < now)) {
+ newSummarizerSet();
+ }
+
+ if (mCurrentSet == NULL) {
+ return;
+ }
+
+ List<MetricsSummarizer *> *summarizers = mCurrentSet->getSummarizers();
+ List<MetricsSummarizer *>::iterator it = summarizers->begin();
+ for (; it != summarizers->end(); it++) {
+ if ((*it)->isMine(*item)) {
+ break;
+ }
+ }
+ if (it == summarizers->end()) {
+ ALOGD("no handler for type %s", item->getKey().c_str());
+ return; // no handler
+ }
+
+ // invoke the summarizer. summarizer will make whatever copies
+ // it wants; the caller retains ownership of item.
+
+ (*it)->handleRecord(item);
+
+}
} // namespace android
diff --git a/services/mediaanalytics/MediaAnalyticsService.h b/services/mediaanalytics/MediaAnalyticsService.h
index d2b0f09..6685967 100644
--- a/services/mediaanalytics/MediaAnalyticsService.h
+++ b/services/mediaanalytics/MediaAnalyticsService.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2017 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.
@@ -28,6 +28,8 @@
#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
namespace android {
@@ -39,12 +41,6 @@
// on this side, caller surrenders ownership
virtual int64_t submit(MediaAnalyticsItem *item, bool forcenew);
- virtual List<MediaAnalyticsItem *>
- *getMediaAnalyticsItemList(bool finished, int64_t ts);
- virtual List<MediaAnalyticsItem *>
- *getMediaAnalyticsItemList(bool finished, int64_t ts, MediaAnalyticsItem::Key key);
-
-
static void instantiate();
virtual status_t dump(int fd, const Vector<String16>& args);
@@ -58,6 +54,7 @@
int64_t mItemsSubmitted;
int64_t mItemsFinalized;
int64_t mItemsDiscarded;
+ int64_t mSetsDiscarded;
MediaAnalyticsItem::SessionID_t mLastSessionID;
// partitioned a bit so we don't over serialize
@@ -67,6 +64,10 @@
// the most we hold in memory
// up to this many in each queue (open, finalized)
int32_t mMaxRecords;
+ // # of sets of summaries
+ int32_t mMaxRecordSets;
+ // nsecs until we start a new record set
+ nsecs_t mNewSetInterval;
// input validation after arrival from client
bool contentValid(MediaAnalyticsItem *item, bool isTrusted);
@@ -82,12 +83,47 @@
MediaAnalyticsItem *findItem(List<MediaAnalyticsItem *> *,
MediaAnalyticsItem *, bool removeit);
+ // summarizers
+ void summarize(MediaAnalyticsItem *item);
+ class SummarizerSet {
+ nsecs_t mStarted;
+ List<MetricsSummarizer *> *mSummarizers;
+
+ public:
+ void appendSummarizer(MetricsSummarizer *s) {
+ if (s) {
+ mSummarizers->push_back(s);
+ }
+ };
+ nsecs_t getStarted() { return mStarted;}
+ void setStarted(nsecs_t started) {mStarted = started;}
+ List<MetricsSummarizer *> *getSummarizers() { return mSummarizers;}
+
+ SummarizerSet();
+ ~SummarizerSet();
+ };
+ void newSummarizerSet();
+ List<SummarizerSet *> *mSummarizerSets;
+ SummarizerSet *mCurrentSet;
+ List<MetricsSummarizer *> *getFirstSet() {
+ List<SummarizerSet *>::iterator first = mSummarizerSets->begin();
+ if (first != mSummarizerSets->end()) {
+ return (*first)->getSummarizers();
+ }
+ return NULL;
+ }
+
void saveItem(MediaAnalyticsItem);
void saveItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *, int);
void deleteItem(List<MediaAnalyticsItem *> *, MediaAnalyticsItem *);
+ // support for generating output
String8 dumpQueue(List<MediaAnalyticsItem*> *);
- String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t);
+ String8 dumpQueue(List<MediaAnalyticsItem*> *, nsecs_t, const char *only);
+
+ 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);
};
diff --git a/services/mediaanalytics/MetricsSummarizer.cpp b/services/mediaanalytics/MetricsSummarizer.cpp
new file mode 100644
index 0000000..fc8f594
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.cpp
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2017 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 "MetricsSummarizer"
+#include <utils/Log.h>
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+#define DEBUG_SORT 0
+#define DEBUG_QUEUE 0
+
+
+MetricsSummarizer::MetricsSummarizer(const char *key)
+ : mIgnorables(NULL)
+{
+ ALOGV("MetricsSummarizer::MetricsSummarizer");
+
+ if (key == NULL) {
+ mKey = key;
+ } else {
+ mKey = strdup(key);
+ }
+
+ mSummaries = new List<MediaAnalyticsItem *>();
+}
+
+MetricsSummarizer::~MetricsSummarizer()
+{
+ ALOGV("MetricsSummarizer::~MetricsSummarizer");
+ if (mKey) {
+ free((void *)mKey);
+ mKey = NULL;
+ }
+
+ // clear the list of items we have saved
+ while (mSummaries->size() > 0) {
+ MediaAnalyticsItem * oitem = *(mSummaries->begin());
+ if (DEBUG_QUEUE) {
+ ALOGD("zap old record: key %s sessionID %" PRId64 " ts %" PRId64 "",
+ oitem->getKey().c_str(), oitem->getSessionID(),
+ oitem->getTimestamp());
+ }
+ mSummaries->erase(mSummaries->begin());
+ delete oitem;
+ }
+}
+
+// so we know what summarizer we were using
+const char *MetricsSummarizer::getKey() {
+ const char *value = mKey;
+ if (value == NULL) {
+ value = "unknown";
+ }
+ return value;
+}
+
+// should the record be given to this summarizer
+bool MetricsSummarizer::isMine(MediaAnalyticsItem &item)
+{
+ const char *incoming = item.getKey().c_str();
+ if (incoming == NULL) {
+ incoming = "unspecified";
+ }
+ if (mKey == NULL)
+ return true;
+ if (strcmp(mKey, incoming) != 0) {
+ return false;
+ }
+ // since nothing failed....
+ return true;
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot)
+{
+ return dumpSummary(slot, NULL);
+}
+
+AString MetricsSummarizer::dumpSummary(int &slot, const char *only)
+{
+ AString value = "";
+
+ List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+ if (it != mSummaries->end()) {
+ char buf[16]; // enough for "#####: "
+ for (; it != mSummaries->end(); it++) {
+ if (only != NULL && strcmp(only, (*it)->getKey().c_str()) != 0) {
+ continue;
+ }
+ AString entry = (*it)->toString();
+ snprintf(buf, sizeof(buf), "%5d: ", slot);
+ value.append(buf);
+ value.append(entry.c_str());
+ value.append("\n");
+ slot++;
+ }
+ }
+ return value;
+}
+
+void MetricsSummarizer::setIgnorables(const char **ignorables) {
+ mIgnorables = ignorables;
+}
+
+const char **MetricsSummarizer::getIgnorables() {
+ return mIgnorables;
+}
+
+void MetricsSummarizer::handleRecord(MediaAnalyticsItem *item) {
+
+ ALOGV("MetricsSummarizer::handleRecord() for %s",
+ item == NULL ? "<nothing>" : item->toString().c_str());
+
+ if (item == NULL) {
+ return;
+ }
+
+ List<MediaAnalyticsItem *>::iterator it = mSummaries->begin();
+ for (; it != mSummaries->end(); it++) {
+ bool good = sameAttributes((*it), item, getIgnorables());
+ ALOGV("Match against %s says %d",
+ (*it)->toString().c_str(), good);
+ if (good)
+ break;
+ }
+ if (it == mSummaries->end()) {
+ ALOGV("save new record");
+ item = item->dup();
+ if (item == NULL) {
+ ALOGE("unable to save MediaMetrics record");
+ }
+ sortProps(item);
+ item->setInt32("count",1);
+ mSummaries->push_back(item);
+ } else {
+ ALOGV("increment existing record");
+ (*it)->addInt32("count",1);
+ mergeRecord(*(*it), *item);
+ }
+}
+
+void MetricsSummarizer::mergeRecord(MediaAnalyticsItem &/*have*/, MediaAnalyticsItem &/*item*/) {
+ // default is no further massaging.
+ ALOGV("MetricsSummarizer::mergeRecord() [default]");
+ return;
+}
+
+
+//
+// Comparators
+//
+
+// testing that all of 'single' is in 'summ'
+// and that the values match.
+// 'summ' may have extra fields.
+// 'ignorable' is a set of things that we don't worry about matching up
+// (usually time- or count-based values we'll sum elsewhere)
+bool MetricsSummarizer::sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+ if (single == NULL || summ == NULL) {
+ return false;
+ }
+ ALOGV("MetricsSummarizer::sameAttributes(): summ %s", summ->toString().c_str());
+ ALOGV("MetricsSummarizer::sameAttributes(): single %s", single->toString().c_str());
+
+ // this can be made better.
+ for(size_t i=0;i<single->mPropCount;i++) {
+ MediaAnalyticsItem::Prop *prop1 = &(single->mProps[i]);
+ const char *attrName = prop1->mName;
+ ALOGV("compare on attr '%s'", attrName);
+
+ // is it something we should ignore
+ if (ignorable != NULL) {
+ const char **ig = ignorable;
+ while (*ig) {
+ if (strcmp(*ig, attrName) == 0) {
+ break;
+ }
+ ig++;
+ }
+ if (*ig) {
+ ALOGV("we don't mind that it has attr '%s'", attrName);
+ continue;
+ }
+ }
+
+ MediaAnalyticsItem::Prop *prop2 = summ->findProp(attrName);
+ if (prop2 == NULL) {
+ ALOGV("summ doesn't have this attr");
+ return false;
+ }
+ if (prop1->mType != prop2->mType) {
+ ALOGV("mismatched attr types");
+ return false;
+ }
+ switch (prop1->mType) {
+ case MediaAnalyticsItem::kTypeInt32:
+ if (prop1->u.int32Value != prop2->u.int32Value)
+ return false;
+ break;
+ case MediaAnalyticsItem::kTypeInt64:
+ if (prop1->u.int64Value != prop2->u.int64Value)
+ return false;
+ break;
+ case MediaAnalyticsItem::kTypeDouble:
+ // XXX: watch out for floating point comparisons!
+ if (prop1->u.doubleValue != prop2->u.doubleValue)
+ return false;
+ break;
+ case MediaAnalyticsItem::kTypeCString:
+ if (strcmp(prop1->u.CStringValue, prop2->u.CStringValue) != 0)
+ return false;
+ break;
+ case MediaAnalyticsItem::kTypeRate:
+ if (prop1->u.rate.count != prop2->u.rate.count)
+ return false;
+ if (prop1->u.rate.duration != prop2->u.rate.duration)
+ return false;
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool MetricsSummarizer::sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignorable) {
+
+ // verify same user
+ if (summ->mPid != single->mPid)
+ return false;
+
+ // and finally do the more expensive validation of the attributes
+ return sameAttributes(summ, single, ignorable);
+}
+
+int MetricsSummarizer::PropSorter(const void *a, const void *b) {
+ MediaAnalyticsItem::Prop *ai = (MediaAnalyticsItem::Prop *)a;
+ MediaAnalyticsItem::Prop *bi = (MediaAnalyticsItem::Prop *)b;
+ return strcmp(ai->mName, bi->mName);
+}
+
+// we sort in the summaries so that it looks pretty in the dumpsys
+void MetricsSummarizer::sortProps(MediaAnalyticsItem *item) {
+ if (item->mPropCount != 0) {
+ if (DEBUG_SORT) {
+ ALOGD("sortProps(pre): %s", item->toString().c_str());
+ }
+ qsort(item->mProps, item->mPropCount,
+ sizeof(MediaAnalyticsItem::Prop), MetricsSummarizer::PropSorter);
+ if (DEBUG_SORT) {
+ ALOGD("sortProps(pst): %s", item->toString().c_str());
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizer.h b/services/mediaanalytics/MetricsSummarizer.h
new file mode 100644
index 0000000..0b64eac
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizer.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZER_H
+#define ANDROID_METRICSSUMMARIZER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+
+namespace android {
+
+class MetricsSummarizer
+{
+
+ public:
+
+ MetricsSummarizer(const char *key);
+ virtual ~MetricsSummarizer();
+
+ // show the key
+ const char * getKey();
+
+ // should the record be given to this summarizer
+ bool isMine(MediaAnalyticsItem &item);
+
+ // hand the record to this summarizer
+ void handleRecord(MediaAnalyticsItem *item);
+
+ virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+ // dump the summarized records (for dumpsys)
+ AString dumpSummary(int &slot);
+ AString dumpSummary(int &slot, const char *only);
+
+ void setIgnorables(const char **);
+ const char **getIgnorables();
+
+ protected:
+
+ // various comparators
+ // "do these records have same attributes and values in those attrs"
+ // ditto, but watch for "error" fields
+ bool sameAttributes(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+ // attributes + from the same app/userid
+ bool sameAttributesId(MediaAnalyticsItem *summ, MediaAnalyticsItem *single, const char **ignoreables);
+
+ static int PropSorter(const void *a, const void *b);
+ void sortProps(MediaAnalyticsItem *item);
+
+ private:
+ const char *mKey;
+ const char **mIgnorables;
+ List<MediaAnalyticsItem *> *mSummaries;
+
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZER_H
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.cpp b/services/mediaanalytics/MetricsSummarizerCodec.cpp
new file mode 100644
index 0000000..8c74782
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 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 "MetricsSummarizerCodec"
+#include <utils/Log.h>
+
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerCodec.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerCodec::MetricsSummarizerCodec(const char *key)
+ : MetricsSummarizer(key)
+{
+ ALOGV("MetricsSummarizerCodec::MetricsSummarizerCodec");
+}
+
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerCodec.h b/services/mediaanalytics/MetricsSummarizerCodec.h
new file mode 100644
index 0000000..c01196f
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerCodec.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERCODEC_H
+#define ANDROID_METRICSSUMMARIZERCODEC_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerCodec : public MetricsSummarizer
+{
+
+ public:
+
+ MetricsSummarizerCodec(const char *key);
+ virtual ~MetricsSummarizerCodec() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERCODEC_H
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.cpp b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
new file mode 100644
index 0000000..190f87d
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.cpp
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 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 "MetricsSummarizerExtractor"
+#include <utils/Log.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerExtractor.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerExtractor::MetricsSummarizerExtractor(const char *key)
+ : MetricsSummarizer(key)
+{
+ ALOGV("MetricsSummarizerExtractor::MetricsSummarizerExtractor");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerExtractor.h b/services/mediaanalytics/MetricsSummarizerExtractor.h
new file mode 100644
index 0000000..eee052b
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerExtractor.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+#define ANDROID_METRICSSUMMARIZEREXTRACTOR_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerExtractor : public MetricsSummarizer
+{
+
+ public:
+
+ MetricsSummarizerExtractor(const char *key);
+ virtual ~MetricsSummarizerExtractor() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZEREXTRACTOR_H
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.cpp b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
new file mode 100644
index 0000000..5162059
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2017 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 "MetricsSummarizerPlayer"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerPlayer.h"
+
+
+
+
+namespace android {
+
+static const char *player_ignorable[] = {
+ "android.media.mediaplayer.durationMs",
+ "android.media.mediaplayer.playingMs",
+ "android.media.mediaplayer.frames",
+ "android.media.mediaplayer.dropped",
+ 0
+};
+
+MetricsSummarizerPlayer::MetricsSummarizerPlayer(const char *key)
+ : MetricsSummarizer(key)
+{
+ ALOGV("MetricsSummarizerPlayer::MetricsSummarizerPlayer");
+ setIgnorables(player_ignorable);
+}
+
+void MetricsSummarizerPlayer::mergeRecord(MediaAnalyticsItem &summation, MediaAnalyticsItem &item) {
+
+ ALOGV("MetricsSummarizerPlayer::mergeRecord()");
+
+ //
+ // we sum time & frames.
+ // be careful about our special "-1" values that indicate 'unknown'
+ // treat those as 0 [basically, not summing them into the totals].
+ int64_t duration = 0;
+ if (item.getInt64("android.media.mediaplayer.durationMs", &duration)) {
+ ALOGV("found durationMs of %" PRId64, duration);
+ summation.addInt64("android.media.mediaplayer.durationMs",duration);
+ }
+ int64_t playing = 0;
+ if (item.getInt64("android.media.mediaplayer.playingMs", &playing))
+ ALOGV("found playingMs of %" PRId64, playing);
+ if (playing >= 0) {
+ summation.addInt64("android.media.mediaplayer.playingMs",playing);
+ }
+ int64_t frames = 0;
+ if (item.getInt64("android.media.mediaplayer.frames", &frames))
+ ALOGV("found framess of %" PRId64, frames);
+ if (frames >= 0) {
+ summation.addInt64("android.media.mediaplayer.frames",frames);
+ }
+ int64_t dropped = 0;
+ if (item.getInt64("android.media.mediaplayer.dropped", &dropped))
+ ALOGV("found dropped of %" PRId64, dropped);
+ if (dropped >= 0) {
+ summation.addInt64("android.media.mediaplayer.dropped",dropped);
+ }
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerPlayer.h b/services/mediaanalytics/MetricsSummarizerPlayer.h
new file mode 100644
index 0000000..ad1bf74
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerPlayer.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERPLAYER_H
+#define ANDROID_METRICSSUMMARIZERPLAYER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerPlayer : public MetricsSummarizer
+{
+
+ public:
+
+ MetricsSummarizerPlayer(const char *key);
+ virtual ~MetricsSummarizerPlayer() {};
+
+ virtual void mergeRecord(MediaAnalyticsItem &have, MediaAnalyticsItem &incoming);
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERPLAYER_H
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.cpp b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
new file mode 100644
index 0000000..c2919c3
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2017 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 "MetricsSummarizerRecorder"
+#include <utils/Log.h>
+
+#include <stdint.h>
+#include <inttypes.h>
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+
+#include "MetricsSummarizer.h"
+#include "MetricsSummarizerRecorder.h"
+
+
+
+
+namespace android {
+
+MetricsSummarizerRecorder::MetricsSummarizerRecorder(const char *key)
+ : MetricsSummarizer(key)
+{
+ ALOGV("MetricsSummarizerRecorder::MetricsSummarizerRecorder");
+}
+
+} // namespace android
diff --git a/services/mediaanalytics/MetricsSummarizerRecorder.h b/services/mediaanalytics/MetricsSummarizerRecorder.h
new file mode 100644
index 0000000..963baab
--- /dev/null
+++ b/services/mediaanalytics/MetricsSummarizerRecorder.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#ifndef ANDROID_METRICSSUMMARIZERRECORDER_H
+#define ANDROID_METRICSSUMMARIZERRECORDER_H
+
+#include <utils/threads.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/String8.h>
+#include <utils/List.h>
+
+#include <media/IMediaAnalyticsService.h>
+#include "MetricsSummarizer.h"
+
+
+namespace android {
+
+class MetricsSummarizerRecorder : public MetricsSummarizer
+{
+
+ public:
+
+ MetricsSummarizerRecorder(const char *key);
+ virtual ~MetricsSummarizerRecorder() {};
+
+};
+
+// ----------------------------------------------------------------------------
+
+}; // namespace android
+
+#endif // ANDROID_METRICSSUMMARIZERRECORDER_H
diff --git a/services/medialog/MediaLogService.h b/services/medialog/MediaLogService.h
index c6b99f1..06c721f 100644
--- a/services/medialog/MediaLogService.h
+++ b/services/medialog/MediaLogService.h
@@ -49,7 +49,8 @@
// Internal dump
static const int kDumpLockRetries = 50;
static const int kDumpLockSleepUs = 20000;
- static const size_t kMergeBufferSize = 16 * 1024; // TODO determine good value for this
+ // Size of merge buffer, in bytes
+ static const size_t kMergeBufferSize = 64 * 1024; // TODO determine good value for this
static bool dumpTryLock(Mutex& mutex);
Mutex mLock;