Merge "screenrecord: Use ui::Rotation"
diff --git a/apex/Android.bp b/apex/Android.bp
index 42a620b..73dc264 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -41,6 +41,8 @@
// Use a custom AndroidManifest.xml used for API targeting.
androidManifest: ":com.android.media-androidManifest",
+
+ legacy_android10_support: true,
}
apex {
@@ -76,6 +78,8 @@
// Use a custom AndroidManifest.xml used for API targeting.
androidManifest: ":com.android.media.swcodec-androidManifest",
+
+ legacy_android10_support: true,
}
prebuilt_etc {
diff --git a/apex/ld.config.txt b/apex/ld.config.txt
index 1c44e65..713f0b7 100644
--- a/apex/ld.config.txt
+++ b/apex/ld.config.txt
@@ -22,6 +22,12 @@
namespace.default.search.paths = /apex/com.android.media.swcodec/${LIB}
namespace.default.asan.search.paths = /apex/com.android.media.swcodec/${LIB}
+# Below lines are required to be able to access libs in APEXes which are
+# actually symlinks to the files under /system/lib. The symlinks exist for
+# bundled APEXes to reduce space.
+namespace.default.permitted.paths = /system/${LIB}
+namespace.default.asan.permitted.paths = /system/${LIB}
+
namespace.default.links = platform
# TODO: replace the following when apex has a way to auto-generate this list
diff --git a/camera/Android.bp b/camera/Android.bp
index b288bcf..ea7259a 100644
--- a/camera/Android.bp
+++ b/camera/Android.bp
@@ -85,6 +85,7 @@
"aidl/android/hardware/ICameraServiceProxy.aidl",
"aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl",
"aidl/android/hardware/camera2/ICameraDeviceUser.aidl",
+ "aidl/android/hardware/camera2/ICameraOfflineSession.aidl",
],
path: "aidl",
}
diff --git a/camera/CameraMetadata.cpp b/camera/CameraMetadata.cpp
index 92fe84b..15c295e 100644
--- a/camera/CameraMetadata.cpp
+++ b/camera/CameraMetadata.cpp
@@ -17,7 +17,9 @@
// #define LOG_NDEBUG 0
#define LOG_TAG "Camera2-Metadata"
+#define ATRACE_TAG ATRACE_TAG_CAMERA
#include <utils/Log.h>
+#include <utils/Trace.h>
#include <utils/Errors.h>
#include <binder/Parcel.h>
@@ -38,11 +40,13 @@
CameraMetadata::CameraMetadata(size_t entryCapacity, size_t dataCapacity) :
mLocked(false)
{
+ ATRACE_CALL();
mBuffer = allocate_camera_metadata(entryCapacity, dataCapacity);
}
CameraMetadata::CameraMetadata(const CameraMetadata &other) :
mLocked(false) {
+ ATRACE_CALL();
mBuffer = clone_camera_metadata(other.mBuffer);
}
@@ -104,6 +108,7 @@
}
void CameraMetadata::clear() {
+ ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -115,6 +120,7 @@
}
void CameraMetadata::acquire(camera_metadata_t *buffer) {
+ ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -128,6 +134,7 @@
}
void CameraMetadata::acquire(CameraMetadata &other) {
+ ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return;
@@ -136,10 +143,12 @@
}
status_t CameraMetadata::append(const CameraMetadata &other) {
+ ATRACE_CALL();
return append(other.mBuffer);
}
status_t CameraMetadata::append(const camera_metadata_t* other) {
+ ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return INVALID_OPERATION;
@@ -161,6 +170,7 @@
}
status_t CameraMetadata::sort() {
+ ATRACE_CALL();
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
return INVALID_OPERATION;
@@ -291,6 +301,7 @@
status_t CameraMetadata::updateImpl(uint32_t tag, const void *data,
size_t data_count) {
+ ATRACE_CALL();
status_t res;
if (mLocked) {
ALOGE("%s: CameraMetadata is locked", __FUNCTION__);
@@ -348,11 +359,13 @@
}
bool CameraMetadata::exists(uint32_t tag) const {
+ ATRACE_CALL();
camera_metadata_ro_entry entry;
return find_camera_metadata_ro_entry(mBuffer, tag, &entry) == 0;
}
camera_metadata_entry_t CameraMetadata::find(uint32_t tag) {
+ ATRACE_CALL();
status_t res;
camera_metadata_entry entry;
if (mLocked) {
@@ -369,6 +382,7 @@
}
camera_metadata_ro_entry_t CameraMetadata::find(uint32_t tag) const {
+ ATRACE_CALL();
status_t res;
camera_metadata_ro_entry entry;
res = find_camera_metadata_ro_entry(mBuffer, tag, &entry);
@@ -380,6 +394,7 @@
}
status_t CameraMetadata::erase(uint32_t tag) {
+ ATRACE_CALL();
camera_metadata_entry_t entry;
status_t res;
if (mLocked) {
@@ -410,6 +425,7 @@
status_t CameraMetadata::removePermissionEntries(metadata_vendor_id_t vendorId,
std::vector<int32_t> *tagsRemoved) {
+ ATRACE_CALL();
uint32_t tagCount = 0;
std::vector<uint32_t> tagsToRemove;
@@ -486,6 +502,7 @@
}
status_t CameraMetadata::resizeIfNeeded(size_t extraEntries, size_t extraData) {
+ ATRACE_CALL();
if (mBuffer == NULL) {
mBuffer = allocate_camera_metadata(extraEntries * 2, extraData * 2);
if (mBuffer == NULL) {
@@ -525,7 +542,7 @@
status_t CameraMetadata::readFromParcel(const Parcel& data,
camera_metadata_t** out) {
-
+ ATRACE_CALL();
status_t err = OK;
camera_metadata_t* metadata = NULL;
@@ -616,6 +633,7 @@
status_t CameraMetadata::writeToParcel(Parcel& data,
const camera_metadata_t* metadata) {
+ ATRACE_CALL();
status_t res = OK;
/**
@@ -710,7 +728,7 @@
}
status_t CameraMetadata::readFromParcel(const Parcel *parcel) {
-
+ ATRACE_CALL();
ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
status_t res = OK;
@@ -742,7 +760,7 @@
}
status_t CameraMetadata::writeToParcel(Parcel *parcel) const {
-
+ ATRACE_CALL();
ALOGV("%s: parcel = %p", __FUNCTION__, parcel);
if (parcel == NULL) {
@@ -771,7 +789,7 @@
status_t CameraMetadata::getTagFromName(const char *name,
const VendorTagDescriptor* vTags, uint32_t *tag) {
-
+ ATRACE_CALL();
if (name == nullptr || tag == nullptr) return BAD_VALUE;
size_t nameLength = strlen(name);
diff --git a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl
index 93549e0..cdb7ac3 100644
--- a/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl
+++ b/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl
@@ -17,6 +17,8 @@
package android.hardware.camera2;
import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.ICameraDeviceCallbacks;
+import android.hardware.camera2.ICameraOfflineSession;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
@@ -177,4 +179,15 @@
* @return the currently applied system-wide audio restriction mode
*/
int getGlobalAudioRestriction();
+
+ /**
+ * Offline processing main entry point
+ *
+ * @param callbacks Object that will receive callbacks from offline session
+ * @param offlineOutputIds The ID of streams that needs to be preserved in offline session
+ *
+ * @return Offline session object.
+ */
+ ICameraOfflineSession switchToOffline(in ICameraDeviceCallbacks callbacks,
+ in Surface[] offlineOutputs);
}
diff --git a/camera/aidl/android/hardware/camera2/ICameraOfflineSession.aidl b/camera/aidl/android/hardware/camera2/ICameraOfflineSession.aidl
new file mode 100644
index 0000000..ab030ab
--- /dev/null
+++ b/camera/aidl/android/hardware/camera2/ICameraOfflineSession.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2;
+
+ /** @hide */
+interface ICameraOfflineSession
+{
+ void disconnect();
+}
diff --git a/camera/ndk/impl/ACameraMetadata.cpp b/camera/ndk/impl/ACameraMetadata.cpp
index 7c41b5e..e15e1a0 100644
--- a/camera/ndk/impl/ACameraMetadata.cpp
+++ b/camera/ndk/impl/ACameraMetadata.cpp
@@ -480,6 +480,7 @@
case ACAMERA_CONTROL_POST_RAW_SENSITIVITY_BOOST:
case ACAMERA_CONTROL_ENABLE_ZSL:
case ACAMERA_CONTROL_BOKEH_MODE:
+ case ACAMERA_CONTROL_ZOOM_RATIO:
case ACAMERA_EDGE_MODE:
case ACAMERA_FLASH_MODE:
case ACAMERA_HOT_PIXEL_MODE:
diff --git a/camera/ndk/include/camera/NdkCameraMetadataTags.h b/camera/ndk/include/camera/NdkCameraMetadataTags.h
index 825f308..6fefa41 100644
--- a/camera/ndk/include/camera/NdkCameraMetadataTags.h
+++ b/camera/ndk/include/camera/NdkCameraMetadataTags.h
@@ -518,11 +518,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same aeRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The aeRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * aeRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of the
+ * scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -698,11 +709,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same afRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The afRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * afRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of the
+ * scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -873,11 +895,22 @@
* region and output only the intersection rectangle as the metering region in the result
* metadata. If the region is entirely outside the crop region, it will be ignored and
* not reported in the result metadata.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoom field of view. This means that the same awbRegions values at different
+ * ACAMERA_CONTROL_ZOOM_RATIO represent different parts of the scene. The awbRegions
+ * coordinates are relative to the activeArray/preCorrectionActiveArray representing the
+ * zoomed field of view. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0 (default), the same
+ * awbRegions at different ACAMERA_SCALER_CROP_REGION still represent the same parts of
+ * the scene as they do before. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use
+ * activeArraySize or preCorrectionActiveArraySize still depends on distortion correction
+ * mode.</p>
* <p>The data representation is <code>int[5 * area_count]</code>.
* Every five elements represent a metering region of <code>(xmin, ymin, xmax, ymax, weight)</code>.
* The rectangle is defined to be inclusive on xmin and ymin, but exclusive on xmax and
* ymax.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
@@ -1311,7 +1344,7 @@
/**
* <p>List of the maximum number of regions that can be used for metering in
* auto-exposure (AE), auto-white balance (AWB), and auto-focus (AF);
- * this corresponds to the the maximum number of elements in
+ * this corresponds to the maximum number of elements in
* ACAMERA_CONTROL_AE_REGIONS, ACAMERA_CONTROL_AWB_REGIONS,
* and ACAMERA_CONTROL_AF_REGIONS.</p>
*
@@ -1735,8 +1768,10 @@
ACAMERA_CONTROL_AF_SCENE_CHANGE = // byte (acamera_metadata_enum_android_control_af_scene_change_t)
ACAMERA_CONTROL_START + 42,
/**
- * <p>The list of bokeh modes that are supported by this camera device, and each bokeh mode's
- * maximum streaming (non-stall) size with bokeh effect.</p>
+ * <p>The list of bokeh modes for ACAMERA_CONTROL_BOKEH_MODE that are supported by this camera
+ * device, and each bokeh mode's maximum streaming (non-stall) size with bokeh effect.</p>
+ *
+ * @see ACAMERA_CONTROL_BOKEH_MODE
*
* <p>Type: int32[3*n]</p>
*
@@ -1761,9 +1796,31 @@
* application configures a stream with larger dimension, the stream may not have bokeh
* effect applied.</p>
*/
- ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES = // int32[3*n]
+ ACAMERA_CONTROL_AVAILABLE_BOKEH_MAX_SIZES = // int32[3*n]
ACAMERA_CONTROL_START + 43,
/**
+ * <p>The ranges of supported zoom ratio for non-OFF ACAMERA_CONTROL_BOKEH_MODE.</p>
+ *
+ * @see ACAMERA_CONTROL_BOKEH_MODE
+ *
+ * <p>Type: float[2*n]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>When bokeh mode is enabled, the camera device may have limited range of zoom ratios
+ * compared to when bokeh mode is disabled. This tag lists the zoom ratio ranges for all
+ * supported non-OFF bokeh modes, in the same order as in
+ * ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES.</p>
+ * <p>Range [1.0, 1.0] means that no zoom (optical or digital) is supported.</p>
+ *
+ * @see ACAMERA_CONTROL_AVAILABLE_BOKEH_CAPABILITIES
+ */
+ ACAMERA_CONTROL_AVAILABLE_BOKEH_ZOOM_RATIO_RANGES = // float[2*n]
+ ACAMERA_CONTROL_START + 44,
+ /**
* <p>Whether bokeh mode is enabled for a particular capture request.</p>
*
* <p>Type: byte (acamera_metadata_enum_android_control_bokeh_mode_t)</p>
@@ -1800,11 +1857,79 @@
* stream combinations of LIMITED hardware level are guaranteed.</p>
* <p>For a logical multi-camera, bokeh may be implemented by stereo vision from sub-cameras
* with different field of view. As a result, when bokeh mode is enabled, the camera device
- * may override android.scaler.CropRegion, and the field of view will be smaller than when
+ * may override ACAMERA_SCALER_CROP_REGION, and the field of view will be smaller than when
* bokeh mode is off.</p>
+ *
+ * @see ACAMERA_SCALER_CROP_REGION
*/
ACAMERA_CONTROL_BOKEH_MODE = // byte (acamera_metadata_enum_android_control_bokeh_mode_t)
- ACAMERA_CONTROL_START + 44,
+ ACAMERA_CONTROL_START + 45,
+ /**
+ * <p>Minimum and maximum zoom ratios supported by this camera device.</p>
+ *
+ * <p>Type: float[2]</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraManager_getCameraCharacteristics</li>
+ * </ul></p>
+ *
+ * <p>If the camera device supports zoom-out from 1x zoom, minZoom will be less than 1.0, and
+ * setting ACAMERA_CONTROL_ZOOM_RATIO to values less than 1.0 increases the camera's field
+ * of view.</p>
+ *
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ */
+ ACAMERA_CONTROL_ZOOM_RATIO_RANGE = // float[2]
+ ACAMERA_CONTROL_START + 46,
+ /**
+ * <p>The desired zoom ratio</p>
+ *
+ * <p>Type: float</p>
+ *
+ * <p>This tag may appear in:
+ * <ul>
+ * <li>ACameraMetadata from ACameraCaptureSession_captureCallback_result callbacks</li>
+ * <li>ACaptureRequest</li>
+ * </ul></p>
+ *
+ * <p>Instead of using ACAMERA_SCALER_CROP_REGION with dual purposes of crop and zoom, the
+ * application can now choose to use this tag to specify the desired zoom level. The
+ * ACAMERA_SCALER_CROP_REGION can still be used to specify the horizontal or vertical
+ * crop to achieve aspect ratios different than the native camera sensor.</p>
+ * <p>By using this control, the application gains a simpler way to control zoom, which can
+ * be a combination of optical and digital zoom. More specifically, for a logical
+ * multi-camera with more than one focal length, using a floating point zoom ratio offers
+ * more zoom precision when a telephoto lens is used, as well as allowing zoom ratio of
+ * less than 1.0 to zoom out to a wide field of view.</p>
+ * <p>Note that the coordinate system of cropRegion, AE/AWB/AF regions, and faces now changes
+ * to the effective after-zoom field-of-view represented by rectangle of (0, 0,
+ * activeArrayWidth, activeArrayHeight).</p>
+ * <p>For example, if ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE is 4032*3024, and the preview stream
+ * is configured to the same 4:3 aspect ratio, the application can achieve 2.0x zoom in
+ * one of two ways:</p>
+ * <ul>
+ * <li>zoomRatio = 2.0, scaler.cropRegion = (0, 0, 4032, 3024)</li>
+ * <li>zoomRatio = 1.0 (default), scaler.cropRegion = (1008, 756, 3024, 2268)</li>
+ * </ul>
+ * <p>If the application intends to set aeRegions to be top-left quarter of the preview
+ * field-of-view, the ACAMERA_CONTROL_AE_REGIONS should be set to (0, 0, 2016, 1512) with
+ * zoomRatio set to 2.0. Alternatively, the application can set aeRegions to the equivalent
+ * region of (1008, 756, 2016, 1512) for zoomRatio of 1.0. If the application doesn't
+ * explicitly set ACAMERA_CONTROL_ZOOM_RATIO, its value defaults to 1.0.</p>
+ * <p>This coordinate system change isn't applicable to RAW capture and its related metadata
+ * such as intrinsicCalibration and lensShadingMap.</p>
+ * <p>One limitation of controlling zoom using zoomRatio is that the ACAMERA_SCALER_CROP_REGION
+ * must only be used for letterboxing or pillarboxing of the sensor active array, and no
+ * FREEFORM cropping can be used with ACAMERA_CONTROL_ZOOM_RATIO other than 1.0.</p>
+ *
+ * @see ACAMERA_CONTROL_AE_REGIONS
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_SCALER_CROP_REGION
+ * @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
+ */
+ ACAMERA_CONTROL_ZOOM_RATIO = // float
+ ACAMERA_CONTROL_START + 47,
ACAMERA_CONTROL_END,
/**
@@ -3163,32 +3288,70 @@
* <p>For devices not supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate
* system always follows that of ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being
* the top-left pixel of the active array.</p>
- * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate
- * system depends on the mode being set.
- * When the distortion correction mode is OFF, the coordinate system follows
- * ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with
- * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
- * When the distortion correction mode is not OFF, the coordinate system follows
- * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with
- * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Output streams use this rectangle to produce their output,
- * cropping to a smaller region if necessary to maintain the
- * stream's aspect ratio, then scaling the sensor input to
+ * <p>For devices supporting ACAMERA_DISTORTION_CORRECTION_MODE control, the coordinate system
+ * depends on the mode being set. When the distortion correction mode is OFF, the
+ * coordinate system follows ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, with <code>(0,
+ * 0)</code> being the top-left pixel of the pre-correction active array. When the distortion
+ * correction mode is not OFF, the coordinate system follows
+ * ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with <code>(0, 0)</code> being the top-left pixel of the
+ * active array.</p>
+ * <p>Output streams use this rectangle to produce their output, cropping to a smaller region
+ * if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
* match the output's configured resolution.</p>
- * <p>The crop region is applied after the RAW to other color
- * space (e.g. YUV) conversion. Since raw streams
- * (e.g. RAW16) don't have the conversion stage, they are not
+ * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
+ * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
* croppable. The crop region will be ignored by raw streams.</p>
- * <p>For non-raw streams, any additional per-stream cropping will
- * be done to maximize the final pixel area of the stream.</p>
- * <p>For example, if the crop region is set to a 4:3 aspect
- * ratio, then 4:3 streams will use the exact crop
- * region. 16:9 streams will further crop vertically
- * (letterbox).</p>
- * <p>Conversely, if the crop region is set to a 16:9, then 4:3
- * outputs will crop horizontally (pillarbox), and 16:9
- * streams will match exactly. These additional crops will
- * be centered within the crop region.</p>
+ * <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
+ * final pixel area of the stream.</p>
+ * <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
+ * the exact crop region. 16:9 streams will further crop vertically (letterbox).</p>
+ * <p>Conversely, if the crop region is set to a 16:9, then 4:3 outputs will crop horizontally
+ * (pillarbox), and 16:9 streams will match exactly. These additional crops will be
+ * centered within the crop region.</p>
+ * <p>To illustrate, here are several scenarios of different crop regions and output streams,
+ * for a hypothetical camera device with an active array of size <code>(2000,1500)</code>. Note that
+ * several of these examples use non-centered crop regions for ease of illustration; such
+ * regions are only supported on devices with FREEFORM capability
+ * (ACAMERA_SCALER_CROPPING_TYPE <code>== FREEFORM</code>), but this does not affect the way the crop
+ * rules work otherwise.</p>
+ * <ul>
+ * <li>Camera Configuration:<ul>
+ * <li>Active array size: <code>2000x1500</code> (3 MP, 4:3 aspect ratio)</li>
+ * <li>Output stream #1: <code>640x480</code> (VGA, 4:3 aspect ratio)</li>
+ * <li>Output stream #2: <code>1280x720</code> (720p, 16:9 aspect ratio)</li>
+ * </ul>
+ * </li>
+ * <li>Case #1: 4:3 crop region with 2x digital zoom<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125) // (left, top, right, bottom)</code></li>
+ * <li><img alt="4:3 aspect ratio crop diagram" src="../images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 375, 1500, 1125)</code> (equal to crop region)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #2: 16:9 crop region with ~1.5x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1833, 1125)</code></li>
+ * <li><img alt="16:9 aspect ratio crop diagram" src="../images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(666, 375, 1666, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 375, 1833, 1125)</code> (equal to crop region)</li>
+ * </ul>
+ * </li>
+ * <li>Case #3: 1:1 crop region with ~2.6x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1250, 1125)</code></li>
+ * <li><img alt="1:1 aspect ratio crop diagram" src="../images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 469, 1250, 1031)</code> (letterboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 543, 1250, 957)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #4: Replace <code>640x480</code> stream with <code>1024x1024</code> stream, with 4:3 crop region:<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125)</code></li>
+ * <li><img alt="Square output, 4:3 aspect ratio crop diagram" src="../images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png" /></li>
+ * <li><code>1024x1024</code> stream source area: <code>(625, 375, 1375, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * <li>Note that in this case, neither of the two outputs is a subset of the other, with
+ * each containing image data the other doesn't have.</li>
+ * </ul>
+ * </li>
+ * </ul>
* <p>If the coordinate system is ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, the width and height
* of the crop region cannot be set to be smaller than
* <code>floor( activeArraySize.width / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code> and
@@ -3199,14 +3362,22 @@
* and
* <code>floor( preCorrectionActiveArraySize.height / ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM )</code>,
* respectively.</p>
- * <p>The camera device may adjust the crop region to account
- * for rounding and other hardware requirements; the final
- * crop region used will be included in the output capture
- * result.</p>
+ * <p>The camera device may adjust the crop region to account for rounding and other hardware
+ * requirements; the final crop region used will be included in the output capture result.</p>
+ * <p>Starting from API level 30, it's strongly recommended to use ACAMERA_CONTROL_ZOOM_RATIO
+ * to take advantage of better support for zoom with logical multi-camera. The benefits
+ * include better precision with optical-digital zoom combination, and ability to do
+ * zoom-out from 1.0x. When using ACAMERA_CONTROL_ZOOM_RATIO for zoom, the crop region in
+ * the capture request must be either letterboxing or pillarboxing (but not both). The
+ * coordinate system is post-zoom, meaning that the activeArraySize or
+ * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
+ * ACAMERA_CONTROL_ZOOM_RATIO for details.</p>
* <p>The data representation is int[4], which maps to (left, top, width, height).</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
* @see ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ * @see ACAMERA_SCALER_CROPPING_TYPE
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
*/
@@ -3232,6 +3403,12 @@
* <p>Crop regions that have a width or height that is smaller
* than this ratio allows will be rounded up to the minimum
* allowed size by the camera device.</p>
+ * <p>Starting from API level 30, when using ACAMERA_CONTROL_ZOOM_RATIO to zoom in or out,
+ * the application must use ACAMERA_CONTROL_ZOOM_RATIO_RANGE to query both the minimum and
+ * maximum zoom ratio.</p>
+ *
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
+ * @see ACAMERA_CONTROL_ZOOM_RATIO_RANGE
*/
ACAMERA_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM = // float
ACAMERA_SCALER_START + 4,
@@ -3406,8 +3583,24 @@
* <p>Camera devices that support FREEFORM cropping will support any crop region that
* is inside of the active array. The camera device will apply the same crop region and
* return the final used crop region in capture result metadata ACAMERA_SCALER_CROP_REGION.</p>
+ * <p>Starting from API level 30,</p>
+ * <ul>
+ * <li>If the camera device supports FREEFORM cropping, in order to do FREEFORM cropping, the
+ * application must set ACAMERA_CONTROL_ZOOM_RATIO to 1.0, and use ACAMERA_SCALER_CROP_REGION
+ * for zoom.</li>
+ * <li>To do CENTER_ONLY zoom, the application has below 2 options:<ol>
+ * <li>Set ACAMERA_CONTROL_ZOOM_RATIO to 1.0; adjust zoom by ACAMERA_SCALER_CROP_REGION.</li>
+ * <li>Adjust zoom by ACAMERA_CONTROL_ZOOM_RATIO; use ACAMERA_SCALER_CROP_REGION to crop
+ * the field of view vertically (letterboxing) or horizontally (pillarboxing), but not
+ * windowboxing.</li>
+ * </ol>
+ * </li>
+ * <li>Setting ACAMERA_CONTROL_ZOOM_RATIO to values different than 1.0 and
+ * ACAMERA_SCALER_CROP_REGION to be windowboxing at the same time is undefined behavior.</li>
+ * </ul>
* <p>LEGACY capability devices will only support CENTER_ONLY cropping.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
*/
@@ -4707,9 +4900,20 @@
* When the distortion correction mode is not OFF, the coordinate system follows
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with
* <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE == FULL</p>
+ * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE == FULL.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * ACAMERA_CONTROL_ZOOM_RATIO, the face landmarks move farther away from the center of the
+ * activeArray or preCorrectionActiveArray. If ACAMERA_CONTROL_ZOOM_RATIO is set to 1.0
+ * (default), the face landmarks coordinates won't change as ACAMERA_SCALER_CROP_REGION
+ * changes. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
+ * @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see ACAMERA_STATISTICS_FACE_DETECT_MODE
@@ -4738,10 +4942,21 @@
* When the distortion correction mode is not OFF, the coordinate system follows
* ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE, with
* <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF
- * The data representation is <code>int[4]</code>, which maps to <code>(left, top, right, bottom)</code>.</p>
+ * <p>Only available if ACAMERA_STATISTICS_FACE_DETECT_MODE != OFF.</p>
+ * <p>Starting from API level 30, the coordinate system of activeArraySize or
+ * preCorrectionActiveArraySize is used to represent post-zoomRatio field of view, not
+ * pre-zoomRatio field of view. This means that if the relative position of faces and
+ * the camera device doesn't change, when zooming in by increasing
+ * ACAMERA_CONTROL_ZOOM_RATIO, the face rectangles grow larger and move farther away from
+ * the center of the activeArray or preCorrectionActiveArray. If ACAMERA_CONTROL_ZOOM_RATIO
+ * is set to 1.0 (default), the face rectangles won't change as ACAMERA_SCALER_CROP_REGION
+ * changes. See ACAMERA_CONTROL_ZOOM_RATIO for details. Whether to use activeArraySize or
+ * preCorrectionActiveArraySize still depends on distortion correction mode.</p>
+ * <p>The data representation is <code>int[4]</code>, which maps to <code>(left, top, right, bottom)</code>.</p>
*
+ * @see ACAMERA_CONTROL_ZOOM_RATIO
* @see ACAMERA_DISTORTION_CORRECTION_MODE
+ * @see ACAMERA_SCALER_CROP_REGION
* @see ACAMERA_SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see ACAMERA_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
* @see ACAMERA_STATISTICS_FACE_DETECT_MODE
@@ -8165,12 +8380,16 @@
// ACAMERA_SENSOR_INFO_TIMESTAMP_SOURCE
typedef enum acamera_metadata_enum_acamera_sensor_info_timestamp_source {
/**
- * <p>Timestamps from ACAMERA_SENSOR_TIMESTAMP are in nanoseconds and monotonic,
- * but can not be compared to timestamps from other subsystems
- * (e.g. accelerometer, gyro etc.), or other instances of the same or different
- * camera devices in the same system. Timestamps between streams and results for
- * a single camera instance are comparable, and the timestamps for all buffers
- * and the result metadata generated by a single capture are identical.</p>
+ * <p>Timestamps from ACAMERA_SENSOR_TIMESTAMP are in nanoseconds and monotonic, but can
+ * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.),
+ * or other instances of the same or different camera devices in the same system with
+ * accuracy. However, the timestamps are roughly in the same timebase as
+ * <a href="https://developer.android.com/reference/android/os/SystemClock.html#uptimeMillis">SystemClock#uptimeMillis</a>. The accuracy is sufficient for tasks
+ * like A/V synchronization for video recording, at least, and the timestamps can be
+ * directly used together with timestamps from the audio subsystem for that task.</p>
+ * <p>Timestamps between streams and results for a single camera instance are comparable,
+ * and the timestamps for all buffers and the result metadata generated by a single
+ * capture are identical.</p>
*
* @see ACAMERA_SENSOR_TIMESTAMP
*/
@@ -8180,6 +8399,14 @@
* <p>Timestamps from ACAMERA_SENSOR_TIMESTAMP are in the same timebase as
* <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">SystemClock#elapsedRealtimeNanos</a>,
* and they can be compared to other timestamps using that base.</p>
+ * <p>When buffers from a REALTIME device are passed directly to a video encoder from the
+ * camera, automatic compensation is done to account for differing timebases of the
+ * audio and camera subsystems. If the application is receiving buffers and then later
+ * sending them to a video encoder or other application where they are compared with
+ * audio subsystem timestamps or similar, this compensation is not present. In those
+ * cases, applications need to adjust the timestamps themselves. Since <a href="https://developer.android.com/reference/android/os/SystemClock.html#elapsedRealtimeNanos">SystemClock#elapsedRealtimeNanos</a> and <a href="https://developer.android.com/reference/android/os/SystemClock.html#uptimeMillis">SystemClock#uptimeMillis</a> only diverge while the device is asleep, an
+ * offset between the two sources can be measured once per active session and applied
+ * to timestamps for sufficient accuracy for A/V sync.</p>
*
* @see ACAMERA_SENSOR_TIMESTAMP
*/
diff --git a/cmds/stagefright/Android.mk b/cmds/stagefright/Android.mk
index defc94f..7b447d3 100644
--- a/cmds/stagefright/Android.mk
+++ b/cmds/stagefright/Android.mk
@@ -23,6 +23,7 @@
LOCAL_MODULE_TAGS := optional
+LOCAL_SYSTEM_EXT_MODULE:= true
LOCAL_MODULE:= stagefright
include $(BUILD_EXECUTABLE)
diff --git a/cmds/stagefright/AudioPlayer.cpp b/cmds/stagefright/AudioPlayer.cpp
index 208713d..eb76953 100644
--- a/cmds/stagefright/AudioPlayer.cpp
+++ b/cmds/stagefright/AudioPlayer.cpp
@@ -23,7 +23,7 @@
#include <binder/IPCThreadState.h>
#include <media/AudioTrack.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/openmax/OMX_Audio.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALookup.h>
diff --git a/cmds/stagefright/AudioPlayer.h b/cmds/stagefright/AudioPlayer.h
index 7c2c36f..107bd71 100644
--- a/cmds/stagefright/AudioPlayer.h
+++ b/cmds/stagefright/AudioPlayer.h
@@ -18,7 +18,7 @@
#define AUDIO_PLAYER_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/MediaPlayerInterface.h>
#include <media/stagefright/MediaBuffer.h>
#include <utils/threads.h>
diff --git a/cmds/stagefright/SineSource.h b/cmds/stagefright/SineSource.h
index 1817291..6f1d98c 100644
--- a/cmds/stagefright/SineSource.h
+++ b/cmds/stagefright/SineSource.h
@@ -2,7 +2,7 @@
#define SINE_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <utils/Compat.h>
namespace android {
diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp
index e8cfece..c430f05 100644
--- a/cmds/stagefright/stagefright.cpp
+++ b/cmds/stagefright/stagefright.cpp
@@ -33,7 +33,7 @@
#include <binder/ProcessState.h>
#include <datasource/DataSourceFactory.h>
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/IMediaHTTPService.h>
#include <media/IMediaPlayerService.h>
#include <media/stagefright/foundation/ABuffer.h>
diff --git a/cmds/stagefright/stream.cpp b/cmds/stagefright/stream.cpp
index fe613a8..5b29158 100644
--- a/cmds/stagefright/stream.cpp
+++ b/cmds/stagefright/stream.cpp
@@ -26,7 +26,7 @@
#include <media/IMediaHTTPService.h>
#include <media/IStreamSource.h>
#include <media/mediaplayer.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/InterfaceUtils.h>
diff --git a/drm/drmserver/DrmManager.cpp b/drm/drmserver/DrmManager.cpp
index e2ea83a..38dc052 100644
--- a/drm/drmserver/DrmManager.cpp
+++ b/drm/drmserver/DrmManager.cpp
@@ -31,7 +31,7 @@
#include <drm/DrmInfoRequest.h>
#include <drm/DrmSupportInfo.h>
#include <drm/DrmConvertedStatus.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <IDrmEngine.h>
#include "DrmManager.h"
@@ -58,7 +58,7 @@
const char func[], const String8& plugInId, const String8& mimeType) {
IDrmEngine& engine = mPlugInManager.getPlugIn(plugInId);
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create("drmmanager"));
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create("drmmanager"));
item->setUid(IPCThreadState::self()->getCallingUid());
item->setCString("function_name", func);
item->setCString("plugin_id", plugInId.getPathLeaf().getBasePath().c_str());
diff --git a/drm/libmediadrm/Android.bp b/drm/libmediadrm/Android.bp
index 6b1b475..255640f 100644
--- a/drm/libmediadrm/Android.bp
+++ b/drm/libmediadrm/Android.bp
@@ -165,4 +165,8 @@
"libmediadrmmetrics_full",
"libutils",
],
+
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/Android.bp b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
index eb623f9..a194416 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/Android.bp
+++ b/drm/mediadrm/plugins/clearkey/hidl/Android.bp
@@ -78,18 +78,37 @@
},
srcs: ["protos/DeviceFiles.proto"],
}
+
cc_binary {
name: "android.hardware.drm@1.2-service.clearkey",
defaults: ["clearkey_service_defaults"],
srcs: ["service.cpp"],
init_rc: ["android.hardware.drm@1.2-service.clearkey.rc"],
- vintf_fragments: ["manifest_android.hardware.drm@1.3-service.clearkey.xml"],
+ vintf_fragments: ["manifest_android.hardware.drm@1.2-service.clearkey.xml"],
}
+
cc_binary {
name: "android.hardware.drm@1.2-service-lazy.clearkey",
overrides: ["android.hardware.drm@1.2-service.clearkey"],
defaults: ["clearkey_service_defaults"],
srcs: ["serviceLazy.cpp"],
init_rc: ["android.hardware.drm@1.2-service-lazy.clearkey.rc"],
+ vintf_fragments: ["manifest_android.hardware.drm@1.2-service.clearkey.xml"],
+}
+
+cc_binary {
+ name: "android.hardware.drm@1.3-service.clearkey",
+ defaults: ["clearkey_service_defaults"],
+ srcs: ["service.cpp"],
+ init_rc: ["android.hardware.drm@1.3-service.clearkey.rc"],
+ vintf_fragments: ["manifest_android.hardware.drm@1.3-service.clearkey.xml"],
+}
+
+cc_binary {
+ name: "android.hardware.drm@1.3-service-lazy.clearkey",
+ overrides: ["android.hardware.drm@1.3-service.clearkey"],
+ defaults: ["clearkey_service_defaults"],
+ srcs: ["serviceLazy.cpp"],
+ init_rc: ["android.hardware.drm@1.3-service-lazy.clearkey.rc"],
vintf_fragments: ["manifest_android.hardware.drm@1.3-service.clearkey.xml"],
}
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc
index cff4d74..9afd3d7 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc
+++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service-lazy.clearkey.rc
@@ -5,8 +5,6 @@
interface android.hardware.drm@1.1::IDrmFactory clearkey
interface android.hardware.drm@1.2::ICryptoFactory clearkey
interface android.hardware.drm@1.2::IDrmFactory clearkey
- interface android.hardware.drm@1.3::ICryptoFactory clearkey
- interface android.hardware.drm@1.3::IDrmFactory clearkey
disabled
oneshot
class hal
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc
index dca232a..c1abe7f 100644
--- a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc
+++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.2-service.clearkey.rc
@@ -5,8 +5,7 @@
interface android.hardware.drm@1.1::IDrmFactory clearkey
interface android.hardware.drm@1.2::ICryptoFactory clearkey
interface android.hardware.drm@1.2::IDrmFactory clearkey
- interface android.hardware.drm@1.3::ICryptoFactory clearkey
- interface android.hardware.drm@1.3::IDrmFactory clearkey
+ disabled
class hal
user media
group media mediadrm
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service-lazy.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service-lazy.clearkey.rc
new file mode 100644
index 0000000..1e0d431
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service-lazy.clearkey.rc
@@ -0,0 +1,16 @@
+service vendor.drm-clearkey-hal-1-3 /vendor/bin/hw/android.hardware.drm@1.3-service-lazy.clearkey
+ interface android.hardware.drm@1.0::ICryptoFactory clearkey
+ interface android.hardware.drm@1.0::IDrmFactory clearkey
+ interface android.hardware.drm@1.1::ICryptoFactory clearkey
+ interface android.hardware.drm@1.1::IDrmFactory clearkey
+ interface android.hardware.drm@1.2::ICryptoFactory clearkey
+ interface android.hardware.drm@1.2::IDrmFactory clearkey
+ interface android.hardware.drm@1.3::ICryptoFactory clearkey
+ interface android.hardware.drm@1.3::IDrmFactory clearkey
+ disabled
+ oneshot
+ class hal
+ user media
+ group media mediadrm
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service.clearkey.rc b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service.clearkey.rc
new file mode 100644
index 0000000..8130511
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/hidl/android.hardware.drm@1.3-service.clearkey.rc
@@ -0,0 +1,14 @@
+service vendor.drm-clearkey-hal-1-3 /vendor/bin/hw/android.hardware.drm@1.3-service.clearkey
+ interface android.hardware.drm@1.0::ICryptoFactory clearkey
+ interface android.hardware.drm@1.0::IDrmFactory clearkey
+ interface android.hardware.drm@1.1::ICryptoFactory clearkey
+ interface android.hardware.drm@1.1::IDrmFactory clearkey
+ interface android.hardware.drm@1.2::ICryptoFactory clearkey
+ interface android.hardware.drm@1.2::IDrmFactory clearkey
+ interface android.hardware.drm@1.3::ICryptoFactory clearkey
+ interface android.hardware.drm@1.3::IDrmFactory clearkey
+ class hal
+ user media
+ group media mediadrm
+ ioprio rt 4
+ writepid /dev/cpuset/foreground/tasks
diff --git a/drm/mediadrm/plugins/clearkey/hidl/manifest_android.hardware.drm@1.2-service.clearkey.xml b/drm/mediadrm/plugins/clearkey/hidl/manifest_android.hardware.drm@1.2-service.clearkey.xml
new file mode 100644
index 0000000..16cba11
--- /dev/null
+++ b/drm/mediadrm/plugins/clearkey/hidl/manifest_android.hardware.drm@1.2-service.clearkey.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<manifest version="1.0" type="device">
+ <hal format="hidl">
+ <name>android.hardware.drm</name>
+ <transport>hwbinder</transport>
+ <fqname>@1.2::ICryptoFactory/clearkey</fqname>
+ <fqname>@1.2::IDrmFactory/clearkey</fqname>
+ </hal>
+</manifest>
diff --git a/include/media/DataSource.h b/include/media/DataSource.h
index 8efa809..2abd5f5 100644
--- a/include/media/DataSource.h
+++ b/include/media/DataSource.h
@@ -22,7 +22,7 @@
#include <android/IDataSource.h>
#include <media/stagefright/MediaErrors.h>
-#include <media/DataSourceBase.h>
+#include <media/stagefright/DataSourceBase.h>
#include <media/MediaExtractorPluginApi.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
diff --git a/include/media/DataSourceBase.h b/include/media/DataSourceBase.h
deleted file mode 120000
index d2ab2f1..0000000
--- a/include/media/DataSourceBase.h
+++ /dev/null
@@ -1 +0,0 @@
-stagefright/DataSourceBase.h
\ No newline at end of file
diff --git a/include/media/IMediaAnalyticsService.h b/include/media/IMediaAnalyticsService.h
deleted file mode 120000
index a596d60..0000000
--- a/include/media/IMediaAnalyticsService.h
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libmediametrics/include/IMediaAnalyticsService.h
\ No newline at end of file
diff --git a/include/media/IMediaMetricsService.h b/include/media/IMediaMetricsService.h
new file mode 120000
index 0000000..84b99ee
--- /dev/null
+++ b/include/media/IMediaMetricsService.h
@@ -0,0 +1 @@
+../../media/libmediametrics/include/IMediaMetricsService.h
\ No newline at end of file
diff --git a/include/media/MediaAnalyticsItem.h b/include/media/MediaAnalyticsItem.h
deleted file mode 120000
index e8124e0..0000000
--- a/include/media/MediaAnalyticsItem.h
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libmediametrics/include/MediaAnalyticsItem.h
\ No newline at end of file
diff --git a/include/media/MediaMetricsItem.h b/include/media/MediaMetricsItem.h
new file mode 120000
index 0000000..5438953
--- /dev/null
+++ b/include/media/MediaMetricsItem.h
@@ -0,0 +1 @@
+../../media/libmediametrics/include/MediaMetricsItem.h
\ No newline at end of file
diff --git a/include/media/MediaSource.h b/include/media/MediaSource.h
deleted file mode 120000
index 34bf65d..0000000
--- a/include/media/MediaSource.h
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libstagefright/include/media/stagefright/MediaSource.h
\ No newline at end of file
diff --git a/include/media/audiohal b/include/media/audiohal
deleted file mode 120000
index f400582..0000000
--- a/include/media/audiohal
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libaudiohal/include/media/audiohal/
\ No newline at end of file
diff --git a/include/media/nbaio/AudioStreamOutSink.h b/include/media/nbaio/AudioStreamOutSink.h
deleted file mode 120000
index 43bfac5..0000000
--- a/include/media/nbaio/AudioStreamOutSink.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../media/libnbaio/include/media/nbaio/AudioStreamOutSink.h
\ No newline at end of file
diff --git a/include/media/nbaio/NBAIO.h b/include/media/nbaio/NBAIO.h
deleted file mode 120000
index ff6a151..0000000
--- a/include/media/nbaio/NBAIO.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../media/libnbaio/include_mono/media/nbaio/NBAIO.h
\ No newline at end of file
diff --git a/include/media/nbaio/SingleStateQueue.h b/include/media/nbaio/SingleStateQueue.h
deleted file mode 120000
index d3e0553..0000000
--- a/include/media/nbaio/SingleStateQueue.h
+++ /dev/null
@@ -1 +0,0 @@
-../../../media/libnbaio/include_mono/media/nbaio/SingleStateQueue.h
\ No newline at end of file
diff --git a/include/media/stagefright b/include/media/stagefright
deleted file mode 120000
index 5393f68..0000000
--- a/include/media/stagefright
+++ /dev/null
@@ -1 +0,0 @@
-../../media/libstagefright/include/media/stagefright/
\ No newline at end of file
diff --git a/include/soundtrigger/ISoundTrigger.h b/include/soundtrigger/ISoundTrigger.h
deleted file mode 100644
index d628c05..0000000
--- a/include/soundtrigger/ISoundTrigger.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2014 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_HARDWARE_ISOUNDTRIGGER_H
-#define ANDROID_HARDWARE_ISOUNDTRIGGER_H
-
-#include <utils/RefBase.h>
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-#include <binder/IMemory.h>
-#include <system/sound_trigger.h>
-
-namespace android {
-
-class ISoundTrigger : public IInterface
-{
-public:
- DECLARE_META_INTERFACE(SoundTrigger);
-
- virtual void detach() = 0;
-
- virtual status_t loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle) = 0;
-
- virtual status_t unloadSoundModel(sound_model_handle_t handle) = 0;
-
- virtual status_t startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory) = 0;
- virtual status_t stopRecognition(sound_model_handle_t handle) = 0;
- virtual status_t getModelState(sound_model_handle_t handle) = 0;
- virtual status_t setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t value) = 0;
- virtual status_t getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t* value) = 0;
- virtual status_t queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range) = 0;
-};
-
-// ----------------------------------------------------------------------------
-
-class BnSoundTrigger: public BnInterface<ISoundTrigger>
-{
-public:
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
-};
-
-}; // namespace android
-
-#endif //ANDROID_HARDWARE_ISOUNDTRIGGER_H
diff --git a/include/soundtrigger/ISoundTriggerClient.h b/include/soundtrigger/ISoundTriggerClient.h
deleted file mode 100644
index 480429a..0000000
--- a/include/soundtrigger/ISoundTriggerClient.h
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2014 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_HARDWARE_ISOUNDTRIGGER_CLIENT_H
-#define ANDROID_HARDWARE_ISOUNDTRIGGER_CLIENT_H
-
-#include <utils/RefBase.h>
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-
-namespace android {
-
-class ISoundTriggerClient : public IInterface
-{
-public:
-
- DECLARE_META_INTERFACE(SoundTriggerClient);
-
- virtual void onRecognitionEvent(const sp<IMemory>& eventMemory) = 0;
-
- virtual void onSoundModelEvent(const sp<IMemory>& eventMemory) = 0;
-
- virtual void onServiceStateChange(const sp<IMemory>& eventMemory) = 0;
-
-};
-
-// ----------------------------------------------------------------------------
-
-class BnSoundTriggerClient : public BnInterface<ISoundTriggerClient>
-{
-public:
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
-};
-
-}; // namespace android
-
-#endif //ANDROID_HARDWARE_ISOUNDTRIGGER_CLIENT_H
diff --git a/include/soundtrigger/ISoundTriggerHwService.h b/include/soundtrigger/ISoundTriggerHwService.h
deleted file mode 100644
index 1faeb0f..0000000
--- a/include/soundtrigger/ISoundTriggerHwService.h
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2014 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_HARDWARE_ISOUNDTRIGGER_SERVICE_H
-#define ANDROID_HARDWARE_ISOUNDTRIGGER_SERVICE_H
-
-#include <utils/RefBase.h>
-#include <binder/IInterface.h>
-#include <binder/Parcel.h>
-#include <system/sound_trigger.h>
-
-namespace android {
-
-class ISoundTrigger;
-class ISoundTriggerClient;
-
-class ISoundTriggerHwService : public IInterface
-{
-public:
-
- DECLARE_META_INTERFACE(SoundTriggerHwService);
-
- virtual status_t listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules) = 0;
-
- virtual status_t attach(const String16& opPackageName,
- const sound_trigger_module_handle_t handle,
- const sp<ISoundTriggerClient>& client,
- sp<ISoundTrigger>& module) = 0;
-
- virtual status_t setCaptureState(bool active) = 0;
-};
-
-// ----------------------------------------------------------------------------
-
-class BnSoundTriggerHwService: public BnInterface<ISoundTriggerHwService>
-{
-public:
- virtual status_t onTransact( uint32_t code,
- const Parcel& data,
- Parcel* reply,
- uint32_t flags = 0);
-};
-
-}; // namespace android
-
-#endif //ANDROID_HARDWARE_ISOUNDTRIGGER_SERVICE_H
diff --git a/include/soundtrigger/OWNERS b/include/soundtrigger/OWNERS
deleted file mode 100644
index e83f6b9..0000000
--- a/include/soundtrigger/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-elaurent@google.com
-thorntonc@google.com
diff --git a/include/soundtrigger/SoundTrigger.h b/include/soundtrigger/SoundTrigger.h
deleted file mode 100644
index 248620a..0000000
--- a/include/soundtrigger/SoundTrigger.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2014 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_HARDWARE_SOUNDTRIGGER_H
-#define ANDROID_HARDWARE_SOUNDTRIGGER_H
-
-#include <binder/IBinder.h>
-#include <utils/threads.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-#include <soundtrigger/ISoundTrigger.h>
-#include <soundtrigger/ISoundTriggerHwService.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-#include <system/sound_trigger.h>
-
-namespace android {
-
-class MemoryDealer;
-
-class SoundTrigger : public BnSoundTriggerClient,
- public IBinder::DeathRecipient
-{
-public:
-
- virtual ~SoundTrigger();
-
- static status_t listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules);
- static sp<SoundTrigger> attach(const String16& opPackageName,
- const sound_trigger_module_handle_t module,
- const sp<SoundTriggerCallback>& callback);
-
- static status_t setCaptureState(bool active);
-
- void detach();
-
- status_t loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle);
-
- status_t unloadSoundModel(sound_model_handle_t handle);
-
- status_t startRecognition(sound_model_handle_t handle, const sp<IMemory>& dataMemory);
- status_t stopRecognition(sound_model_handle_t handle);
- status_t getModelState(sound_model_handle_t handle);
- status_t setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t value);
- status_t getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t* value);
- status_t queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range);
-
- // BpSoundTriggerClient
- virtual void onRecognitionEvent(const sp<IMemory>& eventMemory);
- virtual void onSoundModelEvent(const sp<IMemory>& eventMemory);
- virtual void onServiceStateChange(const sp<IMemory>& eventMemory);
-
- //IBinder::DeathRecipient
- virtual void binderDied(const wp<IBinder>& who);
-
- static status_t stringToGuid(const char *str, sound_trigger_uuid_t *guid);
- static status_t guidToString(const sound_trigger_uuid_t *guid,
- char *str, size_t maxLen);
-
-private:
- SoundTrigger(sound_trigger_module_handle_t module,
- const sp<SoundTriggerCallback>&);
- static const sp<ISoundTriggerHwService> getSoundTriggerHwService();
-
- Mutex mLock;
- sp<ISoundTrigger> mISoundTrigger;
- sp<SoundTriggerCallback> mCallback;
-};
-
-}; // namespace android
-
-#endif //ANDROID_HARDWARE_SOUNDTRIGGER_H
diff --git a/include/soundtrigger/SoundTriggerCallback.h b/include/soundtrigger/SoundTriggerCallback.h
deleted file mode 100644
index b5277f2..0000000
--- a/include/soundtrigger/SoundTriggerCallback.h
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2014 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_HARDWARE_SOUNDTRIGGER_CALLBACK_H
-#define ANDROID_HARDWARE_SOUNDTRIGGER_CALLBACK_H
-
-#include <utils/RefBase.h>
-#include <system/sound_trigger.h>
-
-namespace android {
-
-class SoundTriggerCallback : public RefBase
-{
-public:
-
- SoundTriggerCallback() {}
- virtual ~SoundTriggerCallback() {}
-
- virtual void onRecognitionEvent(struct sound_trigger_recognition_event *event) = 0;
-
- virtual void onSoundModelEvent(struct sound_trigger_model_event *event) = 0;
-
- virtual void onServiceStateChange(sound_trigger_service_state_t state) = 0;
-
- virtual void onServiceDied() = 0;
-
-};
-
-}; // namespace android
-
-#endif //ANDROID_HARDWARE_SOUNDTRIGGER_CALLBACK_H
diff --git a/media/audioserver/Android.mk b/media/audioserver/Android.mk
index e33804d..79b77a0 100644
--- a/media/audioserver/Android.mk
+++ b/media/audioserver/Android.mk
@@ -19,10 +19,12 @@
libmediautils \
libnbaio \
libnblog \
- libsoundtriggerservice \
libutils \
libvibrator
+LOCAL_HEADER_LIBRARIES := \
+ libaudiohal_headers \
+
# TODO oboeservice is the old folder name for aaudioservice. It will be changed.
LOCAL_C_INCLUDES := \
frameworks/av/services/audioflinger \
@@ -33,7 +35,6 @@
frameworks/av/services/audiopolicy/service \
frameworks/av/services/medialog \
frameworks/av/services/oboeservice \
- frameworks/av/services/soundtrigger \
frameworks/av/media/libaaudio/include \
frameworks/av/media/libaaudio/src \
frameworks/av/media/libaaudio/src/binding \
diff --git a/media/audioserver/main_audioserver.cpp b/media/audioserver/main_audioserver.cpp
index db57248..4a2ac1d 100644
--- a/media/audioserver/main_audioserver.cpp
+++ b/media/audioserver/main_audioserver.cpp
@@ -36,7 +36,6 @@
#include "utility/AAudioUtilities.h"
#include "MediaLogService.h"
#include "MediaUtils.h"
-#include "SoundTriggerHwService.h"
using namespace android;
@@ -148,7 +147,6 @@
AAudioService::instantiate();
}
- SoundTriggerHwService::instantiate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
diff --git a/media/bufferpool/2.0/Android.bp b/media/bufferpool/2.0/Android.bp
index 97f114a..557b7ef 100644
--- a/media/bufferpool/2.0/Android.bp
+++ b/media/bufferpool/2.0/Android.bp
@@ -30,6 +30,8 @@
name: "libstagefright_bufferpool@2.0.1",
defaults: ["libstagefright_bufferpool@2.0-default"],
vendor_available: true,
+ // TODO: b/147147992
+ double_loadable: true,
cflags: [
"-DBUFFERPOOL_CLONE_HANDLES",
],
@@ -40,6 +42,8 @@
name: "libstagefright_bufferpool@2.0",
defaults: ["libstagefright_bufferpool@2.0-default"],
vendor_available: true,
+ // TODO: b/147147992
+ double_loadable: true,
vndk: {
enabled: true,
},
diff --git a/media/codec2/components/avc/C2SoftAvcDec.cpp b/media/codec2/components/avc/C2SoftAvcDec.cpp
index 2662f0f..56813c4 100644
--- a/media/codec2/components/avc/C2SoftAvcDec.cpp
+++ b/media/codec2/components/avc/C2SoftAvcDec.cpp
@@ -501,7 +501,7 @@
status_t C2SoftAvcDec::initDecoder() {
if (OK != createDecoder()) return UNKNOWN_ERROR;
mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES);
- mStride = ALIGN64(mWidth);
+ mStride = ALIGN128(mWidth);
mSignalledError = false;
resetPlugin();
(void) setNumCores();
@@ -756,8 +756,8 @@
ALOGE("not supposed to be here, invalid decoder context");
return C2_CORRUPTED;
}
- if (mStride != ALIGN64(mWidth)) {
- mStride = ALIGN64(mWidth);
+ if (mStride != ALIGN128(mWidth)) {
+ mStride = ALIGN128(mWidth);
if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED;
}
if (mOutBlock &&
@@ -909,7 +909,7 @@
if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
if (mHeaderDecoded == false) {
mHeaderDecoded = true;
- setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
+ setParams(ALIGN128(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
}
if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) {
mWidth = s_decode_op.u4_pic_wd;
diff --git a/media/codec2/components/avc/C2SoftAvcDec.h b/media/codec2/components/avc/C2SoftAvcDec.h
index 4414a26..ed27493 100644
--- a/media/codec2/components/avc/C2SoftAvcDec.h
+++ b/media/codec2/components/avc/C2SoftAvcDec.h
@@ -40,6 +40,7 @@
#define ivdext_ctl_get_vui_params_ip_t ih264d_ctl_get_vui_params_ip_t
#define ivdext_ctl_get_vui_params_op_t ih264d_ctl_get_vui_params_op_t
#define ALIGN64(x) ((((x) + 63) >> 6) << 6)
+#define ALIGN128(x) ((((x) + 127) >> 7) << 7)
#define MAX_NUM_CORES 4
#define IVDEXT_CMD_CTL_SET_NUM_CORES \
(IVD_CONTROL_API_COMMAND_TYPE_T)IH264D_CMD_CTL_SET_NUM_CORES
diff --git a/media/codec2/components/cmds/codec2.cpp b/media/codec2/components/cmds/codec2.cpp
index 38eaf88..d6025de 100644
--- a/media/codec2/components/cmds/codec2.cpp
+++ b/media/codec2/components/cmds/codec2.cpp
@@ -34,7 +34,7 @@
#include <media/DataSource.h>
#include <mediadrm/ICrypto.h>
#include <media/IMediaHTTPService.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AMessage.h>
diff --git a/media/codec2/components/hevc/C2SoftHevcDec.cpp b/media/codec2/components/hevc/C2SoftHevcDec.cpp
index df677c2..6db4387 100644
--- a/media/codec2/components/hevc/C2SoftHevcDec.cpp
+++ b/media/codec2/components/hevc/C2SoftHevcDec.cpp
@@ -497,7 +497,7 @@
status_t C2SoftHevcDec::initDecoder() {
if (OK != createDecoder()) return UNKNOWN_ERROR;
mNumCores = MIN(getCpuCoreCount(), MAX_NUM_CORES);
- mStride = ALIGN64(mWidth);
+ mStride = ALIGN128(mWidth);
mSignalledError = false;
resetPlugin();
(void) setNumCores();
@@ -752,8 +752,8 @@
ALOGE("not supposed to be here, invalid decoder context");
return C2_CORRUPTED;
}
- if (mStride != ALIGN64(mWidth)) {
- mStride = ALIGN64(mWidth);
+ if (mStride != ALIGN128(mWidth)) {
+ mStride = ALIGN128(mWidth);
if (OK != setParams(mStride, IVD_DECODE_FRAME)) return C2_CORRUPTED;
}
if (mOutBlock &&
@@ -904,7 +904,7 @@
if (0 < s_decode_op.u4_pic_wd && 0 < s_decode_op.u4_pic_ht) {
if (mHeaderDecoded == false) {
mHeaderDecoded = true;
- setParams(ALIGN64(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
+ setParams(ALIGN128(s_decode_op.u4_pic_wd), IVD_DECODE_FRAME);
}
if (s_decode_op.u4_pic_wd != mWidth || s_decode_op.u4_pic_ht != mHeight) {
mWidth = s_decode_op.u4_pic_wd;
diff --git a/media/codec2/components/hevc/C2SoftHevcDec.h b/media/codec2/components/hevc/C2SoftHevcDec.h
index ce63a6c..aecd101 100644
--- a/media/codec2/components/hevc/C2SoftHevcDec.h
+++ b/media/codec2/components/hevc/C2SoftHevcDec.h
@@ -38,6 +38,7 @@
#define ivdext_ctl_get_vui_params_ip_t ihevcd_cxa_ctl_get_vui_params_ip_t
#define ivdext_ctl_get_vui_params_op_t ihevcd_cxa_ctl_get_vui_params_op_t
#define ALIGN64(x) ((((x) + 63) >> 6) << 6)
+#define ALIGN128(x) ((((x) + 127) >> 7) << 7)
#define MAX_NUM_CORES 4
#define IVDEXT_CMD_CTL_SET_NUM_CORES \
(IVD_CONTROL_API_COMMAND_TYPE_T)IHEVCD_CXA_CMD_CTL_SET_NUM_CORES
diff --git a/media/codec2/core/Android.bp b/media/codec2/core/Android.bp
index a7e8997..1f9d7ab 100644
--- a/media/codec2/core/Android.bp
+++ b/media/codec2/core/Android.bp
@@ -10,6 +10,7 @@
vndk: {
enabled: true,
},
+ double_loadable: true,
srcs: ["C2.cpp"],
diff --git a/media/codec2/sfplugin/Android.bp b/media/codec2/sfplugin/Android.bp
index 59ac94b..534c1a7 100644
--- a/media/codec2/sfplugin/Android.bp
+++ b/media/codec2/sfplugin/Android.bp
@@ -1,6 +1,8 @@
cc_library_shared {
name: "libsfplugin_ccodec",
+ export_include_dirs: ["include"],
+
srcs: [
"C2OMXNode.cpp",
"CCodec.cpp",
@@ -11,7 +13,6 @@
"Codec2InfoBuilder.cpp",
"PipelineWatcher.cpp",
"ReflectedParamUpdater.cpp",
- "SkipCutBuffer.cpp",
],
cflags: [
@@ -32,6 +33,7 @@
"android.hardware.media.omx@1.0",
"libbase",
"libbinder",
+ "libcodec2",
"libcodec2_client",
"libcodec2_vndk",
"libcutils",
@@ -51,6 +53,11 @@
"libutils",
],
+ export_shared_lib_headers: [
+ "libcodec2",
+ "libcodec2_client",
+ ],
+
sanitize: {
cfi: true,
misc_undefined: [
diff --git a/media/codec2/sfplugin/CCodec.cpp b/media/codec2/sfplugin/CCodec.cpp
index 31e5406..5c572e1 100644
--- a/media/codec2/sfplugin/CCodec.cpp
+++ b/media/codec2/sfplugin/CCodec.cpp
@@ -40,13 +40,14 @@
#include <media/openmax/OMX_IndexExt.h>
#include <media/stagefright/omx/1.0/WGraphicBufferSource.h>
#include <media/stagefright/omx/OmxGraphicBufferSource.h>
+#include <media/stagefright/CCodec.h>
#include <media/stagefright/BufferProducerWrapper.h>
#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/PersistentSurface.h>
#include "C2OMXNode.h"
-#include "CCodec.h"
#include "CCodecBufferChannel.h"
+#include "CCodecConfig.h"
#include "InputSurfaceWrapper.h"
extern "C" android::PersistentSurface *CreateInputSurface();
@@ -59,6 +60,7 @@
using ::android::hardware::media::c2::V1_0::IInputSurface;
typedef hardware::media::omx::V1_0::IGraphicBufferSource HGraphicBufferSource;
+typedef CCodecConfig Config;
namespace {
@@ -571,7 +573,8 @@
// CCodec
CCodec::CCodec()
- : mChannel(new CCodecBufferChannel(std::make_shared<CCodecCallbackImpl>(this))) {
+ : mChannel(new CCodecBufferChannel(std::make_shared<CCodecCallbackImpl>(this))),
+ mConfig(new CCodecConfig) {
}
CCodec::~CCodec() {
@@ -662,7 +665,8 @@
}
// initialize config here in case setParameters is called prior to configure
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
status_t err = config->initialize(mClient, comp);
if (err != OK) {
ALOGW("Failed to initialize configuration support");
@@ -736,7 +740,8 @@
setSurface(surface);
}
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
config->mUsingSurface = surface != nullptr;
// Enforce required parameters
@@ -1052,7 +1057,8 @@
return;
}
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
mCallback->onComponentConfigured(config->mInputFormat, config->mOutputFormat);
}
@@ -1126,7 +1132,8 @@
sp<AMessage> outputFormat;
uint64_t usage = 0;
{
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
inputFormat = config->mInputFormat;
outputFormat = config->mOutputFormat;
usage = config->mISConfig ? config->mISConfig->mUsage : 0;
@@ -1170,7 +1177,8 @@
}
status_t CCodec::setupInputSurface(const std::shared_ptr<InputSurfaceWrapper> &surface) {
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
config->mUsingSurface = true;
// we are now using surface - apply default color aspects to input format - as well as
@@ -1215,7 +1223,8 @@
sp<AMessage> outputFormat;
uint64_t usage = 0;
{
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
inputFormat = config->mInputFormat;
outputFormat = config->mOutputFormat;
usage = config->mISConfig ? config->mISConfig->mUsage : 0;
@@ -1291,7 +1300,8 @@
sp<AMessage> outputFormat;
status_t err2 = OK;
{
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
inputFormat = config->mInputFormat;
outputFormat = config->mOutputFormat;
if (config->mInputSurface) {
@@ -1377,7 +1387,8 @@
}
{
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
if (config->mInputSurface) {
config->mInputSurface->disconnect();
config->mInputSurface = nullptr;
@@ -1425,7 +1436,8 @@
}
if (clearInputSurfaceIfNeeded) {
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
if (config->mInputSurface) {
config->mInputSurface->disconnect();
config->mInputSurface = nullptr;
@@ -1583,7 +1595,8 @@
params->removeEntryAt(params->findEntryByName(KEY_BIT_RATE));
}
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
/**
* Handle input surface parameters
@@ -1642,7 +1655,8 @@
comp = state->comp;
}
ALOGV("request IDR");
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
std::vector<std::unique_ptr<C2Param>> params;
params.push_back(
std::make_unique<C2StreamRequestSyncFrameTuning::output>(0u, true));
@@ -1661,7 +1675,8 @@
mChannel->onInputBufferDone(frameIndex, arrayIndex);
if (arrayIndex == 0) {
// We always put no more than one buffer per work, if we use an input surface.
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
if (config->mInputSurface) {
config->mInputSurface->onInputBufferDone(frameIndex);
}
@@ -1738,7 +1753,8 @@
}
// handle configuration changes in work done
- Mutexed<Config>::Locked config(mConfig);
+ Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
+ const std::unique_ptr<Config> &config = *configLocked;
bool changed = false;
Config::Watcher<C2StreamInitDataInfo::output> initData =
config->watch<C2StreamInitDataInfo::output>();
@@ -1867,14 +1883,8 @@
mCallback->onError(UNKNOWN_ERROR, ACTION_CODE_FATAL);
}
-} // namespace android
-
-extern "C" android::CodecBase *CreateCodec() {
- return new android::CCodec;
-}
-
-// Create Codec 2.0 input surface
-extern "C" android::PersistentSurface *CreateInputSurface() {
+// static
+PersistentSurface *CCodec::CreateInputSurface() {
using namespace android;
using ::android::hardware::media::omx::V1_0::implementation::TWGraphicBufferSource;
// Attempt to create a Codec2's input surface.
@@ -1901,3 +1911,5 @@
inputSurface->getHalInterface()));
}
+} // namespace android
+
diff --git a/media/codec2/sfplugin/CCodecBufferChannel.cpp b/media/codec2/sfplugin/CCodecBufferChannel.cpp
index 2bcb7e2..3218491 100644
--- a/media/codec2/sfplugin/CCodecBufferChannel.cpp
+++ b/media/codec2/sfplugin/CCodecBufferChannel.cpp
@@ -41,12 +41,12 @@
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecConstants.h>
+#include <media/stagefright/SkipCutBuffer.h>
#include <media/MediaCodecBuffer.h>
#include <system/window.h>
#include "CCodecBufferChannel.h"
#include "Codec2Buffer.h"
-#include "SkipCutBuffer.h"
namespace android {
diff --git a/media/codec2/sfplugin/CCodecBuffers.cpp b/media/codec2/sfplugin/CCodecBuffers.cpp
index ed8b832..2a04810 100644
--- a/media/codec2/sfplugin/CCodecBuffers.cpp
+++ b/media/codec2/sfplugin/CCodecBuffers.cpp
@@ -22,6 +22,7 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/MediaCodecConstants.h>
+#include <media/stagefright/SkipCutBuffer.h>
#include "CCodecBuffers.h"
diff --git a/media/codec2/sfplugin/CCodecBuffers.h b/media/codec2/sfplugin/CCodecBuffers.h
index 2cb6b81..ad972ce 100644
--- a/media/codec2/sfplugin/CCodecBuffers.h
+++ b/media/codec2/sfplugin/CCodecBuffers.h
@@ -25,10 +25,11 @@
#include <media/MediaCodecBuffer.h>
#include "Codec2Buffer.h"
-#include "SkipCutBuffer.h"
namespace android {
+class SkipCutBuffer;
+
constexpr size_t kLinearBufferSize = 1048576;
// This can fit 4K RGBA frame, and most likely client won't need more than this.
constexpr size_t kMaxLinearBufferSize = 4096 * 2304 * 4;
diff --git a/media/codec2/sfplugin/Codec2InfoBuilder.cpp b/media/codec2/sfplugin/Codec2InfoBuilder.cpp
index 745d701..5990116 100644
--- a/media/codec2/sfplugin/Codec2InfoBuilder.cpp
+++ b/media/codec2/sfplugin/Codec2InfoBuilder.cpp
@@ -43,13 +43,12 @@
#include <codec2/hidl/client.h>
#include <cutils/native_handle.h>
#include <media/omx/1.0/WOmxNode.h>
-#include <media/stagefright/MediaCodecConstants.h>
#include <media/stagefright/foundation/ALookup.h>
#include <media/stagefright/foundation/MediaDefs.h>
#include <media/stagefright/omx/OMXUtils.h>
#include <media/stagefright/xmlparser/MediaCodecsXmlParser.h>
-
-#include "Codec2InfoBuilder.h"
+#include <media/stagefright/Codec2InfoBuilder.h>
+#include <media/stagefright/MediaCodecConstants.h>
namespace android {
diff --git a/media/codec2/sfplugin/SkipCutBuffer.cpp b/media/codec2/sfplugin/SkipCutBuffer.cpp
deleted file mode 100644
index 8d1de65..0000000
--- a/media/codec2/sfplugin/SkipCutBuffer.cpp
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "SkipCutBuffer"
-#include <utils/Log.h>
-
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/MediaBuffer.h>
-#include "SkipCutBuffer.h"
-
-namespace android {
-
-SkipCutBuffer::SkipCutBuffer(size_t skip, size_t cut, size_t num16BitChannels) {
-
- mWriteHead = 0;
- mReadHead = 0;
- mCapacity = 0;
- mCutBuffer = nullptr;
-
- if (num16BitChannels == 0 || num16BitChannels > INT32_MAX / 2) {
- ALOGW("# channels out of range: %zu, using passthrough instead", num16BitChannels);
- return;
- }
- size_t frameSize = num16BitChannels * 2;
- if (skip > INT32_MAX / frameSize || cut > INT32_MAX / frameSize
- || cut * frameSize > INT32_MAX - 4096) {
- ALOGW("out of range skip/cut: %zu/%zu, using passthrough instead",
- skip, cut);
- return;
- }
- skip *= frameSize;
- cut *= frameSize;
-
- mFrontPadding = mSkip = skip;
- mBackPadding = cut;
- mCapacity = cut + 4096;
- mCutBuffer = new (std::nothrow) char[mCapacity];
- ALOGV("skipcutbuffer %zu %zu %d", skip, cut, mCapacity);
-}
-
-SkipCutBuffer::~SkipCutBuffer() {
- delete[] mCutBuffer;
-}
-
-void SkipCutBuffer::submit(MediaBuffer *buffer) {
- if (mCutBuffer == nullptr) {
- // passthrough mode
- return;
- }
-
- int32_t offset = buffer->range_offset();
- int32_t buflen = buffer->range_length();
-
- // drop the initial data from the buffer if needed
- if (mFrontPadding > 0) {
- // still data left to drop
- int32_t to_drop = (buflen < mFrontPadding) ? buflen : mFrontPadding;
- offset += to_drop;
- buflen -= to_drop;
- buffer->set_range(offset, buflen);
- mFrontPadding -= to_drop;
- }
-
-
- // append data to cutbuffer
- char *src = ((char*) buffer->data()) + offset;
- write(src, buflen);
-
-
- // the mediabuffer is now empty. Fill it from cutbuffer, always leaving
- // at least mBackPadding bytes in the cutbuffer
- char *dst = (char*) buffer->data();
- size_t copied = read(dst, buffer->size());
- buffer->set_range(0, copied);
-}
-
-template <typename T>
-void SkipCutBuffer::submitInternal(const sp<T>& buffer) {
- if (mCutBuffer == nullptr) {
- // passthrough mode
- return;
- }
-
- int32_t offset = buffer->offset();
- int32_t buflen = buffer->size();
-
- // drop the initial data from the buffer if needed
- if (mFrontPadding > 0) {
- // still data left to drop
- int32_t to_drop = (buflen < mFrontPadding) ? buflen : mFrontPadding;
- offset += to_drop;
- buflen -= to_drop;
- buffer->setRange(offset, buflen);
- mFrontPadding -= to_drop;
- }
-
-
- // append data to cutbuffer
- char *src = (char*) buffer->data();
- write(src, buflen);
-
-
- // the mediabuffer is now empty. Fill it from cutbuffer, always leaving
- // at least mBackPadding bytes in the cutbuffer
- char *dst = (char*) buffer->base();
- size_t copied = read(dst, buffer->capacity());
- buffer->setRange(0, copied);
-}
-
-void SkipCutBuffer::submit(const sp<ABuffer>& buffer) {
- submitInternal(buffer);
-}
-
-void SkipCutBuffer::submit(const sp<MediaCodecBuffer>& buffer) {
- submitInternal(buffer);
-}
-
-void SkipCutBuffer::clear() {
- mWriteHead = mReadHead = 0;
- mFrontPadding = mSkip;
-}
-
-void SkipCutBuffer::write(const char *src, size_t num) {
- int32_t sizeused = (mWriteHead - mReadHead);
- if (sizeused < 0) sizeused += mCapacity;
-
- // Everything must fit. Make sure the buffer is a little larger than needed,
- // so there is no ambiguity as to whether mWriteHead == mReadHead means buffer
- // full or empty
- size_t available = mCapacity - sizeused - 32;
- if (available < num) {
- int32_t newcapacity = mCapacity + (num - available);
- char * newbuffer = new char[newcapacity];
- memcpy(newbuffer, mCutBuffer, mCapacity);
- delete [] mCutBuffer;
- mCapacity = newcapacity;
- mCutBuffer = newbuffer;
- ALOGV("reallocated buffer at size %d", newcapacity);
- }
-
- size_t copyfirst = (mCapacity - mWriteHead);
- if (copyfirst > num) copyfirst = num;
- if (copyfirst) {
- memcpy(mCutBuffer + mWriteHead, src, copyfirst);
- num -= copyfirst;
- src += copyfirst;
- mWriteHead += copyfirst;
- CHECK_LE(mWriteHead, mCapacity);
- if (mWriteHead == mCapacity) mWriteHead = 0;
- if (num) {
- memcpy(mCutBuffer, src, num);
- mWriteHead += num;
- }
- }
-}
-
-size_t SkipCutBuffer::read(char *dst, size_t num) {
- int32_t available = (mWriteHead - mReadHead);
- if (available < 0) available += mCapacity;
-
- available -= mBackPadding;
- if (available <=0) {
- return 0;
- }
- if (available < int32_t(num)) {
- num = available;
- }
-
- size_t copyfirst = (mCapacity - mReadHead);
- if (copyfirst > num) copyfirst = num;
- if (copyfirst) {
- memcpy(dst, mCutBuffer + mReadHead, copyfirst);
- num -= copyfirst;
- dst += copyfirst;
- mReadHead += copyfirst;
- CHECK_LE(mReadHead, mCapacity);
- if (mReadHead == mCapacity) mReadHead = 0;
- if (num) {
- memcpy(dst, mCutBuffer, num);
- mReadHead += num;
- }
- }
- return available;
-}
-
-size_t SkipCutBuffer::size() {
- int32_t available = (mWriteHead - mReadHead);
- if (available < 0) available += mCapacity;
- return available;
-}
-
-} // namespace android
diff --git a/media/codec2/sfplugin/SkipCutBuffer.h b/media/codec2/sfplugin/SkipCutBuffer.h
deleted file mode 100644
index 0fb5690..0000000
--- a/media/codec2/sfplugin/SkipCutBuffer.h
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * Copyright (C) 2012 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 SKIP_CUT_BUFFER_H_
-
-#define SKIP_CUT_BUFFER_H_
-
-#include <media/MediaCodecBuffer.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/foundation/ABuffer.h>
-
-namespace android {
-
-/**
- * utility class to cut the start and end off a stream of data in MediaBuffers
- *
- */
-class SkipCutBuffer: public RefBase {
- public:
- // 'skip' is the number of frames to skip from the beginning
- // 'cut' is the number of frames to cut from the end
- // 'num16BitChannels' is the number of channels, which are assumed to be 16 bit wide each
- SkipCutBuffer(size_t skip, size_t cut, size_t num16Channels);
-
- // Submit one MediaBuffer for skipping and cutting. This may consume all or
- // some of the data in the buffer, or it may add data to it.
- // After this, the caller should continue processing the buffer as usual.
- void submit(MediaBuffer *buffer);
- void submit(const sp<ABuffer>& buffer); // same as above, but with an ABuffer
- void submit(const sp<MediaCodecBuffer>& buffer); // same as above, but with an ABuffer
- void clear();
- size_t size(); // how many bytes are currently stored in the buffer
-
- protected:
- virtual ~SkipCutBuffer();
-
- private:
- void write(const char *src, size_t num);
- size_t read(char *dst, size_t num);
- template <typename T>
- void submitInternal(const sp<T>& buffer);
- int32_t mSkip;
- int32_t mFrontPadding;
- int32_t mBackPadding;
- int32_t mWriteHead;
- int32_t mReadHead;
- int32_t mCapacity;
- char* mCutBuffer;
- DISALLOW_EVIL_CONSTRUCTORS(SkipCutBuffer);
-};
-
-} // namespace android
-
-#endif // OMX_CODEC_H_
diff --git a/media/codec2/sfplugin/CCodec.h b/media/codec2/sfplugin/include/media/stagefright/CCodec.h
similarity index 97%
rename from media/codec2/sfplugin/CCodec.h
rename to media/codec2/sfplugin/include/media/stagefright/CCodec.h
index b0b3c4f..30dc945 100644
--- a/media/codec2/sfplugin/CCodec.h
+++ b/media/codec2/sfplugin/include/media/stagefright/CCodec.h
@@ -36,12 +36,11 @@
#include <hardware/gralloc.h>
#include <nativebase/nativebase.h>
-#include "CCodecConfig.h"
-
namespace android {
class CCodecBufferChannel;
class InputSurfaceWrapper;
+struct CCodecConfig;
struct MediaCodecInfo;
class CCodec : public CodecBase {
@@ -69,6 +68,8 @@
void onWorkDone(std::list<std::unique_ptr<C2Work>> &workItems);
void onInputBufferDone(uint64_t frameIndex, size_t arrayIndex);
+ static PersistentSurface *CreateInputSurface();
+
protected:
virtual ~CCodec();
@@ -173,7 +174,7 @@
Mutexed<NamedTimePoint> mDeadline;
typedef CCodecConfig Config;
- Mutexed<Config> mConfig;
+ Mutexed<std::unique_ptr<CCodecConfig>> mConfig;
Mutexed<std::list<std::unique_ptr<C2Work>>> mWorkDoneQueue;
friend class CCodecCallbackImpl;
diff --git a/media/codec2/sfplugin/Codec2InfoBuilder.h b/media/codec2/sfplugin/include/media/stagefright/Codec2InfoBuilder.h
similarity index 100%
rename from media/codec2/sfplugin/Codec2InfoBuilder.h
rename to media/codec2/sfplugin/include/media/stagefright/Codec2InfoBuilder.h
diff --git a/media/codec2/sfplugin/utils/Android.bp b/media/codec2/sfplugin/utils/Android.bp
index 8c8f025..205abdc 100644
--- a/media/codec2/sfplugin/utils/Android.bp
+++ b/media/codec2/sfplugin/utils/Android.bp
@@ -1,6 +1,7 @@
cc_library_shared {
name: "libsfplugin_ccodec_utils",
vendor_available: true,
+ double_loadable: true,
srcs: [
"Codec2BufferUtils.cpp",
diff --git a/media/codec2/vndk/Android.bp b/media/codec2/vndk/Android.bp
index 071bb74..a1f145b 100644
--- a/media/codec2/vndk/Android.bp
+++ b/media/codec2/vndk/Android.bp
@@ -14,6 +14,8 @@
cc_library_shared {
name: "libcodec2_vndk",
vendor_available: true,
+ // TODO: b/147147883
+ double_loadable: true,
srcs: [
"C2AllocatorBlob.cpp",
@@ -68,6 +70,8 @@
"libutils",
],
+ tidy: false, // b/146435095, clang-tidy segmentation fault
+
cflags: [
"-Werror",
"-Wall",
diff --git a/media/extractors/aac/Android.bp b/media/extractors/aac/Android.bp
index a58167a..53b394f 100644
--- a/media/extractors/aac/Android.bp
+++ b/media/extractors/aac/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["AACExtractor.cpp"],
diff --git a/media/extractors/amr/Android.bp b/media/extractors/amr/Android.bp
index 4bd933d..cd76062 100644
--- a/media/extractors/amr/Android.bp
+++ b/media/extractors/amr/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["AMRExtractor.cpp"],
diff --git a/media/extractors/flac/Android.bp b/media/extractors/flac/Android.bp
index 3a3d051..c669b34 100644
--- a/media/extractors/flac/Android.bp
+++ b/media/extractors/flac/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["FLACExtractor.cpp"],
diff --git a/media/extractors/midi/Android.bp b/media/extractors/midi/Android.bp
index d36cb49..40c91e7 100644
--- a/media/extractors/midi/Android.bp
+++ b/media/extractors/midi/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["MidiExtractor.cpp"],
diff --git a/media/extractors/mkv/Android.bp b/media/extractors/mkv/Android.bp
index 38821fd..942c88a 100644
--- a/media/extractors/mkv/Android.bp
+++ b/media/extractors/mkv/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["MatroskaExtractor.cpp"],
diff --git a/media/extractors/mkv/MatroskaExtractor.cpp b/media/extractors/mkv/MatroskaExtractor.cpp
index 23022e4..044c4d0 100644
--- a/media/extractors/mkv/MatroskaExtractor.cpp
+++ b/media/extractors/mkv/MatroskaExtractor.cpp
@@ -22,7 +22,7 @@
#include "MatroskaExtractor.h"
#include "common/webmids.h"
-#include <media/DataSourceBase.h>
+#include <media/stagefright/DataSourceBase.h>
#include <media/ExtractorUtils.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AUtils.h>
diff --git a/media/extractors/mp3/Android.bp b/media/extractors/mp3/Android.bp
index 4e2f248..6f02b0f 100644
--- a/media/extractors/mp3/Android.bp
+++ b/media/extractors/mp3/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: [
"MP3Extractor.cpp",
diff --git a/media/extractors/mp4/Android.bp b/media/extractors/mp4/Android.bp
index 1b308aa..d9f11fc 100644
--- a/media/extractors/mp4/Android.bp
+++ b/media/extractors/mp4/Android.bp
@@ -35,7 +35,7 @@
compile_multilib: "first",
}
-cc_library_shared {
+cc_library {
name: "libmp4extractor",
diff --git a/media/extractors/mp4/MPEG4Extractor.cpp b/media/extractors/mp4/MPEG4Extractor.cpp
index 6f85960..9e0bc96 100755
--- a/media/extractors/mp4/MPEG4Extractor.cpp
+++ b/media/extractors/mp4/MPEG4Extractor.cpp
@@ -33,7 +33,7 @@
#include "ItemTable.h"
#include "include/ESDS.h"
-#include <media/DataSourceBase.h>
+#include <media/stagefright/DataSourceBase.h>
#include <media/ExtractorUtils.h>
#include <media/stagefright/foundation/ABitReader.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -83,7 +83,8 @@
const Trex *trex,
off64_t firstMoofOffset,
const sp<ItemTable> &itemTable,
- uint64_t elstShiftStartTicks);
+ uint64_t elstShiftStartTicks,
+ uint64_t elstInitialEmptyEditTicks);
virtual status_t init();
virtual media_status_t start();
@@ -151,6 +152,7 @@
// Start offset from composition time to presentation time.
// Support shift only for video tracks through mElstShiftStartTicks for now.
uint64_t mElstShiftStartTicks;
+ uint64_t mElstInitialEmptyEditTicks;
size_t parseNALSize(const uint8_t *data) const;
status_t parseChunk(off64_t *offset);
@@ -484,12 +486,11 @@
int64_t duration;
int32_t samplerate;
// Only for audio track.
- if (track->has_elst && mHeaderTimescale != 0 &&
- AMediaFormat_getInt64(track->meta, AMEDIAFORMAT_KEY_DURATION, &duration) &&
- AMediaFormat_getInt32(track->meta, AMEDIAFORMAT_KEY_SAMPLE_RATE, &samplerate)) {
-
+ if (track->elst_needs_processing && mHeaderTimescale != 0 &&
+ AMediaFormat_getInt64(track->meta, AMEDIAFORMAT_KEY_DURATION, &duration) &&
+ AMediaFormat_getInt32(track->meta, AMEDIAFORMAT_KEY_SAMPLE_RATE, &samplerate)) {
// Elst has to be processed only the first time this function is called.
- track->has_elst = false;
+ track->elst_needs_processing = false;
if (track->elst_segment_duration > INT64_MAX) {
return;
@@ -1098,10 +1099,11 @@
track_b->sampleTable = mLastTrack->sampleTable;
track_b->includes_expensive_metadata = mLastTrack->includes_expensive_metadata;
track_b->skipTrack = mLastTrack->skipTrack;
- track_b->has_elst = mLastTrack->has_elst;
+ track_b->elst_needs_processing = mLastTrack->elst_needs_processing;
track_b->elst_media_time = mLastTrack->elst_media_time;
track_b->elst_segment_duration = mLastTrack->elst_segment_duration;
- track_b->elstShiftStartTicks = mLastTrack->elstShiftStartTicks;
+ track_b->elst_shift_start_ticks = mLastTrack->elst_shift_start_ticks;
+ track_b->elst_initial_empty_edit_ticks = mLastTrack->elst_initial_empty_edit_ticks;
track_b->subsample_encryption = mLastTrack->subsample_encryption;
track_b->mTx3gBuffer = mLastTrack->mTx3gBuffer;
@@ -1204,39 +1206,86 @@
return ERROR_IO;
}
- if (entry_count != 1) {
- // we only support a single entry at the moment, for gapless playback
- // or start offset
+ if (entry_count > 2) {
+ /* We support a single entry for gapless playback or negating offset for
+ * reordering B frames, two entries (empty edit) for start offset at the moment.
+ */
ALOGW("ignoring edit list with %d entries", entry_count);
} else {
off64_t entriesoffset = data_offset + 8;
uint64_t segment_duration;
int64_t media_time;
-
- if (version == 1) {
- if (!mDataSource->getUInt64(entriesoffset, &segment_duration) ||
- !mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) {
- return ERROR_IO;
- }
- } else if (version == 0) {
- uint32_t sd;
- int32_t mt;
- if (!mDataSource->getUInt32(entriesoffset, &sd) ||
+ uint64_t empty_edit_ticks = 0;
+ bool empty_edit_present = false;
+ for (int i = 0; i < entry_count; ++i) {
+ switch (version) {
+ case 0: {
+ uint32_t sd;
+ int32_t mt;
+ if (!mDataSource->getUInt32(entriesoffset, &sd) ||
!mDataSource->getUInt32(entriesoffset + 4, (uint32_t*)&mt)) {
- return ERROR_IO;
+ return ERROR_IO;
+ }
+ segment_duration = sd;
+ media_time = mt;
+ // 4(segment duration) + 4(media time) + 4(media rate)
+ entriesoffset += 12;
+ break;
}
- segment_duration = sd;
- media_time = mt;
- } else {
- return ERROR_IO;
+ case 1: {
+ if (!mDataSource->getUInt64(entriesoffset, &segment_duration) ||
+ !mDataSource->getUInt64(entriesoffset + 8, (uint64_t*)&media_time)) {
+ return ERROR_IO;
+ }
+ // 8(segment duration) + 8(media time) + 4(media rate)
+ entriesoffset += 20;
+ break;
+ }
+ default:
+ return ERROR_IO;
+ break;
+ }
+ // Empty edit entry would have to be first entry.
+ if (media_time == -1 && i == 0) {
+ int64_t durationUs;
+ if (AMediaFormat_getInt64(mFileMetaData, AMEDIAFORMAT_KEY_DURATION,
+ &durationUs)) {
+ empty_edit_ticks = segment_duration;
+ ALOGV("initial empty edit ticks: %" PRIu64, empty_edit_ticks);
+ empty_edit_present = true;
+ }
+ }
+ // Process second entry only when the first entry was an empty edit entry.
+ if (empty_edit_present && i == 1) {
+ int64_t durationUs;
+ if (AMediaFormat_getInt64(mLastTrack->meta, AMEDIAFORMAT_KEY_DURATION,
+ &durationUs) &&
+ mHeaderTimescale != 0) {
+ // Support only segment_duration<=track_duration and media_time==0 case.
+ uint64_t segmentDurationUs =
+ segment_duration * 1000000 / mHeaderTimescale;
+ if (segmentDurationUs == 0 || segmentDurationUs > durationUs ||
+ media_time != 0) {
+ ALOGW("for now, unsupported second entry in empty edit list");
+ }
+ }
+ }
}
-
// save these for later, because the elst atom might precede
// the atoms that actually gives us the duration and sample rate
// needed to calculate the padding and delay values
- mLastTrack->has_elst = true;
- mLastTrack->elst_media_time = media_time;
- mLastTrack->elst_segment_duration = segment_duration;
+ mLastTrack->elst_needs_processing = true;
+ if (empty_edit_present) {
+ /* In movie header timescale, and needs to be converted to media timescale once
+ * we get that from a track's 'mdhd' atom, which at times come after 'elst'.
+ */
+ mLastTrack->elst_initial_empty_edit_ticks = empty_edit_ticks;
+ } else {
+ mLastTrack->elst_media_time = media_time;
+ mLastTrack->elst_segment_duration = segment_duration;
+ ALOGV("segment_duration: %" PRIu64 " media_time: %" PRId64, segment_duration,
+ media_time);
+ }
}
break;
}
@@ -1631,8 +1680,12 @@
{
*offset += chunk_size;
- if (mLastTrack == NULL)
+ // the absolute minimum size of a compliant mett box is 11 bytes:
+ // 6 byte reserved, 2 byte index, null byte, one char mime_format, null byte
+ // The resulting mime_format would be invalid at that size though.
+ if (mLastTrack == NULL || chunk_data_size < 11) {
return ERROR_MALFORMED;
+ }
auto buffer = heapbuffer<uint8_t>(chunk_data_size);
if (buffer.get() == NULL) {
@@ -1644,10 +1697,24 @@
return ERROR_IO;
}
+ // ISO-14496-12:
+ // int8 reserved[6]; // should be all zeroes
+ // int16_t data_reference_index;
+ // char content_encoding[]; // null terminated, optional (= just the null byte)
+ // char mime_format[]; // null terminated, mandatory
+ // optional other boxes
+ //
+ // API < 29:
+ // char mime_format[]; // null terminated
+ //
+ // API >= 29
+ // char mime_format[]; // null terminated
+ // char mime_format[]; // null terminated
+
// Prior to API 29, the metadata track was not compliant with ISO/IEC
// 14496-12-2015. This led to some ISO-compliant parsers failing to read the
// metatrack. As of API 29 and onwards, a change was made to metadata track to
- // make it compliant with the standard. The workaround is to write the
+ // make it somewhat compatible with the standard. The workaround is to write the
// null-terminated mime_format string twice. This allows compliant parsers to
// read the missing reserved, data_reference_index, and content_encoding fields
// from the first mime_type string. The actual mime_format field would then be
@@ -1656,27 +1723,27 @@
// as it would only read the first null-terminated mime_format string. To enable
// reading metadata tracks generated from both the non-compliant and compliant
// formats, a check needs to be done to see which format is used.
- int null_pos = 0;
- const unsigned char *str = buffer.get();
- while (null_pos < chunk_data_size) {
- if (*(str + null_pos) == '\0') {
- break;
- }
- ++null_pos;
- }
+ const char *str = (const char*) buffer.get();
+ size_t string_length = strnlen(str, chunk_data_size);
- if (null_pos == chunk_data_size - 1) {
- // This is not a standard ompliant metadata track.
- String8 mimeFormat((const char *)(buffer.get()), chunk_data_size);
- AMediaFormat_setString(mLastTrack->meta,
- AMEDIAFORMAT_KEY_MIME, mimeFormat.string());
+ if (string_length == chunk_data_size - 1) {
+ // This is likely a pre API 29 file, since it's a single null terminated
+ // string filling the entire box.
+ AMediaFormat_setString(mLastTrack->meta, AMEDIAFORMAT_KEY_MIME, str);
} else {
- // This is a standard compliant metadata track.
- String8 contentEncoding((const char *)(buffer.get() + 8));
- String8 mimeFormat((const char *)(buffer.get() + 8 + contentEncoding.size() + 1),
- chunk_data_size - 8 - contentEncoding.size() - 1);
- AMediaFormat_setString(mLastTrack->meta,
- AMEDIAFORMAT_KEY_MIME, mimeFormat.string());
+ // This might be a fully compliant metadata track, a "double mime" compatibility
+ // track, or anything else, including a single non-terminated string, so we need
+ // to determine the length of each string we want to parse out of the box.
+ size_t encoding_length = strnlen(str + 8, chunk_data_size - 8);
+ if (encoding_length + 8 >= chunk_data_size - 2) {
+ // the encoding extends to the end of the box, so there's no mime_format
+ return ERROR_MALFORMED;
+ }
+ String8 contentEncoding(str + 8, encoding_length);
+ String8 mimeFormat(str + 8 + encoding_length + 1,
+ chunk_data_size - 8 - encoding_length - 1);
+ AMediaFormat_setString(mLastTrack->meta,
+ AMEDIAFORMAT_KEY_MIME, mimeFormat.string());
}
break;
}
@@ -4257,15 +4324,28 @@
}
}
- if (track->has_elst and !strncasecmp("video/", mime, 6) and track->elst_media_time > 0) {
- track->elstShiftStartTicks = track->elst_media_time;
- ALOGV("video track->elstShiftStartTicks :%" PRIu64, track->elstShiftStartTicks);
+ // media_time is in media timescale as are STTS/CTTS entries.
+ track->elst_shift_start_ticks = track->elst_media_time;
+ ALOGV("track->elst_shift_start_ticks :%" PRIu64, track->elst_shift_start_ticks);
+ if (mHeaderTimescale != 0) {
+ // Convert empty_edit_ticks from movie timescale to media timescale.
+ uint64_t elst_initial_empty_edit_ticks_mul = 0, elst_initial_empty_edit_ticks_add = 0;
+ if (__builtin_mul_overflow(track->elst_initial_empty_edit_ticks, track->timescale,
+ &elst_initial_empty_edit_ticks_mul) ||
+ __builtin_add_overflow(elst_initial_empty_edit_ticks_mul, (mHeaderTimescale / 2),
+ &elst_initial_empty_edit_ticks_add)) {
+ ALOGE("track->elst_initial_empty_edit_ticks overflow");
+ return nullptr;
+ }
+ track->elst_initial_empty_edit_ticks = elst_initial_empty_edit_ticks_add / mHeaderTimescale;
+ ALOGV("track->elst_initial_empty_edit_ticks :%" PRIu64,
+ track->elst_initial_empty_edit_ticks);
}
- MPEG4Source *source = new MPEG4Source(
- track->meta, mDataSource, track->timescale, track->sampleTable,
- mSidxEntries, trex, mMoofOffset, itemTable,
- track->elstShiftStartTicks);
+ MPEG4Source* source =
+ new MPEG4Source(track->meta, mDataSource, track->timescale, track->sampleTable,
+ mSidxEntries, trex, mMoofOffset, itemTable,
+ track->elst_shift_start_ticks, track->elst_initial_empty_edit_ticks);
if (source->init() != OK) {
delete source;
return NULL;
@@ -4758,7 +4838,8 @@
const Trex *trex,
off64_t firstMoofOffset,
const sp<ItemTable> &itemTable,
- uint64_t elstShiftStartTicks)
+ uint64_t elstShiftStartTicks,
+ uint64_t elstInitialEmptyEditTicks)
: mFormat(format),
mDataSource(dataSource),
mTimescale(timeScale),
@@ -4788,7 +4869,8 @@
mSrcBuffer(NULL),
mIsHeif(itemTable != NULL),
mItemTable(itemTable),
- mElstShiftStartTicks(elstShiftStartTicks) {
+ mElstShiftStartTicks(elstShiftStartTicks),
+ mElstInitialEmptyEditTicks(elstInitialEmptyEditTicks) {
memset(&mTrackFragmentHeaderInfo, 0, sizeof(mTrackFragmentHeaderInfo));
@@ -4907,35 +4989,14 @@
}
CHECK(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_TRACK_ID, &mTrackId));
-
}
status_t MPEG4Source::init() {
- status_t err = OK;
- const char *mime;
- CHECK(AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime));
if (mFirstMoofOffset != 0) {
off64_t offset = mFirstMoofOffset;
- err = parseChunk(&offset);
- if(err == OK && !strncasecmp("video/", mime, 6)
- && !mCurrentSamples.isEmpty()) {
- // Start offset should be less or equal to composition time of first sample.
- // ISO : sample_composition_time_offset, version 0 (unsigned) for major brands.
- mElstShiftStartTicks = std::min(mElstShiftStartTicks,
- (uint64_t)(*mCurrentSamples.begin()).compositionOffset);
- }
- return err;
+ return parseChunk(&offset);
}
-
- if (!strncasecmp("video/", mime, 6)) {
- uint64_t firstSampleCTS = 0;
- err = mSampleTable->getMetaDataForSample(0, NULL, NULL, &firstSampleCTS);
- // Start offset should be less or equal to composition time of first sample.
- // Composition time stamp of first sample cannot be negative.
- mElstShiftStartTicks = std::min(mElstShiftStartTicks, firstSampleCTS);
- }
-
- return err;
+ return OK;
}
MPEG4Source::~MPEG4Source() {
@@ -5783,6 +5844,7 @@
int64_t seekTimeUs;
ReadOptions::SeekMode mode;
+
if (options && options->getSeekTo(&seekTimeUs, &mode)) {
if (mIsHeif) {
@@ -5824,6 +5886,8 @@
}
if( mode != ReadOptions::SEEK_FRAME_INDEX) {
seekTimeUs += ((long double)mElstShiftStartTicks * 1000000) / mTimescale;
+ ALOGV("shifted seekTimeUs :%" PRId64 ", mElstShiftStartTicks:%" PRIu64, seekTimeUs,
+ mElstShiftStartTicks);
}
uint32_t sampleIndex;
@@ -5898,7 +5962,8 @@
off64_t offset = 0;
size_t size = 0;
- uint64_t cts, stts;
+ int64_t cts;
+ uint64_t stts;
bool isSyncSample;
bool newBuffer = false;
if (mBuffer == NULL) {
@@ -5906,14 +5971,15 @@
status_t err;
if (!mIsHeif) {
- err = mSampleTable->getMetaDataForSample(
- mCurrentSampleIndex, &offset, &size, &cts, &isSyncSample, &stts);
+ err = mSampleTable->getMetaDataForSample(mCurrentSampleIndex, &offset, &size,
+ (uint64_t*)&cts, &isSyncSample, &stts);
if(err == OK) {
- /* Composition Time Stamp cannot be negative. Some files have video Sample
- * Time(STTS)delta with zero value(b/117402420). Hence subtract only
- * min(cts, mElstShiftStartTicks), so that audio tracks can be played.
- */
- cts -= std::min(cts, mElstShiftStartTicks);
+ if (mElstInitialEmptyEditTicks > 0) {
+ cts += mElstInitialEmptyEditTicks;
+ } else {
+ // cts can be negative. for example, initial audio samples for gapless playback.
+ cts -= (int64_t)mElstShiftStartTicks;
+ }
}
} else {
@@ -6254,7 +6320,7 @@
off64_t offset = 0;
size_t size = 0;
- uint64_t cts = 0;
+ int64_t cts = 0;
bool isSyncSample = false;
bool newBuffer = false;
if (mBuffer == NULL || mCurrentSampleIndex >= mCurrentSamples.size()) {
@@ -6286,11 +6352,13 @@
offset = smpl->offset;
size = smpl->size;
cts = mCurrentTime + smpl->compositionOffset;
- /* Composition Time Stamp cannot be negative. Some files have video Sample
- * Time(STTS)delta with zero value(b/117402420). Hence subtract only
- * min(cts, mElstShiftStartTicks), so that audio tracks can be played.
- */
- cts -= std::min(cts, mElstShiftStartTicks);
+
+ if (mElstInitialEmptyEditTicks > 0) {
+ cts += mElstInitialEmptyEditTicks;
+ } else {
+ // cts can be negative. for example, initial audio samples for gapless playback.
+ cts -= (int64_t)mElstShiftStartTicks;
+ }
mCurrentTime += smpl->duration;
isSyncSample = (mCurrentSampleIndex == 0);
diff --git a/media/extractors/mp4/MPEG4Extractor.h b/media/extractors/mp4/MPEG4Extractor.h
index fcddbb8..53ec6bc 100644
--- a/media/extractors/mp4/MPEG4Extractor.h
+++ b/media/extractors/mp4/MPEG4Extractor.h
@@ -82,14 +82,16 @@
sp<SampleTable> sampleTable;
bool includes_expensive_metadata;
bool skipTrack;
- bool has_elst;
+ bool elst_needs_processing;
/* signed int, ISO Spec allows media_time = -1 for other use cases.
* but we don't support empty edits for now.
*/
int64_t elst_media_time;
uint64_t elst_segment_duration;
- // unsigned int, shift start offset only when media_time > 0.
- uint64_t elstShiftStartTicks;
+ // Shift start offset only when media_time > 0.
+ uint64_t elst_shift_start_ticks;
+ // Initial start offset, empty edit list entry.
+ uint64_t elst_initial_empty_edit_ticks;
bool subsample_encryption;
uint8_t *mTx3gBuffer;
@@ -102,9 +104,11 @@
timescale = 0;
includes_expensive_metadata = false;
skipTrack = false;
- has_elst = false;
+ elst_needs_processing = false;
elst_media_time = 0;
- elstShiftStartTicks = 0;
+ elst_segment_duration = 0;
+ elst_shift_start_ticks = 0;
+ elst_initial_empty_edit_ticks = 0;
subsample_encryption = false;
mTx3gBuffer = NULL;
mTx3gSize = mTx3gFilled = 0;
diff --git a/media/extractors/mpeg2/Android.bp b/media/extractors/mpeg2/Android.bp
index 8638cdc..8d9145d 100644
--- a/media/extractors/mpeg2/Android.bp
+++ b/media/extractors/mpeg2/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: [
"ExtractorBundle.cpp",
@@ -71,4 +71,11 @@
"com.android.media",
"test_com.android.media",
],
+
+ static: {
+ apex_available: [
+ // Needed for unit tests
+ "//apex_available:platform",
+ ],
+ },
}
diff --git a/media/extractors/mpeg2/MPEG2TSExtractor.cpp b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
index 50ce657..f4643c5 100644
--- a/media/extractors/mpeg2/MPEG2TSExtractor.cpp
+++ b/media/extractors/mpeg2/MPEG2TSExtractor.cpp
@@ -24,13 +24,13 @@
#include "MPEG2TSExtractor.h"
-#include <media/DataSourceBase.h>
#include <media/IStreamSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
#include <media/stagefright/foundation/AUtils.h>
#include <media/stagefright/foundation/MediaKeys.h>
+#include <media/stagefright/DataSourceBase.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
diff --git a/media/extractors/ogg/Android.bp b/media/extractors/ogg/Android.bp
index 604ec59..e661b5d 100644
--- a/media/extractors/ogg/Android.bp
+++ b/media/extractors/ogg/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["OggExtractor.cpp"],
diff --git a/media/extractors/ogg/OggExtractor.cpp b/media/extractors/ogg/OggExtractor.cpp
index bfb2deb..828bcd6 100644
--- a/media/extractors/ogg/OggExtractor.cpp
+++ b/media/extractors/ogg/OggExtractor.cpp
@@ -22,7 +22,7 @@
#include <cutils/properties.h>
#include <utils/Vector.h>
-#include <media/DataSourceBase.h>
+#include <media/stagefright/DataSourceBase.h>
#include <media/ExtractorUtils.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
diff --git a/media/extractors/wav/Android.bp b/media/extractors/wav/Android.bp
index 7e89271..51e3c31 100644
--- a/media/extractors/wav/Android.bp
+++ b/media/extractors/wav/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
srcs: ["WAVExtractor.cpp"],
diff --git a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h b/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
deleted file mode 100644
index 8eb70b1..0000000
--- a/media/libaaudio/examples/loopback/src/LoopbackAnalyzer.h
+++ /dev/null
@@ -1,1114 +0,0 @@
-/*
- * 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.
- */
-
-/**
- * Tools for measuring latency and for detecting glitches.
- * These classes are pure math and can be used with any audio system.
- */
-
-#ifndef AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
-#define AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H
-
-#include <algorithm>
-#include <assert.h>
-#include <cctype>
-#include <math.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
-
-#include <audio_utils/sndfile.h>
-
-// Tag for machine readable results as property = value pairs
-#define LOOPBACK_RESULT_TAG "RESULT: "
-
-constexpr int32_t kDefaultSampleRate = 48000;
-constexpr int32_t kMillisPerSecond = 1000;
-constexpr int32_t kMinLatencyMillis = 4; // arbitrary and very low
-constexpr int32_t kMaxLatencyMillis = 400; // arbitrary and generous
-constexpr double kMaxEchoGain = 10.0; // based on experiments, otherwise too noisy
-constexpr double kMinimumConfidence = 0.5;
-
-static void printAudioScope(float sample) {
- const int maxStars = 80; // arbitrary, fits on one line
- char c = '*';
- if (sample < -1.0) {
- sample = -1.0;
- c = '$';
- } else if (sample > 1.0) {
- sample = 1.0;
- c = '$';
- }
- int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
- for (int i = 0; i < numSpaces; i++) {
- putchar(' ');
- }
- printf("%c\n", c);
-}
-
-/*
-
-FIR filter designed with
-http://t-filter.appspot.com
-
-sampling frequency: 48000 Hz
-
-* 0 Hz - 8000 Hz
- gain = 1.2
- desired ripple = 5 dB
- actual ripple = 5.595266169703693 dB
-
-* 12000 Hz - 20000 Hz
- gain = 0
- desired attenuation = -40 dB
- actual attenuation = -37.58691566571914 dB
-
-*/
-
-#define FILTER_TAP_NUM 11
-
-static const float sFilterTaps8000[FILTER_TAP_NUM] = {
- -0.05944219353343189f,
- -0.07303434839503208f,
- -0.037690487672689066f,
- 0.1870480506596512f,
- 0.3910337357836833f,
- 0.5333672385425637f,
- 0.3910337357836833f,
- 0.1870480506596512f,
- -0.037690487672689066f,
- -0.07303434839503208f,
- -0.05944219353343189f
-};
-
-class LowPassFilter {
-public:
-
- /*
- * Filter one input sample.
- * @return filtered output
- */
- float filter(float input) {
- float output = 0.0f;
- mX[mCursor] = input;
- // Index backwards over x.
- int xIndex = mCursor + FILTER_TAP_NUM;
- // Write twice so we avoid having to wrap in the middle of the convolution.
- mX[xIndex] = input;
- for (int i = 0; i < FILTER_TAP_NUM; i++) {
- output += sFilterTaps8000[i] * mX[xIndex--];
- }
- if (++mCursor >= FILTER_TAP_NUM) {
- mCursor = 0;
- }
- return output;
- }
-
- /**
- * @return true if PASSED
- */
- bool test() {
- // Measure the impulse of the filter at different phases so we exercise
- // all the wraparound cases in the FIR.
- for (int offset = 0; offset < (FILTER_TAP_NUM * 2); offset++ ) {
- // printf("LowPassFilter: cursor = %d\n", mCursor);
- // Offset by one each time.
- if (filter(0.0f) != 0.0f) {
- printf("ERROR: filter should return 0.0 before impulse response\n");
- return false;
- }
- for (int i = 0; i < FILTER_TAP_NUM; i++) {
- float output = filter((i == 0) ? 1.0f : 0.0f); // impulse
- if (output != sFilterTaps8000[i]) {
- printf("ERROR: filter should return impulse response\n");
- return false;
- }
- }
- for (int i = 0; i < FILTER_TAP_NUM; i++) {
- if (filter(0.0f) != 0.0f) {
- printf("ERROR: filter should return 0.0 after impulse response\n");
- return false;
- }
- }
- }
- return true;
- }
-
-private:
- float mX[FILTER_TAP_NUM * 2]{}; // twice as big as needed to avoid wrapping
- int32_t mCursor = 0;
-};
-
-// A narrow impulse seems to have better immunity against over estimating the
-// latency due to detecting subharmonics by the auto-correlator.
-static const float s_Impulse[] = {
- 0.0f, 0.0f, 0.0f, 0.0f, 0.3f, // silence on each side of the impulse
- 0.99f, 0.0f, -0.99f, // bipolar with one zero crossing in middle
- -0.3f, 0.0f, 0.0f, 0.0f, 0.0f
-};
-
-constexpr int32_t kImpulseSizeInFrames = (int32_t)(sizeof(s_Impulse) / sizeof(s_Impulse[0]));
-
-class PseudoRandom {
-public:
- PseudoRandom() {}
- PseudoRandom(int64_t seed)
- : mSeed(seed)
- {}
-
- /**
- * Returns the next random double from -1.0 to 1.0
- *
- * @return value from -1.0 to 1.0
- */
- double nextRandomDouble() {
- return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
- }
-
- /** Calculate random 32 bit number using linear-congruential method. */
- int32_t nextRandomInteger() {
- // Use values for 64-bit sequence from MMIX by Donald Knuth.
- mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
- return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
- }
-
-private:
- int64_t mSeed = 99887766;
-};
-
-
-typedef struct LatencyReport_s {
- double latencyInFrames;
- double confidence;
-} LatencyReport;
-
-static double calculateCorrelation(const float *a,
- const float *b,
- int windowSize)
-{
- double correlation = 0.0;
- double sumProducts = 0.0;
- double sumSquares = 0.0;
-
- // Correlate a against b.
- for (int i = 0; i < windowSize; i++) {
- float s1 = a[i];
- float s2 = b[i];
- // Use a normalized cross-correlation.
- sumProducts += s1 * s2;
- sumSquares += ((s1 * s1) + (s2 * s2));
- }
-
- if (sumSquares >= 0.00000001) {
- correlation = (float) (2.0 * sumProducts / sumSquares);
- }
- return correlation;
-}
-
-static int measureLatencyFromEchos(const float *data,
- int32_t numFloats,
- int32_t sampleRate,
- LatencyReport *report) {
- // Allocate results array
- const int minReasonableLatencyFrames = sampleRate * kMinLatencyMillis / kMillisPerSecond;
- const int maxReasonableLatencyFrames = sampleRate * kMaxLatencyMillis / kMillisPerSecond;
- int32_t maxCorrelationSize = maxReasonableLatencyFrames * 3;
- int numCorrelations = std::min(numFloats, maxCorrelationSize);
- float *correlations = new float[numCorrelations]{};
- float *harmonicSums = new float[numCorrelations]{};
-
- // Perform sliding auto-correlation.
- // Skip first frames to avoid huge peak at zero offset.
- for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
- int32_t remaining = numFloats - i;
- float correlation = (float) calculateCorrelation(&data[i], data, remaining);
- correlations[i] = correlation;
- // printf("correlation[%d] = %f\n", ic, correlation);
- }
-
- // Apply a technique similar to Harmonic Product Spectrum Analysis to find echo fundamental.
- // Add higher harmonics mapped onto lower harmonics. This reinforces the "fundamental" echo.
- const int numEchoes = 8;
- for (int partial = 1; partial < numEchoes; partial++) {
- for (int i = minReasonableLatencyFrames; i < numCorrelations; i++) {
- harmonicSums[i / partial] += correlations[i] / partial;
- }
- }
-
- // Find highest peak in correlation array.
- float maxCorrelation = 0.0;
- int peakIndex = 0;
- for (int i = 0; i < numCorrelations; i++) {
- if (harmonicSums[i] > maxCorrelation) {
- maxCorrelation = harmonicSums[i];
- peakIndex = i;
- // printf("maxCorrelation = %f at %d\n", maxCorrelation, peakIndex);
- }
- }
- report->latencyInFrames = peakIndex;
-/*
- {
- int32_t topPeak = peakIndex * 7 / 2;
- for (int i = 0; i < topPeak; i++) {
- float sample = harmonicSums[i];
- printf("%4d: %7.5f ", i, sample);
- printAudioScope(sample);
- }
- }
-*/
-
- // Calculate confidence.
- if (maxCorrelation < 0.001) {
- report->confidence = 0.0;
- } else {
- // Compare peak to average value around peak.
- int32_t numSamples = std::min(numCorrelations, peakIndex * 2);
- if (numSamples <= 0) {
- report->confidence = 0.0;
- } else {
- double sum = 0.0;
- for (int i = 0; i < numSamples; i++) {
- sum += harmonicSums[i];
- }
- const double average = sum / numSamples;
- const double ratio = average / maxCorrelation; // will be < 1.0
- report->confidence = 1.0 - sqrt(ratio);
- }
- }
-
- delete[] correlations;
- delete[] harmonicSums;
- return 0;
-}
-
-class AudioRecording
-{
-public:
- AudioRecording() {
- }
- ~AudioRecording() {
- delete[] mData;
- }
-
- void allocate(int maxFrames) {
- delete[] mData;
- mData = new float[maxFrames];
- mMaxFrames = maxFrames;
- }
-
- // Write SHORT data from the first channel.
- int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
- // stop at end of buffer
- if ((mFrameCounter + numFrames) > mMaxFrames) {
- numFrames = mMaxFrames - mFrameCounter;
- }
- for (int i = 0; i < numFrames; i++) {
- mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
- }
- return numFrames;
- }
-
- // Write FLOAT data from the first channel.
- int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
- // stop at end of buffer
- if ((mFrameCounter + numFrames) > mMaxFrames) {
- numFrames = mMaxFrames - mFrameCounter;
- }
- for (int i = 0; i < numFrames; i++) {
- mData[mFrameCounter++] = inputData[i * inputChannelCount];
- }
- return numFrames;
- }
-
- int32_t size() {
- return mFrameCounter;
- }
-
- float *getData() {
- return mData;
- }
-
- void setSampleRate(int32_t sampleRate) {
- mSampleRate = sampleRate;
- }
-
- int32_t getSampleRate() {
- return mSampleRate;
- }
-
- int save(const char *fileName, bool writeShorts = true) {
- SNDFILE *sndFile = nullptr;
- int written = 0;
- SF_INFO info = {
- .frames = mFrameCounter,
- .samplerate = mSampleRate,
- .channels = 1,
- .format = SF_FORMAT_WAV | (writeShorts ? SF_FORMAT_PCM_16 : SF_FORMAT_FLOAT)
- };
-
- sndFile = sf_open(fileName, SFM_WRITE, &info);
- if (sndFile == nullptr) {
- printf("AudioRecording::save(%s) failed to open file\n", fileName);
- return -errno;
- }
-
- written = sf_writef_float(sndFile, mData, mFrameCounter);
-
- sf_close(sndFile);
- return written;
- }
-
- int load(const char *fileName) {
- SNDFILE *sndFile = nullptr;
- SF_INFO info;
-
- sndFile = sf_open(fileName, SFM_READ, &info);
- if (sndFile == nullptr) {
- printf("AudioRecording::load(%s) failed to open file\n", fileName);
- return -errno;
- }
-
- assert(info.channels == 1);
- assert(info.format == SF_FORMAT_FLOAT);
-
- setSampleRate(info.samplerate);
- allocate(info.frames);
- mFrameCounter = sf_readf_float(sndFile, mData, info.frames);
-
- sf_close(sndFile);
- return mFrameCounter;
- }
-
- /**
- * Square the samples so they are all positive and so the peaks are emphasized.
- */
- void square() {
- for (int i = 0; i < mFrameCounter; i++) {
- const float sample = mData[i];
- mData[i] = sample * sample;
- }
- }
-
- /**
- * Low pass filter the recording using a simple FIR filter.
- * Note that the lowpass filter cutoff tracks the sample rate.
- * That is OK because the impulse width is a fixed number of samples.
- */
- void lowPassFilter() {
- for (int i = 0; i < mFrameCounter; i++) {
- mData[i] = mLowPassFilter.filter(mData[i]);
- }
- }
-
- /**
- * Remove DC offset using a one-pole one-zero IIR filter.
- */
- void dcBlocker() {
- const float R = 0.996; // narrow notch at zero Hz
- float x1 = 0.0;
- float y1 = 0.0;
- for (int i = 0; i < mFrameCounter; i++) {
- const float x = mData[i];
- const float y = x - x1 + (R * y1);
- mData[i] = y;
- y1 = y;
- x1 = x;
- }
- }
-
-private:
- float *mData = nullptr;
- int32_t mFrameCounter = 0;
- int32_t mMaxFrames = 0;
- int32_t mSampleRate = kDefaultSampleRate; // common default
- LowPassFilter mLowPassFilter;
-};
-
-// ====================================================================================
-class LoopbackProcessor {
-public:
- virtual ~LoopbackProcessor() = default;
-
-
- enum process_result {
- PROCESS_RESULT_OK,
- PROCESS_RESULT_GLITCH
- };
-
- virtual void reset() {}
-
- virtual process_result process(float *inputData, int inputChannelCount,
- float *outputData, int outputChannelCount,
- int numFrames) = 0;
-
-
- virtual void report() = 0;
-
- virtual void printStatus() {};
-
- int32_t getResult() {
- return mResult;
- }
-
- void setResult(int32_t result) {
- mResult = result;
- }
-
- virtual bool isDone() {
- return false;
- }
-
- virtual int save(const char *fileName) {
- (void) fileName;
- return AAUDIO_ERROR_UNIMPLEMENTED;
- }
-
- virtual int load(const char *fileName) {
- (void) fileName;
- return AAUDIO_ERROR_UNIMPLEMENTED;
- }
-
- virtual void setSampleRate(int32_t sampleRate) {
- mSampleRate = sampleRate;
- }
-
- int32_t getSampleRate() {
- return mSampleRate;
- }
-
- // Measure peak amplitude of buffer.
- static float measurePeakAmplitude(float *inputData, int inputChannelCount, int numFrames) {
- float peak = 0.0f;
- for (int i = 0; i < numFrames; i++) {
- const float pos = fabs(*inputData);
- if (pos > peak) {
- peak = pos;
- }
- inputData += inputChannelCount;
- }
- return peak;
- }
-
-
-private:
- int32_t mSampleRate = kDefaultSampleRate;
- int32_t mResult = 0;
-};
-
-class PeakDetector {
-public:
- float process(float input) {
- float output = mPrevious * mDecay;
- if (input > output) {
- output = input;
- }
- mPrevious = output;
- return output;
- }
-
-private:
- float mDecay = 0.99f;
- float mPrevious = 0.0f;
-};
-
-// ====================================================================================
-/**
- * Measure latency given a loopback stream data.
- * Uses a state machine to cycle through various stages including:
- *
- */
-class EchoAnalyzer : public LoopbackProcessor {
-public:
-
- EchoAnalyzer() : LoopbackProcessor() {
- mAudioRecording.allocate(2 * getSampleRate());
- mAudioRecording.setSampleRate(getSampleRate());
- }
-
- void setSampleRate(int32_t sampleRate) override {
- LoopbackProcessor::setSampleRate(sampleRate);
- mAudioRecording.setSampleRate(sampleRate);
- }
-
- void reset() override {
- mDownCounter = getSampleRate() / 2;
- mLoopCounter = 0;
- mMeasuredLoopGain = 0.0f;
- mEchoGain = 1.0f;
- mState = STATE_INITIAL_SILENCE;
- }
-
- virtual bool isDone() {
- return mState == STATE_DONE || mState == STATE_FAILED;
- }
-
- void setGain(float gain) {
- mEchoGain = gain;
- }
-
- float getGain() {
- return mEchoGain;
- }
-
- bool testLowPassFilter() {
- LowPassFilter filter;
- return filter.test();
- }
-
- void report() override {
- printf("EchoAnalyzer ---------------\n");
- if (getResult() != 0) {
- printf(LOOPBACK_RESULT_TAG "result = %d\n", getResult());
- return;
- }
-
- // printf("LowPassFilter test %s\n", testLowPassFilter() ? "PASSED" : "FAILED");
-
- printf(LOOPBACK_RESULT_TAG "measured.gain = %8f\n", mMeasuredLoopGain);
- printf(LOOPBACK_RESULT_TAG "echo.gain = %8f\n", mEchoGain);
- printf(LOOPBACK_RESULT_TAG "test.state = %8d\n", mState);
- printf(LOOPBACK_RESULT_TAG "test.state.name = %8s\n", convertStateToText(mState));
-
- if (mState == STATE_WAITING_FOR_SILENCE) {
- printf("WARNING - Stuck waiting for silence. Input may be too noisy!\n");
- setResult(ERROR_NOISY);
- } else if (mMeasuredLoopGain >= 0.9999) {
- printf(" ERROR - clipping, turn down volume slightly\n");
- setResult(ERROR_CLIPPING);
- } else if (mState != STATE_DONE && mState != STATE_GATHERING_ECHOS) {
- printf("WARNING - Bad state. Check volume on device.\n");
- setResult(ERROR_INVALID_STATE);
- } else {
- // Cleanup the signal to improve the auto-correlation.
- mAudioRecording.dcBlocker();
- mAudioRecording.square();
- mAudioRecording.lowPassFilter();
-
- printf("Please wait several seconds for auto-correlation to complete.\n");
- measureLatencyFromEchos(mAudioRecording.getData(),
- mAudioRecording.size(),
- getSampleRate(),
- &mLatencyReport);
-
- double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
- / getSampleRate();
- printf(LOOPBACK_RESULT_TAG "latency.frames = %8.2f\n",
- mLatencyReport.latencyInFrames);
- printf(LOOPBACK_RESULT_TAG "latency.msec = %8.2f\n",
- latencyMillis);
- printf(LOOPBACK_RESULT_TAG "latency.confidence = %8.6f\n",
- mLatencyReport.confidence);
- if (mLatencyReport.confidence < kMinimumConfidence) {
- printf(" ERROR - confidence too low!\n");
- setResult(ERROR_CONFIDENCE);
- }
- }
- }
-
- void printStatus() override {
- printf("st = %d, echo gain = %f ", mState, mEchoGain);
- }
-
- void sendImpulses(float *outputData, int outputChannelCount, int numFrames) {
- while (numFrames-- > 0) {
- float sample = s_Impulse[mSampleIndex++];
- if (mSampleIndex >= kImpulseSizeInFrames) {
- mSampleIndex = 0;
- }
-
- *outputData = sample;
- outputData += outputChannelCount;
- }
- }
-
- void sendOneImpulse(float *outputData, int outputChannelCount) {
- mSampleIndex = 0;
- sendImpulses(outputData, outputChannelCount, kImpulseSizeInFrames);
- }
-
- // @return number of frames for a typical block of processing
- int32_t getBlockFrames() {
- return getSampleRate() / 8;
- }
-
- process_result process(float *inputData, int inputChannelCount,
- float *outputData, int outputChannelCount,
- int numFrames) override {
- int channelsValid = std::min(inputChannelCount, outputChannelCount);
- float peak = 0.0f;
- int numWritten;
- int numSamples;
-
- echo_state nextState = mState;
-
- switch (mState) {
- case STATE_INITIAL_SILENCE:
- // Output silence at the beginning.
- numSamples = numFrames * outputChannelCount;
- for (int i = 0; i < numSamples; i++) {
- outputData[i] = 0;
- }
- mDownCounter -= numFrames;
- if (mDownCounter <= 0) {
- nextState = STATE_MEASURING_GAIN;
- //printf("%5d: switch to STATE_MEASURING_GAIN\n", mLoopCounter);
- mDownCounter = getBlockFrames() * 2;
- }
- break;
-
- case STATE_MEASURING_GAIN:
- sendImpulses(outputData, outputChannelCount, numFrames);
- peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
- // If we get several in a row then go to next state.
- if (peak > mPulseThreshold) {
- mDownCounter -= numFrames;
- if (mDownCounter <= 0) {
- //printf("%5d: switch to STATE_WAITING_FOR_SILENCE, measured peak = %f\n",
- // mLoopCounter, peak);
- mDownCounter = getBlockFrames();
- mMeasuredLoopGain = peak; // assumes original pulse amplitude is one
- mSilenceThreshold = peak * 0.1; // scale silence to measured pulse
- // Calculate gain that will give us a nice decaying echo.
- mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
- if (mEchoGain > kMaxEchoGain) {
- printf("ERROR - loop gain too low. Increase the volume.\n");
- nextState = STATE_FAILED;
- } else {
- nextState = STATE_WAITING_FOR_SILENCE;
- }
- }
- } else if (numFrames > kImpulseSizeInFrames){ // ignore short callbacks
- mDownCounter = getBlockFrames();
- }
- break;
-
- case STATE_WAITING_FOR_SILENCE:
- // Output silence and wait for the echos to die down.
- numSamples = numFrames * outputChannelCount;
- for (int i = 0; i < numSamples; i++) {
- outputData[i] = 0;
- }
- peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
- // If we get several in a row then go to next state.
- if (peak < mSilenceThreshold) {
- mDownCounter -= numFrames;
- if (mDownCounter <= 0) {
- nextState = STATE_SENDING_PULSE;
- //printf("%5d: switch to STATE_SENDING_PULSE\n", mLoopCounter);
- mDownCounter = getBlockFrames();
- }
- } else {
- mDownCounter = getBlockFrames();
- }
- break;
-
- case STATE_SENDING_PULSE:
- mAudioRecording.write(inputData, inputChannelCount, numFrames);
- sendOneImpulse(outputData, outputChannelCount);
- nextState = STATE_GATHERING_ECHOS;
- //printf("%5d: switch to STATE_GATHERING_ECHOS\n", mLoopCounter);
- break;
-
- case STATE_GATHERING_ECHOS:
- numWritten = mAudioRecording.write(inputData, inputChannelCount, numFrames);
- peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
- if (peak > mMeasuredLoopGain) {
- mMeasuredLoopGain = peak; // AGC might be raising gain so adjust it on the fly.
- // Recalculate gain that will give us a nice decaying echo.
- mEchoGain = mDesiredEchoGain / mMeasuredLoopGain;
- }
- // Echo input to output.
- for (int i = 0; i < numFrames; i++) {
- int ic;
- for (ic = 0; ic < channelsValid; ic++) {
- outputData[ic] = inputData[ic] * mEchoGain;
- }
- for (; ic < outputChannelCount; ic++) {
- outputData[ic] = 0;
- }
- inputData += inputChannelCount;
- outputData += outputChannelCount;
- }
- if (numWritten < numFrames) {
- nextState = STATE_DONE;
- }
- break;
-
- case STATE_DONE:
- case STATE_FAILED:
- default:
- break;
- }
-
- mState = nextState;
- mLoopCounter++;
- return PROCESS_RESULT_OK;
- }
-
- int save(const char *fileName) override {
- return mAudioRecording.save(fileName);
- }
-
- int load(const char *fileName) override {
- int result = mAudioRecording.load(fileName);
- setSampleRate(mAudioRecording.getSampleRate());
- mState = STATE_DONE;
- return result;
- }
-
-private:
-
- enum error_code {
- ERROR_OK = 0,
- ERROR_NOISY = -99,
- ERROR_CLIPPING,
- ERROR_CONFIDENCE,
- ERROR_INVALID_STATE
- };
-
- enum echo_state {
- STATE_INITIAL_SILENCE,
- STATE_MEASURING_GAIN,
- STATE_WAITING_FOR_SILENCE,
- STATE_SENDING_PULSE,
- STATE_GATHERING_ECHOS,
- STATE_DONE,
- STATE_FAILED
- };
-
- const char *convertStateToText(echo_state state) {
- const char *result = "Unknown";
- switch(state) {
- case STATE_INITIAL_SILENCE:
- result = "INIT";
- break;
- case STATE_MEASURING_GAIN:
- result = "GAIN";
- break;
- case STATE_WAITING_FOR_SILENCE:
- result = "SILENCE";
- break;
- case STATE_SENDING_PULSE:
- result = "PULSE";
- break;
- case STATE_GATHERING_ECHOS:
- result = "ECHOS";
- break;
- case STATE_DONE:
- result = "DONE";
- break;
- case STATE_FAILED:
- result = "FAILED";
- break;
- }
- return result;
- }
-
-
- int32_t mDownCounter = 500;
- int32_t mLoopCounter = 0;
- int32_t mSampleIndex = 0;
- float mPulseThreshold = 0.02f;
- float mSilenceThreshold = 0.002f;
- float mMeasuredLoopGain = 0.0f;
- float mDesiredEchoGain = 0.95f;
- float mEchoGain = 1.0f;
- echo_state mState = STATE_INITIAL_SILENCE;
-
- AudioRecording mAudioRecording; // contains only the input after the gain detection burst
- LatencyReport mLatencyReport;
- // PeakDetector mPeakDetector;
-};
-
-
-// ====================================================================================
-/**
- * Output a steady sinewave and analyze the return signal.
- *
- * Use a cosine transform to measure the predicted magnitude and relative phase of the
- * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
- */
-class SineAnalyzer : public LoopbackProcessor {
-public:
-
- void report() override {
- printf("SineAnalyzer ------------------\n");
- printf(LOOPBACK_RESULT_TAG "peak.amplitude = %8f\n", mPeakAmplitude);
- printf(LOOPBACK_RESULT_TAG "sine.magnitude = %8f\n", mMagnitude);
- printf(LOOPBACK_RESULT_TAG "peak.noise = %8f\n", mPeakNoise);
- printf(LOOPBACK_RESULT_TAG "rms.noise = %8f\n", mRootMeanSquareNoise);
- float amplitudeRatio = mMagnitude / mPeakNoise;
- float signalToNoise = amplitudeRatio * amplitudeRatio;
- printf(LOOPBACK_RESULT_TAG "signal.to.noise = %8.2f\n", signalToNoise);
- float signalToNoiseDB = 10.0 * log(signalToNoise);
- printf(LOOPBACK_RESULT_TAG "signal.to.noise.db = %8.2f\n", signalToNoiseDB);
- if (signalToNoiseDB < MIN_SNRATIO_DB) {
- printf("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.\n", MIN_SNRATIO_DB);
- setResult(ERROR_NOISY);
- }
- printf(LOOPBACK_RESULT_TAG "frames.accumulated = %8d\n", mFramesAccumulated);
- printf(LOOPBACK_RESULT_TAG "sine.period = %8d\n", mSinePeriod);
- printf(LOOPBACK_RESULT_TAG "test.state = %8d\n", mState);
- printf(LOOPBACK_RESULT_TAG "frame.count = %8d\n", mFrameCounter);
- // Did we ever get a lock?
- bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
- if (!gotLock) {
- printf("ERROR - failed to lock on reference sine tone\n");
- setResult(ERROR_NO_LOCK);
- } else {
- // Only print if meaningful.
- printf(LOOPBACK_RESULT_TAG "glitch.count = %8d\n", mGlitchCount);
- printf(LOOPBACK_RESULT_TAG "max.glitch = %8f\n", mMaxGlitchDelta);
- if (mGlitchCount > 0) {
- printf("ERROR - number of glitches > 0\n");
- setResult(ERROR_GLITCHES);
- }
- }
- }
-
- void printStatus() override {
- printf("st = %d, #gl = %3d,", mState, mGlitchCount);
- }
-
- double calculateMagnitude(double *phasePtr = NULL) {
- if (mFramesAccumulated == 0) {
- return 0.0;
- }
- double sinMean = mSinAccumulator / mFramesAccumulated;
- double cosMean = mCosAccumulator / mFramesAccumulated;
- double magnitude = 2.0 * sqrt( (sinMean * sinMean) + (cosMean * cosMean ));
- if( phasePtr != NULL )
- {
- double phase = M_PI_2 - atan2( sinMean, cosMean );
- *phasePtr = phase;
- }
- return magnitude;
- }
-
- /**
- * @param inputData contains microphone data with sine signal feedback
- * @param outputData contains the reference sine wave
- */
- process_result process(float *inputData, int inputChannelCount,
- float *outputData, int outputChannelCount,
- int numFrames) override {
- process_result result = PROCESS_RESULT_OK;
- mProcessCount++;
-
- float peak = measurePeakAmplitude(inputData, inputChannelCount, numFrames);
- if (peak > mPeakAmplitude) {
- mPeakAmplitude = peak;
- }
-
- for (int i = 0; i < numFrames; i++) {
- bool sineEnabled = true;
- float sample = inputData[i * inputChannelCount];
-
- float sinOut = sinf(mPhase);
-
- switch (mState) {
- case STATE_IDLE:
- sineEnabled = false;
- mDownCounter--;
- if (mDownCounter <= 0) {
- mState = STATE_MEASURE_NOISE;
- mDownCounter = NOISE_FRAME_COUNT;
- }
- break;
- case STATE_MEASURE_NOISE:
- sineEnabled = false;
- mPeakNoise = std::max(abs(sample), mPeakNoise);
- mNoiseSumSquared += sample * sample;
- mDownCounter--;
- if (mDownCounter <= 0) {
- mState = STATE_WAITING_FOR_SIGNAL;
- mRootMeanSquareNoise = sqrt(mNoiseSumSquared / NOISE_FRAME_COUNT);
- mTolerance = std::max(MIN_TOLERANCE, mPeakNoise * 2.0f);
- mPhase = 0.0; // prevent spike at start
- }
- break;
-
- case STATE_IMMUNE:
- mDownCounter--;
- if (mDownCounter <= 0) {
- mState = STATE_WAITING_FOR_SIGNAL;
- }
- break;
-
- case STATE_WAITING_FOR_SIGNAL:
- if (peak > mThreshold) {
- mState = STATE_WAITING_FOR_LOCK;
- //printf("%5d: switch to STATE_WAITING_FOR_LOCK\n", mFrameCounter);
- resetAccumulator();
- }
- break;
-
- case STATE_WAITING_FOR_LOCK:
- mSinAccumulator += sample * sinOut;
- mCosAccumulator += sample * cosf(mPhase);
- mFramesAccumulated++;
- // Must be a multiple of the period or the calculation will not be accurate.
- if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
- mPhaseOffset = 0.0;
- mMagnitude = calculateMagnitude(&mPhaseOffset);
- if (mMagnitude > mThreshold) {
- if (fabs(mPreviousPhaseOffset - mPhaseOffset) < 0.001) {
- mState = STATE_LOCKED;
- //printf("%5d: switch to STATE_LOCKED\n", mFrameCounter);
- }
- mPreviousPhaseOffset = mPhaseOffset;
- }
- resetAccumulator();
- }
- break;
-
- case STATE_LOCKED: {
- // Predict next sine value
- float predicted = sinf(mPhase + mPhaseOffset) * mMagnitude;
- // printf(" predicted = %f, actual = %f\n", predicted, sample);
-
- float diff = predicted - sample;
- float absDiff = fabs(diff);
- mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
- if (absDiff > mTolerance) {
- mGlitchCount++;
- result = PROCESS_RESULT_GLITCH;
- //printf("%5d: Got a glitch # %d, predicted = %f, actual = %f\n",
- // mFrameCounter, mGlitchCount, predicted, sample);
- mState = STATE_IMMUNE;
- mDownCounter = mSinePeriod * PERIODS_IMMUNE;
- }
-
- // Track incoming signal and slowly adjust magnitude to account
- // for drift in the DRC or AGC.
- mSinAccumulator += sample * sinOut;
- mCosAccumulator += sample * cosf(mPhase);
- mFramesAccumulated++;
- // Must be a multiple of the period or the calculation will not be accurate.
- if (mFramesAccumulated == mSinePeriod) {
- const double coefficient = 0.1;
- double phaseOffset = 0.0;
- double magnitude = calculateMagnitude(&phaseOffset);
- // One pole averaging filter.
- mMagnitude = (mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient);
- resetAccumulator();
- }
- } break;
- }
-
- float output = 0.0f;
- // Output sine wave so we can measure it.
- if (sineEnabled) {
- output = (sinOut * mOutputAmplitude)
- + (mWhiteNoise.nextRandomDouble() * mNoiseAmplitude);
- // printf("%5d: sin(%f) = %f, %f\n", i, mPhase, sinOut, mPhaseIncrement);
- // advance and wrap phase
- mPhase += mPhaseIncrement;
- if (mPhase > M_PI) {
- mPhase -= (2.0 * M_PI);
- }
- }
- outputData[i * outputChannelCount] = output;
-
-
- mFrameCounter++;
- }
- return result;
- }
-
- void resetAccumulator() {
- mFramesAccumulated = 0;
- mSinAccumulator = 0.0;
- mCosAccumulator = 0.0;
- }
-
- void reset() override {
- mGlitchCount = 0;
- mState = STATE_IDLE;
- mDownCounter = IDLE_FRAME_COUNT;
- mPhaseIncrement = 2.0 * M_PI / mSinePeriod;
- printf("phaseInc = %f for period %d\n", mPhaseIncrement, mSinePeriod);
- resetAccumulator();
- mProcessCount = 0;
- mPeakNoise = 0.0f;
- mNoiseSumSquared = 0.0;
- mRootMeanSquareNoise = 0.0;
- mPhase = 0.0f;
- mMaxGlitchDelta = 0.0;
- }
-
-private:
-
- enum error_code {
- OK,
- ERROR_NO_LOCK = -80,
- ERROR_GLITCHES,
- ERROR_NOISY
- };
-
- enum sine_state_t {
- STATE_IDLE,
- STATE_MEASURE_NOISE,
- STATE_IMMUNE,
- STATE_WAITING_FOR_SIGNAL,
- STATE_WAITING_FOR_LOCK,
- STATE_LOCKED
- };
-
- enum constants {
- // Arbitrary durations, assuming 48000 Hz
- IDLE_FRAME_COUNT = 48 * 100,
- NOISE_FRAME_COUNT = 48 * 600,
- PERIODS_NEEDED_FOR_LOCK = 8,
- PERIODS_IMMUNE = 2,
- MIN_SNRATIO_DB = 65
- };
-
- static constexpr float MIN_TOLERANCE = 0.01;
-
- int mSinePeriod = 79;
- double mPhaseIncrement = 0.0;
- double mPhase = 0.0;
- double mPhaseOffset = 0.0;
- double mPreviousPhaseOffset = 0.0;
- double mMagnitude = 0.0;
- double mThreshold = 0.005;
- double mTolerance = MIN_TOLERANCE;
- int32_t mFramesAccumulated = 0;
- int32_t mProcessCount = 0;
- double mSinAccumulator = 0.0;
- double mCosAccumulator = 0.0;
- float mMaxGlitchDelta = 0.0f;
- int32_t mGlitchCount = 0;
- double mPeakAmplitude = 0.0;
- int mDownCounter = IDLE_FRAME_COUNT;
- int32_t mFrameCounter = 0;
- float mOutputAmplitude = 0.75;
-
- // measure background noise
- float mPeakNoise = 0.0f;
- double mNoiseSumSquared = 0.0;
- double mRootMeanSquareNoise = 0.0;
-
- PseudoRandom mWhiteNoise;
- float mNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
-
- sine_state_t mState = STATE_IDLE;
-};
-
-#undef LOOPBACK_RESULT_TAG
-
-#endif /* AAUDIO_EXAMPLES_LOOPBACK_ANALYSER_H */
diff --git a/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h b/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h
new file mode 100644
index 0000000..04435d1
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/GlitchAnalyzer.h
@@ -0,0 +1,445 @@
+/*
+ * 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 ANALYZER_GLITCH_ANALYZER_H
+#define ANALYZER_GLITCH_ANALYZER_H
+
+#include <algorithm>
+#include <cctype>
+#include <iomanip>
+#include <iostream>
+
+#include "LatencyAnalyzer.h"
+#include "PseudoRandom.h"
+
+/**
+ * Output a steady sine wave and analyze the return signal.
+ *
+ * Use a cosine transform to measure the predicted magnitude and relative phase of the
+ * looped back sine wave. Then generate a predicted signal and compare with the actual signal.
+ */
+class GlitchAnalyzer : public LoopbackProcessor {
+public:
+
+ int32_t getState() const {
+ return mState;
+ }
+
+ double getPeakAmplitude() const {
+ return mPeakFollower.getLevel();
+ }
+
+ double getTolerance() {
+ return mTolerance;
+ }
+
+ void setTolerance(double tolerance) {
+ mTolerance = tolerance;
+ mScaledTolerance = mMagnitude * mTolerance;
+ }
+
+ void setMagnitude(double magnitude) {
+ mMagnitude = magnitude;
+ mScaledTolerance = mMagnitude * mTolerance;
+ }
+
+ int32_t getGlitchCount() const {
+ return mGlitchCount;
+ }
+
+ int32_t getStateFrameCount(int state) const {
+ return mStateFrameCounters[state];
+ }
+
+ double getSignalToNoiseDB() {
+ static const double threshold = 1.0e-14;
+ if (mMeanSquareSignal < threshold || mMeanSquareNoise < threshold) {
+ return 0.0;
+ } else {
+ double signalToNoise = mMeanSquareSignal / mMeanSquareNoise; // power ratio
+ double signalToNoiseDB = 10.0 * log(signalToNoise);
+ if (signalToNoiseDB < MIN_SNR_DB) {
+ ALOGD("ERROR - signal to noise ratio is too low! < %d dB. Adjust volume.",
+ MIN_SNR_DB);
+ setResult(ERROR_VOLUME_TOO_LOW);
+ }
+ return signalToNoiseDB;
+ }
+ }
+
+ std::string analyze() override {
+ std::stringstream report;
+ report << "GlitchAnalyzer ------------------\n";
+ report << LOOPBACK_RESULT_TAG "peak.amplitude = " << std::setw(8)
+ << getPeakAmplitude() << "\n";
+ report << LOOPBACK_RESULT_TAG "sine.magnitude = " << std::setw(8)
+ << mMagnitude << "\n";
+ report << LOOPBACK_RESULT_TAG "rms.noise = " << std::setw(8)
+ << mMeanSquareNoise << "\n";
+ report << LOOPBACK_RESULT_TAG "signal.to.noise.db = " << std::setw(8)
+ << getSignalToNoiseDB() << "\n";
+ report << LOOPBACK_RESULT_TAG "frames.accumulated = " << std::setw(8)
+ << mFramesAccumulated << "\n";
+ report << LOOPBACK_RESULT_TAG "sine.period = " << std::setw(8)
+ << mSinePeriod << "\n";
+ report << LOOPBACK_RESULT_TAG "test.state = " << std::setw(8)
+ << mState << "\n";
+ report << LOOPBACK_RESULT_TAG "frame.count = " << std::setw(8)
+ << mFrameCounter << "\n";
+ // Did we ever get a lock?
+ bool gotLock = (mState == STATE_LOCKED) || (mGlitchCount > 0);
+ if (!gotLock) {
+ report << "ERROR - failed to lock on reference sine tone.\n";
+ setResult(ERROR_NO_LOCK);
+ } else {
+ // Only print if meaningful.
+ report << LOOPBACK_RESULT_TAG "glitch.count = " << std::setw(8)
+ << mGlitchCount << "\n";
+ report << LOOPBACK_RESULT_TAG "max.glitch = " << std::setw(8)
+ << mMaxGlitchDelta << "\n";
+ if (mGlitchCount > 0) {
+ report << "ERROR - number of glitches > 0\n";
+ setResult(ERROR_GLITCHES);
+ }
+ }
+ return report.str();
+ }
+
+ void printStatus() override {
+ ALOGD("st = %d, #gl = %3d,", mState, mGlitchCount);
+ }
+ /**
+ * Calculate the magnitude of the component of the input signal
+ * that matches the analysis frequency.
+ * Also calculate the phase that we can use to create a
+ * signal that matches that component.
+ * The phase will be between -PI and +PI.
+ */
+ double calculateMagnitude(double *phasePtr = nullptr) {
+ if (mFramesAccumulated == 0) {
+ return 0.0;
+ }
+ double sinMean = mSinAccumulator / mFramesAccumulated;
+ double cosMean = mCosAccumulator / mFramesAccumulated;
+ double magnitude = 2.0 * sqrt((sinMean * sinMean) + (cosMean * cosMean));
+ if (phasePtr != nullptr) {
+ double phase = M_PI_2 - atan2(sinMean, cosMean);
+ *phasePtr = phase;
+ }
+ return magnitude;
+ }
+
+ /**
+ * @param frameData contains microphone data with sine signal feedback
+ * @param channelCount
+ */
+ result_code processInputFrame(float *frameData, int /* channelCount */) override {
+ result_code result = RESULT_OK;
+
+ float sample = frameData[0];
+ float peak = mPeakFollower.process(sample);
+
+ // Force a periodic glitch to test the detector!
+ if (mForceGlitchDuration > 0) {
+ if (mForceGlitchCounter == 0) {
+ ALOGE("%s: force a glitch!!", __func__);
+ mForceGlitchCounter = getSampleRate();
+ } else if (mForceGlitchCounter <= mForceGlitchDuration) {
+ // Force an abrupt offset.
+ sample += (sample > 0.0) ? -0.5f : 0.5f;
+ }
+ --mForceGlitchCounter;
+ }
+
+ mStateFrameCounters[mState]++; // count how many frames we are in each state
+
+ switch (mState) {
+ case STATE_IDLE:
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mState = STATE_IMMUNE;
+ mDownCounter = IMMUNE_FRAME_COUNT;
+ mInputPhase = 0.0; // prevent spike at start
+ mOutputPhase = 0.0;
+ }
+ break;
+
+ case STATE_IMMUNE:
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mState = STATE_WAITING_FOR_SIGNAL;
+ }
+ break;
+
+ case STATE_WAITING_FOR_SIGNAL:
+ if (peak > mThreshold) {
+ mState = STATE_WAITING_FOR_LOCK;
+ //ALOGD("%5d: switch to STATE_WAITING_FOR_LOCK", mFrameCounter);
+ resetAccumulator();
+ }
+ break;
+
+ case STATE_WAITING_FOR_LOCK:
+ mSinAccumulator += sample * sinf(mInputPhase);
+ mCosAccumulator += sample * cosf(mInputPhase);
+ mFramesAccumulated++;
+ // Must be a multiple of the period or the calculation will not be accurate.
+ if (mFramesAccumulated == mSinePeriod * PERIODS_NEEDED_FOR_LOCK) {
+ double phaseOffset = 0.0;
+ setMagnitude(calculateMagnitude(&phaseOffset));
+// ALOGD("%s() mag = %f, offset = %f, prev = %f",
+// __func__, mMagnitude, mPhaseOffset, mPreviousPhaseOffset);
+ if (mMagnitude > mThreshold) {
+ if (abs(phaseOffset) < kMaxPhaseError) {
+ mState = STATE_LOCKED;
+// ALOGD("%5d: switch to STATE_LOCKED", mFrameCounter);
+ }
+ // Adjust mInputPhase to match measured phase
+ mInputPhase += phaseOffset;
+ }
+ resetAccumulator();
+ }
+ incrementInputPhase();
+ break;
+
+ case STATE_LOCKED: {
+ // Predict next sine value
+ double predicted = sinf(mInputPhase) * mMagnitude;
+ double diff = predicted - sample;
+ double absDiff = fabs(diff);
+ mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+ if (absDiff > mScaledTolerance) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+// LOGI("diff glitch detected, absDiff = %g", absDiff);
+ } else {
+ mSumSquareSignal += predicted * predicted;
+ mSumSquareNoise += diff * diff;
+ // Track incoming signal and slowly adjust magnitude to account
+ // for drift in the DRC or AGC.
+ mSinAccumulator += sample * sinf(mInputPhase);
+ mCosAccumulator += sample * cosf(mInputPhase);
+ mFramesAccumulated++;
+ // Must be a multiple of the period or the calculation will not be accurate.
+ if (mFramesAccumulated == mSinePeriod) {
+ const double coefficient = 0.1;
+ double phaseOffset = 0.0;
+ double magnitude = calculateMagnitude(&phaseOffset);
+ // One pole averaging filter.
+ setMagnitude((mMagnitude * (1.0 - coefficient)) + (magnitude * coefficient));
+
+ mMeanSquareNoise = mSumSquareNoise * mInverseSinePeriod;
+ mMeanSquareSignal = mSumSquareSignal * mInverseSinePeriod;
+ resetAccumulator();
+
+ if (abs(phaseOffset) > kMaxPhaseError) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+ ALOGD("phase glitch detected, phaseOffset = %g", phaseOffset);
+ } else if (mMagnitude < mThreshold) {
+ result = ERROR_GLITCHES;
+ onGlitchStart();
+ ALOGD("magnitude glitch detected, mMagnitude = %g", mMagnitude);
+ }
+ }
+ }
+ incrementInputPhase();
+ } break;
+
+ case STATE_GLITCHING: {
+ // Predict next sine value
+ mGlitchLength++;
+ double predicted = sinf(mInputPhase) * mMagnitude;
+ double diff = predicted - sample;
+ double absDiff = fabs(diff);
+ mMaxGlitchDelta = std::max(mMaxGlitchDelta, absDiff);
+ if (absDiff < mScaledTolerance) { // close enough?
+ // If we get a full sine period of non-glitch samples in a row then consider the glitch over.
+ // We don't want to just consider a zero crossing the end of a glitch.
+ if (mNonGlitchCount++ > mSinePeriod) {
+ onGlitchEnd();
+ }
+ } else {
+ mNonGlitchCount = 0;
+ if (mGlitchLength > (4 * mSinePeriod)) {
+ relock();
+ }
+ }
+ incrementInputPhase();
+ } break;
+
+ case NUM_STATES: // not a real state
+ break;
+ }
+
+ mFrameCounter++;
+
+ return result;
+ }
+
+ // advance and wrap phase
+ void incrementInputPhase() {
+ mInputPhase += mPhaseIncrement;
+ if (mInputPhase > M_PI) {
+ mInputPhase -= (2.0 * M_PI);
+ }
+ }
+
+ // advance and wrap phase
+ void incrementOutputPhase() {
+ mOutputPhase += mPhaseIncrement;
+ if (mOutputPhase > M_PI) {
+ mOutputPhase -= (2.0 * M_PI);
+ }
+ }
+
+ /**
+ * @param frameData upon return, contains the reference sine wave
+ * @param channelCount
+ */
+ result_code processOutputFrame(float *frameData, int channelCount) override {
+ float output = 0.0f;
+ // Output sine wave so we can measure it.
+ if (mState != STATE_IDLE) {
+ float sinOut = sinf(mOutputPhase);
+ incrementOutputPhase();
+ output = (sinOut * mOutputAmplitude)
+ + (mWhiteNoise.nextRandomDouble() * kNoiseAmplitude);
+ // ALOGD("sin(%f) = %f, %f\n", mOutputPhase, sinOut, mPhaseIncrement);
+ }
+ frameData[0] = output;
+ for (int i = 1; i < channelCount; i++) {
+ frameData[i] = 0.0f;
+ }
+ return RESULT_OK;
+ }
+
+ void onGlitchStart() {
+ mGlitchCount++;
+// ALOGD("%5d: STARTED a glitch # %d", mFrameCounter, mGlitchCount);
+ mState = STATE_GLITCHING;
+ mGlitchLength = 1;
+ mNonGlitchCount = 0;
+ }
+
+ void onGlitchEnd() {
+// ALOGD("%5d: ENDED a glitch # %d, length = %d", mFrameCounter, mGlitchCount, mGlitchLength);
+ mState = STATE_LOCKED;
+ resetAccumulator();
+ }
+
+ // reset the sine wave detector
+ void resetAccumulator() {
+ mFramesAccumulated = 0;
+ mSinAccumulator = 0.0;
+ mCosAccumulator = 0.0;
+ mSumSquareSignal = 0.0;
+ mSumSquareNoise = 0.0;
+ }
+
+ void relock() {
+// ALOGD("relock: %d because of a very long %d glitch", mFrameCounter, mGlitchLength);
+ mState = STATE_WAITING_FOR_LOCK;
+ resetAccumulator();
+ }
+
+ void reset() override {
+ LoopbackProcessor::reset();
+ mState = STATE_IDLE;
+ mDownCounter = IDLE_FRAME_COUNT;
+ resetAccumulator();
+ }
+
+ void prepareToTest() override {
+ LoopbackProcessor::prepareToTest();
+ mSinePeriod = getSampleRate() / kTargetGlitchFrequency;
+ mOutputPhase = 0.0f;
+ mInverseSinePeriod = 1.0 / mSinePeriod;
+ mPhaseIncrement = 2.0 * M_PI * mInverseSinePeriod;
+ mGlitchCount = 0;
+ mMaxGlitchDelta = 0.0;
+ for (int i = 0; i < NUM_STATES; i++) {
+ mStateFrameCounters[i] = 0;
+ }
+ }
+
+private:
+
+ // These must match the values in GlitchActivity.java
+ enum sine_state_t {
+ STATE_IDLE, // beginning
+ STATE_IMMUNE, // ignoring input, waiting fo HW to settle
+ STATE_WAITING_FOR_SIGNAL, // looking for a loud signal
+ STATE_WAITING_FOR_LOCK, // trying to lock onto the phase of the sine
+ STATE_LOCKED, // locked on the sine wave, looking for glitches
+ STATE_GLITCHING, // locked on the sine wave but glitching
+ NUM_STATES
+ };
+
+ enum constants {
+ // Arbitrary durations, assuming 48000 Hz
+ IDLE_FRAME_COUNT = 48 * 100,
+ IMMUNE_FRAME_COUNT = 48 * 100,
+ PERIODS_NEEDED_FOR_LOCK = 8,
+ MIN_SNR_DB = 65
+ };
+
+ static constexpr float kNoiseAmplitude = 0.00; // Used to experiment with warbling caused by DRC.
+ static constexpr int kTargetGlitchFrequency = 607;
+ static constexpr double kMaxPhaseError = M_PI * 0.05;
+
+ float mTolerance = 0.10; // scaled from 0.0 to 1.0
+ double mThreshold = 0.005;
+ int mSinePeriod = 1; // this will be set before use
+ double mInverseSinePeriod = 1.0;
+
+ int32_t mStateFrameCounters[NUM_STATES];
+
+ double mPhaseIncrement = 0.0;
+ double mInputPhase = 0.0;
+ double mOutputPhase = 0.0;
+ double mMagnitude = 0.0;
+ int32_t mFramesAccumulated = 0;
+ double mSinAccumulator = 0.0;
+ double mCosAccumulator = 0.0;
+ double mMaxGlitchDelta = 0.0;
+ int32_t mGlitchCount = 0;
+ int32_t mNonGlitchCount = 0;
+ int32_t mGlitchLength = 0;
+ // This is used for processing every frame so we cache it here.
+ double mScaledTolerance = 0.0;
+ int mDownCounter = IDLE_FRAME_COUNT;
+ int32_t mFrameCounter = 0;
+ double mOutputAmplitude = 0.75;
+
+ int32_t mForceGlitchDuration = 0; // if > 0 then force a glitch for debugging
+ int32_t mForceGlitchCounter = 4 * 48000; // count down and trigger at zero
+
+ // measure background noise continuously as a deviation from the expected signal
+ double mSumSquareSignal = 0.0;
+ double mSumSquareNoise = 0.0;
+ double mMeanSquareSignal = 0.0;
+ double mMeanSquareNoise = 0.0;
+
+ PeakDetector mPeakFollower;
+
+ PseudoRandom mWhiteNoise;
+
+ sine_state_t mState = STATE_IDLE;
+};
+
+
+#endif //ANALYZER_GLITCH_ANALYZER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h b/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h
new file mode 100644
index 0000000..e506791
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/LatencyAnalyzer.h
@@ -0,0 +1,606 @@
+/*
+ * 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.
+ */
+
+/**
+ * Tools for measuring latency and for detecting glitches.
+ * These classes are pure math and can be used with any audio system.
+ */
+
+#ifndef ANALYZER_LATENCY_ANALYZER_H
+#define ANALYZER_LATENCY_ANALYZER_H
+
+#include <algorithm>
+#include <assert.h>
+#include <cctype>
+#include <iomanip>
+#include <iostream>
+#include <math.h>
+#include <memory>
+#include <sstream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <vector>
+
+#include "PeakDetector.h"
+#include "PseudoRandom.h"
+#include "RandomPulseGenerator.h"
+
+// This is used when the code is in Oboe.
+#ifndef ALOGD
+#define ALOGD printf
+#define ALOGE printf
+#define ALOGW printf
+#endif
+
+#define LOOPBACK_RESULT_TAG "RESULT: "
+
+static constexpr int32_t kDefaultSampleRate = 48000;
+static constexpr int32_t kMillisPerSecond = 1000;
+static constexpr int32_t kMaxLatencyMillis = 700; // arbitrary and generous
+static constexpr double kMinimumConfidence = 0.2;
+
+struct LatencyReport {
+ int32_t latencyInFrames = 0.0;
+ double confidence = 0.0;
+
+ void reset() {
+ latencyInFrames = 0;
+ confidence = 0.0;
+ }
+};
+
+// Calculate a normalized cross correlation.
+static double calculateNormalizedCorrelation(const float *a,
+ const float *b,
+ int windowSize) {
+ double correlation = 0.0;
+ double sumProducts = 0.0;
+ double sumSquares = 0.0;
+
+ // Correlate a against b.
+ for (int i = 0; i < windowSize; i++) {
+ float s1 = a[i];
+ float s2 = b[i];
+ // Use a normalized cross-correlation.
+ sumProducts += s1 * s2;
+ sumSquares += ((s1 * s1) + (s2 * s2));
+ }
+
+ if (sumSquares >= 1.0e-9) {
+ correlation = 2.0 * sumProducts / sumSquares;
+ }
+ return correlation;
+}
+
+static double calculateRootMeanSquare(float *data, int32_t numSamples) {
+ double sum = 0.0;
+ for (int32_t i = 0; i < numSamples; i++) {
+ float sample = data[i];
+ sum += sample * sample;
+ }
+ return sqrt(sum / numSamples);
+}
+
+/**
+ * Monophonic recording with processing.
+ */
+class AudioRecording
+{
+public:
+
+ void allocate(int maxFrames) {
+ mData = std::make_unique<float[]>(maxFrames);
+ mMaxFrames = maxFrames;
+ }
+
+ // Write SHORT data from the first channel.
+ int32_t write(int16_t *inputData, int32_t inputChannelCount, int32_t numFrames) {
+ // stop at end of buffer
+ if ((mFrameCounter + numFrames) > mMaxFrames) {
+ numFrames = mMaxFrames - mFrameCounter;
+ }
+ for (int i = 0; i < numFrames; i++) {
+ mData[mFrameCounter++] = inputData[i * inputChannelCount] * (1.0f / 32768);
+ }
+ return numFrames;
+ }
+
+ // Write FLOAT data from the first channel.
+ int32_t write(float *inputData, int32_t inputChannelCount, int32_t numFrames) {
+ // stop at end of buffer
+ if ((mFrameCounter + numFrames) > mMaxFrames) {
+ numFrames = mMaxFrames - mFrameCounter;
+ }
+ for (int i = 0; i < numFrames; i++) {
+ mData[mFrameCounter++] = inputData[i * inputChannelCount];
+ }
+ return numFrames;
+ }
+
+ // Write FLOAT data from the first channel.
+ int32_t write(float sample) {
+ // stop at end of buffer
+ if (mFrameCounter < mMaxFrames) {
+ mData[mFrameCounter++] = sample;
+ return 1;
+ }
+ return 0;
+ }
+
+ void clear() {
+ mFrameCounter = 0;
+ }
+ int32_t size() const {
+ return mFrameCounter;
+ }
+
+ bool isFull() const {
+ return mFrameCounter >= mMaxFrames;
+ }
+
+ float *getData() const {
+ return mData.get();
+ }
+
+ void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
+ }
+
+ int32_t getSampleRate() const {
+ return mSampleRate;
+ }
+
+ /**
+ * Square the samples so they are all positive and so the peaks are emphasized.
+ */
+ void square() {
+ float *x = mData.get();
+ for (int i = 0; i < mFrameCounter; i++) {
+ x[i] *= x[i];
+ }
+ }
+
+ /**
+ * Amplify a signal so that the peak matches the specified target.
+ *
+ * @param target final max value
+ * @return gain applied to signal
+ */
+ float normalize(float target) {
+ float maxValue = 1.0e-9f;
+ for (int i = 0; i < mFrameCounter; i++) {
+ maxValue = std::max(maxValue, abs(mData[i]));
+ }
+ float gain = target / maxValue;
+ for (int i = 0; i < mFrameCounter; i++) {
+ mData[i] *= gain;
+ }
+ return gain;
+ }
+
+private:
+ std::unique_ptr<float[]> mData;
+ int32_t mFrameCounter = 0;
+ int32_t mMaxFrames = 0;
+ int32_t mSampleRate = kDefaultSampleRate; // common default
+};
+
+static int measureLatencyFromPulse(AudioRecording &recorded,
+ AudioRecording &pulse,
+ LatencyReport *report) {
+
+ report->latencyInFrames = 0;
+ report->confidence = 0.0;
+
+ int numCorrelations = recorded.size() - pulse.size();
+ if (numCorrelations < 10) {
+ ALOGE("%s() recording too small = %d frames\n", __func__, recorded.size());
+ return -1;
+ }
+ std::unique_ptr<float[]> correlations= std::make_unique<float[]>(numCorrelations);
+
+ // Correlate pulse against the recorded data.
+ for (int i = 0; i < numCorrelations; i++) {
+ float correlation = (float) calculateNormalizedCorrelation(&recorded.getData()[i],
+ &pulse.getData()[0],
+ pulse.size());
+ correlations[i] = correlation;
+ }
+
+ // Find highest peak in correlation array.
+ float peakCorrelation = 0.0;
+ int peakIndex = -1;
+ for (int i = 0; i < numCorrelations; i++) {
+ float value = abs(correlations[i]);
+ if (value > peakCorrelation) {
+ peakCorrelation = value;
+ peakIndex = i;
+ }
+ }
+ if (peakIndex < 0) {
+ ALOGE("%s() no signal for correlation\n", __func__);
+ return -2;
+ }
+
+ report->latencyInFrames = peakIndex;
+ report->confidence = peakCorrelation;
+
+ return 0;
+}
+
+// ====================================================================================
+class LoopbackProcessor {
+public:
+ virtual ~LoopbackProcessor() = default;
+
+ enum result_code {
+ RESULT_OK = 0,
+ ERROR_NOISY = -99,
+ ERROR_VOLUME_TOO_LOW,
+ ERROR_VOLUME_TOO_HIGH,
+ ERROR_CONFIDENCE,
+ ERROR_INVALID_STATE,
+ ERROR_GLITCHES,
+ ERROR_NO_LOCK
+ };
+
+ virtual void prepareToTest() {
+ reset();
+ }
+
+ virtual void reset() {
+ mResult = 0;
+ mResetCount++;
+ }
+
+ virtual result_code processInputFrame(float *frameData, int channelCount) = 0;
+ virtual result_code processOutputFrame(float *frameData, int channelCount) = 0;
+
+ void process(float *inputData, int inputChannelCount, int numInputFrames,
+ float *outputData, int outputChannelCount, int numOutputFrames) {
+ int numBoth = std::min(numInputFrames, numOutputFrames);
+ // Process one frame at a time.
+ for (int i = 0; i < numBoth; i++) {
+ processInputFrame(inputData, inputChannelCount);
+ inputData += inputChannelCount;
+ processOutputFrame(outputData, outputChannelCount);
+ outputData += outputChannelCount;
+ }
+ // If there is more input than output.
+ for (int i = numBoth; i < numInputFrames; i++) {
+ processInputFrame(inputData, inputChannelCount);
+ inputData += inputChannelCount;
+ }
+ // If there is more output than input.
+ for (int i = numBoth; i < numOutputFrames; i++) {
+ processOutputFrame(outputData, outputChannelCount);
+ outputData += outputChannelCount;
+ }
+ }
+
+ virtual std::string analyze() = 0;
+
+ virtual void printStatus() {};
+
+ int32_t getResult() {
+ return mResult;
+ }
+
+ void setResult(int32_t result) {
+ mResult = result;
+ }
+
+ virtual bool isDone() {
+ return false;
+ }
+
+ virtual int save(const char *fileName) {
+ (void) fileName;
+ return -1;
+ }
+
+ virtual int load(const char *fileName) {
+ (void) fileName;
+ return -1;
+ }
+
+ virtual void setSampleRate(int32_t sampleRate) {
+ mSampleRate = sampleRate;
+ }
+
+ int32_t getSampleRate() const {
+ return mSampleRate;
+ }
+
+ int32_t getResetCount() const {
+ return mResetCount;
+ }
+
+ /** Called when not enough input frames could be read after synchronization.
+ */
+ virtual void onInsufficientRead() {
+ reset();
+ }
+
+protected:
+ int32_t mResetCount = 0;
+
+private:
+ int32_t mSampleRate = kDefaultSampleRate;
+ int32_t mResult = 0;
+};
+
+class LatencyAnalyzer : public LoopbackProcessor {
+public:
+
+ LatencyAnalyzer() : LoopbackProcessor() {}
+ virtual ~LatencyAnalyzer() = default;
+
+ virtual int32_t getProgress() const = 0;
+
+ virtual int getState() = 0;
+
+ // @return latency in frames
+ virtual int32_t getMeasuredLatency() = 0;
+
+ virtual double getMeasuredConfidence() = 0;
+
+ virtual double getBackgroundRMS() = 0;
+
+ virtual double getSignalRMS() = 0;
+
+};
+
+// ====================================================================================
+/**
+ * Measure latency given a loopback stream data.
+ * Use an encoded bit train as the sound source because it
+ * has an unambiguous correlation value.
+ * Uses a state machine to cycle through various stages.
+ *
+ */
+class PulseLatencyAnalyzer : public LatencyAnalyzer {
+public:
+
+ PulseLatencyAnalyzer() : LatencyAnalyzer() {
+ int32_t maxLatencyFrames = getSampleRate() * kMaxLatencyMillis / kMillisPerSecond;
+ int32_t numPulseBits = getSampleRate() * kPulseLengthMillis
+ / (kFramesPerEncodedBit * kMillisPerSecond);
+ int32_t pulseLength = numPulseBits * kFramesPerEncodedBit;
+ mFramesToRecord = pulseLength + maxLatencyFrames;
+ mAudioRecording.allocate(mFramesToRecord);
+ mAudioRecording.setSampleRate(getSampleRate());
+ generateRandomPulse(pulseLength);
+ }
+
+ void generateRandomPulse(int32_t pulseLength) {
+ mPulse.allocate(pulseLength);
+ RandomPulseGenerator pulser(kFramesPerEncodedBit);
+ for (int i = 0; i < pulseLength; i++) {
+ mPulse.write(pulser.nextFloat());
+ }
+ }
+
+ int getState() override {
+ return mState;
+ }
+
+ void setSampleRate(int32_t sampleRate) override {
+ LoopbackProcessor::setSampleRate(sampleRate);
+ mAudioRecording.setSampleRate(sampleRate);
+ }
+
+ void reset() override {
+ LoopbackProcessor::reset();
+ mDownCounter = getSampleRate() / 2;
+ mLoopCounter = 0;
+
+ mPulseCursor = 0;
+ mBackgroundSumSquare = 0.0f;
+ mBackgroundSumCount = 0;
+ mBackgroundRMS = 0.0f;
+ mSignalRMS = 0.0f;
+
+ mState = STATE_MEASURE_BACKGROUND;
+ mAudioRecording.clear();
+ mLatencyReport.reset();
+ }
+
+ bool hasEnoughData() {
+ return mAudioRecording.isFull();
+ }
+
+ bool isDone() override {
+ return mState == STATE_DONE;
+ }
+
+ int32_t getProgress() const override {
+ return mAudioRecording.size();
+ }
+
+ std::string analyze() override {
+ std::stringstream report;
+ report << "PulseLatencyAnalyzer ---------------\n";
+ report << LOOPBACK_RESULT_TAG "test.state = "
+ << std::setw(8) << mState << "\n";
+ report << LOOPBACK_RESULT_TAG "test.state.name = "
+ << convertStateToText(mState) << "\n";
+ report << LOOPBACK_RESULT_TAG "background.rms = "
+ << std::setw(8) << mBackgroundRMS << "\n";
+
+ int32_t newResult = RESULT_OK;
+ if (mState != STATE_GOT_DATA) {
+ report << "WARNING - Bad state. Check volume on device.\n";
+ // setResult(ERROR_INVALID_STATE);
+ } else {
+ float gain = mAudioRecording.normalize(1.0f);
+ measureLatencyFromPulse(mAudioRecording,
+ mPulse,
+ &mLatencyReport);
+
+ if (mLatencyReport.confidence < kMinimumConfidence) {
+ report << " ERROR - confidence too low!";
+ newResult = ERROR_CONFIDENCE;
+ } else {
+ mSignalRMS = calculateRootMeanSquare(
+ &mAudioRecording.getData()[mLatencyReport.latencyInFrames], mPulse.size())
+ / gain;
+ }
+ double latencyMillis = kMillisPerSecond * (double) mLatencyReport.latencyInFrames
+ / getSampleRate();
+ report << LOOPBACK_RESULT_TAG "latency.frames = " << std::setw(8)
+ << mLatencyReport.latencyInFrames << "\n";
+ report << LOOPBACK_RESULT_TAG "latency.msec = " << std::setw(8)
+ << latencyMillis << "\n";
+ report << LOOPBACK_RESULT_TAG "latency.confidence = " << std::setw(8)
+ << mLatencyReport.confidence << "\n";
+ }
+ mState = STATE_DONE;
+ if (getResult() == RESULT_OK) {
+ setResult(newResult);
+ }
+
+ return report.str();
+ }
+
+ int32_t getMeasuredLatency() override {
+ return mLatencyReport.latencyInFrames;
+ }
+
+ double getMeasuredConfidence() override {
+ return mLatencyReport.confidence;
+ }
+
+ double getBackgroundRMS() override {
+ return mBackgroundRMS;
+ }
+
+ double getSignalRMS() override {
+ return mSignalRMS;
+ }
+
+ void printStatus() override {
+ ALOGD("st = %d", mState);
+ }
+
+ result_code processInputFrame(float *frameData, int channelCount) override {
+ echo_state nextState = mState;
+ mLoopCounter++;
+
+ switch (mState) {
+ case STATE_MEASURE_BACKGROUND:
+ // Measure background RMS on channel 0
+ mBackgroundSumSquare += frameData[0] * frameData[0];
+ mBackgroundSumCount++;
+ mDownCounter--;
+ if (mDownCounter <= 0) {
+ mBackgroundRMS = sqrtf(mBackgroundSumSquare / mBackgroundSumCount);
+ nextState = STATE_IN_PULSE;
+ mPulseCursor = 0;
+ }
+ break;
+
+ case STATE_IN_PULSE:
+ // Record input until the mAudioRecording is full.
+ mAudioRecording.write(frameData, channelCount, 1);
+ if (hasEnoughData()) {
+ nextState = STATE_GOT_DATA;
+ }
+ break;
+
+ case STATE_GOT_DATA:
+ case STATE_DONE:
+ default:
+ break;
+ }
+
+ mState = nextState;
+ return RESULT_OK;
+ }
+
+ result_code processOutputFrame(float *frameData, int channelCount) override {
+ switch (mState) {
+ case STATE_IN_PULSE:
+ if (mPulseCursor < mPulse.size()) {
+ float pulseSample = mPulse.getData()[mPulseCursor++];
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = pulseSample;
+ }
+ } else {
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = 0;
+ }
+ }
+ break;
+
+ case STATE_MEASURE_BACKGROUND:
+ case STATE_GOT_DATA:
+ case STATE_DONE:
+ default:
+ for (int i = 0; i < channelCount; i++) {
+ frameData[i] = 0.0f; // silence
+ }
+ break;
+ }
+
+ return RESULT_OK;
+ }
+
+private:
+
+ enum echo_state {
+ STATE_MEASURE_BACKGROUND,
+ STATE_IN_PULSE,
+ STATE_GOT_DATA, // must match RoundTripLatencyActivity.java
+ STATE_DONE,
+ };
+
+ const char *convertStateToText(echo_state state) {
+ switch (state) {
+ case STATE_MEASURE_BACKGROUND:
+ return "INIT";
+ case STATE_IN_PULSE:
+ return "PULSE";
+ case STATE_GOT_DATA:
+ return "GOT_DATA";
+ case STATE_DONE:
+ return "DONE";
+ }
+ return "UNKNOWN";
+ }
+
+ int32_t mDownCounter = 500;
+ int32_t mLoopCounter = 0;
+ echo_state mState = STATE_MEASURE_BACKGROUND;
+
+ static constexpr int32_t kFramesPerEncodedBit = 8; // multiple of 2
+ static constexpr int32_t kPulseLengthMillis = 500;
+
+ AudioRecording mPulse;
+ int32_t mPulseCursor = 0;
+
+ double mBackgroundSumSquare = 0.0;
+ int32_t mBackgroundSumCount = 0;
+ double mBackgroundRMS = 0.0;
+ double mSignalRMS = 0.0;
+ int32_t mFramesToRecord = 0;
+
+ AudioRecording mAudioRecording; // contains only the input after starting the pulse
+ LatencyReport mLatencyReport;
+};
+
+#endif // ANALYZER_LATENCY_ANALYZER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h b/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h
new file mode 100644
index 0000000..0a4bd5b
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/ManchesterEncoder.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_MANCHESTER_ENCODER_H
+#define ANALYZER_MANCHESTER_ENCODER_H
+
+#include <cstdint>
+
+/**
+ * Encode bytes using Manchester Coding scheme.
+ *
+ * Manchester Code is self clocking.
+ * There is a transition in the middle of every bit.
+ * Zero is high then low.
+ * One is low then high.
+ *
+ * This avoids having long DC sections that would droop when
+ * passed though analog circuits with AC coupling.
+ *
+ * IEEE 802.3 compatible.
+ */
+
+class ManchesterEncoder {
+public:
+ ManchesterEncoder(int samplesPerPulse)
+ : mSamplesPerPulse(samplesPerPulse)
+ , mSamplesPerPulseHalf(samplesPerPulse / 2)
+ , mCursor(samplesPerPulse) {
+ }
+
+ virtual ~ManchesterEncoder() = default;
+
+ /**
+ * This will be called when the next byte is needed.
+ * @return
+ */
+ virtual uint8_t onNextByte() = 0;
+
+ /**
+ * Generate the next floating point sample.
+ * @return
+ */
+ virtual float nextFloat() {
+ advanceSample();
+ if (mCurrentBit) {
+ return (mCursor < mSamplesPerPulseHalf) ? -1.0f : 1.0f; // one
+ } else {
+ return (mCursor < mSamplesPerPulseHalf) ? 1.0f : -1.0f; // zero
+ }
+ }
+
+protected:
+ /**
+ * This will be called when a new bit is ready to be encoded.
+ * It can be used to prepare the encoded samples.
+ * @param current
+ */
+ virtual void onNextBit(bool /* current */) {};
+
+ void advanceSample() {
+ // Are we ready for a new bit?
+ if (++mCursor >= mSamplesPerPulse) {
+ mCursor = 0;
+ if (mBitsLeft == 0) {
+ mCurrentByte = onNextByte();
+ mBitsLeft = 8;
+ }
+ --mBitsLeft;
+ mCurrentBit = (mCurrentByte >> mBitsLeft) & 1;
+ onNextBit(mCurrentBit);
+ }
+ }
+
+ bool getCurrentBit() {
+ return mCurrentBit;
+ }
+
+ const int mSamplesPerPulse;
+ const int mSamplesPerPulseHalf;
+ int mCursor;
+ int mBitsLeft = 0;
+ uint8_t mCurrentByte = 0;
+ bool mCurrentBit = false;
+};
+#endif //ANALYZER_MANCHESTER_ENCODER_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h b/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h
new file mode 100644
index 0000000..4b3b4e7
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/PeakDetector.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_PEAK_DETECTOR_H
+#define ANALYZER_PEAK_DETECTOR_H
+
+#include <math.h>
+
+/**
+ * Measure a peak envelope by rising with the peaks,
+ * and decaying exponentially after each peak.
+ * The absolute value of the input signal is used.
+ */
+class PeakDetector {
+public:
+
+ void reset() {
+ mLevel = 0.0;
+ }
+
+ double process(double input) {
+ mLevel *= mDecay; // exponential decay
+ input = fabs(input);
+ // never fall below the input signal
+ if (input > mLevel) {
+ mLevel = input;
+ }
+ return mLevel;
+ }
+
+ double getLevel() const {
+ return mLevel;
+ }
+
+ double getDecay() const {
+ return mDecay;
+ }
+
+ /**
+ * Multiply the level by this amount on every iteration.
+ * This provides an exponential decay curve.
+ * A value just under 1.0 is best, for example, 0.99;
+ * @param decay scale level for each input
+ */
+ void setDecay(double decay) {
+ mDecay = decay;
+ }
+
+private:
+ static constexpr double kDefaultDecay = 0.99f;
+
+ double mLevel = 0.0;
+ double mDecay = kDefaultDecay;
+};
+#endif //ANALYZER_PEAK_DETECTOR_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h b/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h
new file mode 100644
index 0000000..1c4938c
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/PseudoRandom.h
@@ -0,0 +1,57 @@
+/*
+ * 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 ANALYZER_PSEUDORANDOM_H
+#define ANALYZER_PSEUDORANDOM_H
+
+#include <cctype>
+
+class PseudoRandom {
+public:
+ PseudoRandom(int64_t seed = 99887766)
+ : mSeed(seed)
+ {}
+
+ /**
+ * Returns the next random double from -1.0 to 1.0
+ *
+ * @return value from -1.0 to 1.0
+ */
+ double nextRandomDouble() {
+ return nextRandomInteger() * (0.5 / (((int32_t)1) << 30));
+ }
+
+ /** Calculate random 32 bit number using linear-congruential method
+ * with known real-time performance.
+ */
+ int32_t nextRandomInteger() {
+#if __has_builtin(__builtin_mul_overflow) && __has_builtin(__builtin_add_overflow)
+ int64_t prod;
+ // Use values for 64-bit sequence from MMIX by Donald Knuth.
+ __builtin_mul_overflow(mSeed, (int64_t)6364136223846793005, &prod);
+ __builtin_add_overflow(prod, (int64_t)1442695040888963407, &mSeed);
+#else
+ mSeed = (mSeed * (int64_t)6364136223846793005) + (int64_t)1442695040888963407;
+#endif
+ return (int32_t) (mSeed >> 32); // The higher bits have a longer sequence.
+ }
+
+private:
+ int64_t mSeed;
+};
+
+#endif //ANALYZER_PSEUDORANDOM_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h b/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h
new file mode 100644
index 0000000..030050b
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/RandomPulseGenerator.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_RANDOM_PULSE_GENERATOR_H
+#define ANALYZER_RANDOM_PULSE_GENERATOR_H
+
+#include <stdlib.h>
+#include "RoundedManchesterEncoder.h"
+
+/**
+ * Encode random ones and zeros using Manchester Code per IEEE 802.3.
+ */
+class RandomPulseGenerator : public RoundedManchesterEncoder {
+public:
+ RandomPulseGenerator(int samplesPerPulse)
+ : RoundedManchesterEncoder(samplesPerPulse) {
+ }
+
+ virtual ~RandomPulseGenerator() = default;
+
+ /**
+ * This will be called when the next byte is needed.
+ * @return random byte
+ */
+ uint8_t onNextByte() override {
+ return static_cast<uint8_t>(rand());
+ }
+};
+
+#endif //ANALYZER_RANDOM_PULSE_GENERATOR_H
diff --git a/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h b/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h
new file mode 100644
index 0000000..f2eba84
--- /dev/null
+++ b/media/libaaudio/examples/loopback/src/analyzer/RoundedManchesterEncoder.h
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
+#define ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
+
+#include <math.h>
+#include <memory.h>
+#include <stdlib.h>
+#include "ManchesterEncoder.h"
+
+/**
+ * Encode bytes using Manchester Code.
+ * Round the edges using a half cosine to reduce ringing caused by a hard edge.
+ */
+
+class RoundedManchesterEncoder : public ManchesterEncoder {
+public:
+ RoundedManchesterEncoder(int samplesPerPulse)
+ : ManchesterEncoder(samplesPerPulse) {
+ int rampSize = samplesPerPulse / 4;
+ mZeroAfterZero = std::make_unique<float[]>(samplesPerPulse);
+ mZeroAfterOne = std::make_unique<float[]>(samplesPerPulse);
+
+ int sampleIndex = 0;
+ for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+ float phase = (rampIndex + 1) * M_PI / rampSize;
+ float sample = -cosf(phase);
+ mZeroAfterZero[sampleIndex] = sample;
+ mZeroAfterOne[sampleIndex] = 1.0f;
+ sampleIndex++;
+ }
+ for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+ mZeroAfterZero[sampleIndex] = 1.0f;
+ mZeroAfterOne[sampleIndex] = 1.0f;
+ sampleIndex++;
+ }
+ for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+ float phase = (rampIndex + 1) * M_PI / rampSize;
+ float sample = cosf(phase);
+ mZeroAfterZero[sampleIndex] = sample;
+ mZeroAfterOne[sampleIndex] = sample;
+ sampleIndex++;
+ }
+ for (int rampIndex = 0; rampIndex < rampSize; rampIndex++) {
+ mZeroAfterZero[sampleIndex] = -1.0f;
+ mZeroAfterOne[sampleIndex] = -1.0f;
+ sampleIndex++;
+ }
+ }
+
+ void onNextBit(bool current) override {
+ // Do we need to use the rounded edge?
+ mCurrentSamples = (current ^ mPreviousBit)
+ ? mZeroAfterOne.get()
+ : mZeroAfterZero.get();
+ mPreviousBit = current;
+ }
+
+ float nextFloat() override {
+ advanceSample();
+ float output = mCurrentSamples[mCursor];
+ if (getCurrentBit()) output = -output;
+ return output;
+ }
+
+private:
+
+ bool mPreviousBit = false;
+ float *mCurrentSamples = nullptr;
+ std::unique_ptr<float[]> mZeroAfterZero;
+ std::unique_ptr<float[]> mZeroAfterOne;
+};
+
+#endif //ANALYZER_ROUNDED_MANCHESTER_ENCODER_H
diff --git a/media/libaaudio/examples/loopback/src/loopback.cpp b/media/libaaudio/examples/loopback/src/loopback.cpp
index 49d921f..0d2ec70 100644
--- a/media/libaaudio/examples/loopback/src/loopback.cpp
+++ b/media/libaaudio/examples/loopback/src/loopback.cpp
@@ -20,6 +20,8 @@
#include <assert.h>
#include <cctype>
#include <errno.h>
+#include <iomanip>
+#include <iostream>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
@@ -33,7 +35,9 @@
#include "AAudioSimplePlayer.h"
#include "AAudioSimpleRecorder.h"
#include "AAudioExampleUtils.h"
-#include "LoopbackAnalyzer.h"
+
+#include "analyzer/GlitchAnalyzer.h"
+#include "analyzer/LatencyAnalyzer.h"
#include "../../utils/AAudioExampleUtils.h"
// V0.4.00 = rectify and low-pass filter the echos, auto-correlate entire echo
@@ -41,7 +45,8 @@
// fix -n option to set output buffer for -tm
// plot first glitch
// V0.4.02 = allow -n0 for minimal buffer size
-#define APP_VERSION "0.4.02"
+// V0.5.00 = use latency analyzer from OboeTester, uses random noise for latency
+#define APP_VERSION "0.5.00"
// Tag for machine readable results as property = value pairs
#define RESULT_TAG "RESULT: "
@@ -57,6 +62,20 @@
constexpr int kDefaultHangTimeMillis = 50;
constexpr int kMaxGlitchEventsToSave = 32;
+static void printAudioScope(float sample) {
+ const int maxStars = 80; // arbitrary, fits on one line
+ char c = '*';
+ if (sample < -1.0) {
+ sample = -1.0;
+ c = '$';
+ } else if (sample > 1.0) {
+ sample = 1.0;
+ c = '$';
+ }
+ int numSpaces = (int) (((sample + 1.0) * 0.5) * maxStars);
+ printf("%*c%c\n", numSpaces, ' ', c);
+}
+
struct LoopbackData {
AAudioStream *inputStream = nullptr;
AAudioStream *outputStream = nullptr;
@@ -83,8 +102,8 @@
aaudio_result_t inputError = AAUDIO_OK;
aaudio_result_t outputError = AAUDIO_OK;
- SineAnalyzer sineAnalyzer;
- EchoAnalyzer echoAnalyzer;
+ GlitchAnalyzer sineAnalyzer;
+ PulseLatencyAnalyzer echoAnalyzer;
AudioRecording audioRecording;
LoopbackProcessor *loopbackProcessor;
@@ -254,17 +273,18 @@
}
// Analyze the data.
- LoopbackProcessor::process_result procResult = myData->loopbackProcessor->process(myData->inputFloatData,
+ myData->loopbackProcessor->process(myData->inputFloatData,
myData->actualInputChannelCount,
+ numFrames,
outputData,
myData->actualOutputChannelCount,
numFrames);
-
- if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
- if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
- myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
- }
- }
+//
+// if (procResult == LoopbackProcessor::PROCESS_RESULT_GLITCH) {
+// if (myData->numGlitchEvents < kMaxGlitchEventsToSave) {
+// myData->glitchFrames[myData->numGlitchEvents++] = myData->audioRecording.size();
+// }
+// }
// Save for later.
myData->audioRecording.write(myData->inputFloatData,
@@ -283,8 +303,8 @@
}
static void MyErrorCallbackProc(
- AAudioStream *stream __unused,
- void *userData __unused,
+ AAudioStream * /* stream */,
+ void * userData,
aaudio_result_t error) {
printf("Error Callback, error: %d\n",(int)error);
LoopbackData *myData = (LoopbackData *) userData;
@@ -305,8 +325,8 @@
printf(" l for _LATENCY\n");
printf(" p for _POWER_SAVING\n");
printf(" -t{test} select test mode\n");
- printf(" m for sine magnitude\n");
- printf(" e for echo latency (default)\n");
+ printf(" g for Glitch detection\n");
+ printf(" l for round trip Latency (default)\n");
printf(" f for file latency, analyzes %s\n\n", FILENAME_ECHOS);
printf(" -X use EXCLUSIVE mode for input\n");
printf("Example: aaudio_loopback -n2 -pl -Pl -x\n");
@@ -333,20 +353,22 @@
}
enum {
- TEST_SINE_MAGNITUDE = 0,
- TEST_ECHO_LATENCY,
+ TEST_GLITCHES = 0,
+ TEST_LATENCY,
TEST_FILE_LATENCY,
};
static int parseTestMode(char c) {
- int testMode = TEST_ECHO_LATENCY;
+ int testMode = TEST_LATENCY;
c = tolower(c);
switch (c) {
- case 'm':
- testMode = TEST_SINE_MAGNITUDE;
+ case 'm': // deprecated
+ case 'g':
+ testMode = TEST_GLITCHES;
break;
- case 'e':
- testMode = TEST_ECHO_LATENCY;
+ case 'e': // deprecated
+ case 'l':
+ testMode = TEST_LATENCY;
break;
case 'f':
testMode = TEST_FILE_LATENCY;
@@ -408,9 +430,10 @@
int32_t actualSampleRate = 0;
int written = 0;
- int testMode = TEST_ECHO_LATENCY;
+ int testMode = TEST_LATENCY;
double gain = 1.0;
int hangTimeMillis = 0;
+ std::string report;
// Make printf print immediately so that debug info is not stuck
// in a buffer if we hang or crash.
@@ -488,22 +511,21 @@
int32_t requestedOutputBursts = argParser.getNumberOfBursts();
switch(testMode) {
- case TEST_SINE_MAGNITUDE:
+ case TEST_GLITCHES:
loopbackData.loopbackProcessor = &loopbackData.sineAnalyzer;
break;
- case TEST_ECHO_LATENCY:
- loopbackData.echoAnalyzer.setGain(gain);
+ case TEST_LATENCY:
+ // TODO loopbackData.echoAnalyzer.setGain(gain);
loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
break;
case TEST_FILE_LATENCY: {
- loopbackData.echoAnalyzer.setGain(gain);
-
+ // TODO loopbackData.echoAnalyzer.setGain(gain);
loopbackData.loopbackProcessor = &loopbackData.echoAnalyzer;
int read = loopbackData.loopbackProcessor->load(FILENAME_ECHOS);
printf("main() read %d mono samples from %s on Android device, rate = %d\n",
read, FILENAME_ECHOS,
loopbackData.loopbackProcessor->getSampleRate());
- loopbackData.loopbackProcessor->report();
+ std::cout << loopbackData.loopbackProcessor->analyze();
goto report_result;
}
break;
@@ -557,7 +579,7 @@
int32_t actualCapacity = AAudioStream_getBufferCapacityInFrames(inputStream);
(void) AAudioStream_setBufferSizeInFrames(inputStream, actualCapacity);
- if (testMode == TEST_SINE_MAGNITUDE
+ if (testMode == TEST_GLITCHES
&& requestedOutputBursts == AAUDIO_UNSPECIFIED) {
result = AAudioStream_setBufferSizeInFrames(outputStream, actualCapacity);
if (result < 0) {
@@ -594,10 +616,10 @@
loopbackData.inputFloatData = new float[loopbackData.inputFramesMaximum *
loopbackData.actualInputChannelCount]{};
- loopbackData.loopbackProcessor->reset();
-
loopbackData.hangTimeMillis = hangTimeMillis;
+ loopbackData.loopbackProcessor->prepareToTest();
+
// Start OUTPUT first so INPUT does not overflow.
result = player.start();
if (result != AAUDIO_OK) {
@@ -669,7 +691,8 @@
printf("input error = %d = %s\n",
loopbackData.inputError, AAudio_convertResultToText(loopbackData.inputError));
-
+/*
+ // TODO Restore this code some day if we want to save files.
written = loopbackData.loopbackProcessor->save(FILENAME_ECHOS);
if (written > 0) {
printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
@@ -681,9 +704,9 @@
printf("main() wrote %8d mono samples to \"%s\" on Android device\n",
written, FILENAME_ALL);
}
-
+*/
if (loopbackData.inputError == AAUDIO_OK) {
- if (testMode == TEST_SINE_MAGNITUDE) {
+ if (testMode == TEST_GLITCHES) {
if (loopbackData.numGlitchEvents > 0) {
// Graph around the first glitch if there is one.
const int32_t start = loopbackData.glitchFrames[0] - 8;
@@ -697,7 +720,8 @@
}
}
- loopbackData.loopbackProcessor->report();
+ std::cout << "Please wait several seconds for analysis to complete.\n";
+ std::cout << loopbackData.loopbackProcessor->analyze();
}
{
diff --git a/media/libaaudio/tests/test_various.cpp b/media/libaaudio/tests/test_various.cpp
index 4b065c9..935d88b 100644
--- a/media/libaaudio/tests/test_various.cpp
+++ b/media/libaaudio/tests/test_various.cpp
@@ -441,7 +441,7 @@
bufferCapacity, bufferCapacity % framesPerBurst);
actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 0);
- EXPECT_GT(actualSize, 0);
+ EXPECT_GE(actualSize, 0); // 0 is legal in R
EXPECT_LE(actualSize, bufferCapacity);
actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, 2 * framesPerBurst);
@@ -469,7 +469,7 @@
EXPECT_LE(actualSize, bufferCapacity);
actualSize = AAudioStream_setBufferSizeInFrames(aaudioStream, INT32_MIN);
- EXPECT_GT(actualSize, 0);
+ EXPECT_GE(actualSize, 0); // 0 is legal in R
EXPECT_LE(actualSize, bufferCapacity);
AAudioStream_close(aaudioStream);
diff --git a/media/libaudioclient/Android.bp b/media/libaudioclient/Android.bp
index d1812e6..3638d2c 100644
--- a/media/libaudioclient/Android.bp
+++ b/media/libaudioclient/Android.bp
@@ -90,7 +90,12 @@
],
export_shared_lib_headers: ["libbinder"],
- local_include_dirs: ["include/media", "aidl"],
+ include_dirs: [
+ "frameworks/av/media/libnbaio/include_mono/",
+ ],
+ local_include_dirs: [
+ "include/media", "aidl"
+ ],
header_libs: [
"libaudioclient_headers",
"libbase_headers",
diff --git a/media/libaudioclient/AudioRecord.cpp b/media/libaudioclient/AudioRecord.cpp
index c22db59..a438c77 100644
--- a/media/libaudioclient/AudioRecord.cpp
+++ b/media/libaudioclient/AudioRecord.cpp
@@ -32,7 +32,7 @@
#include <private/media/AudioTrackShared.h>
#include <processgroup/sched_policy.h>
#include <media/IAudioFlinger.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/TypeConverter.h>
#define WAIT_PERIOD_MS 10
@@ -79,37 +79,37 @@
#define MM_PREFIX "android.media.audiorecord." // avoid cut-n-paste errors.
// Java API 28 entries, do not change.
- mAnalyticsItem->setCString(MM_PREFIX "encoding", toString(record->mFormat).c_str());
- mAnalyticsItem->setCString(MM_PREFIX "source", toString(record->mAttributes.source).c_str());
- mAnalyticsItem->setInt32(MM_PREFIX "latency", (int32_t)record->mLatency); // bad estimate.
- mAnalyticsItem->setInt32(MM_PREFIX "samplerate", (int32_t)record->mSampleRate);
- mAnalyticsItem->setInt32(MM_PREFIX "channels", (int32_t)record->mChannelCount);
+ mMetricsItem->setCString(MM_PREFIX "encoding", toString(record->mFormat).c_str());
+ mMetricsItem->setCString(MM_PREFIX "source", toString(record->mAttributes.source).c_str());
+ mMetricsItem->setInt32(MM_PREFIX "latency", (int32_t)record->mLatency); // bad estimate.
+ mMetricsItem->setInt32(MM_PREFIX "samplerate", (int32_t)record->mSampleRate);
+ mMetricsItem->setInt32(MM_PREFIX "channels", (int32_t)record->mChannelCount);
// Non-API entries, these can change.
- mAnalyticsItem->setInt32(MM_PREFIX "portId", (int32_t)record->mPortId);
- mAnalyticsItem->setInt32(MM_PREFIX "frameCount", (int32_t)record->mFrameCount);
- mAnalyticsItem->setCString(MM_PREFIX "attributes", toString(record->mAttributes).c_str());
- mAnalyticsItem->setInt64(MM_PREFIX "channelMask", (int64_t)record->mChannelMask);
+ mMetricsItem->setInt32(MM_PREFIX "portId", (int32_t)record->mPortId);
+ mMetricsItem->setInt32(MM_PREFIX "frameCount", (int32_t)record->mFrameCount);
+ mMetricsItem->setCString(MM_PREFIX "attributes", toString(record->mAttributes).c_str());
+ mMetricsItem->setInt64(MM_PREFIX "channelMask", (int64_t)record->mChannelMask);
// log total duration recording, including anything currently running.
int64_t activeNs = 0;
if (mStartedNs != 0) {
activeNs = systemTime() - mStartedNs;
}
- mAnalyticsItem->setDouble(MM_PREFIX "durationMs", (mDurationNs + activeNs) * 1e-6);
- mAnalyticsItem->setInt64(MM_PREFIX "startCount", (int64_t)mCount);
+ mMetricsItem->setDouble(MM_PREFIX "durationMs", (mDurationNs + activeNs) * 1e-6);
+ mMetricsItem->setInt64(MM_PREFIX "startCount", (int64_t)mCount);
if (mLastError != NO_ERROR) {
- mAnalyticsItem->setInt32(MM_PREFIX "lastError.code", (int32_t)mLastError);
- mAnalyticsItem->setCString(MM_PREFIX "lastError.at", mLastErrorFunc.c_str());
+ mMetricsItem->setInt32(MM_PREFIX "lastError.code", (int32_t)mLastError);
+ mMetricsItem->setCString(MM_PREFIX "lastError.at", mLastErrorFunc.c_str());
}
}
// hand the user a snapshot of the metrics.
-status_t AudioRecord::getMetrics(MediaAnalyticsItem * &item)
+status_t AudioRecord::getMetrics(mediametrics::Item * &item)
{
mMediaMetrics.gather(this);
- MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ mediametrics::Item *tmp = mMediaMetrics.dup();
if (tmp == nullptr) {
return BAD_VALUE;
}
@@ -896,7 +896,6 @@
{
// previous and new IAudioRecord sequence numbers are used to detect track re-creation
uint32_t oldSequence = 0;
- uint32_t newSequence;
Proxy::Buffer buffer;
status_t status = NO_ERROR;
@@ -914,7 +913,7 @@
// start of lock scope
AutoMutex lock(mLock);
- newSequence = mSequence;
+ uint32_t newSequence = mSequence;
// did previous obtainBuffer() fail due to media server death or voluntary invalidation?
if (status == DEAD_OBJECT) {
// re-create track, unless someone else has already done so
@@ -951,6 +950,7 @@
audioBuffer->frameCount = buffer.mFrameCount;
audioBuffer->size = buffer.mFrameCount * mFrameSize;
audioBuffer->raw = buffer.mRaw;
+ audioBuffer->sequence = oldSequence;
if (nonContig != NULL) {
*nonContig = buffer.mNonContig;
}
@@ -971,6 +971,12 @@
buffer.mRaw = audioBuffer->raw;
AutoMutex lock(mLock);
+ if (audioBuffer->sequence != mSequence) {
+ // This Buffer came from a different IAudioRecord instance, so ignore the releaseBuffer
+ ALOGD("%s is no-op due to IAudioRecord sequence mismatch %u != %u",
+ __func__, audioBuffer->sequence, mSequence);
+ return;
+ }
mInOverrun = false;
mProxy->releaseBuffer(&buffer);
diff --git a/media/libaudioclient/AudioSystem.cpp b/media/libaudioclient/AudioSystem.cpp
index ad39abe..0742091 100644
--- a/media/libaudioclient/AudioSystem.cpp
+++ b/media/libaudioclient/AudioSystem.cpp
@@ -1017,6 +1017,16 @@
return aps->getDevicesForStream(stream);
}
+status_t AudioSystem::getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices) {
+ if (devices == nullptr) {
+ return BAD_VALUE;
+ }
+ const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
+ if (aps == 0) return PERMISSION_DENIED;
+ return aps->getDevicesForAttributes(aa, devices);
+}
+
audio_io_handle_t AudioSystem::getOutputForEffect(const effect_descriptor_t *desc)
{
const sp<IAudioPolicyService>& aps = AudioSystem::get_audio_policy_service();
@@ -1491,7 +1501,14 @@
}
}
}
- ALOGE("invalid attributes %s when converting to stream", toString(attr).c_str());
+ switch (attr.usage) {
+ case AUDIO_USAGE_VIRTUAL_SOURCE:
+ // virtual source is not expected to have an associated product strategy
+ break;
+ default:
+ ALOGE("invalid attributes %s when converting to stream", toString(attr).c_str());
+ break;
+ }
return AUDIO_STREAM_MUSIC;
}
diff --git a/media/libaudioclient/AudioTrack.cpp b/media/libaudioclient/AudioTrack.cpp
index e8d7b60..3151feb 100644
--- a/media/libaudioclient/AudioTrack.cpp
+++ b/media/libaudioclient/AudioTrack.cpp
@@ -35,7 +35,7 @@
#include <media/AudioParameter.h>
#include <media/AudioResamplerPublic.h>
#include <media/AudioSystem.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/TypeConverter.h>
#define WAIT_PERIOD_MS 10
@@ -183,26 +183,26 @@
#define MM_PREFIX "android.media.audiotrack." // avoid cut-n-paste errors.
// Java API 28 entries, do not change.
- mAnalyticsItem->setCString(MM_PREFIX "streamtype", toString(track->streamType()).c_str());
- mAnalyticsItem->setCString(MM_PREFIX "type",
+ mMetricsItem->setCString(MM_PREFIX "streamtype", toString(track->streamType()).c_str());
+ mMetricsItem->setCString(MM_PREFIX "type",
toString(track->mAttributes.content_type).c_str());
- mAnalyticsItem->setCString(MM_PREFIX "usage", toString(track->mAttributes.usage).c_str());
+ mMetricsItem->setCString(MM_PREFIX "usage", toString(track->mAttributes.usage).c_str());
// Non-API entries, these can change due to a Java string mistake.
- mAnalyticsItem->setInt32(MM_PREFIX "sampleRate", (int32_t)track->mSampleRate);
- mAnalyticsItem->setInt64(MM_PREFIX "channelMask", (int64_t)track->mChannelMask);
+ mMetricsItem->setInt32(MM_PREFIX "sampleRate", (int32_t)track->mSampleRate);
+ mMetricsItem->setInt64(MM_PREFIX "channelMask", (int64_t)track->mChannelMask);
// Non-API entries, these can change.
- mAnalyticsItem->setInt32(MM_PREFIX "portId", (int32_t)track->mPortId);
- mAnalyticsItem->setCString(MM_PREFIX "encoding", toString(track->mFormat).c_str());
- mAnalyticsItem->setInt32(MM_PREFIX "frameCount", (int32_t)track->mFrameCount);
- mAnalyticsItem->setCString(MM_PREFIX "attributes", toString(track->mAttributes).c_str());
+ mMetricsItem->setInt32(MM_PREFIX "portId", (int32_t)track->mPortId);
+ mMetricsItem->setCString(MM_PREFIX "encoding", toString(track->mFormat).c_str());
+ mMetricsItem->setInt32(MM_PREFIX "frameCount", (int32_t)track->mFrameCount);
+ mMetricsItem->setCString(MM_PREFIX "attributes", toString(track->mAttributes).c_str());
}
// hand the user a snapshot of the metrics.
-status_t AudioTrack::getMetrics(MediaAnalyticsItem * &item)
+status_t AudioTrack::getMetrics(mediametrics::Item * &item)
{
mMediaMetrics.gather(this);
- MediaAnalyticsItem *tmp = mMediaMetrics.dup();
+ mediametrics::Item *tmp = mMediaMetrics.dup();
if (tmp == nullptr) {
return BAD_VALUE;
}
diff --git a/media/libaudioclient/IAudioPolicyService.cpp b/media/libaudioclient/IAudioPolicyService.cpp
index ab942df..87802a1 100644
--- a/media/libaudioclient/IAudioPolicyService.cpp
+++ b/media/libaudioclient/IAudioPolicyService.cpp
@@ -109,6 +109,7 @@
SET_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY,
REMOVE_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY,
GET_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY,
+ GET_DEVICES_FOR_ATTRIBUTES,
};
#define MAX_ITEMS_PER_LIST 1024
@@ -1348,6 +1349,41 @@
}
return static_cast<status_t>(reply.readInt32());
}
+
+ virtual status_t getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices) const
+ {
+ if (devices == nullptr) {
+ return BAD_VALUE;
+ }
+ Parcel data, reply;
+ data.writeInterfaceToken(IAudioPolicyService::getInterfaceDescriptor());
+ status_t status = aa.writeToParcel(&data);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ status = remote()->transact(GET_DEVICES_FOR_ATTRIBUTES, data, &reply);
+ if (status != NO_ERROR) {
+ // transaction failed, return error
+ return status;
+ }
+ status = static_cast<status_t>(reply.readInt32());
+ if (status != NO_ERROR) {
+ // APM method call failed, return error
+ return status;
+ }
+
+ const size_t numberOfDevices = (size_t)reply.readInt32();
+ for (size_t i = 0; i < numberOfDevices; i++) {
+ AudioDeviceTypeAddr device;
+ if (device.readFromParcel((Parcel*)&reply) == NO_ERROR) {
+ devices->push_back(device);
+ } else {
+ return FAILED_TRANSACTION;
+ }
+ }
+ return NO_ERROR;
+ }
};
IMPLEMENT_META_INTERFACE(AudioPolicyService, "android.media.IAudioPolicyService");
@@ -1371,8 +1407,6 @@
case UNREGISTER_EFFECT:
case SET_EFFECT_ENABLED:
case GET_OUTPUT_FOR_ATTR:
- case ACQUIRE_SOUNDTRIGGER_SESSION:
- case RELEASE_SOUNDTRIGGER_SESSION:
case MOVE_EFFECTS_TO_IO:
ALOGW("%s: transaction %d received from PID %d",
__func__, code, IPCThreadState::self()->getCallingPid());
@@ -1410,11 +1444,14 @@
case GET_OFFLOAD_FORMATS_A2DP:
case LIST_AUDIO_VOLUME_GROUPS:
case GET_VOLUME_GROUP_FOR_ATTRIBUTES:
+ case ACQUIRE_SOUNDTRIGGER_SESSION:
+ case RELEASE_SOUNDTRIGGER_SESSION:
case SET_RTT_ENABLED:
case IS_CALL_SCREEN_MODE_SUPPORTED:
case SET_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY:
case REMOVE_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY:
- case GET_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY: {
+ case GET_PREFERRED_DEVICE_FOR_PRODUCT_STRATEGY:
+ case GET_DEVICES_FOR_ATTRIBUTES: {
if (!isServiceUid(IPCThreadState::self()->getCallingUid())) {
ALOGW("%s: transaction %d received from PID %d unauthorized UID %d",
__func__, code, IPCThreadState::self()->getCallingPid(),
@@ -2027,8 +2064,6 @@
case ACQUIRE_SOUNDTRIGGER_SESSION: {
CHECK_INTERFACE(IAudioPolicyService, data, reply);
- sp<IAudioPolicyServiceClient> client = interface_cast<IAudioPolicyServiceClient>(
- data.readStrongBinder());
audio_session_t session = AUDIO_SESSION_NONE;
audio_io_handle_t ioHandle = AUDIO_IO_HANDLE_NONE;
audio_devices_t device = AUDIO_DEVICE_NONE;
@@ -2044,8 +2079,6 @@
case RELEASE_SOUNDTRIGGER_SESSION: {
CHECK_INTERFACE(IAudioPolicyService, data, reply);
- sp<IAudioPolicyServiceClient> client = interface_cast<IAudioPolicyServiceClient>(
- data.readStrongBinder());
audio_session_t session = (audio_session_t)data.readInt32();
status_t status = releaseSoundTriggerSession(session);
reply->writeInt32(status);
@@ -2477,6 +2510,37 @@
return NO_ERROR;
}
+ case GET_DEVICES_FOR_ATTRIBUTES: {
+ CHECK_INTERFACE(IAudioPolicyService, data, reply);
+ AudioAttributes attributes;
+ status_t status = attributes.readFromParcel(&data);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ AudioDeviceTypeAddrVector devices;
+ status = getDevicesForAttributes(attributes.getAttributes(), &devices);
+ // reply data formatted as:
+ // - (int32) method call result from APM
+ // - (int32) number of devices (n) if method call returned NO_ERROR
+ // - n AudioDeviceTypeAddr if method call returned NO_ERROR
+ reply->writeInt32(status);
+ if (status != NO_ERROR) {
+ return NO_ERROR;
+ }
+ status = reply->writeInt32(devices.size());
+ if (status != NO_ERROR) {
+ return status;
+ }
+ for (const auto& device : devices) {
+ status = device.writeToParcel(reply);
+ if (status != NO_ERROR) {
+ return status;
+ }
+ }
+
+ return NO_ERROR;
+ }
+
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libaudioclient/include/media/AudioRecord.h b/media/libaudioclient/include/media/AudioRecord.h
index a3c0fe4..b510378 100644
--- a/media/libaudioclient/include/media/AudioRecord.h
+++ b/media/libaudioclient/include/media/AudioRecord.h
@@ -24,7 +24,7 @@
#include <cutils/sched_policy.h>
#include <media/AudioSystem.h>
#include <media/AudioTimestamp.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/Modulo.h>
#include <media/MicrophoneInfo.h>
#include <media/RecordingActivityTracker.h>
@@ -92,6 +92,11 @@
int8_t* i8; // unsigned 8-bit, offset by 0x80
// input to obtainBuffer(): unused, output: pointer to buffer
};
+
+ uint32_t sequence; // IAudioRecord instance sequence number, as of obtainBuffer().
+ // It is set by obtainBuffer() and confirmed by releaseBuffer().
+ // Not "user-serviceable".
+ // TODO Consider sp<IMemory> instead, or in addition to this.
};
/* As a convenience, if a callback is supplied, a handler thread
@@ -270,7 +275,7 @@
/*
* return metrics information for the current instance.
*/
- status_t getMetrics(MediaAnalyticsItem * &item);
+ status_t getMetrics(mediametrics::Item * &item);
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
@@ -420,14 +425,17 @@
* frameCount number of frames requested
* size ignored
* raw ignored
+ * sequence ignored
* After error return:
* frameCount 0
* size 0
* raw undefined
+ * sequence undefined
* After successful return:
* frameCount actual number of frames available, <= number requested
* size actual number of bytes available
* raw pointer to the buffer
+ * sequence IAudioRecord instance sequence number, as of obtainBuffer()
*/
status_t obtainBuffer(Buffer* audioBuffer, int32_t waitCount,
@@ -733,27 +741,27 @@
private:
class MediaMetrics {
public:
- MediaMetrics() : mAnalyticsItem(MediaAnalyticsItem::create("audiorecord")),
+ MediaMetrics() : mMetricsItem(mediametrics::Item::create("audiorecord")),
mCreatedNs(systemTime(SYSTEM_TIME_REALTIME)),
mStartedNs(0), mDurationNs(0), mCount(0),
mLastError(NO_ERROR) {
}
~MediaMetrics() {
- // mAnalyticsItem alloc failure will be flagged in the constructor
+ // mMetricsItem alloc failure will be flagged in the constructor
// don't log empty records
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
+ if (mMetricsItem->count() > 0) {
+ mMetricsItem->selfrecord();
}
}
void gather(const AudioRecord *record);
- MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
+ mediametrics::Item *dup() { return mMetricsItem->dup(); }
void logStart(nsecs_t when) { mStartedNs = when; mCount++; }
void logStop(nsecs_t when) { mDurationNs += (when-mStartedNs); mStartedNs = 0;}
void markError(status_t errcode, const char *func)
{ mLastError = errcode; mLastErrorFunc = func;}
private:
- std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
+ std::unique_ptr<mediametrics::Item> mMetricsItem;
nsecs_t mCreatedNs; // XXX: perhaps not worth it in production
nsecs_t mStartedNs;
nsecs_t mDurationNs;
diff --git a/media/libaudioclient/include/media/AudioSystem.h b/media/libaudioclient/include/media/AudioSystem.h
index c4b528e..ae8b59c 100644
--- a/media/libaudioclient/include/media/AudioSystem.h
+++ b/media/libaudioclient/include/media/AudioSystem.h
@@ -279,6 +279,8 @@
static uint32_t getStrategyForStream(audio_stream_type_t stream);
static audio_devices_t getDevicesForStream(audio_stream_type_t stream);
+ static status_t getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices);
static audio_io_handle_t getOutputForEffect(const effect_descriptor_t *desc);
static status_t registerEffect(const effect_descriptor_t *desc,
diff --git a/media/libaudioclient/include/media/AudioTrack.h b/media/libaudioclient/include/media/AudioTrack.h
index df5eabc..cfbce03 100644
--- a/media/libaudioclient/include/media/AudioTrack.h
+++ b/media/libaudioclient/include/media/AudioTrack.h
@@ -22,7 +22,7 @@
#include <media/AudioTimestamp.h>
#include <media/IAudioTrack.h>
#include <media/AudioResamplerPublic.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/Modulo.h>
#include <utils/threads.h>
@@ -400,7 +400,7 @@
/*
* return metrics information for the current track.
*/
- status_t getMetrics(MediaAnalyticsItem * &item);
+ status_t getMetrics(mediametrics::Item * &item);
/* After it's created the track is not active. Call start() to
* make it active. If set, the callback will start being called.
@@ -1230,19 +1230,19 @@
private:
class MediaMetrics {
public:
- MediaMetrics() : mAnalyticsItem(MediaAnalyticsItem::create("audiotrack")) {
+ MediaMetrics() : mMetricsItem(mediametrics::Item::create("audiotrack")) {
}
~MediaMetrics() {
- // mAnalyticsItem alloc failure will be flagged in the constructor
+ // mMetricsItem alloc failure will be flagged in the constructor
// don't log empty records
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
+ if (mMetricsItem->count() > 0) {
+ mMetricsItem->selfrecord();
}
}
void gather(const AudioTrack *track);
- MediaAnalyticsItem *dup() { return mAnalyticsItem->dup(); }
+ mediametrics::Item *dup() { return mMetricsItem->dup(); }
private:
- std::unique_ptr<MediaAnalyticsItem> mAnalyticsItem;
+ std::unique_ptr<mediametrics::Item> mMetricsItem;
};
MediaMetrics mMediaMetrics;
};
diff --git a/media/libaudioclient/include/media/IAudioPolicyService.h b/media/libaudioclient/include/media/IAudioPolicyService.h
index 6623061..742762d 100644
--- a/media/libaudioclient/include/media/IAudioPolicyService.h
+++ b/media/libaudioclient/include/media/IAudioPolicyService.h
@@ -108,6 +108,8 @@
virtual uint32_t getStrategyForStream(audio_stream_type_t stream) = 0;
virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream) = 0;
+ virtual status_t getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices) const = 0;
virtual audio_io_handle_t getOutputForEffect(const effect_descriptor_t *desc) = 0;
virtual status_t registerEffect(const effect_descriptor_t *desc,
audio_io_handle_t io,
diff --git a/media/libaudiofoundation/AudioDeviceTypeAddr.cpp b/media/libaudiofoundation/AudioDeviceTypeAddr.cpp
index d390467..b44043a 100644
--- a/media/libaudiofoundation/AudioDeviceTypeAddr.cpp
+++ b/media/libaudiofoundation/AudioDeviceTypeAddr.cpp
@@ -26,6 +26,16 @@
return mType == other.mType && mAddress == other.mAddress;
}
+bool AudioDeviceTypeAddr::operator<(const AudioDeviceTypeAddr& other) const {
+ if (mType < other.mType) return true;
+ if (mType > other.mType) return false;
+
+ if (mAddress < other.mAddress) return true;
+ // if (mAddress > other.mAddress) return false;
+
+ return false;
+}
+
void AudioDeviceTypeAddr::reset() {
mType = AUDIO_DEVICE_NONE;
mAddress = "";
diff --git a/media/libaudiofoundation/include/media/AudioDeviceTypeAddr.h b/media/libaudiofoundation/include/media/AudioDeviceTypeAddr.h
index acc37ca..60ea78e 100644
--- a/media/libaudiofoundation/include/media/AudioDeviceTypeAddr.h
+++ b/media/libaudiofoundation/include/media/AudioDeviceTypeAddr.h
@@ -39,6 +39,8 @@
AudioDeviceTypeAddr& operator= (const AudioDeviceTypeAddr&) = default;
+ bool operator<(const AudioDeviceTypeAddr& other) const;
+
void reset();
status_t readFromParcel(const Parcel *parcel) override;
diff --git a/media/libaudiohal/impl/DeviceHalHidl.cpp b/media/libaudiohal/impl/DeviceHalHidl.cpp
index cd3ac1f..7d0d83d 100644
--- a/media/libaudiohal/impl/DeviceHalHidl.cpp
+++ b/media/libaudiohal/impl/DeviceHalHidl.cpp
@@ -28,6 +28,7 @@
#include <common/all-versions/VersionUtils.h>
#include "DeviceHalHidl.h"
+#include "EffectHalHidl.h"
#include "HidlUtils.h"
#include "StreamHalHidl.h"
#include "VersionUtils.h"
@@ -43,6 +44,8 @@
using namespace ::android::hardware::audio::common::CPP_VERSION;
using namespace ::android::hardware::audio::CPP_VERSION;
+using EffectHalHidl = ::android::effect::CPP_VERSION::EffectHalHidl;
+
namespace {
status_t deviceAddressFromHal(
@@ -323,25 +326,45 @@
if (mDevice == 0) return NO_INIT;
if (patch == nullptr) return BAD_VALUE;
+#if MAJOR_VERSION < 6
if (*patch != AUDIO_PATCH_HANDLE_NONE) {
status_t status = releaseAudioPatch(*patch);
ALOGW_IF(status != NO_ERROR, "%s error %d releasing patch handle %d",
__func__, status, *patch);
+ *patch = AUDIO_PATCH_HANDLE_NONE;
}
+#endif
hidl_vec<AudioPortConfig> hidlSources, hidlSinks;
HidlUtils::audioPortConfigsFromHal(num_sources, sources, &hidlSources);
HidlUtils::audioPortConfigsFromHal(num_sinks, sinks, &hidlSinks);
- Result retval;
- Return<void> ret = mDevice->createAudioPatch(
- hidlSources, hidlSinks,
- [&](Result r, AudioPatchHandle hidlPatch) {
- retval = r;
- if (retval == Result::OK) {
- *patch = static_cast<audio_patch_handle_t>(hidlPatch);
- }
- });
- return processReturn("createAudioPatch", ret, retval);
+ Result retval = Result::OK;
+ Return<void> ret;
+ std::string methodName = "createAudioPatch";
+ if (*patch == AUDIO_PATCH_HANDLE_NONE) { // always true for MAJOR_VERSION < 6
+ ret = mDevice->createAudioPatch(
+ hidlSources, hidlSinks,
+ [&](Result r, AudioPatchHandle hidlPatch) {
+ retval = r;
+ if (retval == Result::OK) {
+ *patch = static_cast<audio_patch_handle_t>(hidlPatch);
+ }
+ });
+ } else {
+#if MAJOR_VERSION >= 6
+ ret = mDevice->updateAudioPatch(
+ *patch,
+ hidlSources, hidlSinks,
+ [&](Result r, AudioPatchHandle hidlPatch) {
+ retval = r;
+ if (retval == Result::OK) {
+ *patch = static_cast<audio_patch_handle_t>(hidlPatch);
+ }
+ });
+ methodName = "updateAudioPatch";
+#endif
+ }
+ return processReturn(methodName.c_str(), ret, retval);
}
status_t DeviceHalHidl::releaseAudioPatch(audio_patch_handle_t patch) {
@@ -397,6 +420,36 @@
}
#endif
+#if MAJOR_VERSION >= 6
+status_t DeviceHalHidl::addDeviceEffect(
+ audio_port_handle_t device, sp<EffectHalInterface> effect) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("addDeviceEffect", mDevice->addDeviceEffect(
+ static_cast<AudioPortHandle>(device),
+ static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+#else
+status_t DeviceHalHidl::addDeviceEffect(
+ audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
+ return INVALID_OPERATION;
+}
+#endif
+
+#if MAJOR_VERSION >= 6
+status_t DeviceHalHidl::removeDeviceEffect(
+ audio_port_handle_t device, sp<EffectHalInterface> effect) {
+ if (mDevice == 0) return NO_INIT;
+ return processReturn("removeDeviceEffect", mDevice->removeDeviceEffect(
+ static_cast<AudioPortHandle>(device),
+ static_cast<EffectHalHidl*>(effect.get())->effectId()));
+}
+#else
+status_t DeviceHalHidl::removeDeviceEffect(
+ audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
+ return INVALID_OPERATION;
+}
+#endif
+
status_t DeviceHalHidl::dump(int fd) {
if (mDevice == 0) return NO_INIT;
native_handle_t* hidlHandle = native_handle_create(1, 0);
diff --git a/media/libaudiohal/impl/DeviceHalHidl.h b/media/libaudiohal/impl/DeviceHalHidl.h
index f7d465f..d342d4a 100644
--- a/media/libaudiohal/impl/DeviceHalHidl.h
+++ b/media/libaudiohal/impl/DeviceHalHidl.h
@@ -113,6 +113,9 @@
// List microphones
virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones);
+ status_t addDeviceEffect(audio_port_handle_t device, sp<EffectHalInterface> effect) override;
+ status_t removeDeviceEffect(audio_port_handle_t device, sp<EffectHalInterface> effect) override;
+
virtual status_t dump(int fd);
private:
diff --git a/media/libaudiohal/impl/DeviceHalLocal.cpp b/media/libaudiohal/impl/DeviceHalLocal.cpp
index dfbb6b2..8021d92 100644
--- a/media/libaudiohal/impl/DeviceHalLocal.cpp
+++ b/media/libaudiohal/impl/DeviceHalLocal.cpp
@@ -206,6 +206,17 @@
}
#endif
+// Local HAL implementation does not support effects
+status_t DeviceHalLocal::addDeviceEffect(
+ audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
+ return INVALID_OPERATION;
+}
+
+status_t DeviceHalLocal::removeDeviceEffect(
+ audio_port_handle_t device __unused, sp<EffectHalInterface> effect __unused) {
+ return INVALID_OPERATION;
+}
+
status_t DeviceHalLocal::dump(int fd) {
return mDev->dump(mDev, fd);
}
diff --git a/media/libaudiohal/impl/DeviceHalLocal.h b/media/libaudiohal/impl/DeviceHalLocal.h
index 36db72e..d85e2a7 100644
--- a/media/libaudiohal/impl/DeviceHalLocal.h
+++ b/media/libaudiohal/impl/DeviceHalLocal.h
@@ -106,6 +106,9 @@
// List microphones
virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones);
+ status_t addDeviceEffect(audio_port_handle_t device, sp<EffectHalInterface> effect) override;
+ status_t removeDeviceEffect(audio_port_handle_t device, sp<EffectHalInterface> effect) override;
+
virtual status_t dump(int fd);
void closeOutputStream(struct audio_stream_out *stream_out);
diff --git a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
index 2200a7f..1e04b21 100644
--- a/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
+++ b/media/libaudiohal/include/media/audiohal/DeviceHalInterface.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_HARDWARE_DEVICE_HAL_INTERFACE_H
#define ANDROID_HARDWARE_DEVICE_HAL_INTERFACE_H
+#include <media/audiohal/EffectHalInterface.h>
#include <media/MicrophoneInfo.h>
#include <system/audio.h>
#include <utils/Errors.h>
@@ -111,6 +112,11 @@
// List microphones
virtual status_t getMicrophones(std::vector<media::MicrophoneInfo> *microphones) = 0;
+ virtual status_t addDeviceEffect(
+ audio_port_handle_t device, sp<EffectHalInterface> effect) = 0;
+ virtual status_t removeDeviceEffect(
+ audio_port_handle_t device, sp<EffectHalInterface> effect) = 0;
+
virtual status_t dump(int fd) = 0;
protected:
diff --git a/media/libaudioprocessing/Android.bp b/media/libaudioprocessing/Android.bp
index 9b5d58c..1a6a5a2 100644
--- a/media/libaudioprocessing/Android.bp
+++ b/media/libaudioprocessing/Android.bp
@@ -32,6 +32,7 @@
],
header_libs: [
+ "libaudiohal_headers",
"libbase_headers",
"libmedia_headers"
],
diff --git a/media/libeffects/config/src/EffectsConfig.cpp b/media/libeffects/config/src/EffectsConfig.cpp
index f39eb0c..218249d 100644
--- a/media/libeffects/config/src/EffectsConfig.cpp
+++ b/media/libeffects/config/src/EffectsConfig.cpp
@@ -100,6 +100,7 @@
{AUDIO_STREAM_ENFORCED_AUDIBLE, "enforced_audible"},
{AUDIO_STREAM_DTMF, "dtmf"},
{AUDIO_STREAM_TTS, "tts"},
+ {AUDIO_STREAM_ASSISTANT, "assistant"},
};
/** All input stream types which support effects.
diff --git a/media/libheif/HeifDecoderImpl.cpp b/media/libheif/HeifDecoderImpl.cpp
index 9d79f89..2208eca 100644
--- a/media/libheif/HeifDecoderImpl.cpp
+++ b/media/libheif/HeifDecoderImpl.cpp
@@ -26,7 +26,7 @@
#include <binder/MemoryDealer.h>
#include <drm/drm_framework_common.h>
#include <media/mediametadataretriever.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ADebug.h>
#include <private/media/VideoFrame.h>
#include <utils/Log.h>
diff --git a/media/libmedia/IMediaSource.cpp b/media/libmedia/IMediaSource.cpp
index 50826c5..b18571f 100644
--- a/media/libmedia/IMediaSource.cpp
+++ b/media/libmedia/IMediaSource.cpp
@@ -26,7 +26,7 @@
#include <media/IMediaSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaBufferGroup.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MetaData.h>
namespace android {
diff --git a/media/libmedia/include/media/CounterMetric.h b/media/libmedia/include/media/CounterMetric.h
index b53470d..8bd4049 100644
--- a/media/libmedia/include/media/CounterMetric.h
+++ b/media/libmedia/include/media/CounterMetric.h
@@ -20,7 +20,7 @@
#include <map>
#include <string>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <utils/Log.h>
namespace android {
diff --git a/media/libmedia/include/media/EventMetric.h b/media/libmedia/include/media/EventMetric.h
index dbb736a..d6f3402 100644
--- a/media/libmedia/include/media/EventMetric.h
+++ b/media/libmedia/include/media/EventMetric.h
@@ -16,7 +16,7 @@
#ifndef ANDROID_EVENT_METRIC_H_
#define ANDROID_EVENT_METRIC_H_
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <utils/Timers.h>
namespace android {
diff --git a/media/libmedia/include/media/IMediaPlayer.h b/media/libmedia/include/media/IMediaPlayer.h
index 97a998e..3c13540 100644
--- a/media/libmedia/include/media/IMediaPlayer.h
+++ b/media/libmedia/include/media/IMediaPlayer.h
@@ -23,7 +23,7 @@
#include <utils/KeyedVector.h>
#include <system/audio.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/VolumeShaper.h>
// Fwd decl to make sure everyone agrees that the scope of struct sockaddr_in is
diff --git a/media/libmedia/include/media/IMediaSource.h b/media/libmedia/include/media/IMediaSource.h
index 381df24..f3fa39b 100644
--- a/media/libmedia/include/media/IMediaSource.h
+++ b/media/libmedia/include/media/IMediaSource.h
@@ -22,7 +22,7 @@
#include <binder/IInterface.h>
#include <binder/IMemory.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaErrors.h>
diff --git a/media/libmedia/include/media/MidiIoWrapper.h b/media/libmedia/include/media/MidiIoWrapper.h
index b19d49e..7beb619 100644
--- a/media/libmedia/include/media/MidiIoWrapper.h
+++ b/media/libmedia/include/media/MidiIoWrapper.h
@@ -19,8 +19,6 @@
#include <libsonivox/eas_types.h>
-#include <media/DataSourceBase.h>
-
namespace android {
struct CDataSource;
diff --git a/media/libmedia/include/media/NdkWrapper.h b/media/libmedia/include/media/NdkWrapper.h
index 8a417f6..39c0574 100644
--- a/media/libmedia/include/media/NdkWrapper.h
+++ b/media/libmedia/include/media/NdkWrapper.h
@@ -19,7 +19,7 @@
#define NDK_WRAPPER_H_
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/NdkMediaDataSource.h>
#include <media/NdkMediaError.h>
#include <media/NdkMediaExtractor.h>
diff --git a/media/libmediahelper/TypeConverter.cpp b/media/libmediahelper/TypeConverter.cpp
index aa54b82..fc037a6 100644
--- a/media/libmediahelper/TypeConverter.cpp
+++ b/media/libmediahelper/TypeConverter.cpp
@@ -312,6 +312,7 @@
MAKE_STRING_FROM_ENUM(AUDIO_STREAM_DTMF),
MAKE_STRING_FROM_ENUM(AUDIO_STREAM_TTS),
MAKE_STRING_FROM_ENUM(AUDIO_STREAM_ACCESSIBILITY),
+ MAKE_STRING_FROM_ENUM(AUDIO_STREAM_ASSISTANT),
MAKE_STRING_FROM_ENUM(AUDIO_STREAM_REROUTING),
MAKE_STRING_FROM_ENUM(AUDIO_STREAM_PATCH),
TERMINATOR
diff --git a/media/libmediametrics/Android.bp b/media/libmediametrics/Android.bp
index f599190..7fd5408 100644
--- a/media/libmediametrics/Android.bp
+++ b/media/libmediametrics/Android.bp
@@ -2,8 +2,8 @@
name: "libmediametrics",
srcs: [
- "IMediaAnalyticsService.cpp",
- "MediaAnalyticsItem.cpp",
+ "IMediaMetricsService.cpp",
+ "MediaMetricsItem.cpp",
"MediaMetrics.cpp",
],
diff --git a/media/libmediametrics/IMediaAnalyticsService.cpp b/media/libmediametrics/IMediaMetricsService.cpp
similarity index 80%
rename from media/libmediametrics/IMediaAnalyticsService.cpp
rename to media/libmediametrics/IMediaMetricsService.cpp
index 534aece..b5675e6 100644
--- a/media/libmediametrics/IMediaAnalyticsService.cpp
+++ b/media/libmediametrics/IMediaMetricsService.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "MediaAnalytics"
+#define LOG_TAG "MediaMetrics"
#include <stdint.h>
#include <inttypes.h>
@@ -29,8 +29,8 @@
#include <utils/Log.h>
#include <utils/String8.h>
-#include <media/MediaAnalyticsItem.h>
-#include <media/IMediaAnalyticsService.h>
+#include <media/MediaMetricsItem.h>
+#include <media/IMediaMetricsService.h>
namespace android {
@@ -41,15 +41,15 @@
SUBMIT_BUFFER,
};
-class BpMediaAnalyticsService: public BpInterface<IMediaAnalyticsService>
+class BpMediaMetricsService: public BpInterface<IMediaMetricsService>
{
public:
- explicit BpMediaAnalyticsService(const sp<IBinder>& impl)
- : BpInterface<IMediaAnalyticsService>(impl)
+ explicit BpMediaMetricsService(const sp<IBinder>& impl)
+ : BpInterface<IMediaMetricsService>(impl)
{
}
- status_t submit(MediaAnalyticsItem *item) override
+ status_t submit(mediametrics::Item *item) override
{
if (item == nullptr) {
return BAD_VALUE;
@@ -57,7 +57,7 @@
ALOGV("%s: (ONEWAY) item=%s", __func__, item->toString().c_str());
Parcel data;
- data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+ data.writeInterfaceToken(IMediaMetricsService::getInterfaceDescriptor());
status_t status = item->writeToParcel(&data);
if (status != NO_ERROR) { // assume failure logged in item
@@ -79,7 +79,7 @@
ALOGV("%s: (ONEWAY) length:%zu", __func__, length);
Parcel data;
- data.writeInterfaceToken(IMediaAnalyticsService::getInterfaceDescriptor());
+ data.writeInterfaceToken(IMediaMetricsService::getInterfaceDescriptor());
status_t status = data.writeInt32(length)
?: data.write((uint8_t*)buffer, length);
@@ -95,18 +95,18 @@
}
};
-IMPLEMENT_META_INTERFACE(MediaAnalyticsService, "android.media.IMediaAnalyticsService");
+IMPLEMENT_META_INTERFACE(MediaMetricsService, "android.media.IMediaMetricsService");
// ----------------------------------------------------------------------
-status_t BnMediaAnalyticsService::onTransact(
+status_t BnMediaMetricsService::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch (code) {
case SUBMIT_ITEM: {
- CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+ CHECK_INTERFACE(IMediaMetricsService, data, reply);
- MediaAnalyticsItem * const item = MediaAnalyticsItem::create();
+ mediametrics::Item * const item = mediametrics::Item::create();
status_t status = item->readFromParcel(data);
if (status != NO_ERROR) { // assume failure logged in item
return status;
@@ -116,7 +116,7 @@
return NO_ERROR;
}
case SUBMIT_BUFFER: {
- CHECK_INTERFACE(IMediaAnalyticsService, data, reply);
+ CHECK_INTERFACE(IMediaMetricsService, data, reply);
int32_t length;
status_t status = data.readInt32(&length);
if (status != NO_ERROR || length <= 0) {
diff --git a/media/libmediametrics/MediaAnalyticsItem.cpp b/media/libmediametrics/MediaAnalyticsItem.cpp
deleted file mode 100644
index 8025e49..0000000
--- a/media/libmediametrics/MediaAnalyticsItem.cpp
+++ /dev/null
@@ -1,1039 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "MediaAnalyticsItem"
-
-#include <inttypes.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-
-#include <mutex>
-#include <set>
-
-#include <binder/Parcel.h>
-#include <utils/Errors.h>
-#include <utils/Log.h>
-#include <utils/SortedVector.h>
-#include <utils/threads.h>
-
-#include <binder/IServiceManager.h>
-#include <media/IMediaAnalyticsService.h>
-#include <media/MediaAnalyticsItem.h>
-#include <private/android_filesystem_config.h>
-
-// Max per-property string size before truncation in toString().
-// Do not make too large, as this is used for dumpsys purposes.
-static constexpr size_t kMaxPropertyStringSize = 4096;
-
-namespace android {
-
-#define DEBUG_SERVICEACCESS 0
-#define DEBUG_API 0
-#define DEBUG_ALLOCATIONS 0
-
-// after this many failed attempts, we stop trying [from this process] and just say that
-// the service is off.
-#define SVC_TRIES 2
-
-MediaAnalyticsItem* MediaAnalyticsItem::convert(mediametrics_handle_t handle) {
- MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
- return item;
-}
-
-mediametrics_handle_t MediaAnalyticsItem::convert(MediaAnalyticsItem *item ) {
- mediametrics_handle_t handle = (mediametrics_handle_t) item;
- return handle;
-}
-
-MediaAnalyticsItem::~MediaAnalyticsItem() {
- if (DEBUG_ALLOCATIONS) {
- ALOGD("Destroy MediaAnalyticsItem @ %p", this);
- }
- clear();
-}
-
-void MediaAnalyticsItem::clear() {
-
- // clean allocated storage from key
- mKey.clear();
-
- // clean attributes
- // contents of the attributes
- for (size_t i = 0 ; i < mPropCount; i++ ) {
- mProps[i].clear();
- }
- // the attribute records themselves
- if (mProps != NULL) {
- free(mProps);
- mProps = NULL;
- }
- mPropSize = 0;
- mPropCount = 0;
-
- return;
-}
-
-// make a deep copy of myself
-MediaAnalyticsItem *MediaAnalyticsItem::dup() {
- MediaAnalyticsItem *dst = new MediaAnalyticsItem(this->mKey);
-
- if (dst != NULL) {
- // key as part of constructor
- dst->mPid = this->mPid;
- dst->mUid = this->mUid;
- dst->mPkgName = this->mPkgName;
- dst->mPkgVersionCode = this->mPkgVersionCode;
- dst->mTimestamp = this->mTimestamp;
-
- // properties aka attributes
- dst->growProps(this->mPropCount);
- for(size_t i=0;i<mPropCount;i++) {
- dst->mProps[i] = this->mProps[i];
- }
- dst->mPropCount = this->mPropCount;
- }
-
- return dst;
-}
-
-MediaAnalyticsItem &MediaAnalyticsItem::setTimestamp(nsecs_t ts) {
- mTimestamp = ts;
- return *this;
-}
-
-nsecs_t MediaAnalyticsItem::getTimestamp() const {
- return mTimestamp;
-}
-
-MediaAnalyticsItem &MediaAnalyticsItem::setPid(pid_t pid) {
- mPid = pid;
- return *this;
-}
-
-pid_t MediaAnalyticsItem::getPid() const {
- return mPid;
-}
-
-MediaAnalyticsItem &MediaAnalyticsItem::setUid(uid_t uid) {
- mUid = uid;
- return *this;
-}
-
-uid_t MediaAnalyticsItem::getUid() const {
- return mUid;
-}
-
-MediaAnalyticsItem &MediaAnalyticsItem::setPkgName(const std::string &pkgName) {
- mPkgName = pkgName;
- return *this;
-}
-
-MediaAnalyticsItem &MediaAnalyticsItem::setPkgVersionCode(int64_t pkgVersionCode) {
- mPkgVersionCode = pkgVersionCode;
- return *this;
-}
-
-int64_t MediaAnalyticsItem::getPkgVersionCode() const {
- return mPkgVersionCode;
-}
-
-
-// find the proper entry in the list
-size_t MediaAnalyticsItem::findPropIndex(const char *name) const
-{
- size_t i = 0;
- for (; i < mPropCount; i++) {
- if (mProps[i].isNamed(name)) break;
- }
- return i;
-}
-
-MediaAnalyticsItem::Prop *MediaAnalyticsItem::findProp(const char *name) const {
- const size_t i = findPropIndex(name);
- if (i < mPropCount) {
- return &mProps[i];
- }
- return nullptr;
-}
-
-// consider this "find-or-allocate".
-// caller validates type and uses clearPropValue() accordingly
-MediaAnalyticsItem::Prop *MediaAnalyticsItem::allocateProp(const char *name) {
- const size_t i = findPropIndex(name);
- if (i < mPropCount) {
- return &mProps[i]; // already have it, return
- }
-
- Prop *prop = allocateProp(); // get a new prop
- if (prop == nullptr) return nullptr;
- prop->setName(name);
- return prop;
-}
-
-MediaAnalyticsItem::Prop *MediaAnalyticsItem::allocateProp() {
- if (mPropCount == mPropSize && growProps() == false) {
- ALOGE("%s: failed allocation for new properties", __func__);
- return nullptr;
- }
- return &mProps[mPropCount++];
-}
-
-// used within the summarizers; return whether property existed
-bool MediaAnalyticsItem::removeProp(const char *name) {
- const size_t i = findPropIndex(name);
- if (i < mPropCount) {
- mProps[i].clear();
- if (i != mPropCount-1) {
- // in the middle, bring last one down to fill gap
- mProps[i].swap(mProps[mPropCount-1]);
- }
- mPropCount--;
- return true;
- }
- return false;
-}
-
-// remove indicated keys and their values
-// return value is # keys removed
-size_t MediaAnalyticsItem::filter(size_t n, const char *attrs[]) {
- size_t zapped = 0;
- for (size_t i = 0; i < n; ++i) {
- const char *name = attrs[i];
- size_t j = findPropIndex(name);
- if (j >= mPropCount) {
- // not there
- continue;
- } else if (j + 1 == mPropCount) {
- // last one, shorten
- zapped++;
- mProps[j].clear();
- mPropCount--;
- } else {
- // in the middle, bring last one down and shorten
- zapped++;
- mProps[j].clear();
- mProps[j] = mProps[mPropCount-1];
- mPropCount--;
- }
- }
- return zapped;
-}
-
-// remove any keys NOT in the provided list
-// return value is # keys removed
-size_t MediaAnalyticsItem::filterNot(size_t n, const char *attrs[]) {
- std::set<std::string> check(attrs, attrs + n);
- size_t zapped = 0;
- for (size_t j = 0; j < mPropCount;) {
- if (check.find(mProps[j].getName()) != check.end()) {
- ++j;
- continue;
- }
- if (j + 1 == mPropCount) {
- // last one, shorten
- zapped++;
- mProps[j].clear();
- mPropCount--;
- break;
- } else {
- // in the middle, bring last one down and shorten
- zapped++;
- mProps[j].clear();
- mProps[j] = mProps[mPropCount-1];
- mPropCount--;
- }
- }
- return zapped;
-}
-
-bool MediaAnalyticsItem::growProps(int increment)
-{
- if (increment <= 0) {
- increment = kGrowProps;
- }
- int nsize = mPropSize + increment;
- Prop *ni = (Prop *)realloc(mProps, sizeof(Prop) * nsize);
-
- if (ni != NULL) {
- for (int i = mPropSize; i < nsize; i++) {
- new (&ni[i]) Prop(); // placement new
- }
- mProps = ni;
- mPropSize = nsize;
- return true;
- } else {
- ALOGW("MediaAnalyticsItem::growProps fails");
- return false;
- }
-}
-
-// Parcel / serialize things for binder calls
-//
-
-status_t MediaAnalyticsItem::readFromParcel(const Parcel& data) {
- int32_t version;
- status_t status = data.readInt32(&version);
- if (status != NO_ERROR) return status;
-
- switch (version) {
- case 0:
- return readFromParcel0(data);
- default:
- ALOGE("%s: unsupported parcel version: %d", __func__, version);
- return INVALID_OPERATION;
- }
-}
-
-status_t MediaAnalyticsItem::readFromParcel0(const Parcel& data) {
- const char *s = data.readCString();
- mKey = s == nullptr ? "" : s;
- int32_t pid, uid;
- status_t status = data.readInt32(&pid) ?: data.readInt32(&uid);
- if (status != NO_ERROR) return status;
- mPid = (pid_t)pid;
- mUid = (uid_t)uid;
- s = data.readCString();
- mPkgName = s == nullptr ? "" : s;
- int32_t count;
- int64_t version, timestamp;
- status = data.readInt64(&version) ?: data.readInt64(×tamp) ?: data.readInt32(&count);
- if (status != NO_ERROR) return status;
- if (count < 0) return BAD_VALUE;
- mPkgVersionCode = version;
- mTimestamp = timestamp;
- for (int i = 0; i < count ; i++) {
- Prop *prop = allocateProp();
- status_t status = prop->readFromParcel(data);
- if (status != NO_ERROR) return status;
- }
- return NO_ERROR;
-}
-
-status_t MediaAnalyticsItem::writeToParcel(Parcel *data) const {
- if (data == nullptr) return BAD_VALUE;
-
- const int32_t version = 0;
- status_t status = data->writeInt32(version);
- if (status != NO_ERROR) return status;
-
- switch (version) {
- case 0:
- return writeToParcel0(data);
- default:
- ALOGE("%s: unsupported parcel version: %d", __func__, version);
- return INVALID_OPERATION;
- }
-}
-
-status_t MediaAnalyticsItem::writeToParcel0(Parcel *data) const {
- status_t status =
- data->writeCString(mKey.c_str())
- ?: data->writeInt32(mPid)
- ?: data->writeInt32(mUid)
- ?: data->writeCString(mPkgName.c_str())
- ?: data->writeInt64(mPkgVersionCode)
- ?: data->writeInt64(mTimestamp);
- if (status != NO_ERROR) return status;
-
- data->writeInt32((int32_t)mPropCount);
- for (size_t i = 0 ; i < mPropCount; ++i) {
- status = mProps[i].writeToParcel(data);
- if (status != NO_ERROR) return status;
- }
- return NO_ERROR;
-}
-
-const char *MediaAnalyticsItem::toCString() {
- return toCString(PROTO_LAST);
-}
-
-const char * MediaAnalyticsItem::toCString(int version) {
- std::string val = toString(version);
- return strdup(val.c_str());
-}
-
-std::string MediaAnalyticsItem::toString() const {
- return toString(PROTO_LAST);
-}
-
-std::string MediaAnalyticsItem::toString(int version) const {
- std::string result;
- char buffer[kMaxPropertyStringSize];
-
- snprintf(buffer, sizeof(buffer), "[%d:%s:%d:%d:%lld:%s:%zu:",
- version, mKey.c_str(), mPid, mUid, (long long)mTimestamp,
- mPkgName.c_str(), mPropCount);
- result.append(buffer);
- for (size_t i = 0 ; i < mPropCount; ++i) {
- mProps[i].toString(buffer, sizeof(buffer));
- result.append(buffer);
- }
- result.append("]");
- return result;
-}
-
-// for the lazy, we offer methods that finds the service and
-// calls the appropriate daemon
-bool MediaAnalyticsItem::selfrecord() {
- ALOGD_IF(DEBUG_API, "%s: delivering %s", __func__, this->toString().c_str());
- sp<IMediaAnalyticsService> svc = getInstance();
- if (svc != NULL) {
- status_t status = svc->submit(this);
- if (status != NO_ERROR) {
- ALOGW("%s: failed to record: %s", __func__, this->toString().c_str());
- return false;
- }
- return true;
- } else {
- return false;
- }
-}
-
-namespace mediametrics {
-//static
-bool BaseItem::isEnabled() {
- // completely skip logging from certain UIDs. We do this here
- // to avoid the multi-second timeouts while we learn that
- // sepolicy will not let us find the service.
- // We do this only for a select set of UIDs
- // The sepolicy protection is still in place, we just want a faster
- // response from this specific, small set of uids.
-
- // This is checked only once in the lifetime of the process.
- const uid_t uid = getuid();
- switch (uid) {
- case AID_RADIO: // telephony subsystem, RIL
- return false;
- }
-
- int enabled = property_get_int32(MediaAnalyticsItem::EnabledProperty, -1);
- if (enabled == -1) {
- enabled = property_get_int32(MediaAnalyticsItem::EnabledPropertyPersist, -1);
- }
- if (enabled == -1) {
- enabled = MediaAnalyticsItem::EnabledProperty_default;
- }
- return enabled > 0;
-}
-
-// monitor health of our connection to the metrics service
-class MediaMetricsDeathNotifier : public IBinder::DeathRecipient {
- virtual void binderDied(const wp<IBinder> &) {
- ALOGW("Reacquire service connection on next request");
- BaseItem::dropInstance();
- }
-};
-
-static sp<MediaMetricsDeathNotifier> sNotifier;
-// static
-sp<IMediaAnalyticsService> BaseItem::sAnalyticsService;
-static std::mutex sServiceMutex;
-static int sRemainingBindAttempts = SVC_TRIES;
-
-// static
-void BaseItem::dropInstance() {
- std::lock_guard _l(sServiceMutex);
- sRemainingBindAttempts = SVC_TRIES;
- sAnalyticsService = nullptr;
-}
-
-// static
-bool BaseItem::submitBuffer(const char *buffer, size_t size) {
-/*
- MediaAnalyticsItem item;
- status_t status = item.readFromByteString(buffer, size);
- ALOGD("%s: status:%d, size:%zu, item:%s", __func__, status, size, item.toString().c_str());
- return item.selfrecord();
- */
-
- ALOGD_IF(DEBUG_API, "%s: delivering %zu bytes", __func__, size);
- sp<IMediaAnalyticsService> svc = getInstance();
- if (svc != nullptr) {
- const status_t status = svc->submitBuffer(buffer, size);
- if (status != NO_ERROR) {
- ALOGW("%s: failed(%d) to record: %zu bytes", __func__, status, size);
- return false;
- }
- return true;
- }
- return false;
-}
-
-//static
-sp<IMediaAnalyticsService> BaseItem::getInstance() {
- static const char *servicename = "media.metrics";
- static const bool enabled = isEnabled(); // singleton initialized
-
- if (enabled == false) {
- ALOGD_IF(DEBUG_SERVICEACCESS, "disabled");
- return nullptr;
- }
- std::lock_guard _l(sServiceMutex);
- // think of remainingBindAttempts as telling us whether service == nullptr because
- // (1) we haven't tried to initialize it yet
- // (2) we've tried to initialize it, but failed.
- if (sAnalyticsService == nullptr && sRemainingBindAttempts > 0) {
- const char *badness = "";
- sp<IServiceManager> sm = defaultServiceManager();
- if (sm != nullptr) {
- sp<IBinder> binder = sm->getService(String16(servicename));
- if (binder != nullptr) {
- sAnalyticsService = interface_cast<IMediaAnalyticsService>(binder);
- sNotifier = new MediaMetricsDeathNotifier();
- binder->linkToDeath(sNotifier);
- } else {
- badness = "did not find service";
- }
- } else {
- badness = "No Service Manager access";
- }
- if (sAnalyticsService == nullptr) {
- if (sRemainingBindAttempts > 0) {
- sRemainingBindAttempts--;
- }
- ALOGD_IF(DEBUG_SERVICEACCESS, "%s: unable to bind to service %s: %s",
- __func__, servicename, badness);
- }
- }
- return sAnalyticsService;
-}
-
-} // namespace mediametrics
-
-// merge the info from 'incoming' into this record.
-// we finish with a union of this+incoming and special handling for collisions
-bool MediaAnalyticsItem::merge(MediaAnalyticsItem *incoming) {
-
- // if I don't have key or session id, take them from incoming
- // 'this' should never be missing both of them...
- if (mKey.empty()) {
- mKey = incoming->mKey;
- }
-
- // for each attribute from 'incoming', resolve appropriately
- int nattr = incoming->mPropCount;
- for (int i = 0 ; i < nattr; i++ ) {
- Prop *iprop = &incoming->mProps[i];
- const char *p = iprop->mName;
- size_t len = strlen(p);
-
- // should ignore a zero length name...
- if (len == 0) {
- continue;
- }
-
- Prop *oprop = findProp(iprop->mName);
-
- if (oprop == NULL) {
- // no oprop, so we insert the new one
- oprop = allocateProp(p);
- if (oprop != NULL) {
- *oprop = *iprop;
- } else {
- ALOGW("dropped property '%s'", iprop->mName);
- }
- } else {
- *oprop = *iprop;
- }
- }
-
- // not sure when we'd return false...
- return true;
-}
-
-namespace {
-
-template <typename T>
-status_t insert(const T& val, char **bufferpptr, char *bufferptrmax)
-{
- const size_t size = sizeof(val);
- if (*bufferpptr + size > bufferptrmax) {
- ALOGE("%s: buffer exceeded with size %zu", __func__, size);
- return BAD_VALUE;
- }
- memcpy(*bufferpptr, &val, size);
- *bufferpptr += size;
- return NO_ERROR;
-}
-
-template <>
-status_t insert(const char * const& val, char **bufferpptr, char *bufferptrmax)
-{
- const size_t size = strlen(val) + 1;
- if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) {
- ALOGE("%s: buffer exceeded with size %zu", __func__, size);
- return BAD_VALUE;
- }
- memcpy(*bufferpptr, val, size);
- *bufferpptr += size;
- return NO_ERROR;
-}
-
-template <>
- __unused
-status_t insert(char * const& val, char **bufferpptr, char *bufferptrmax)
-{
- return insert((const char *)val, bufferpptr, bufferptrmax);
-}
-
-template <typename T>
-status_t extract(T *val, const char **bufferpptr, const char *bufferptrmax)
-{
- const size_t size = sizeof(*val);
- if (*bufferpptr + size > bufferptrmax) {
- ALOGE("%s: buffer exceeded with size %zu", __func__, size);
- return BAD_VALUE;
- }
- memcpy(val, *bufferpptr, size);
- *bufferpptr += size;
- return NO_ERROR;
-}
-
-template <>
-status_t extract(char **val, const char **bufferpptr, const char *bufferptrmax)
-{
- const char *ptr = *bufferpptr;
- while (*ptr != 0) {
- if (ptr >= bufferptrmax) {
- ALOGE("%s: buffer exceeded", __func__);
- return BAD_VALUE;
- }
- ++ptr;
- }
- const size_t size = (ptr - *bufferpptr) + 1;
- *val = (char *)malloc(size);
- memcpy(*val, *bufferpptr, size);
- *bufferpptr += size;
- return NO_ERROR;
-}
-
-} // namespace
-
-status_t MediaAnalyticsItem::writeToByteString(char **pbuffer, size_t *plength) const
-{
- if (pbuffer == nullptr || plength == nullptr)
- return BAD_VALUE;
-
- // get size
- const size_t keySizeZeroTerminated = strlen(mKey.c_str()) + 1;
- if (keySizeZeroTerminated > UINT16_MAX) {
- ALOGW("%s: key size %zu too large", __func__, keySizeZeroTerminated);
- return INVALID_OPERATION;
- }
- const uint16_t version = 0;
- const uint32_t header_size =
- sizeof(uint32_t) // total size
- + sizeof(header_size) // header size
- + sizeof(version) // encoding version
- + sizeof(uint16_t) // key size
- + keySizeZeroTerminated // key, zero terminated
- + sizeof(int32_t) // pid
- + sizeof(int32_t) // uid
- + sizeof(int64_t) // timestamp
- ;
-
- uint32_t size = header_size
- + sizeof(uint32_t) // # properties
- ;
- for (size_t i = 0 ; i < mPropCount; ++i) {
- const size_t propSize = mProps[i].getByteStringSize();
- if (propSize > UINT16_MAX) {
- ALOGW("%s: prop %zu size %zu too large", __func__, i, propSize);
- return INVALID_OPERATION;
- }
- if (__builtin_add_overflow(size, propSize, &size)) {
- ALOGW("%s: item size overflow at property %zu", __func__, i);
- return INVALID_OPERATION;
- }
- }
-
- // since we fill every byte in the buffer (there is no padding),
- // malloc is used here instead of calloc.
- char * const build = (char *)malloc(size);
- if (build == nullptr) return NO_MEMORY;
-
- char *filling = build;
- char *buildmax = build + size;
- if (insert((uint32_t)size, &filling, buildmax) != NO_ERROR
- || insert(header_size, &filling, buildmax) != NO_ERROR
- || insert(version, &filling, buildmax) != NO_ERROR
- || insert((uint16_t)keySizeZeroTerminated, &filling, buildmax) != NO_ERROR
- || insert(mKey.c_str(), &filling, buildmax) != NO_ERROR
- || insert((int32_t)mPid, &filling, buildmax) != NO_ERROR
- || insert((int32_t)mUid, &filling, buildmax) != NO_ERROR
- || insert((int64_t)mTimestamp, &filling, buildmax) != NO_ERROR
- || insert((uint32_t)mPropCount, &filling, buildmax) != NO_ERROR) {
- ALOGE("%s:could not write header", __func__); // shouldn't happen
- free(build);
- return INVALID_OPERATION;
- }
- for (size_t i = 0 ; i < mPropCount; ++i) {
- if (mProps[i].writeToByteString(&filling, buildmax) != NO_ERROR) {
- free(build);
- // shouldn't happen
- ALOGE("%s:could not write prop %zu of %zu", __func__, i, mPropCount);
- return INVALID_OPERATION;
- }
- }
-
- if (filling != buildmax) {
- ALOGE("%s: problems populating; wrote=%d planned=%d",
- __func__, (int)(filling - build), (int)size);
- free(build);
- return INVALID_OPERATION;
- }
- *pbuffer = build;
- *plength = size;
- return NO_ERROR;
-}
-
-status_t MediaAnalyticsItem::readFromByteString(const char *bufferptr, size_t length)
-{
- if (bufferptr == nullptr) return BAD_VALUE;
-
- const char *read = bufferptr;
- const char *readend = bufferptr + length;
-
- uint32_t size;
- uint32_t header_size;
- uint16_t version;
- uint16_t key_size;
- char *key = nullptr;
- int32_t pid;
- int32_t uid;
- int64_t timestamp;
- uint32_t propCount;
- if (extract(&size, &read, readend) != NO_ERROR
- || extract(&header_size, &read, readend) != NO_ERROR
- || extract(&version, &read, readend) != NO_ERROR
- || extract(&key_size, &read, readend) != NO_ERROR
- || extract(&key, &read, readend) != NO_ERROR
- || extract(&pid, &read, readend) != NO_ERROR
- || extract(&uid, &read, readend) != NO_ERROR
- || extract(×tamp, &read, readend) != NO_ERROR
- || size > length
- || strlen(key) + 1 != key_size
- || header_size > size) {
- free(key);
- ALOGW("%s: invalid header", __func__);
- return INVALID_OPERATION;
- }
- mKey = key;
- free(key);
- const size_t pos = read - bufferptr;
- if (pos > header_size) {
- ALOGW("%s: invalid header pos:%zu > header_size:%u",
- __func__, pos, header_size);
- return INVALID_OPERATION;
- } else if (pos < header_size) {
- ALOGW("%s: mismatched header pos:%zu < header_size:%u, advancing",
- __func__, pos, header_size);
- read += (header_size - pos);
- }
- if (extract(&propCount, &read, readend) != NO_ERROR) {
- ALOGD("%s: cannot read prop count", __func__);
- return INVALID_OPERATION;
- }
- mPid = pid;
- mUid = uid;
- mTimestamp = timestamp;
- for (size_t i = 0; i < propCount; ++i) {
- Prop *prop = allocateProp();
- if (prop->readFromByteString(&read, readend) != NO_ERROR) {
- ALOGW("%s: cannot read prop %zu", __func__, i);
- return INVALID_OPERATION;
- }
- }
- return NO_ERROR;
-}
-
-status_t MediaAnalyticsItem::Prop::writeToParcel(Parcel *data) const
-{
- switch (mType) {
- case kTypeInt32:
- return data->writeCString(mName)
- ?: data->writeInt32(mType)
- ?: data->writeInt32(u.int32Value);
- case kTypeInt64:
- return data->writeCString(mName)
- ?: data->writeInt32(mType)
- ?: data->writeInt64(u.int64Value);
- case kTypeDouble:
- return data->writeCString(mName)
- ?: data->writeInt32(mType)
- ?: data->writeDouble(u.doubleValue);
- case kTypeRate:
- return data->writeCString(mName)
- ?: data->writeInt32(mType)
- ?: data->writeInt64(u.rate.first)
- ?: data->writeInt64(u.rate.second);
- case kTypeCString:
- return data->writeCString(mName)
- ?: data->writeInt32(mType)
- ?: data->writeCString(u.CStringValue);
- default:
- ALOGE("%s: found bad type: %d, name %s", __func__, mType, mName);
- return BAD_VALUE;
- }
-}
-
-status_t MediaAnalyticsItem::Prop::readFromParcel(const Parcel& data)
-{
- const char *key = data.readCString();
- if (key == nullptr) return BAD_VALUE;
- int32_t type;
- status_t status = data.readInt32(&type);
- if (status != NO_ERROR) return status;
- switch (type) {
- case kTypeInt32:
- status = data.readInt32(&u.int32Value);
- break;
- case kTypeInt64:
- status = data.readInt64(&u.int64Value);
- break;
- case kTypeDouble:
- status = data.readDouble(&u.doubleValue);
- break;
- case kTypeCString: {
- const char *s = data.readCString();
- if (s == nullptr) return BAD_VALUE;
- set(s);
- break;
- }
- case kTypeRate: {
- std::pair<int64_t, int64_t> rate;
- status = data.readInt64(&rate.first)
- ?: data.readInt64(&rate.second);
- if (status == NO_ERROR) {
- set(rate);
- }
- break;
- }
- default:
- ALOGE("%s: reading bad item type: %d", __func__, mType);
- return BAD_VALUE;
- }
- if (status == NO_ERROR) {
- setName(key);
- mType = (Type)type;
- }
- return status;
-}
-
-void MediaAnalyticsItem::Prop::toString(char *buffer, size_t length) const
-{
- switch (mType) {
- case kTypeInt32:
- snprintf(buffer, length, "%s=%d:", mName, u.int32Value);
- break;
- case MediaAnalyticsItem::kTypeInt64:
- snprintf(buffer, length, "%s=%lld:", mName, (long long)u.int64Value);
- break;
- case MediaAnalyticsItem::kTypeDouble:
- snprintf(buffer, length, "%s=%e:", mName, u.doubleValue);
- break;
- case MediaAnalyticsItem::kTypeRate:
- snprintf(buffer, length, "%s=%lld/%lld:",
- mName, (long long)u.rate.first, (long long)u.rate.second);
- break;
- case MediaAnalyticsItem::kTypeCString:
- // TODO sanitize string for ':' '='
- snprintf(buffer, length, "%s=%s:", mName, u.CStringValue);
- break;
- default:
- ALOGE("%s: bad item type: %d for %s", __func__, mType, mName);
- if (length > 0) buffer[0] = 0;
- break;
- }
-}
-
-size_t MediaAnalyticsItem::Prop::getByteStringSize() const
-{
- const size_t header =
- sizeof(uint16_t) // length
- + sizeof(uint8_t) // type
- + strlen(mName) + 1; // mName + 0 termination
- size_t payload = 0;
- switch (mType) {
- case MediaAnalyticsItem::kTypeInt32:
- payload = sizeof(u.int32Value);
- break;
- case MediaAnalyticsItem::kTypeInt64:
- payload = sizeof(u.int64Value);
- break;
- case MediaAnalyticsItem::kTypeDouble:
- payload = sizeof(u.doubleValue);
- break;
- case MediaAnalyticsItem::kTypeRate:
- payload = sizeof(u.rate.first) + sizeof(u.rate.second);
- break;
- case MediaAnalyticsItem::kTypeCString:
- payload = strlen(u.CStringValue) + 1;
- break;
- default:
- ALOGE("%s: found bad prop type: %d, name %s",
- __func__, mType, mName); // no payload computed
- break;
- }
- return header + payload;
-}
-
-namespace mediametrics {
-
-// TODO: fold into a template later.
-status_t BaseItem::writeToByteString(
- const char *name, int32_t value, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeInt32, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax)
- ?: insert(value, bufferpptr, bufferptrmax);
-}
-
-status_t BaseItem::writeToByteString(
- const char *name, int64_t value, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeInt64, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax)
- ?: insert(value, bufferpptr, bufferptrmax);
-}
-
-status_t BaseItem::writeToByteString(
- const char *name, double value, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1 + sizeof(value);
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeDouble, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax)
- ?: insert(value, bufferpptr, bufferptrmax);
-}
-
-status_t BaseItem::writeToByteString(
- const char *name, const std::pair<int64_t, int64_t> &value, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1 + 8 + 8;
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeRate, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax)
- ?: insert(value.first, bufferpptr, bufferptrmax)
- ?: insert(value.second, bufferpptr, bufferptrmax);
-}
-
-status_t BaseItem::writeToByteString(
- const char *name, char * const &value, char **bufferpptr, char *bufferptrmax)
-{
- return writeToByteString(name, (const char *)value, bufferpptr, bufferptrmax);
-}
-
-status_t BaseItem::writeToByteString(
- const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax)
- ?: insert(value, bufferpptr, bufferptrmax);
-}
-
-
-status_t BaseItem::writeToByteString(
- const char *name, const none_t &, char **bufferpptr, char *bufferptrmax)
-{
- const size_t len = 2 + 1 + strlen(name) + 1;
- if (len > UINT16_MAX) return BAD_VALUE;
- return insert((uint16_t)len, bufferpptr, bufferptrmax)
- ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax)
- ?: insert(name, bufferpptr, bufferptrmax);
-}
-
-} // namespace mediametrics
-
-status_t MediaAnalyticsItem::Prop::writeToByteString(
- char **bufferpptr, char *bufferptrmax) const
-{
- switch (mType) {
- case kTypeInt32:
- return BaseItem::writeToByteString(mName, u.int32Value, bufferpptr, bufferptrmax);
- case kTypeInt64:
- return BaseItem::writeToByteString(mName, u.int64Value, bufferpptr, bufferptrmax);
- case kTypeDouble:
- return BaseItem::writeToByteString(mName, u.doubleValue, bufferpptr, bufferptrmax);
- case kTypeRate:
- return BaseItem::writeToByteString(mName, u.rate, bufferpptr, bufferptrmax);
- case kTypeCString:
- return BaseItem::writeToByteString(mName, u.CStringValue, bufferpptr, bufferptrmax);
- case kTypeNone:
- return BaseItem::writeToByteString(mName, none_t{}, bufferpptr, bufferptrmax);
- default:
- ALOGE("%s: found bad prop type: %d, name %s",
- __func__, mType, mName); // no payload sent
- return BAD_VALUE;
- }
-}
-
-status_t MediaAnalyticsItem::Prop::readFromByteString(
- const char **bufferpptr, const char *bufferptrmax)
-{
- uint16_t len;
- char *name;
- uint8_t type;
- status_t status = extract(&len, bufferpptr, bufferptrmax)
- ?: extract(&type, bufferpptr, bufferptrmax)
- ?: extract(&name, bufferpptr, bufferptrmax);
- if (status != NO_ERROR) return status;
- if (mName != nullptr) {
- free(mName);
- }
- mName = name;
- if (mType == kTypeCString) {
- free(u.CStringValue);
- u.CStringValue = nullptr;
- }
- mType = (Type)type;
- switch (mType) {
- case kTypeInt32:
- return extract(&u.int32Value, bufferpptr, bufferptrmax);
- case kTypeInt64:
- return extract(&u.int64Value, bufferpptr, bufferptrmax);
- case kTypeDouble:
- return extract(&u.doubleValue, bufferpptr, bufferptrmax);
- case kTypeRate:
- return extract(&u.rate.first, bufferpptr, bufferptrmax)
- ?: extract(&u.rate.second, bufferpptr, bufferptrmax);
- case kTypeCString:
- status = extract(&u.CStringValue, bufferpptr, bufferptrmax);
- if (status != NO_ERROR) mType = kTypeNone;
- return status;
- case kTypeNone:
- return NO_ERROR;
- default:
- mType = kTypeNone;
- ALOGE("%s: found bad prop type: %d, name %s",
- __func__, mType, mName); // no payload sent
- return BAD_VALUE;
- }
-}
-
-} // namespace android
diff --git a/media/libmediametrics/MediaMetrics.cpp b/media/libmediametrics/MediaMetrics.cpp
index cf268e0..a3c2f1a 100644
--- a/media/libmediametrics/MediaMetrics.cpp
+++ b/media/libmediametrics/MediaMetrics.cpp
@@ -21,7 +21,7 @@
#include <string.h>
#include <sys/types.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MediaMetrics.h>
//
@@ -31,28 +31,31 @@
// ALL functions returning a char * give responsibility for the allocated buffer
// to the caller. The caller is responsible to call free() on that pointer.
//
+//
+
+using namespace android::mediametrics;
// manage the overall record
mediametrics_handle_t mediametrics_create(mediametricskey_t key) {
- android::MediaAnalyticsItem *item = android::MediaAnalyticsItem::create(key);
+ Item *item = Item::create(key);
return (mediametrics_handle_t) item;
}
void mediametrics_delete(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return;
delete item;
}
mediametricskey_t mediametrics_getKey(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return NULL;
return strdup(item->getKey().c_str());
}
// nuplayer, et al use it when acting as proxies
void mediametrics_setUid(mediametrics_handle_t handle, uid_t uid) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setUid(uid);
}
@@ -61,31 +64,31 @@
void mediametrics_setInt32(mediametrics_handle_t handle, attr_t attr,
int32_t value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setInt32(attr, value);
}
void mediametrics_setInt64(mediametrics_handle_t handle, attr_t attr,
int64_t value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setInt64(attr, value);
}
void mediametrics_setDouble(mediametrics_handle_t handle, attr_t attr,
double value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setDouble(attr, value);
}
void mediametrics_setRate(mediametrics_handle_t handle, attr_t attr,
int64_t count, int64_t duration) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setRate(attr, count, duration);
}
void mediametrics_setCString(mediametrics_handle_t handle, attr_t attr,
const char *value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->setCString(attr, value);
}
@@ -94,25 +97,25 @@
void mediametrics_addInt32(mediametrics_handle_t handle, attr_t attr,
int32_t value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->addInt32(attr, value);
}
void mediametrics_addInt64(mediametrics_handle_t handle, attr_t attr,
int64_t value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->addInt64(attr, value);
}
void mediametrics_addDouble(mediametrics_handle_t handle, attr_t attr,
double value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->addDouble(attr, value);
}
void mediametrics_addRate(mediametrics_handle_t handle, attr_t attr,
int64_t count, int64_t duration) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item != NULL) item->addRate(attr, count, duration);
}
@@ -123,28 +126,28 @@
bool mediametrics_getInt32(mediametrics_handle_t handle, attr_t attr,
int32_t * value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->getInt32(attr, value);
}
bool mediametrics_getInt64(mediametrics_handle_t handle, attr_t attr,
int64_t * value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->getInt64(attr, value);
}
bool mediametrics_getDouble(mediametrics_handle_t handle, attr_t attr,
double *value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->getDouble(attr, value);
}
bool mediametrics_getRate(mediametrics_handle_t handle, attr_t attr,
int64_t * count, int64_t * duration, double *rate) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->getRate(attr, count, duration, rate);
}
@@ -152,7 +155,7 @@
// NB: caller owns the string that comes back, is responsible for freeing it
bool mediametrics_getCString(mediametrics_handle_t handle, attr_t attr,
char **value) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->getCString(attr, value);
@@ -164,36 +167,36 @@
}
bool mediametrics_selfRecord(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->selfrecord();
}
mediametrics_handle_t mediametrics_dup(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
- if (item == NULL) return android::MediaAnalyticsItem::convert(item);
- return android::MediaAnalyticsItem::convert(item->dup());
+ Item *item = (Item *) handle;
+ if (item == NULL) return Item::convert(item);
+ return Item::convert(item->dup());
}
const char *mediametrics_readable(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return "";
return item->toCString();
}
int32_t mediametrics_count(mediametrics_handle_t handle) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return 0;
return item->count();
}
bool mediametrics_isEnabled() {
// static, so doesn't need an instance
- return android::MediaAnalyticsItem::isEnabled();
+ return Item::isEnabled();
}
bool mediametrics_getAttributes(mediametrics_handle_t handle, char **buffer, size_t *length) {
- android::MediaAnalyticsItem *item = (android::MediaAnalyticsItem *) handle;
+ Item *item = (Item *) handle;
if (item == NULL) return false;
return item->writeToByteString(buffer, length) == android::NO_ERROR;
diff --git a/media/libmediametrics/MediaMetricsItem.cpp b/media/libmediametrics/MediaMetricsItem.cpp
new file mode 100644
index 0000000..62af0f7
--- /dev/null
+++ b/media/libmediametrics/MediaMetricsItem.cpp
@@ -0,0 +1,607 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "mediametrics::Item"
+
+#include <inttypes.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+
+#include <mutex>
+#include <set>
+
+#include <binder/Parcel.h>
+#include <utils/Errors.h>
+#include <utils/Log.h>
+#include <utils/SortedVector.h>
+#include <utils/threads.h>
+
+#include <binder/IServiceManager.h>
+#include <media/IMediaMetricsService.h>
+#include <media/MediaMetricsItem.h>
+#include <private/android_filesystem_config.h>
+
+// Max per-property string size before truncation in toString().
+// Do not make too large, as this is used for dumpsys purposes.
+static constexpr size_t kMaxPropertyStringSize = 4096;
+
+namespace android::mediametrics {
+
+#define DEBUG_SERVICEACCESS 0
+#define DEBUG_API 0
+#define DEBUG_ALLOCATIONS 0
+
+// after this many failed attempts, we stop trying [from this process] and just say that
+// the service is off.
+#define SVC_TRIES 2
+
+mediametrics::Item* mediametrics::Item::convert(mediametrics_handle_t handle) {
+ mediametrics::Item *item = (android::mediametrics::Item *) handle;
+ return item;
+}
+
+mediametrics_handle_t mediametrics::Item::convert(mediametrics::Item *item ) {
+ mediametrics_handle_t handle = (mediametrics_handle_t) item;
+ return handle;
+}
+
+mediametrics::Item::~Item() {
+ if (DEBUG_ALLOCATIONS) {
+ ALOGD("Destroy mediametrics::Item @ %p", this);
+ }
+}
+
+mediametrics::Item &mediametrics::Item::setTimestamp(nsecs_t ts) {
+ mTimestamp = ts;
+ return *this;
+}
+
+nsecs_t mediametrics::Item::getTimestamp() const {
+ return mTimestamp;
+}
+
+mediametrics::Item &mediametrics::Item::setPid(pid_t pid) {
+ mPid = pid;
+ return *this;
+}
+
+pid_t mediametrics::Item::getPid() const {
+ return mPid;
+}
+
+mediametrics::Item &mediametrics::Item::setUid(uid_t uid) {
+ mUid = uid;
+ return *this;
+}
+
+uid_t mediametrics::Item::getUid() const {
+ return mUid;
+}
+
+mediametrics::Item &mediametrics::Item::setPkgName(const std::string &pkgName) {
+ mPkgName = pkgName;
+ return *this;
+}
+
+mediametrics::Item &mediametrics::Item::setPkgVersionCode(int64_t pkgVersionCode) {
+ mPkgVersionCode = pkgVersionCode;
+ return *this;
+}
+
+int64_t mediametrics::Item::getPkgVersionCode() const {
+ return mPkgVersionCode;
+}
+
+// remove indicated keys and their values
+// return value is # keys removed
+size_t mediametrics::Item::filter(size_t n, const char *attrs[]) {
+ size_t zapped = 0;
+ for (size_t i = 0; i < n; ++i) {
+ zapped += mProps.erase(attrs[i]);
+ }
+ return zapped;
+}
+
+// remove any keys NOT in the provided list
+// return value is # keys removed
+size_t mediametrics::Item::filterNot(size_t n, const char *attrs[]) {
+ std::set<std::string> check(attrs, attrs + n);
+ size_t zapped = 0;
+ for (auto it = mProps.begin(); it != mProps.end();) {
+ if (check.find(it->first) != check.end()) {
+ ++it;
+ } else {
+ it = mProps.erase(it);
+ ++zapped;
+ }
+ }
+ return zapped;
+}
+
+// Parcel / serialize things for binder calls
+//
+
+status_t mediametrics::Item::readFromParcel(const Parcel& data) {
+ int32_t version;
+ status_t status = data.readInt32(&version);
+ if (status != NO_ERROR) return status;
+
+ switch (version) {
+ case 0:
+ return readFromParcel0(data);
+ default:
+ ALOGE("%s: unsupported parcel version: %d", __func__, version);
+ return INVALID_OPERATION;
+ }
+}
+
+status_t mediametrics::Item::readFromParcel0(const Parcel& data) {
+ const char *s = data.readCString();
+ mKey = s == nullptr ? "" : s;
+ int32_t pid, uid;
+ status_t status = data.readInt32(&pid) ?: data.readInt32(&uid);
+ if (status != NO_ERROR) return status;
+ mPid = (pid_t)pid;
+ mUid = (uid_t)uid;
+ s = data.readCString();
+ mPkgName = s == nullptr ? "" : s;
+ int32_t count;
+ int64_t version, timestamp;
+ status = data.readInt64(&version) ?: data.readInt64(×tamp) ?: data.readInt32(&count);
+ if (status != NO_ERROR) return status;
+ if (count < 0) return BAD_VALUE;
+ mPkgVersionCode = version;
+ mTimestamp = timestamp;
+ for (int i = 0; i < count; i++) {
+ Prop prop;
+ status_t status = prop.readFromParcel(data);
+ if (status != NO_ERROR) return status;
+ mProps[prop.getName()] = std::move(prop);
+ }
+ return NO_ERROR;
+}
+
+status_t mediametrics::Item::writeToParcel(Parcel *data) const {
+ if (data == nullptr) return BAD_VALUE;
+
+ const int32_t version = 0;
+ status_t status = data->writeInt32(version);
+ if (status != NO_ERROR) return status;
+
+ switch (version) {
+ case 0:
+ return writeToParcel0(data);
+ default:
+ ALOGE("%s: unsupported parcel version: %d", __func__, version);
+ return INVALID_OPERATION;
+ }
+}
+
+status_t mediametrics::Item::writeToParcel0(Parcel *data) const {
+ status_t status =
+ data->writeCString(mKey.c_str())
+ ?: data->writeInt32(mPid)
+ ?: data->writeInt32(mUid)
+ ?: data->writeCString(mPkgName.c_str())
+ ?: data->writeInt64(mPkgVersionCode)
+ ?: data->writeInt64(mTimestamp);
+ if (status != NO_ERROR) return status;
+
+ data->writeInt32((int32_t)mProps.size());
+ for (auto &prop : *this) {
+ status = prop.writeToParcel(data);
+ if (status != NO_ERROR) return status;
+ }
+ return NO_ERROR;
+}
+
+const char *mediametrics::Item::toCString() {
+ return toCString(PROTO_LAST);
+}
+
+const char * mediametrics::Item::toCString(int version) {
+ std::string val = toString(version);
+ return strdup(val.c_str());
+}
+
+std::string mediametrics::Item::toString() const {
+ return toString(PROTO_LAST);
+}
+
+std::string mediametrics::Item::toString(int version) const {
+ std::string result;
+ char buffer[kMaxPropertyStringSize];
+
+ snprintf(buffer, sizeof(buffer), "[%d:%s:%d:%d:%lld:%s:%zu:",
+ version, mKey.c_str(), mPid, mUid, (long long)mTimestamp,
+ mPkgName.c_str(), mProps.size());
+ result.append(buffer);
+ for (auto &prop : *this) {
+ prop.toStringBuffer(buffer, sizeof(buffer));
+ result.append(buffer);
+ }
+ result.append("]");
+ return result;
+}
+
+// for the lazy, we offer methods that finds the service and
+// calls the appropriate daemon
+bool mediametrics::Item::selfrecord() {
+ ALOGD_IF(DEBUG_API, "%s: delivering %s", __func__, this->toString().c_str());
+ sp<IMediaMetricsService> svc = getService();
+ if (svc != NULL) {
+ status_t status = svc->submit(this);
+ if (status != NO_ERROR) {
+ ALOGW("%s: failed to record: %s", __func__, this->toString().c_str());
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+//static
+bool BaseItem::isEnabled() {
+ // completely skip logging from certain UIDs. We do this here
+ // to avoid the multi-second timeouts while we learn that
+ // sepolicy will not let us find the service.
+ // We do this only for a select set of UIDs
+ // The sepolicy protection is still in place, we just want a faster
+ // response from this specific, small set of uids.
+
+ // This is checked only once in the lifetime of the process.
+ const uid_t uid = getuid();
+ switch (uid) {
+ case AID_RADIO: // telephony subsystem, RIL
+ return false;
+ }
+
+ int enabled = property_get_int32(Item::EnabledProperty, -1);
+ if (enabled == -1) {
+ enabled = property_get_int32(Item::EnabledPropertyPersist, -1);
+ }
+ if (enabled == -1) {
+ enabled = Item::EnabledProperty_default;
+ }
+ return enabled > 0;
+}
+
+// monitor health of our connection to the metrics service
+class MediaMetricsDeathNotifier : public IBinder::DeathRecipient {
+ virtual void binderDied(const wp<IBinder> &) {
+ ALOGW("Reacquire service connection on next request");
+ BaseItem::dropInstance();
+ }
+};
+
+static sp<MediaMetricsDeathNotifier> sNotifier;
+// static
+sp<IMediaMetricsService> BaseItem::sMediaMetricsService;
+static std::mutex sServiceMutex;
+static int sRemainingBindAttempts = SVC_TRIES;
+
+// static
+void BaseItem::dropInstance() {
+ std::lock_guard _l(sServiceMutex);
+ sRemainingBindAttempts = SVC_TRIES;
+ sMediaMetricsService = nullptr;
+}
+
+// static
+bool BaseItem::submitBuffer(const char *buffer, size_t size) {
+/*
+ mediametrics::Item item;
+ status_t status = item.readFromByteString(buffer, size);
+ ALOGD("%s: status:%d, size:%zu, item:%s", __func__, status, size, item.toString().c_str());
+ return item.selfrecord();
+ */
+
+ ALOGD_IF(DEBUG_API, "%s: delivering %zu bytes", __func__, size);
+ sp<IMediaMetricsService> svc = getService();
+ if (svc != nullptr) {
+ const status_t status = svc->submitBuffer(buffer, size);
+ if (status != NO_ERROR) {
+ ALOGW("%s: failed(%d) to record: %zu bytes", __func__, status, size);
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+//static
+sp<IMediaMetricsService> BaseItem::getService() {
+ static const char *servicename = "media.metrics";
+ static const bool enabled = isEnabled(); // singleton initialized
+
+ if (enabled == false) {
+ ALOGD_IF(DEBUG_SERVICEACCESS, "disabled");
+ return nullptr;
+ }
+ std::lock_guard _l(sServiceMutex);
+ // think of remainingBindAttempts as telling us whether service == nullptr because
+ // (1) we haven't tried to initialize it yet
+ // (2) we've tried to initialize it, but failed.
+ if (sMediaMetricsService == nullptr && sRemainingBindAttempts > 0) {
+ const char *badness = "";
+ sp<IServiceManager> sm = defaultServiceManager();
+ if (sm != nullptr) {
+ sp<IBinder> binder = sm->getService(String16(servicename));
+ if (binder != nullptr) {
+ sMediaMetricsService = interface_cast<IMediaMetricsService>(binder);
+ sNotifier = new MediaMetricsDeathNotifier();
+ binder->linkToDeath(sNotifier);
+ } else {
+ badness = "did not find service";
+ }
+ } else {
+ badness = "No Service Manager access";
+ }
+ if (sMediaMetricsService == nullptr) {
+ if (sRemainingBindAttempts > 0) {
+ sRemainingBindAttempts--;
+ }
+ ALOGD_IF(DEBUG_SERVICEACCESS, "%s: unable to bind to service %s: %s",
+ __func__, servicename, badness);
+ }
+ }
+ return sMediaMetricsService;
+}
+
+
+status_t mediametrics::Item::writeToByteString(char **pbuffer, size_t *plength) const
+{
+ if (pbuffer == nullptr || plength == nullptr)
+ return BAD_VALUE;
+
+ // get size
+ const size_t keySizeZeroTerminated = strlen(mKey.c_str()) + 1;
+ if (keySizeZeroTerminated > UINT16_MAX) {
+ ALOGW("%s: key size %zu too large", __func__, keySizeZeroTerminated);
+ return INVALID_OPERATION;
+ }
+ const uint16_t version = 0;
+ const uint32_t header_size =
+ sizeof(uint32_t) // total size
+ + sizeof(header_size) // header size
+ + sizeof(version) // encoding version
+ + sizeof(uint16_t) // key size
+ + keySizeZeroTerminated // key, zero terminated
+ + sizeof(int32_t) // pid
+ + sizeof(int32_t) // uid
+ + sizeof(int64_t) // timestamp
+ ;
+
+ uint32_t size = header_size
+ + sizeof(uint32_t) // # properties
+ ;
+ for (auto &prop : *this) {
+ const size_t propSize = prop.getByteStringSize();
+ if (propSize > UINT16_MAX) {
+ ALOGW("%s: prop %s size %zu too large", __func__, prop.getName(), propSize);
+ return INVALID_OPERATION;
+ }
+ if (__builtin_add_overflow(size, propSize, &size)) {
+ ALOGW("%s: item size overflow at property %s", __func__, prop.getName());
+ return INVALID_OPERATION;
+ }
+ }
+
+ // since we fill every byte in the buffer (there is no padding),
+ // malloc is used here instead of calloc.
+ char * const build = (char *)malloc(size);
+ if (build == nullptr) return NO_MEMORY;
+
+ char *filling = build;
+ char *buildmax = build + size;
+ if (insert((uint32_t)size, &filling, buildmax) != NO_ERROR
+ || insert(header_size, &filling, buildmax) != NO_ERROR
+ || insert(version, &filling, buildmax) != NO_ERROR
+ || insert((uint16_t)keySizeZeroTerminated, &filling, buildmax) != NO_ERROR
+ || insert(mKey.c_str(), &filling, buildmax) != NO_ERROR
+ || insert((int32_t)mPid, &filling, buildmax) != NO_ERROR
+ || insert((int32_t)mUid, &filling, buildmax) != NO_ERROR
+ || insert((int64_t)mTimestamp, &filling, buildmax) != NO_ERROR
+ || insert((uint32_t)mProps.size(), &filling, buildmax) != NO_ERROR) {
+ ALOGE("%s:could not write header", __func__); // shouldn't happen
+ free(build);
+ return INVALID_OPERATION;
+ }
+ for (auto &prop : *this) {
+ if (prop.writeToByteString(&filling, buildmax) != NO_ERROR) {
+ free(build);
+ // shouldn't happen
+ ALOGE("%s:could not write prop %s", __func__, prop.getName());
+ return INVALID_OPERATION;
+ }
+ }
+
+ if (filling != buildmax) {
+ ALOGE("%s: problems populating; wrote=%d planned=%d",
+ __func__, (int)(filling - build), (int)size);
+ free(build);
+ return INVALID_OPERATION;
+ }
+ *pbuffer = build;
+ *plength = size;
+ return NO_ERROR;
+}
+
+status_t mediametrics::Item::readFromByteString(const char *bufferptr, size_t length)
+{
+ if (bufferptr == nullptr) return BAD_VALUE;
+
+ const char *read = bufferptr;
+ const char *readend = bufferptr + length;
+
+ uint32_t size;
+ uint32_t header_size;
+ uint16_t version;
+ uint16_t key_size;
+ std::string key;
+ int32_t pid;
+ int32_t uid;
+ int64_t timestamp;
+ uint32_t propCount;
+ if (extract(&size, &read, readend) != NO_ERROR
+ || extract(&header_size, &read, readend) != NO_ERROR
+ || extract(&version, &read, readend) != NO_ERROR
+ || extract(&key_size, &read, readend) != NO_ERROR
+ || extract(&key, &read, readend) != NO_ERROR
+ || extract(&pid, &read, readend) != NO_ERROR
+ || extract(&uid, &read, readend) != NO_ERROR
+ || extract(×tamp, &read, readend) != NO_ERROR
+ || size > length
+ || key.size() + 1 != key_size
+ || header_size > size) {
+ ALOGW("%s: invalid header", __func__);
+ return INVALID_OPERATION;
+ }
+ mKey = std::move(key);
+ const size_t pos = read - bufferptr;
+ if (pos > header_size) {
+ ALOGW("%s: invalid header pos:%zu > header_size:%u",
+ __func__, pos, header_size);
+ return INVALID_OPERATION;
+ } else if (pos < header_size) {
+ ALOGW("%s: mismatched header pos:%zu < header_size:%u, advancing",
+ __func__, pos, header_size);
+ read += (header_size - pos);
+ }
+ if (extract(&propCount, &read, readend) != NO_ERROR) {
+ ALOGD("%s: cannot read prop count", __func__);
+ return INVALID_OPERATION;
+ }
+ mPid = pid;
+ mUid = uid;
+ mTimestamp = timestamp;
+ for (size_t i = 0; i < propCount; ++i) {
+ Prop prop;
+ if (prop.readFromByteString(&read, readend) != NO_ERROR) {
+ ALOGW("%s: cannot read prop %zu", __func__, i);
+ return INVALID_OPERATION;
+ }
+ mProps[prop.getName()] = std::move(prop);
+ }
+ return NO_ERROR;
+}
+
+status_t mediametrics::Item::Prop::readFromParcel(const Parcel& data)
+{
+ const char *key = data.readCString();
+ if (key == nullptr) return BAD_VALUE;
+ int32_t type;
+ status_t status = data.readInt32(&type);
+ if (status != NO_ERROR) return status;
+ switch (type) {
+ case mediametrics::kTypeInt32: {
+ int32_t value;
+ status = data.readInt32(&value);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeInt64: {
+ int64_t value;
+ status = data.readInt64(&value);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeDouble: {
+ double value;
+ status = data.readDouble(&value);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeCString: {
+ const char *s = data.readCString();
+ if (s == nullptr) return BAD_VALUE;
+ mElem = s;
+ } break;
+ case mediametrics::kTypeRate: {
+ std::pair<int64_t, int64_t> rate;
+ status = data.readInt64(&rate.first)
+ ?: data.readInt64(&rate.second);
+ if (status != NO_ERROR) return status;
+ mElem = rate;
+ } break;
+ case mediametrics::kTypeNone: {
+ mElem = std::monostate{};
+ } break;
+ default:
+ ALOGE("%s: reading bad item type: %d", __func__, type);
+ return BAD_VALUE;
+ }
+ setName(key);
+ return NO_ERROR;
+}
+
+status_t mediametrics::Item::Prop::readFromByteString(
+ const char **bufferpptr, const char *bufferptrmax)
+{
+ uint16_t len;
+ std::string name;
+ uint8_t type;
+ status_t status = extract(&len, bufferpptr, bufferptrmax)
+ ?: extract(&type, bufferpptr, bufferptrmax)
+ ?: extract(&name, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ switch (type) {
+ case mediametrics::kTypeInt32: {
+ int32_t value;
+ status = extract(&value, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeInt64: {
+ int64_t value;
+ status = extract(&value, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeDouble: {
+ double value;
+ status = extract(&value, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeRate: {
+ std::pair<int64_t, int64_t> value;
+ status = extract(&value.first, bufferpptr, bufferptrmax)
+ ?: extract(&value.second, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ mElem = value;
+ } break;
+ case mediametrics::kTypeCString: {
+ std::string value;
+ status = extract(&value, bufferpptr, bufferptrmax);
+ if (status != NO_ERROR) return status;
+ mElem = std::move(value);
+ } break;
+ case mediametrics::kTypeNone: {
+ mElem = std::monostate{};
+ } break;
+ default:
+ ALOGE("%s: found bad prop type: %d, name %s",
+ __func__, (int)type, mName.c_str()); // no payload sent
+ return BAD_VALUE;
+ }
+ mName = name;
+ return NO_ERROR;
+}
+
+} // namespace android::mediametrics
diff --git a/media/libmediametrics/include/IMediaAnalyticsService.h b/media/libmediametrics/include/IMediaMetricsService.h
similarity index 83%
rename from media/libmediametrics/include/IMediaAnalyticsService.h
rename to media/libmediametrics/include/IMediaMetricsService.h
index 30c63e7..d6871ec 100644
--- a/media/libmediametrics/include/IMediaAnalyticsService.h
+++ b/media/libmediametrics/include/IMediaMetricsService.h
@@ -29,15 +29,14 @@
#include <binder/IServiceManager.h>
-#include <media/MediaAnalyticsItem.h>
-// nope...#include <media/MediaAnalytics.h>
+#include <media/MediaMetricsItem.h>
namespace android {
-class IMediaAnalyticsService: public IInterface
+class IMediaMetricsService: public IInterface
{
public:
- DECLARE_META_INTERFACE(MediaAnalyticsService);
+ DECLARE_META_INTERFACE(MediaMetricsService);
/**
* Submits the indicated record to the mediaanalytics service, where
@@ -48,14 +47,14 @@
* \return status which is negative if an error is detected (some errors
may be silent and return 0 - success).
*/
- virtual status_t submit(MediaAnalyticsItem *item) = 0;
+ virtual status_t submit(mediametrics::Item *item) = 0;
virtual status_t submitBuffer(const char *buffer, size_t length) = 0;
};
// ----------------------------------------------------------------------------
-class BnMediaAnalyticsService: public BnInterface<IMediaAnalyticsService>
+class BnMediaMetricsService: public BnInterface<IMediaMetricsService>
{
public:
status_t onTransact(uint32_t code,
@@ -66,7 +65,7 @@
protected:
// Internal call where release is true if the service is to delete the item.
virtual status_t submitInternal(
- MediaAnalyticsItem *item, bool release) = 0;
+ mediametrics::Item *item, bool release) = 0;
};
}; // namespace android
diff --git a/media/libmediametrics/include/MediaAnalyticsItem.h b/media/libmediametrics/include/MediaAnalyticsItem.h
deleted file mode 100644
index bff7c9b..0000000
--- a/media/libmediametrics/include/MediaAnalyticsItem.h
+++ /dev/null
@@ -1,943 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_MEDIA_MEDIAANALYTICSITEM_H
-#define ANDROID_MEDIA_MEDIAANALYTICSITEM_H
-
-#include "MediaMetrics.h"
-
-#include <algorithm>
-#include <string>
-#include <sys/types.h>
-
-#include <cutils/properties.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/RefBase.h>
-#include <utils/StrongPointer.h>
-#include <utils/Timers.h>
-
-namespace android {
-
-class IMediaAnalyticsService;
-class Parcel;
-
-/*
- * MediaMetrics Item
- *
- * Byte string format.
- *
- * For Java
- * int64 corresponds to long
- * int32, uint32 corresponds to int
- * uint16 corresponds to char
- * uint8, int8 corresponds to byte
- *
- * Hence uint8 and uint32 values are limited to INT8_MAX and INT32_MAX.
- *
- * Physical layout of integers and doubles within the MediaMetrics byte string
- * is in Native / host order, which is nearly always little endian.
- *
- * -- begin of item
- * -- begin of header
- * (uint32) item size: including the item size field
- * (uint32) header size, including the item size and header size fields.
- * (uint16) version: exactly 0
- * (uint16) key size, that is key strlen + 1 for zero termination.
- * (int8)+ key string which is 0 terminated
- * (int32) pid
- * (int32) uid
- * (int64) timestamp
- * -- end of header
- * -- begin body
- * (uint32) number of properties
- * -- repeat for number of properties
- * (uint16) property size, including property size field itself
- * (uint8) type of property
- * (int8)+ key string, including 0 termination
- * based on type of property (given above), one of:
- * (int32)
- * (int64)
- * (double)
- * (int8)+ for cstring, including 0 termination
- * (int64, int64) for rate
- * -- end body
- * -- end of item
- */
-
-namespace mediametrics {
-
-// Type must match MediaMetrics.java
-enum Type {
- kTypeNone = 0,
- kTypeInt32 = 1,
- kTypeInt64 = 2,
- kTypeDouble = 3,
- kTypeCString = 4,
- kTypeRate = 5,
-};
-
-template<size_t N>
-static inline bool startsWith(const std::string &s, const char (&comp)[N]) {
- return !strncmp(s.c_str(), comp, N-1);
-}
-
-/**
- * Media Metrics BaseItem
- *
- * A base class which contains utility static functions to write to a byte stream
- * and access the Media Metrics service.
- */
-
-class BaseItem {
- friend class MediaMetricsDeathNotifier; // for dropInstance
- // enabled 1, disabled 0
-public:
- // are we collecting analytics data
- static bool isEnabled();
-
-protected:
- static constexpr const char * const EnabledProperty = "media.metrics.enabled";
- static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
- static const int EnabledProperty_default = 1;
-
- // let's reuse a binder connection
- static sp<IMediaAnalyticsService> sAnalyticsService;
- static sp<IMediaAnalyticsService> getInstance();
- static void dropInstance();
- static bool submitBuffer(const char *buffer, size_t len);
-
- static status_t writeToByteString(
- const char *name, int32_t value, char **bufferpptr, char *bufferptrmax);
- static status_t writeToByteString(
- const char *name, int64_t value, char **bufferpptr, char *bufferptrmax);
- static status_t writeToByteString(
- const char *name, double value, char **bufferpptr, char *bufferptrmax);
- static status_t writeToByteString(
- const char *name, const std::pair<int64_t, int64_t> &value,
- char **bufferpptr, char *bufferptrmax);
- static status_t writeToByteString(
- const char *name, char * const &value, char **bufferpptr, char *bufferptrmax);
- static status_t writeToByteString(
- const char *name, const char * const &value, char **bufferpptr, char *bufferptrmax);
- struct none_t {}; // for kTypeNone
- static status_t writeToByteString(
- const char *name, const none_t &, char **bufferpptr, char *bufferptrmax);
-
- template<typename T>
- static status_t sizeOfByteString(const char *name, const T& value) {
- return 2 + 1 + strlen(name) + 1 + sizeof(value);
- }
- template<> // static
- status_t sizeOfByteString(const char *name, char * const &value) {
- return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
- }
- template<> // static
- status_t sizeOfByteString(const char *name, const char * const &value) {
- return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
- }
- template<> // static
- status_t sizeOfByteString(const char *name, const none_t &) {
- return 2 + 1 + strlen(name) + 1;
- }
-};
-
-/**
- * Media Metrics BufferedItem
- *
- * A base class which represents a put-only Media Metrics item, storing
- * the Media Metrics data in a buffer with begin and end pointers.
- *
- * If a property key is entered twice, it will be stored in the buffer twice,
- * and (implementation defined) the last value for that key will be used
- * by the Media Metrics service.
- *
- * For realloc, a baseRealloc pointer must be passed in either explicitly
- * or implicitly in the constructor. This will be updated with the value used on realloc.
- */
-class BufferedItem : public BaseItem {
-public:
- static inline constexpr uint16_t kVersion = 0;
-
- virtual ~BufferedItem() = default;
- BufferedItem(const BufferedItem&) = delete;
- BufferedItem& operator=(const BufferedItem&) = delete;
-
- BufferedItem(const std::string key, char *begin, char *end)
- : BufferedItem(key.c_str(), begin, end) { }
-
- BufferedItem(const char *key, char *begin, char *end)
- : BufferedItem(key, begin, end, nullptr) { }
-
- BufferedItem(const char *key, char **begin, char *end)
- : BufferedItem(key, *begin, end, begin) { }
-
- BufferedItem(const char *key, char *begin, char *end, char **baseRealloc)
- : mBegin(begin)
- , mEnd(end)
- , mBaseRealloc(baseRealloc)
- {
- init(key);
- }
-
- template<typename T>
- BufferedItem &set(const char *key, const T& value) {
- reallocFor(sizeOfByteString(key, value));
- if (mStatus == NO_ERROR) {
- mStatus = BaseItem::writeToByteString(key, value, &mBptr, mEnd);
- ++mPropCount;
- }
- return *this;
- }
-
- template<typename T>
- BufferedItem &set(const std::string& key, const T& value) {
- return set(key.c_str(), value);
- }
-
- BufferedItem &setPid(pid_t pid) {
- if (mStatus == NO_ERROR) {
- copyTo(mBegin + mHeaderLen - 16, (int32_t)pid);
- }
- return *this;
- }
-
- BufferedItem &setUid(uid_t uid) {
- if (mStatus == NO_ERROR) {
- copyTo(mBegin + mHeaderLen - 12, (int32_t)uid);
- }
- return *this;
- }
-
- BufferedItem &setTimestamp(nsecs_t timestamp) {
- if (mStatus == NO_ERROR) {
- copyTo(mBegin + mHeaderLen - 8, (int64_t)timestamp);
- }
- return *this;
- }
-
- bool record() {
- return updateHeader()
- && BaseItem::submitBuffer(getBuffer(), getLength());
- }
-
- bool isValid () const {
- return mStatus == NO_ERROR;
- }
-
- char *getBuffer() const { return mBegin; }
- size_t getLength() const { return mBptr - mBegin; }
- size_t getRemaining() const { return mEnd - mBptr; }
- size_t getCapacity() const { return mEnd - mBegin; }
-
- bool updateHeader() {
- if (mStatus != NO_ERROR) return false;
- copyTo(mBegin + 0, (uint32_t)getLength());
- copyTo(mBegin + 4, (uint32_t)mHeaderLen);
- copyTo(mBegin + mHeaderLen, (uint32_t)mPropCount);
- return true;
- }
-
-protected:
- BufferedItem() = default;
-
- void reallocFor(size_t required) {
- if (mStatus != NO_ERROR) return;
- const size_t remaining = getRemaining();
- if (required <= remaining) return;
- if (mBaseRealloc == nullptr) {
- mStatus = NO_MEMORY;
- return;
- }
-
- const size_t current = getLength();
- size_t minimum = current + required;
- if (minimum > SSIZE_MAX >> 1) {
- mStatus = NO_MEMORY;
- return;
- }
- minimum <<= 1;
- void *newptr = realloc(*mBaseRealloc, minimum);
- if (newptr == nullptr) {
- mStatus = NO_MEMORY;
- return;
- }
- if (newptr != *mBaseRealloc) {
- // ALOGD("base changed! current:%zu new size %zu", current, minimum);
- if (*mBaseRealloc == nullptr) {
- memcpy(newptr, mBegin, current);
- }
- mBegin = (char *)newptr;
- *mBaseRealloc = mBegin;
- mEnd = mBegin + minimum;
- mBptr = mBegin + current;
- } else {
- // ALOGD("base kept! current:%zu new size %zu", current, minimum);
- mEnd = mBegin + minimum;
- }
- }
- template<typename T>
- void copyTo(char *ptr, const T& value) {
- memcpy(ptr, &value, sizeof(value));
- }
-
- void init(const char *key) {
- mBptr = mBegin;
- const size_t keylen = strlen(key) + 1;
- mHeaderLen = 4 + 4 + 2 + 2 + keylen + 4 + 4 + 8;
- reallocFor(mHeaderLen);
- if (mStatus != NO_ERROR) return;
- mBptr = mBegin + mHeaderLen + 4; // this includes propcount.
-
- if (mEnd < mBptr || keylen > UINT16_MAX) {
- mStatus = NO_MEMORY;
- mBptr = mEnd;
- return;
- }
- copyTo(mBegin + 8, kVersion);
- copyTo(mBegin + 10, (uint16_t)keylen);
- strcpy(mBegin + 12, key);
-
- // initialize some parameters (that could be overridden)
- setPid(-1);
- setUid(-1);
- setTimestamp(0);
- }
-
- char *mBegin = nullptr;
- char *mEnd = nullptr;
- char **mBaseRealloc = nullptr; // set to an address if realloc should be done.
- // upon return, that pointer is updated with
- // whatever needs to be freed.
- char *mBptr = nullptr;
- status_t mStatus = NO_ERROR;
- uint32_t mPropCount = 0;
- uint32_t mHeaderLen = 0;
-};
-
-/**
- * MediaMetrics Item is a stack allocated media analytics item used for
- * fast logging. It falls over to a malloc if needed.
- *
- * This is templated with a buffer size to allocate on the stack.
- */
-template <size_t N = 4096>
-class Item : public BufferedItem {
-public:
- explicit Item(const std::string key) : Item(key.c_str()) { }
-
- // Since this class will not be defined before the base class, we initialize variables
- // in our own order.
- explicit Item(const char *key) {
- mBegin = mBuffer;
- mEnd = mBuffer + N;
- mBaseRealloc = &mReallocPtr;
- init(key);
- }
-
- ~Item() override {
- if (mReallocPtr != nullptr) { // do the check before calling free to avoid overhead.
- free(mReallocPtr);
- }
- }
-
-private:
- char *mReallocPtr = nullptr; // set non-null by base class if realloc happened.
- char mBuffer[N];
-};
-
-} // mediametrics
-
-/**
- * Media Metrics MediaAnalyticsItem
- *
- * A mutable item representing an event or record that will be
- * logged with the Media Metrics service. For client logging, one should
- * use the mediametrics::Item.
- *
- * The MediaAnalyticsItem is designed for the service as it has getters.
- */
-class MediaAnalyticsItem : public mediametrics::BaseItem {
- friend class MediaMetricsJNI; // TODO: remove this access
-
-public:
-
- // TODO: remove this duplicate definition when frameworks base is updated.
- enum Type {
- kTypeNone = 0,
- kTypeInt32 = 1,
- kTypeInt64 = 2,
- kTypeDouble = 3,
- kTypeCString = 4,
- kTypeRate = 5,
- };
-
- static constexpr const char * const kKeyNone = "none";
- static constexpr const char * const kKeyAny = "any";
-
- enum {
- PROTO_V0 = 0,
- PROTO_FIRST = PROTO_V0,
- PROTO_V1 = 1,
- PROTO_LAST = PROTO_V1,
- };
-
- // T must be convertible to mKey
- template <typename T>
- explicit MediaAnalyticsItem(T key)
- : mKey(key) { }
- MediaAnalyticsItem() = default;
-
- MediaAnalyticsItem(const MediaAnalyticsItem&) = delete;
- MediaAnalyticsItem &operator=(const MediaAnalyticsItem&) = delete;
-
- bool operator==(const MediaAnalyticsItem& other) const {
- if (mPropCount != other.mPropCount
- || mPid != other.mPid
- || mUid != other.mUid
- || mPkgName != other.mPkgName
- || mPkgVersionCode != other.mPkgVersionCode
- || mKey != other.mKey
- || mTimestamp != other.mTimestamp) return false;
- for (size_t i = 0; i < mPropCount; ++i) {
- Prop *p = other.findProp(mProps[i].getName());
- if (p == nullptr || mProps[i] != *p) return false;
- }
- return true;
- }
- bool operator!=(const MediaAnalyticsItem& other) const {
- return !(*this == other);
- }
-
- template <typename T>
- static MediaAnalyticsItem* create(T key) {
- return new MediaAnalyticsItem(key);
- }
- static MediaAnalyticsItem* create() {
- return new MediaAnalyticsItem();
- }
-
- static MediaAnalyticsItem* convert(mediametrics_handle_t);
- static mediametrics_handle_t convert(MediaAnalyticsItem *);
-
- // access functions for the class
- ~MediaAnalyticsItem();
-
- // reset all contents, discarding any extra data
- void clear();
- MediaAnalyticsItem *dup();
-
- MediaAnalyticsItem &setKey(const char *key) {
- mKey = key;
- return *this;
- }
- const std::string& getKey() const { return mKey; }
-
- // # of properties in the record
- size_t count() const { return mPropCount; }
-
- template<typename S, typename T>
- MediaAnalyticsItem &set(S key, T value) {
- allocateProp(key)->set(value);
- return *this;
- }
-
- // set values appropriately
- MediaAnalyticsItem &setInt32(const char *key, int32_t value) {
- return set(key, value);
- }
- MediaAnalyticsItem &setInt64(const char *key, int64_t value) {
- return set(key, value);
- }
- MediaAnalyticsItem &setDouble(const char *key, double value) {
- return set(key, value);
- }
- MediaAnalyticsItem &setRate(const char *key, int64_t count, int64_t duration) {
- return set(key, std::make_pair(count, duration));
- }
- MediaAnalyticsItem &setCString(const char *key, const char *value) {
- return set(key, value);
- }
-
- // fused get/add/set; if attr wasn't there, it's a simple set.
- // type-mismatch counts as "wasn't there".
- template<typename S, typename T>
- MediaAnalyticsItem &add(S key, T value) {
- allocateProp(key)->add(value);
- return *this;
- }
-
- MediaAnalyticsItem &addInt32(const char *key, int32_t value) {
- return add(key, value);
- }
- MediaAnalyticsItem &addInt64(const char *key, int64_t value) {
- return add(key, value);
- }
- MediaAnalyticsItem &addDouble(const char *key, double value) {
- return add(key, value);
- }
- MediaAnalyticsItem &addRate(const char *key, int64_t count, int64_t duration) {
- return add(key, std::make_pair(count, duration));
- }
-
- // find & extract values
- // return indicates whether attr exists (and thus value filled in)
- // NULL parameter value suppresses storage of value.
- template<typename S, typename T>
- bool get(S key, T *value) const {
- Prop *prop = findProp(key);
- return prop != nullptr && prop->get(value);
- }
-
- bool getInt32(const char *key, int32_t *value) const {
- return get(key, value);
- }
- bool getInt64(const char *key, int64_t *value) const {
- return get(key, value);
- }
- bool getDouble(const char *key, double *value) const {
- return get(key, value);
- }
- bool getRate(const char *key, int64_t *count, int64_t *duration, double *rate) const {
- std::pair<int64_t, int64_t> value;
- if (!get(key, &value)) return false;
- if (count != nullptr) *count = value.first;
- if (duration != nullptr) *duration = value.second;
- if (rate != nullptr) {
- if (value.second != 0) {
- *rate = (double)value.first / value.second; // TODO: isn't INF OK?
- } else {
- *rate = 0.;
- }
- }
- return true;
- }
- // Caller owns the returned string
- bool getCString(const char *key, char **value) const {
- const char *cs;
- if (get(key, &cs)) {
- *value = cs != nullptr ? strdup(cs) : nullptr;
- return true;
- }
- return false;
- }
- bool getString(const char *key, std::string *value) const {
- return get(key, value);
- }
-
- // Deliver the item to MediaMetrics
- bool selfrecord();
-
- // remove indicated attributes and their values
- // filterNot() could also be called keepOnly()
- // return value is # attributes removed
- // XXX: perhaps 'remove' instead of 'filter'
- // XXX: filterNot would become 'keep'
- size_t filter(size_t count, const char *attrs[]);
- size_t filterNot(size_t count, const char *attrs[]);
- size_t filter(const char *attr) { return filter(1, &attr); }
-
- // below here are used on server side or to talk to server
- // clients need not worry about these.
-
- // timestamp, pid, and uid only used on server side
- // timestamp is in 'nanoseconds, unix time'
- MediaAnalyticsItem &setTimestamp(nsecs_t);
- nsecs_t getTimestamp() const;
-
- MediaAnalyticsItem &setPid(pid_t);
- pid_t getPid() const;
-
- MediaAnalyticsItem &setUid(uid_t);
- uid_t getUid() const;
-
- MediaAnalyticsItem &setPkgName(const std::string &pkgName);
- std::string getPkgName() const { return mPkgName; }
-
- MediaAnalyticsItem &setPkgVersionCode(int64_t);
- int64_t getPkgVersionCode() const;
-
- // our serialization code for binder calls
- status_t writeToParcel(Parcel *) const;
- status_t readFromParcel(const Parcel&);
-
- status_t writeToByteString(char **bufferptr, size_t *length) const;
- status_t readFromByteString(const char *bufferptr, size_t length);
-
-
- std::string toString() const;
- std::string toString(int version) const;
- const char *toCString();
- const char *toCString(int version);
-
- protected:
-
- // merge fields from arg into this
- // with rules for first/last/add, etc
- // XXX: document semantics and how they are indicated
- // caller continues to own 'incoming'
- bool merge(MediaAnalyticsItem *incoming);
-
-private:
- // handle Parcel version 0
- int32_t writeToParcel0(Parcel *) const;
- int32_t readFromParcel0(const Parcel&);
-
-
-
- // checks equality even with nullptr.
- static bool stringEquals(const char *a, const char *b) {
- if (a == nullptr) {
- return b == nullptr;
- } else {
- return b != nullptr && strcmp(a, b) == 0;
- }
- }
-
-public:
-
- class Prop {
- friend class MediaMetricsJNI; // TODO: remove this access
- public:
- Prop() = default;
- Prop(const Prop& other) {
- *this = other;
- }
- Prop& operator=(const Prop& other) {
- if (other.mName != nullptr) {
- mName = strdup(other.mName);
- } else {
- mName = nullptr;
- }
- mType = other.mType;
- switch (mType) {
- case kTypeInt32:
- u.int32Value = other.u.int32Value;
- break;
- case kTypeInt64:
- u.int64Value = other.u.int64Value;
- break;
- case kTypeDouble:
- u.doubleValue = other.u.doubleValue;
- break;
- case kTypeCString:
- u.CStringValue = strdup(other.u.CStringValue);
- break;
- case kTypeRate:
- u.rate = other.u.rate;
- break;
- case kTypeNone:
- break;
- default:
- // abort?
- break;
- }
- return *this;
- }
- bool operator==(const Prop& other) const {
- if (!stringEquals(mName, other.mName)
- || mType != other.mType) return false;
- switch (mType) {
- case kTypeInt32:
- return u.int32Value == other.u.int32Value;
- case kTypeInt64:
- return u.int64Value == other.u.int64Value;
- case kTypeDouble:
- return u.doubleValue == other.u.doubleValue;
- case kTypeCString:
- return stringEquals(u.CStringValue, other.u.CStringValue);
- case kTypeRate:
- return u.rate == other.u.rate;
- case kTypeNone:
- default:
- return true;
- }
- }
- bool operator!=(const Prop& other) const {
- return !(*this == other);
- }
-
- void clear() {
- free(mName);
- mName = nullptr;
- clearValue();
- }
- void clearValue() {
- if (mType == kTypeCString) {
- free(u.CStringValue);
- u.CStringValue = nullptr;
- }
- mType = kTypeNone;
- }
-
- Type getType() const {
- return mType;
- }
-
- const char *getName() const {
- return mName;
- }
-
- void swap(Prop& other) {
- std::swap(mName, other.mName);
- std::swap(mType, other.mType);
- std::swap(u, other.u);
- }
-
- void setName(const char *name) {
- free(mName);
- if (name != nullptr) {
- mName = strdup(name);
- } else {
- mName = nullptr;
- }
- }
-
- bool isNamed(const char *name) const {
- return stringEquals(name, mName);
- }
-
- template <typename T> void visit(T f) const {
- switch (mType) {
- case MediaAnalyticsItem::kTypeInt32:
- f(u.int32Value);
- return;
- case MediaAnalyticsItem::kTypeInt64:
- f(u.int64Value);
- return;
- case MediaAnalyticsItem::kTypeDouble:
- f(u.doubleValue);
- return;
- case MediaAnalyticsItem::kTypeRate:
- f(u.rate);
- return;
- case MediaAnalyticsItem::kTypeCString:
- f(u.CStringValue);
- return;
- default:
- return;
- }
- }
-
- template <typename T> bool get(T *value) const = delete;
- template <>
- bool get(int32_t *value) const {
- if (mType != kTypeInt32) return false;
- if (value != nullptr) *value = u.int32Value;
- return true;
- }
- template <>
- bool get(int64_t *value) const {
- if (mType != kTypeInt64) return false;
- if (value != nullptr) *value = u.int64Value;
- return true;
- }
- template <>
- bool get(double *value) const {
- if (mType != kTypeDouble) return false;
- if (value != nullptr) *value = u.doubleValue;
- return true;
- }
- template <>
- bool get(const char** value) const {
- if (mType != kTypeCString) return false;
- if (value != nullptr) *value = u.CStringValue;
- return true;
- }
- template <>
- bool get(std::string* value) const {
- if (mType != kTypeCString) return false;
- if (value != nullptr) *value = u.CStringValue;
- return true;
- }
- template <>
- bool get(std::pair<int64_t, int64_t> *value) const {
- if (mType != kTypeRate) return false;
- if (value != nullptr) {
- *value = u.rate;
- }
- return true;
- }
-
- template <typename T> void set(const T& value) = delete;
- template <>
- void set(const int32_t& value) {
- mType = kTypeInt32;
- u.int32Value = value;
- }
- template <>
- void set(const int64_t& value) {
- mType = kTypeInt64;
- u.int64Value = value;
- }
- template <>
- void set(const double& value) {
- mType = kTypeDouble;
- u.doubleValue = value;
- }
- template <>
- void set(const char* const& value) {
- if (mType == kTypeCString) {
- free(u.CStringValue);
- } else {
- mType = kTypeCString;
- }
- if (value == nullptr) {
- u.CStringValue = nullptr;
- } else {
- size_t len = strlen(value);
- if (len > UINT16_MAX - 1) {
- len = UINT16_MAX - 1;
- }
- u.CStringValue = (char *)malloc(len + 1);
- strncpy(u.CStringValue, value, len);
- u.CStringValue[len] = 0;
- }
- }
- template <>
- void set(const std::pair<int64_t, int64_t> &value) {
- mType = kTypeRate;
- u.rate = {value.first, value.second};
- }
-
- template <typename T> void add(const T& value) = delete;
- template <>
- void add(const int32_t& value) {
- if (mType == kTypeInt32) {
- u.int32Value += value;
- } else {
- mType = kTypeInt32;
- u.int32Value = value;
- }
- }
- template <>
- void add(const int64_t& value) {
- if (mType == kTypeInt64) {
- u.int64Value += value;
- } else {
- mType = kTypeInt64;
- u.int64Value = value;
- }
- }
- template <>
- void add(const double& value) {
- if (mType == kTypeDouble) {
- u.doubleValue += value;
- } else {
- mType = kTypeDouble;
- u.doubleValue = value;
- }
- }
- template <>
- void add(const std::pair<int64_t, int64_t>& value) {
- if (mType == kTypeRate) {
- u.rate.first += value.first;
- u.rate.second += value.second;
- } else {
- mType = kTypeRate;
- u.rate = value;
- }
- }
-
- status_t writeToParcel(Parcel *data) const;
- status_t readFromParcel(const Parcel& data);
- void toString(char *buffer, size_t length) const;
- size_t getByteStringSize() const;
- status_t writeToByteString(char **bufferpptr, char *bufferptrmax) const;
- status_t readFromByteString(const char **bufferpptr, const char *bufferptrmax);
-
- // TODO: make private (and consider converting to std::variant)
- // private:
- char *mName = nullptr;
- Type mType = kTypeNone;
- union u__ {
- u__() { zero(); }
- u__(u__ &&other) {
- *this = std::move(other);
- }
- u__& operator=(u__ &&other) {
- memcpy(this, &other, sizeof(*this));
- other.zero();
- return *this;
- }
- void zero() { memset(this, 0, sizeof(*this)); }
-
- int32_t int32Value;
- int64_t int64Value;
- double doubleValue;
- char *CStringValue;
- std::pair<int64_t, int64_t> rate;
- } u;
- };
-
- class iterator {
- public:
- iterator(size_t pos, const MediaAnalyticsItem &_item)
- : i(std::min(pos, _item.count()))
- , item(_item) { }
- iterator &operator++() {
- i = std::min(i + 1, item.count());
- return *this;
- }
- bool operator!=(iterator &other) const {
- return i != other.i;
- }
- Prop &operator*() const {
- return item.mProps[i];
- }
-
- private:
- size_t i;
- const MediaAnalyticsItem &item;
- };
-
- iterator begin() const {
- return iterator(0, *this);
- }
- iterator end() const {
- return iterator(SIZE_MAX, *this);
- }
-
-private:
-
- // TODO: make prop management class
- size_t findPropIndex(const char *name) const;
- Prop *findProp(const char *name) const;
- Prop *allocateProp();
-
- enum {
- kGrowProps = 10
- };
- bool growProps(int increment = kGrowProps);
- Prop *allocateProp(const char *name);
- bool removeProp(const char *name);
- Prop *allocateProp(const std::string& name) { return allocateProp(name.c_str()); }
-
- size_t mPropCount = 0;
- size_t mPropSize = 0;
- Prop *mProps = nullptr;
-
- pid_t mPid = -1;
- uid_t mUid = -1;
- std::string mPkgName;
- int64_t mPkgVersionCode = 0;
- std::string mKey{kKeyNone};
- nsecs_t mTimestamp = 0;
-};
-
-} // namespace android
-
-#endif
diff --git a/media/libmediametrics/include/MediaMetricsItem.h b/media/libmediametrics/include/MediaMetricsItem.h
new file mode 100644
index 0000000..536c0e1
--- /dev/null
+++ b/media/libmediametrics/include/MediaMetricsItem.h
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_MEDIA_MEDIAMETRICSITEM_H
+#define ANDROID_MEDIA_MEDIAMETRICSITEM_H
+
+#include "MediaMetrics.h"
+
+#include <algorithm>
+#include <map>
+#include <string>
+#include <sys/types.h>
+#include <variant>
+
+#include <binder/Parcel.h>
+#include <cutils/properties.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+#include <utils/StrongPointer.h>
+#include <utils/Timers.h>
+
+namespace android {
+
+class IMediaMetricsService;
+class Parcel;
+
+/*
+ * MediaMetrics Item
+ *
+ * The MediaMetrics Item allows get/set operations and recording to the service.
+ *
+ * The MediaMetrics LogItem is a faster logging variant. It allows set operations only,
+ * and then recording to the service.
+ *
+ * The Byte String format is as follows:
+ *
+ * For Java
+ * int64 corresponds to long
+ * int32, uint32 corresponds to int
+ * uint16 corresponds to char
+ * uint8, int8 corresponds to byte
+ *
+ * For items transmitted from Java, uint8 and uint32 values are limited
+ * to INT8_MAX and INT32_MAX. This constrains the size of large items
+ * to 2GB, which is consistent with ByteBuffer max size. A native item
+ * can conceivably have size of 4GB.
+ *
+ * Physical layout of integers and doubles within the MediaMetrics byte string
+ * is in Native / host order, which is usually little endian.
+ *
+ * Note that primitive data (ints, doubles) within a Byte String has
+ * no extra padding or alignment requirements, like ByteBuffer.
+ *
+ * -- begin of item
+ * -- begin of header
+ * (uint32) item size: including the item size field
+ * (uint32) header size, including the item size and header size fields.
+ * (uint16) version: exactly 0
+ * (uint16) key size, that is key strlen + 1 for zero termination.
+ * (int8)+ key, a string which is 0 terminated (UTF-8).
+ * (int32) pid
+ * (int32) uid
+ * (int64) timestamp
+ * -- end of header
+ * -- begin body
+ * (uint32) number of properties
+ * -- repeat for number of properties
+ * (uint16) property size, including property size field itself
+ * (uint8) type of property
+ * (int8)+ key string, including 0 termination
+ * based on type of property (given above), one of:
+ * (int32)
+ * (int64)
+ * (double)
+ * (int8)+ for TYPE_CSTRING, including 0 termination
+ * (int64, int64) for rate
+ * -- end body
+ * -- end of item
+ *
+ * The Byte String format must match MediaMetrics.java.
+ */
+
+namespace mediametrics {
+
+// Type must match MediaMetrics.java
+enum Type {
+ kTypeNone = 0,
+ kTypeInt32 = 1,
+ kTypeInt64 = 2,
+ kTypeDouble = 3,
+ kTypeCString = 4,
+ kTypeRate = 5,
+};
+
+/**
+ * The MediaMetrics Item has special Item properties,
+ * derived internally or through dedicated setters.
+ *
+ * For consistency we use the following keys to represent
+ * these special Item properties when in a generic Bundle
+ * or in a std::map.
+ *
+ * These values must match MediaMetrics.java
+ */
+static inline constexpr const char *BUNDLE_TOTAL_SIZE = "_totalSize";
+static inline constexpr const char *BUNDLE_HEADER_SIZE = "_headerSize";
+static inline constexpr const char *BUNDLE_VERSION = "_version";
+static inline constexpr const char *BUNDLE_KEY_SIZE = "_keySize";
+static inline constexpr const char *BUNDLE_KEY = "_key";
+static inline constexpr const char *BUNDLE_PID = "_pid";
+static inline constexpr const char *BUNDLE_UID = "_uid";
+static inline constexpr const char *BUNDLE_TIMESTAMP = "_timestamp";
+static inline constexpr const char *BUNDLE_PROPERTY_COUNT = "_propertyCount";
+
+template<size_t N>
+static inline bool startsWith(const std::string &s, const char (&comp)[N]) {
+ return !strncmp(s.c_str(), comp, N - 1);
+}
+
+static inline bool startsWith(const std::string& s, const std::string& comp) {
+ return !strncmp(s.c_str(), comp.c_str(), comp.size() - 1);
+}
+
+/**
+ * Defers a function to run in the destructor.
+ *
+ * This helper class is used to log results on exit of a method.
+ */
+class Defer {
+public:
+ template <typename U>
+ Defer(U &&f) : mThunk(std::forward<U>(f)) {}
+ ~Defer() { mThunk(); }
+
+private:
+ const std::function<void()> mThunk;
+};
+
+/**
+ * Media Metrics BaseItem
+ *
+ * A base class which contains utility static functions to write to a byte stream
+ * and access the Media Metrics service.
+ */
+
+class BaseItem {
+ friend class MediaMetricsDeathNotifier; // for dropInstance
+ // enabled 1, disabled 0
+public:
+ // are we collecting metrics data
+ static bool isEnabled();
+ static sp<IMediaMetricsService> getService();
+
+protected:
+ static constexpr const char * const EnabledProperty = "media.metrics.enabled";
+ static constexpr const char * const EnabledPropertyPersist = "persist.media.metrics.enabled";
+ static const int EnabledProperty_default = 1;
+
+ // let's reuse a binder connection
+ static sp<IMediaMetricsService> sMediaMetricsService;
+
+ static void dropInstance();
+ static bool submitBuffer(const char *buffer, size_t len);
+
+ template <typename T>
+ struct is_item_type {
+ static constexpr inline bool value =
+ std::is_same<T, int32_t>::value
+ || std::is_same<T, int64_t>::value
+ || std::is_same<T, double>::value
+ || std::is_same<T, std::pair<int64_t, int64_t>>:: value
+ || std::is_same<T, std::string>::value
+ || std::is_same<T, std::monostate>::value;
+ };
+
+ template <typename T>
+ struct get_type_of {
+ static_assert(is_item_type<T>::value);
+ static constexpr inline Type value =
+ std::is_same<T, int32_t>::value ? kTypeInt32
+ : std::is_same<T, int64_t>::value ? kTypeInt64
+ : std::is_same<T, double>::value ? kTypeDouble
+ : std::is_same<T, std::pair<int64_t, int64_t>>:: value ? kTypeRate
+ : std::is_same<T, std::string>::value ? kTypeCString
+ : std::is_same<T, std::monostate>::value ? kTypeNone
+ : kTypeNone;
+ };
+
+ template <typename T>
+ static size_t sizeOfByteString(const char *name, const T& value) {
+ static_assert(is_item_type<T>::value);
+ return 2 + 1 + strlen(name) + 1 + sizeof(value);
+ }
+ template <> // static
+ size_t sizeOfByteString(const char *name, const std::string& value) {
+ return 2 + 1 + strlen(name) + 1 + value.size() + 1;
+ }
+ template <> // static
+ size_t sizeOfByteString(const char *name, const std::monostate&) {
+ return 2 + 1 + strlen(name) + 1;
+ }
+ // for speed
+ static size_t sizeOfByteString(const char *name, const char *value) {
+ return 2 + 1 + strlen(name) + 1 + strlen(value) + 1;
+ }
+
+ template <typename T>
+ static status_t insert(const T& val, char **bufferpptr, char *bufferptrmax) {
+ static_assert(std::is_trivially_constructible<T>::value);
+ const size_t size = sizeof(val);
+ if (*bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(*bufferpptr, &val, size);
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t insert(const std::string& val, char **bufferpptr, char *bufferptrmax) {
+ const size_t size = val.size() + 1;
+ if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(*bufferpptr, val.c_str(), size);
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t insert(const std::pair<int64_t, int64_t>& val,
+ char **bufferpptr, char *bufferptrmax) {
+ const size_t size = sizeof(val.first) + sizeof(val.second);
+ if (*bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(*bufferpptr, &val.first, sizeof(val.first));
+ memcpy(*bufferpptr + sizeof(val.first), &val.second, sizeof(val.second));
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t insert(const std::monostate&, char **, char *) {
+ return NO_ERROR;
+ }
+ // for speed
+ static status_t insert(const char *val, char **bufferpptr, char *bufferptrmax) {
+ const size_t size = strlen(val) + 1;
+ if (size > UINT16_MAX || *bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(*bufferpptr, val, size);
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+
+ template <typename T>
+ static status_t writeToByteString(
+ const char *name, const T& value, char **bufferpptr, char *bufferptrmax) {
+ static_assert(is_item_type<T>::value);
+ const size_t len = sizeOfByteString(name, value);
+ if (len > UINT16_MAX) return BAD_VALUE;
+ return insert((uint16_t)len, bufferpptr, bufferptrmax)
+ ?: insert((uint8_t)get_type_of<T>::value, bufferpptr, bufferptrmax)
+ ?: insert(name, bufferpptr, bufferptrmax)
+ ?: insert(value, bufferpptr, bufferptrmax);
+ }
+ // for speed
+ static status_t writeToByteString(
+ const char *name, const char *value, char **bufferpptr, char *bufferptrmax) {
+ const size_t len = sizeOfByteString(name, value);
+ if (len > UINT16_MAX) return BAD_VALUE;
+ return insert((uint16_t)len, bufferpptr, bufferptrmax)
+ ?: insert((uint8_t)kTypeCString, bufferpptr, bufferptrmax)
+ ?: insert(name, bufferpptr, bufferptrmax)
+ ?: insert(value, bufferpptr, bufferptrmax);
+ }
+
+ template <typename T>
+ static void toStringBuffer(
+ const char *name, const T& value, char *buffer, size_t length) = delete;
+ template <> // static
+ void toStringBuffer(
+ const char *name, const int32_t& value, char *buffer, size_t length) {
+ snprintf(buffer, length, "%s=%d:", name, value);
+ }
+ template <> // static
+ void toStringBuffer(
+ const char *name, const int64_t& value, char *buffer, size_t length) {
+ snprintf(buffer, length, "%s=%lld:", name, (long long)value);
+ }
+ template <> // static
+ void toStringBuffer(
+ const char *name, const double& value, char *buffer, size_t length) {
+ snprintf(buffer, length, "%s=%e:", name, value);
+ }
+ template <> // static
+ void toStringBuffer(
+ const char *name, const std::pair<int64_t, int64_t>& value,
+ char *buffer, size_t length) {
+ snprintf(buffer, length, "%s=%lld/%lld:",
+ name, (long long)value.first, (long long)value.second);
+ }
+ template <> // static
+ void toStringBuffer(
+ const char *name, const std::string& value, char *buffer, size_t length) {
+ // TODO sanitize string for ':' '='
+ snprintf(buffer, length, "%s=%s:", name, value.c_str());
+ }
+ template <> // static
+ void toStringBuffer(
+ const char *name, const std::monostate&, char *buffer, size_t length) {
+ snprintf(buffer, length, "%s=():", name);
+ }
+
+ template <typename T>
+ static status_t writeToParcel(
+ const char *name, const T& value, Parcel *parcel) = delete;
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const int32_t& value, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of<int32_t>::value)
+ ?: parcel->writeInt32(value);
+ }
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const int64_t& value, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of<int64_t>::value)
+ ?: parcel->writeInt64(value);
+ }
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const double& value, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of<double>::value)
+ ?: parcel->writeDouble(value);
+ }
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const std::pair<int64_t, int64_t>& value, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of< std::pair<int64_t, int64_t>>::value)
+ ?: parcel->writeInt64(value.first)
+ ?: parcel->writeInt64(value.second);
+ }
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const std::string& value, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of<std::string>::value)
+ ?: parcel->writeCString(value.c_str());
+ }
+ template <> // static
+ status_t writeToParcel(
+ const char *name, const std::monostate&, Parcel *parcel) {
+ return parcel->writeCString(name)
+ ?: parcel->writeInt32(get_type_of<std::monostate>::value);
+ }
+
+ template <typename T>
+ static status_t extract(T *val, const char **bufferpptr, const char *bufferptrmax) {
+ static_assert(std::is_trivially_constructible<T>::value);
+ const size_t size = sizeof(*val);
+ if (*bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(val, *bufferpptr, size);
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t extract(std::string *val, const char **bufferpptr, const char *bufferptrmax) {
+ const char *ptr = *bufferpptr;
+ while (*ptr != 0) {
+ if (ptr >= bufferptrmax) {
+ ALOGE("%s: buffer exceeded", __func__);
+ return BAD_VALUE;
+ }
+ ++ptr;
+ }
+ const size_t size = (ptr - *bufferpptr) + 1;
+ *val = *bufferpptr;
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t extract(std::pair<int64_t, int64_t> *val,
+ const char **bufferpptr, const char *bufferptrmax) {
+ const size_t size = sizeof(val->first) + sizeof(val->second);
+ if (*bufferpptr + size > bufferptrmax) {
+ ALOGE("%s: buffer exceeded with size %zu", __func__, size);
+ return BAD_VALUE;
+ }
+ memcpy(&val->first, *bufferpptr, sizeof(val->first));
+ memcpy(&val->second, *bufferpptr + sizeof(val->first), sizeof(val->second));
+ *bufferpptr += size;
+ return NO_ERROR;
+ }
+ template <> // static
+ status_t extract(std::monostate *, const char **, const char *) {
+ return NO_ERROR;
+ }
+};
+
+/**
+ * Media Metrics BufferedItem
+ *
+ * A base class which represents a put-only Media Metrics item, storing
+ * the Media Metrics data in a buffer with begin and end pointers.
+ *
+ * If a property key is entered twice, it will be stored in the buffer twice,
+ * and (implementation defined) the last value for that key will be used
+ * by the Media Metrics service.
+ *
+ * For realloc, a baseRealloc pointer must be passed in either explicitly
+ * or implicitly in the constructor. This will be updated with the value used on realloc.
+ */
+class BufferedItem : public BaseItem {
+public:
+ static inline constexpr uint16_t kVersion = 0;
+
+ virtual ~BufferedItem() = default;
+ BufferedItem(const BufferedItem&) = delete;
+ BufferedItem& operator=(const BufferedItem&) = delete;
+
+ BufferedItem(const std::string key, char *begin, char *end)
+ : BufferedItem(key.c_str(), begin, end) { }
+
+ BufferedItem(const char *key, char *begin, char *end)
+ : BufferedItem(key, begin, end, nullptr) { }
+
+ BufferedItem(const char *key, char **begin, char *end)
+ : BufferedItem(key, *begin, end, begin) { }
+
+ BufferedItem(const char *key, char *begin, char *end, char **baseRealloc)
+ : mBegin(begin)
+ , mEnd(end)
+ , mBaseRealloc(baseRealloc)
+ {
+ init(key);
+ }
+
+ template<typename T>
+ BufferedItem &set(const char *key, const T& value) {
+ reallocFor(sizeOfByteString(key, value));
+ if (mStatus == NO_ERROR) {
+ mStatus = BaseItem::writeToByteString(key, value, &mBptr, mEnd);
+ ++mPropCount;
+ }
+ return *this;
+ }
+
+ template<typename T>
+ BufferedItem &set(const std::string& key, const T& value) {
+ return set(key.c_str(), value);
+ }
+
+ BufferedItem &setPid(pid_t pid) {
+ if (mStatus == NO_ERROR) {
+ copyTo(mBegin + mHeaderLen - 16, (int32_t)pid);
+ }
+ return *this;
+ }
+
+ BufferedItem &setUid(uid_t uid) {
+ if (mStatus == NO_ERROR) {
+ copyTo(mBegin + mHeaderLen - 12, (int32_t)uid);
+ }
+ return *this;
+ }
+
+ BufferedItem &setTimestamp(nsecs_t timestamp) {
+ if (mStatus == NO_ERROR) {
+ copyTo(mBegin + mHeaderLen - 8, (int64_t)timestamp);
+ }
+ return *this;
+ }
+
+ bool record() {
+ return updateHeader()
+ && BaseItem::submitBuffer(getBuffer(), getLength());
+ }
+
+ bool isValid () const {
+ return mStatus == NO_ERROR;
+ }
+
+ char *getBuffer() const { return mBegin; }
+ size_t getLength() const { return mBptr - mBegin; }
+ size_t getRemaining() const { return mEnd - mBptr; }
+ size_t getCapacity() const { return mEnd - mBegin; }
+
+ bool updateHeader() {
+ if (mStatus != NO_ERROR) return false;
+ copyTo(mBegin + 0, (uint32_t)getLength());
+ copyTo(mBegin + 4, (uint32_t)mHeaderLen);
+ copyTo(mBegin + mHeaderLen, (uint32_t)mPropCount);
+ return true;
+ }
+
+protected:
+ BufferedItem() = default;
+
+ void reallocFor(size_t required) {
+ if (mStatus != NO_ERROR) return;
+ const size_t remaining = getRemaining();
+ if (required <= remaining) return;
+ if (mBaseRealloc == nullptr) {
+ mStatus = NO_MEMORY;
+ return;
+ }
+
+ const size_t current = getLength();
+ size_t minimum = current + required;
+ if (minimum > SSIZE_MAX >> 1) {
+ mStatus = NO_MEMORY;
+ return;
+ }
+ minimum <<= 1;
+ void *newptr = realloc(*mBaseRealloc, minimum);
+ if (newptr == nullptr) {
+ mStatus = NO_MEMORY;
+ return;
+ }
+ if (newptr != *mBaseRealloc) {
+ // ALOGD("base changed! current:%zu new size %zu", current, minimum);
+ if (*mBaseRealloc == nullptr) {
+ memcpy(newptr, mBegin, current);
+ }
+ mBegin = (char *)newptr;
+ *mBaseRealloc = mBegin;
+ mEnd = mBegin + minimum;
+ mBptr = mBegin + current;
+ } else {
+ // ALOGD("base kept! current:%zu new size %zu", current, minimum);
+ mEnd = mBegin + minimum;
+ }
+ }
+ template<typename T>
+ void copyTo(char *ptr, const T& value) {
+ memcpy(ptr, &value, sizeof(value));
+ }
+
+ void init(const char *key) {
+ mBptr = mBegin;
+ const size_t keylen = strlen(key) + 1;
+ mHeaderLen = 4 + 4 + 2 + 2 + keylen + 4 + 4 + 8;
+ reallocFor(mHeaderLen);
+ if (mStatus != NO_ERROR) return;
+ mBptr = mBegin + mHeaderLen + 4; // this includes propcount.
+
+ if (mEnd < mBptr || keylen > UINT16_MAX) {
+ mStatus = NO_MEMORY;
+ mBptr = mEnd;
+ return;
+ }
+ copyTo(mBegin + 8, kVersion);
+ copyTo(mBegin + 10, (uint16_t)keylen);
+ strcpy(mBegin + 12, key);
+
+ // initialize some parameters (that could be overridden)
+ setPid(-1);
+ setUid(-1);
+ setTimestamp(0);
+ }
+
+ char *mBegin = nullptr;
+ char *mEnd = nullptr;
+ char **mBaseRealloc = nullptr; // set to an address if realloc should be done.
+ // upon return, that pointer is updated with
+ // whatever needs to be freed.
+ char *mBptr = nullptr;
+ status_t mStatus = NO_ERROR;
+ uint32_t mPropCount = 0;
+ uint32_t mHeaderLen = 0;
+};
+
+/**
+ * MediaMetrics LogItem is a stack allocated mediametrics item used for
+ * fast logging. It falls over to a malloc if needed.
+ *
+ * This is templated with a buffer size to allocate on the stack.
+ */
+template <size_t N = 4096>
+class LogItem : public BufferedItem {
+public:
+ explicit LogItem(const std::string key) : LogItem(key.c_str()) { }
+
+ // Since this class will not be defined before the base class, we initialize variables
+ // in our own order.
+ explicit LogItem(const char *key) {
+ mBegin = mBuffer;
+ mEnd = mBuffer + N;
+ mBaseRealloc = &mReallocPtr;
+ init(key);
+ }
+
+ ~LogItem() override {
+ if (mReallocPtr != nullptr) { // do the check before calling free to avoid overhead.
+ free(mReallocPtr);
+ }
+ }
+
+private:
+ char *mReallocPtr = nullptr; // set non-null by base class if realloc happened.
+ char mBuffer[N];
+};
+
+
+/**
+ * Media Metrics Item
+ *
+ * A mutable item representing an event or record that will be
+ * logged with the Media Metrics service. For client logging, one should
+ * use the mediametrics::Item.
+ *
+ * The Item is designed for the service as it has getters.
+ */
+class Item final : public mediametrics::BaseItem {
+public:
+
+ class Prop {
+ public:
+ using Elem = std::variant<
+ std::monostate, // kTypeNone
+ int32_t, // kTypeInt32
+ int64_t, // kTypeInt64
+ double, // kTypeDouble
+ std::string, // kTypeCString
+ std::pair<int64_t, int64_t> // kTypeRate
+ >;
+
+ Prop() = default;
+ Prop(const Prop& other) {
+ *this = other;
+ }
+ Prop& operator=(const Prop& other) {
+ mName = other.mName;
+ mElem = other.mElem;
+ return *this;
+ }
+ Prop(Prop&& other) {
+ *this = std::move(other);
+ }
+ Prop& operator=(Prop&& other) {
+ mName = std::move(other.mName);
+ mElem = std::move(other.mElem);
+ return *this;
+ }
+
+ bool operator==(const Prop& other) const {
+ return mName == other.mName && mElem == other.mElem;
+ }
+ bool operator!=(const Prop& other) const {
+ return !(*this == other);
+ }
+
+ void clear() {
+ mName.clear();
+ mElem = std::monostate{};
+ }
+ void clearValue() {
+ mElem = std::monostate{};
+ }
+
+ const char *getName() const {
+ return mName.c_str();
+ }
+
+ void swap(Prop& other) {
+ std::swap(mName, other.mName);
+ std::swap(mElem, other.mElem);
+ }
+
+ void setName(const char *name) {
+ mName = name;
+ }
+
+ bool isNamed(const char *name) const {
+ return mName == name;
+ }
+
+ template <typename T> void visit(T f) const {
+ std::visit(f, mElem);
+ }
+
+ template <typename T> bool get(T *value) const {
+ auto pval = std::get_if<T>(&mElem);
+ if (pval != nullptr) {
+ *value = *pval;
+ return true;
+ }
+ return false;
+ }
+
+ const Elem& get() const {
+ return mElem;
+ }
+
+ template <typename T> void set(const T& value) {
+ mElem = value;
+ }
+
+ template <typename T> void add(const T& value) {
+ auto pval = std::get_if<T>(&mElem);
+ if (pval != nullptr) {
+ *pval += value;
+ } else {
+ mElem = value;
+ }
+ }
+
+ template <> void add(const std::pair<int64_t, int64_t>& value) {
+ auto pval = std::get_if<std::pair<int64_t, int64_t>>(&mElem);
+ if (pval != nullptr) {
+ pval->first += value.first;
+ pval->second += value.second;
+ } else {
+ mElem = value;
+ }
+ }
+
+ status_t writeToParcel(Parcel *parcel) const {
+ return std::visit([this, parcel](auto &value) {
+ return BaseItem::writeToParcel(mName.c_str(), value, parcel);}, mElem);
+ }
+
+ void toStringBuffer(char *buffer, size_t length) const {
+ return std::visit([this, buffer, length](auto &value) {
+ BaseItem::toStringBuffer(mName.c_str(), value, buffer, length);}, mElem);
+ }
+
+ size_t getByteStringSize() const {
+ return std::visit([this](auto &value) {
+ return BaseItem::sizeOfByteString(mName.c_str(), value);}, mElem);
+ }
+
+ status_t writeToByteString(char **bufferpptr, char *bufferptrmax) const {
+ return std::visit([this, bufferpptr, bufferptrmax](auto &value) {
+ return BaseItem::writeToByteString(mName.c_str(), value, bufferpptr, bufferptrmax);
+ }, mElem);
+ }
+
+ status_t readFromParcel(const Parcel& data);
+
+ status_t readFromByteString(const char **bufferpptr, const char *bufferptrmax);
+
+ private:
+ std::string mName;
+ Elem mElem;
+ };
+
+ // Iteration of props within item
+ class iterator {
+ public:
+ iterator(const std::map<std::string, Prop>::const_iterator &_it) : it(_it) { }
+ iterator &operator++() {
+ ++it;
+ return *this;
+ }
+ bool operator!=(iterator &other) const {
+ return it != other.it;
+ }
+ const Prop &operator*() const {
+ return it->second;
+ }
+
+ private:
+ std::map<std::string, Prop>::const_iterator it;
+ };
+
+ iterator begin() const {
+ return iterator(mProps.cbegin());
+ }
+
+ iterator end() const {
+ return iterator(mProps.cend());
+ }
+
+ enum {
+ PROTO_V0 = 0,
+ PROTO_FIRST = PROTO_V0,
+ PROTO_V1 = 1,
+ PROTO_LAST = PROTO_V1,
+ };
+
+ // T must be convertible to mKey
+ template <typename T>
+ explicit Item(T key)
+ : mKey(key) { }
+ Item() = default;
+
+ // We enable default copy and move constructors and make this class final
+ // to prevent a derived class; this avoids possible data slicing.
+ Item(const Item& other) = default;
+ Item(Item&& other) = default;
+ Item& operator=(const Item& other) = default;
+ Item& operator=(Item&& other) = default;
+
+ bool operator==(const Item& other) const {
+ return mPid == other.mPid
+ && mUid == other.mUid
+ && mPkgName == other.mPkgName
+ && mPkgVersionCode == other.mPkgVersionCode
+ && mKey == other.mKey
+ && mTimestamp == other.mTimestamp
+ && mProps == other.mProps
+ ;
+ }
+ bool operator!=(const Item& other) const {
+ return !(*this == other);
+ }
+
+ template <typename T>
+ static Item* create(T key) {
+ return new Item(key);
+ }
+ static Item* create() {
+ return new Item();
+ }
+
+ static Item* convert(mediametrics_handle_t);
+ static mediametrics_handle_t convert(Item *);
+
+ // access functions for the class
+ ~Item();
+
+ void clear() {
+ mPid = -1;
+ mUid = -1;
+ mPkgName.clear();
+ mPkgVersionCode = 0;
+ mTimestamp = 0;
+ mKey.clear();
+ mProps.clear();
+ }
+
+ Item *dup() const { return new Item(*this); }
+
+ Item &setKey(const char *key) {
+ mKey = key;
+ return *this;
+ }
+ const std::string& getKey() const { return mKey; }
+
+ // # of properties in the record
+ size_t count() const { return mProps.size(); }
+
+ template<typename S, typename T>
+ Item &set(S key, T value) {
+ findOrAllocateProp(key).set(value);
+ return *this;
+ }
+
+ // set values appropriately
+ Item &setInt32(const char *key, int32_t value) {
+ return set(key, value);
+ }
+ Item &setInt64(const char *key, int64_t value) {
+ return set(key, value);
+ }
+ Item &setDouble(const char *key, double value) {
+ return set(key, value);
+ }
+ Item &setRate(const char *key, int64_t count, int64_t duration) {
+ return set(key, std::make_pair(count, duration));
+ }
+ Item &setCString(const char *key, const char *value) {
+ return set(key, value);
+ }
+
+ // fused get/add/set; if attr wasn't there, it's a simple set.
+ // type-mismatch counts as "wasn't there".
+ template<typename S, typename T>
+ Item &add(S key, T value) {
+ findOrAllocateProp(key).add(value);
+ return *this;
+ }
+
+ Item &addInt32(const char *key, int32_t value) {
+ return add(key, value);
+ }
+ Item &addInt64(const char *key, int64_t value) {
+ return add(key, value);
+ }
+ Item &addDouble(const char *key, double value) {
+ return add(key, value);
+ }
+ Item &addRate(const char *key, int64_t count, int64_t duration) {
+ return add(key, std::make_pair(count, duration));
+ }
+
+ // find & extract values
+ // return indicates whether attr exists (and thus value filled in)
+ // NULL parameter value suppresses storage of value.
+ template<typename S, typename T>
+ bool get(S key, T *value) const {
+ const Prop *prop = findProp(key);
+ return prop != nullptr && prop->get(value);
+ }
+
+ bool getInt32(const char *key, int32_t *value) const {
+ return get(key, value);
+ }
+ bool getInt64(const char *key, int64_t *value) const {
+ return get(key, value);
+ }
+ bool getDouble(const char *key, double *value) const {
+ return get(key, value);
+ }
+ bool getRate(const char *key, int64_t *count, int64_t *duration, double *rate) const {
+ std::pair<int64_t, int64_t> value;
+ if (!get(key, &value)) return false;
+ if (count != nullptr) *count = value.first;
+ if (duration != nullptr) *duration = value.second;
+ if (rate != nullptr) {
+ if (value.second != 0) {
+ *rate = (double)value.first / value.second; // TODO: isn't INF OK?
+ } else {
+ *rate = 0.;
+ }
+ }
+ return true;
+ }
+ // Caller owns the returned string
+ bool getCString(const char *key, char **value) const {
+ std::string s;
+ if (get(key, &s)) {
+ *value = strdup(s.c_str());
+ return true;
+ }
+ return false;
+ }
+ bool getString(const char *key, std::string *value) const {
+ return get(key, value);
+ }
+
+ const Prop::Elem* get(const char *key) const {
+ const Prop *prop = findProp(key);
+ return prop == nullptr ? nullptr : &prop->get();
+ }
+
+ // Deliver the item to MediaMetrics
+ bool selfrecord();
+
+ // remove indicated attributes and their values
+ // filterNot() could also be called keepOnly()
+ // return value is # attributes removed
+ // XXX: perhaps 'remove' instead of 'filter'
+ // XXX: filterNot would become 'keep'
+ size_t filter(size_t count, const char *attrs[]);
+ size_t filterNot(size_t count, const char *attrs[]);
+ size_t filter(const char *attr) { return filter(1, &attr); }
+
+ // below here are used on server side or to talk to server
+ // clients need not worry about these.
+
+ // timestamp, pid, and uid only used on server side
+ // timestamp is in 'nanoseconds, unix time'
+ Item &setTimestamp(nsecs_t);
+ nsecs_t getTimestamp() const;
+
+ Item &setPid(pid_t);
+ pid_t getPid() const;
+
+ Item &setUid(uid_t);
+ uid_t getUid() const;
+
+ Item &setPkgName(const std::string &pkgName);
+ std::string getPkgName() const { return mPkgName; }
+
+ Item &setPkgVersionCode(int64_t);
+ int64_t getPkgVersionCode() const;
+
+ // our serialization code for binder calls
+ status_t writeToParcel(Parcel *) const;
+ status_t readFromParcel(const Parcel&);
+
+ status_t writeToByteString(char **bufferptr, size_t *length) const;
+ status_t readFromByteString(const char *bufferptr, size_t length);
+
+
+ std::string toString() const;
+ std::string toString(int version) const;
+ const char *toCString();
+ const char *toCString(int version);
+
+ /**
+ * Returns true if the item has a property with a target value.
+ *
+ * If propName is nullptr, hasPropElem() returns false.
+ *
+ * \param propName is the property name.
+ * \param elem is the value to match. std::monostate matches any.
+ */
+ bool hasPropElem(const char *propName, const Prop::Elem& elem) const {
+ if (propName == nullptr) return false;
+ const Prop::Elem *e = get(propName);
+ return e != nullptr && (std::holds_alternative<std::monostate>(elem) || elem == *e);
+ }
+
+ /**
+ * Returns -2, -1, 0 (success) if the item has a property (wildcard matched) with a
+ * target value.
+ *
+ * The enum RecursiveWildcardCheck designates the meaning of the returned value.
+ *
+ * RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
+ * RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
+ * RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0.
+ *
+ * If url is nullptr, RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD is returned.
+ *
+ * \param url is the full item + property name, which may have wildcards '*'
+ * denoting an arbitrary sequence of 0 or more characters.
+ * \param elem is the target property value to match. std::monostate matches any.
+ * \return 0 if the property was matched,
+ * -1 if the property was not matched and a wildcard char was encountered,
+ * -2 if the property was not matched with no wildcard char encountered.
+ */
+ enum RecursiveWildcardCheck {
+ RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD = -2,
+ RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND = -1,
+ RECURSIVE_WILDCARD_CHECK_MATCH_FOUND = 0,
+ };
+
+ enum RecursiveWildcardCheck recursiveWildcardCheckElem(
+ const char *url, const Prop::Elem& elem) const {
+ if (url == nullptr) return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
+ return recursiveWildcardCheckElem(getKey().c_str(), url, elem);
+ }
+
+private:
+
+ enum RecursiveWildcardCheck recursiveWildcardCheckElem(
+ const char *itemKeyPtr, const char *url, const Prop::Elem& elem) const {
+ for (; *url && *itemKeyPtr; ++url, ++itemKeyPtr) {
+ if (*url != *itemKeyPtr) {
+ if (*url == '*') { // wildcard
+ ++url;
+ while (true) {
+ if (recursiveWildcardCheckElem(itemKeyPtr, url, elem)
+ == RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
+ return RECURSIVE_WILDCARD_CHECK_MATCH_FOUND;
+ }
+ if (*itemKeyPtr == 0) break;
+ ++itemKeyPtr;
+ }
+ return RECURSIVE_WILDCARD_CHECK_NO_MATCH_WILDCARD_FOUND;
+ }
+ return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
+ }
+ }
+ if (itemKeyPtr[0] != 0 || url[0] != '.') {
+ return RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
+ }
+ const char *propName = url + 1; // skip the '.'
+ return hasPropElem(propName, elem)
+ ? RECURSIVE_WILDCARD_CHECK_MATCH_FOUND
+ : RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD;
+ }
+
+ // handle Parcel version 0
+ int32_t writeToParcel0(Parcel *) const;
+ int32_t readFromParcel0(const Parcel&);
+
+ const Prop *findProp(const char *key) const {
+ auto it = mProps.find(key);
+ return it != mProps.end() ? &it->second : nullptr;
+ }
+
+ Prop &findOrAllocateProp(const char *key) {
+ auto it = mProps.find(key);
+ if (it != mProps.end()) return it->second;
+ Prop &prop = mProps[key];
+ prop.setName(key);
+ return prop;
+ }
+
+ // Changes to member variables below require changes to clear().
+ pid_t mPid = -1;
+ uid_t mUid = -1;
+ std::string mPkgName;
+ int64_t mPkgVersionCode = 0;
+ std::string mKey;
+ nsecs_t mTimestamp = 0;
+ std::map<std::string, Prop> mProps;
+};
+
+} // namespace mediametrics
+} // namespace android
+
+#endif // ANDROID_MEDIA_MEDIAMETRICSITEM_H
diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp
index d36c6e3..c10e6e0 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.cpp
+++ b/media/libmediaplayerservice/StagefrightRecorder.cpp
@@ -31,7 +31,7 @@
#include <binder/IServiceManager.h>
#include <media/IMediaPlayerService.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
@@ -138,7 +138,7 @@
}
// log the current record, provided it has some information worth recording
- // NB: this also reclaims & clears mAnalyticsItem.
+ // NB: this also reclaims & clears mMetricsItem.
flushAndResetMetrics(false);
}
@@ -147,69 +147,69 @@
// we run as part of the media player service; what we really want to
// know is the app which requested the recording.
- mAnalyticsItem->setUid(mClientUid);
+ mMetricsItem->setUid(mClientUid);
// populate the values from the raw fields.
// TBD mOutputFormat = OUTPUT_FORMAT_THREE_GPP;
// TBD mAudioEncoder = AUDIO_ENCODER_AMR_NB;
// TBD mVideoEncoder = VIDEO_ENCODER_DEFAULT;
- mAnalyticsItem->setInt32(kRecorderHeight, mVideoHeight);
- mAnalyticsItem->setInt32(kRecorderWidth, mVideoWidth);
- mAnalyticsItem->setInt32(kRecorderFrameRate, mFrameRate);
- mAnalyticsItem->setInt32(kRecorderVideoBitrate, mVideoBitRate);
- mAnalyticsItem->setInt32(kRecorderAudioSampleRate, mSampleRate);
- mAnalyticsItem->setInt32(kRecorderAudioChannels, mAudioChannels);
- mAnalyticsItem->setInt32(kRecorderAudioBitrate, mAudioBitRate);
+ mMetricsItem->setInt32(kRecorderHeight, mVideoHeight);
+ mMetricsItem->setInt32(kRecorderWidth, mVideoWidth);
+ mMetricsItem->setInt32(kRecorderFrameRate, mFrameRate);
+ mMetricsItem->setInt32(kRecorderVideoBitrate, mVideoBitRate);
+ mMetricsItem->setInt32(kRecorderAudioSampleRate, mSampleRate);
+ mMetricsItem->setInt32(kRecorderAudioChannels, mAudioChannels);
+ mMetricsItem->setInt32(kRecorderAudioBitrate, mAudioBitRate);
// TBD mInterleaveDurationUs = 0;
- mAnalyticsItem->setInt32(kRecorderVideoIframeInterval, mIFramesIntervalSec);
+ mMetricsItem->setInt32(kRecorderVideoIframeInterval, mIFramesIntervalSec);
// TBD mAudioSourceNode = 0;
// TBD mUse64BitFileOffset = false;
if (mMovieTimeScale != -1)
- mAnalyticsItem->setInt32(kRecorderMovieTimescale, mMovieTimeScale);
+ mMetricsItem->setInt32(kRecorderMovieTimescale, mMovieTimeScale);
if (mAudioTimeScale != -1)
- mAnalyticsItem->setInt32(kRecorderAudioTimescale, mAudioTimeScale);
+ mMetricsItem->setInt32(kRecorderAudioTimescale, mAudioTimeScale);
if (mVideoTimeScale != -1)
- mAnalyticsItem->setInt32(kRecorderVideoTimescale, mVideoTimeScale);
+ mMetricsItem->setInt32(kRecorderVideoTimescale, mVideoTimeScale);
// TBD mCameraId = 0;
// TBD mStartTimeOffsetMs = -1;
- mAnalyticsItem->setInt32(kRecorderVideoProfile, mVideoEncoderProfile);
- mAnalyticsItem->setInt32(kRecorderVideoLevel, mVideoEncoderLevel);
+ mMetricsItem->setInt32(kRecorderVideoProfile, mVideoEncoderProfile);
+ mMetricsItem->setInt32(kRecorderVideoLevel, mVideoEncoderLevel);
// TBD mMaxFileDurationUs = 0;
// TBD mMaxFileSizeBytes = 0;
// TBD mTrackEveryTimeDurationUs = 0;
- mAnalyticsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
- mAnalyticsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
+ mMetricsItem->setInt32(kRecorderCaptureFpsEnable, mCaptureFpsEnable);
+ mMetricsItem->setDouble(kRecorderCaptureFps, mCaptureFps);
// TBD mCameraSourceTimeLapse = NULL;
// TBD mMetaDataStoredInVideoBuffers = kMetadataBufferTypeInvalid;
// TBD mEncoderProfiles = MediaProfiles::getInstance();
- mAnalyticsItem->setInt32(kRecorderRotation, mRotationDegrees);
+ mMetricsItem->setInt32(kRecorderRotation, mRotationDegrees);
// PII mLatitudex10000 = -3600000;
// PII mLongitudex10000 = -3600000;
// TBD mTotalBitRate = 0;
// duration information (recorded, paused, # of pauses)
- mAnalyticsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
+ mMetricsItem->setInt64(kRecorderDurationMs, (mDurationRecordedUs+500)/1000 );
if (mNPauses != 0) {
- mAnalyticsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
- mAnalyticsItem->setInt32(kRecorderNumPauses, mNPauses);
+ mMetricsItem->setInt64(kRecorderPaused, (mDurationPausedUs+500)/1000 );
+ mMetricsItem->setInt32(kRecorderNumPauses, mNPauses);
}
}
void StagefrightRecorder::flushAndResetMetrics(bool reinitialize) {
ALOGV("flushAndResetMetrics");
// flush anything we have, maybe setup a new record
- if (mAnalyticsDirty && mAnalyticsItem != NULL) {
+ if (mAnalyticsDirty && mMetricsItem != NULL) {
updateMetrics();
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
+ if (mMetricsItem->count() > 0) {
+ mMetricsItem->selfrecord();
}
- delete mAnalyticsItem;
- mAnalyticsItem = NULL;
+ delete mMetricsItem;
+ mMetricsItem = NULL;
}
mAnalyticsDirty = false;
if (reinitialize) {
- mAnalyticsItem = MediaAnalyticsItem::create(kKeyRecorder);
+ mMetricsItem = mediametrics::Item::create(kKeyRecorder);
}
}
@@ -1184,10 +1184,10 @@
}
// log audio mime type for media metrics
- if (mAnalyticsItem != NULL) {
+ if (mMetricsItem != NULL) {
AString audiomime;
if (format->findString("mime", &audiomime)) {
- mAnalyticsItem->setCString(kRecorderAudioMime, audiomime.c_str());
+ mMetricsItem->setCString(kRecorderAudioMime, audiomime.c_str());
}
}
@@ -1745,10 +1745,10 @@
}
// log video mime type for media metrics
- if (mAnalyticsItem != NULL) {
+ if (mMetricsItem != NULL) {
AString videomime;
if (format->findString("mime", &videomime)) {
- mAnalyticsItem->setCString(kRecorderVideoMime, videomime.c_str());
+ mMetricsItem->setCString(kRecorderVideoMime, videomime.c_str());
}
}
@@ -2002,7 +2002,6 @@
(*meta)->setInt32(kKeyTimeScale, mMovieTimeScale);
}
if (mOutputFormat != OUTPUT_FORMAT_WEBM) {
- (*meta)->setInt32(kKey64BitFileOffset, mUse64BitFileOffset);
if (mTrackEveryTimeDurationUs > 0) {
(*meta)->setInt64(kKeyTrackTimeStatus, mTrackEveryTimeDurationUs);
}
@@ -2266,12 +2265,12 @@
return BAD_VALUE;
}
- if (mAnalyticsItem == NULL) {
+ if (mMetricsItem == NULL) {
return UNKNOWN_ERROR;
}
updateMetrics();
- mAnalyticsItem->writeToParcel(reply);
+ mMetricsItem->writeToParcel(reply);
return OK;
}
diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h
index 9e0f4b7..a725bee 100644
--- a/media/libmediaplayerservice/StagefrightRecorder.h
+++ b/media/libmediaplayerservice/StagefrightRecorder.h
@@ -18,7 +18,7 @@
#define STAGEFRIGHT_RECORDER_H_
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MediaRecorderBase.h>
#include <camera/CameraParameters.h>
#include <utils/String8.h>
@@ -104,7 +104,7 @@
int mOutputFd;
sp<AudioSource> mAudioSourceNode;
- MediaAnalyticsItem *mAnalyticsItem;
+ mediametrics::Item *mMetricsItem;
bool mAnalyticsDirty;
void flushAndResetMetrics(bool reinitialize);
void updateMetrics();
diff --git a/media/libmediaplayerservice/nuplayer/GenericSource.cpp b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
index a49e72e..0eaa503 100644
--- a/media/libmediaplayerservice/nuplayer/GenericSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/GenericSource.cpp
@@ -29,7 +29,7 @@
#include <datasource/NuCachedSource2.h>
#include <media/DataSource.h>
#include <media/MediaBufferHolder.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <android/IMediaExtractorService.h>
#include <media/IMediaHTTPService.h>
#include <media/stagefright/foundation/ABuffer.h>
@@ -418,7 +418,7 @@
sp<IMediaExtractorService> mediaExService(
interface_cast<IMediaExtractorService>(binder));
sp<IDataSource> source;
- mediaExService->makeIDataSource(mFd, mOffset, mLength, &source);
+ mediaExService->makeIDataSource(base::unique_fd(dup(mFd.get())), mOffset, mLength, &source);
ALOGV("IDataSource(FileSource): %p %d %lld %lld",
source.get(), mFd.get(), (long long)mOffset, (long long)mLength);
if (source.get() != nullptr) {
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
index 3388097..c1c4b55 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayer.cpp
@@ -1798,7 +1798,9 @@
}
void NuPlayer::closeAudioSink() {
- mRenderer->closeAudioSink();
+ if (mRenderer != NULL) {
+ mRenderer->closeAudioSink();
+ }
}
void NuPlayer::restartAudio(
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
index 4d9872a..24afd43 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.cpp
@@ -35,8 +35,6 @@
#include <media/stagefright/Utils.h>
#include <media/stagefright/FoundationUtils.h>
-#include <media/IMediaAnalyticsService.h>
-
static const int kDumpLockRetries = 50;
static const int kDumpLockSleepUs = 20000;
@@ -87,7 +85,7 @@
mMediaClock(new MediaClock),
mPlayer(new NuPlayer(pid, mMediaClock)),
mPlayerFlags(0),
- mAnalyticsItem(NULL),
+ mMetricsItem(NULL),
mClientUid(-1),
mAtEOS(false),
mLooping(false),
@@ -98,7 +96,7 @@
mMediaClock->init();
// set up an analytics record
- mAnalyticsItem = MediaAnalyticsItem::create(kKeyPlayer);
+ mMetricsItem = mediametrics::Item::create(kKeyPlayer);
mLooper->start(
false, /* runOnCallingThread */
@@ -118,9 +116,9 @@
updateMetrics("destructor");
logMetrics("destructor");
- if (mAnalyticsItem != NULL) {
- delete mAnalyticsItem;
- mAnalyticsItem = NULL;
+ if (mMetricsItem != NULL) {
+ delete mMetricsItem;
+ mMetricsItem = NULL;
}
}
@@ -131,8 +129,8 @@
status_t NuPlayerDriver::setUID(uid_t uid) {
mPlayer->setUID(uid);
mClientUid = uid;
- if (mAnalyticsItem) {
- mAnalyticsItem->setUid(mClientUid);
+ if (mMetricsItem) {
+ mMetricsItem->setUid(mClientUid);
}
return OK;
@@ -560,15 +558,15 @@
if (mime.startsWith("video/")) {
int32_t width, height;
- mAnalyticsItem->setCString(kPlayerVMime, mime.c_str());
+ mMetricsItem->setCString(kPlayerVMime, mime.c_str());
if (!name.empty()) {
- mAnalyticsItem->setCString(kPlayerVCodec, name.c_str());
+ mMetricsItem->setCString(kPlayerVCodec, name.c_str());
}
if (stats->findInt32("width", &width)
&& stats->findInt32("height", &height)) {
- mAnalyticsItem->setInt32(kPlayerWidth, width);
- mAnalyticsItem->setInt32(kPlayerHeight, height);
+ mMetricsItem->setInt32(kPlayerWidth, width);
+ mMetricsItem->setInt32(kPlayerHeight, height);
}
int64_t numFramesTotal = 0;
@@ -576,18 +574,18 @@
stats->findInt64("frames-total", &numFramesTotal);
stats->findInt64("frames-dropped-output", &numFramesDropped);
- mAnalyticsItem->setInt64(kPlayerFrames, numFramesTotal);
- mAnalyticsItem->setInt64(kPlayerFramesDropped, numFramesDropped);
+ mMetricsItem->setInt64(kPlayerFrames, numFramesTotal);
+ mMetricsItem->setInt64(kPlayerFramesDropped, numFramesDropped);
float frameRate = 0;
if (stats->findFloat("frame-rate-total", &frameRate)) {
- mAnalyticsItem->setDouble(kPlayerFrameRate, (double) frameRate);
+ mMetricsItem->setDouble(kPlayerFrameRate, (double) frameRate);
}
} else if (mime.startsWith("audio/")) {
- mAnalyticsItem->setCString(kPlayerAMime, mime.c_str());
+ mMetricsItem->setCString(kPlayerAMime, mime.c_str());
if (!name.empty()) {
- mAnalyticsItem->setCString(kPlayerACodec, name.c_str());
+ mMetricsItem->setCString(kPlayerACodec, name.c_str());
}
}
}
@@ -598,20 +596,20 @@
// getDuration() uses mLock for mutex -- careful where we use it.
int duration_ms = -1;
getDuration(&duration_ms);
- mAnalyticsItem->setInt64(kPlayerDuration, duration_ms);
+ mMetricsItem->setInt64(kPlayerDuration, duration_ms);
mPlayer->updateInternalTimers();
- mAnalyticsItem->setInt64(kPlayerPlaying, (mPlayingTimeUs+500)/1000 );
+ mMetricsItem->setInt64(kPlayerPlaying, (mPlayingTimeUs+500)/1000 );
if (mRebufferingEvents != 0) {
- mAnalyticsItem->setInt64(kPlayerRebuffering, (mRebufferingTimeUs+500)/1000 );
- mAnalyticsItem->setInt32(kPlayerRebufferingCount, mRebufferingEvents);
- mAnalyticsItem->setInt32(kPlayerRebufferingAtExit, mRebufferingAtExit);
+ mMetricsItem->setInt64(kPlayerRebuffering, (mRebufferingTimeUs+500)/1000 );
+ mMetricsItem->setInt32(kPlayerRebufferingCount, mRebufferingEvents);
+ mMetricsItem->setInt32(kPlayerRebufferingAtExit, mRebufferingAtExit);
}
- mAnalyticsItem->setCString(kPlayerDataSourceType, mPlayer->getDataSourceType());
+ mMetricsItem->setCString(kPlayerDataSourceType, mPlayer->getDataSourceType());
}
@@ -621,7 +619,7 @@
}
ALOGV("logMetrics(%p) from %s at state %d", this, where, mState);
- if (mAnalyticsItem == NULL || mAnalyticsItem->isEnabled() == false) {
+ if (mMetricsItem == NULL || mMetricsItem->isEnabled() == false) {
return;
}
@@ -630,18 +628,18 @@
// and that always injects 3 fields (duration, playing time, and
// datasource) into the record.
// So the canonical "empty" record has 3 elements in it.
- if (mAnalyticsItem->count() > 3) {
+ if (mMetricsItem->count() > 3) {
- mAnalyticsItem->selfrecord();
+ mMetricsItem->selfrecord();
// re-init in case we prepare() and start() again.
- delete mAnalyticsItem ;
- mAnalyticsItem = MediaAnalyticsItem::create(kKeyPlayer);
- if (mAnalyticsItem) {
- mAnalyticsItem->setUid(mClientUid);
+ delete mMetricsItem ;
+ mMetricsItem = mediametrics::Item::create(kKeyPlayer);
+ if (mMetricsItem) {
+ mMetricsItem->setUid(mClientUid);
}
} else {
- ALOGV("nothing to record (only %zu fields)", mAnalyticsItem->count());
+ ALOGV("nothing to record (only %zu fields)", mMetricsItem->count());
}
}
@@ -779,11 +777,11 @@
status_t NuPlayerDriver::getParameter(int key, Parcel *reply) {
- if (key == FOURCC('m','t','r','X') && mAnalyticsItem != NULL) {
+ if (key == FOURCC('m','t','r','X') && mMetricsItem != NULL) {
// mtrX -- a play on 'metrics' (not matrix)
// gather current info all together, parcel it, and send it back
updateMetrics("api");
- mAnalyticsItem->writeToParcel(reply);
+ mMetricsItem->writeToParcel(reply);
return OK;
}
@@ -1007,12 +1005,12 @@
// when we have an error, add it to the analytics for this playback.
// ext1 is our primary 'error type' value. Only add ext2 when non-zero.
// [test against msg is due to fall through from previous switch value]
- if (msg == MEDIA_ERROR && mAnalyticsItem != NULL) {
- mAnalyticsItem->setInt32(kPlayerError, ext1);
+ if (msg == MEDIA_ERROR && mMetricsItem != NULL) {
+ mMetricsItem->setInt32(kPlayerError, ext1);
if (ext2 != 0) {
- mAnalyticsItem->setInt32(kPlayerErrorCode, ext2);
+ mMetricsItem->setInt32(kPlayerErrorCode, ext2);
}
- mAnalyticsItem->setCString(kPlayerErrorState, stateString(mState).c_str());
+ mMetricsItem->setCString(kPlayerErrorState, stateString(mState).c_str());
}
mAtEOS = true;
break;
diff --git a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
index ad878f8..7001f4a 100644
--- a/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
+++ b/media/libmediaplayerservice/nuplayer/NuPlayerDriver.h
@@ -16,7 +16,7 @@
#include <media/MediaPlayerInterface.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/foundation/ABase.h>
namespace android {
@@ -141,7 +141,7 @@
sp<AudioSink> mAudioSink;
uint32_t mPlayerFlags;
- MediaAnalyticsItem *mAnalyticsItem;
+ mediametrics::Item *mMetricsItem;
uid_t mClientUid;
bool mAtEOS;
diff --git a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
index f21d2b3..14f1323 100644
--- a/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
+++ b/media/libmediaplayerservice/nuplayer/StreamingSource.cpp
@@ -24,7 +24,7 @@
#include "AnotherPacketSource.h"
#include "NuPlayerStreamListener.h"
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
diff --git a/media/libnblog/ReportPerformance.cpp b/media/libnblog/ReportPerformance.cpp
index b050b83..aa678ba 100644
--- a/media/libnblog/ReportPerformance.cpp
+++ b/media/libnblog/ReportPerformance.cpp
@@ -30,7 +30,7 @@
#include <sys/time.h>
#include <utility>
#include <json/json.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/nblog/Events.h>
#include <media/nblog/PerformanceAnalysis.h>
#include <media/nblog/ReportPerformance.h>
@@ -168,7 +168,7 @@
return false;
}
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create("audiothread"));
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create("audiothread"));
const Histogram &workHist = data.workHist;
if (workHist.totalCount() > 0) {
diff --git a/media/libstagefright/AACWriter.cpp b/media/libstagefright/AACWriter.cpp
index e173974..9c0a7a5 100644
--- a/media/libstagefright/AACWriter.cpp
+++ b/media/libstagefright/AACWriter.cpp
@@ -31,7 +31,7 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/mediarecorder.h>
namespace android {
diff --git a/media/libstagefright/AMRWriter.cpp b/media/libstagefright/AMRWriter.cpp
index 24f6f0b..a1aa5dd 100644
--- a/media/libstagefright/AMRWriter.cpp
+++ b/media/libstagefright/AMRWriter.cpp
@@ -26,7 +26,7 @@
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/MetaData.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/mediarecorder.h>
namespace android {
diff --git a/media/libstagefright/Android.bp b/media/libstagefright/Android.bp
index e78e1e7..1f5ec55 100644
--- a/media/libstagefright/Android.bp
+++ b/media/libstagefright/Android.bp
@@ -53,6 +53,7 @@
"CodecBase.cpp",
"FrameRenderTracker.cpp",
"MediaCodecListWriter.cpp",
+ "SkipCutBuffer.cpp",
],
cflags: [
@@ -62,6 +63,7 @@
header_libs: [
"libmediadrm_headers",
+ "media_ndk_headers",
],
shared_libs: [
@@ -69,6 +71,7 @@
"libhidlallocatorutils",
"liblog",
"libmedia_codeclist",
+ "libmedia_omx",
"libstagefright_foundation",
"libui",
"libutils",
@@ -212,9 +215,7 @@
"RemoteMediaExtractor.cpp",
"RemoteMediaSource.cpp",
"SimpleDecodingSource.cpp",
- "SkipCutBuffer.cpp",
"StagefrightMediaScanner.cpp",
- "StagefrightPluginLoader.cpp",
"SurfaceUtils.cpp",
"ThrottledSource.cpp",
"Utils.cpp",
@@ -244,6 +245,7 @@
"libui",
"libutils",
"libmedia_helper",
+ "libsfplugin_ccodec",
"libstagefright_codecbase",
"libstagefright_foundation",
"libstagefright_omx_utils",
diff --git a/media/libstagefright/MPEG2TSWriter.cpp b/media/libstagefright/MPEG2TSWriter.cpp
index a9715c9..34b840e 100644
--- a/media/libstagefright/MPEG2TSWriter.cpp
+++ b/media/libstagefright/MPEG2TSWriter.cpp
@@ -17,7 +17,7 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "MPEG2TSWriter"
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/hexdump.h>
#include <media/stagefright/foundation/ABuffer.h>
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
index bf4e7de..c88a82a 100644
--- a/media/libstagefright/MPEG4Writer.cpp
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -31,8 +31,9 @@
#include <utils/Log.h>
#include <functional>
+#include <fcntl.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AUtils.h>
@@ -64,9 +65,6 @@
namespace android {
static const int64_t kMinStreamableFileSizeInBytes = 5 * 1024 * 1024;
-static const int64_t kMax32BitFileSize = 0x00ffffffffLL; // 2^32-1 : max FAT32
- // filesystem file size
- // used by most SD cards
static const uint8_t kNalUnitTypeSeqParamSet = 0x07;
static const uint8_t kNalUnitTypePicParamSet = 0x08;
static const int64_t kInitialDelayTimeUs = 700000LL;
@@ -118,7 +116,7 @@
int64_t getDurationUs() const;
int64_t getEstimatedTrackSizeBytes() const;
int32_t getMetaSizeIncrease(int32_t angle, int32_t trackCount) const;
- void writeTrackHeader(bool use32BitOffset = true);
+ void writeTrackHeader();
int64_t getMinCttsOffsetTimeUs();
void bufferChunk(int64_t timestampUs);
bool isAvc() const { return mIsAvc; }
@@ -136,6 +134,7 @@
static const char *getFourCCForMime(const char *mime);
const char *getTrackType() const;
void resetInternal();
+ int64_t trackMetaDataSize();
private:
// A helper class to handle faster write box with table entries
@@ -295,20 +294,16 @@
int64_t mTrackDurationUs;
int64_t mMaxChunkDurationUs;
int64_t mLastDecodingTimeUs;
-
int64_t mEstimatedTrackSizeBytes;
int64_t mMdatSizeBytes;
int32_t mTimeScale;
pthread_t mThread;
-
List<MediaBuffer *> mChunkSamples;
- bool mSamplesHaveSameSize;
+ bool mSamplesHaveSameSize;
ListTableEntries<uint32_t, 1> *mStszTableEntries;
-
- ListTableEntries<uint32_t, 1> *mStcoTableEntries;
ListTableEntries<off64_t, 1> *mCo64TableEntries;
ListTableEntries<uint32_t, 3> *mStscTableEntries;
ListTableEntries<uint32_t, 1> *mStssTableEntries;
@@ -352,6 +347,8 @@
int64_t mStartTimestampUs;
int64_t mStartTimeRealUs;
int64_t mFirstSampleTimeRealUs;
+ // Captures negative start offset of a track(track starttime < 0).
+ int64_t mFirstSampleStartOffsetUs;
int64_t mPreviousTrackTimeUs;
int64_t mTrackEveryTimeDurationUs;
@@ -361,6 +358,7 @@
ItemRefs mDimgRefs;
Vector<uint16_t> mExifList;
uint16_t mImageItemId;
+ uint16_t mItemIdBase;
int32_t mIsPrimary;
int32_t mWidth, mHeight;
int32_t mTileWidth, mTileHeight;
@@ -410,10 +408,8 @@
void updateTrackSizeEstimate();
void addOneStscTableEntry(size_t chunkId, size_t sampleId);
void addOneStssTableEntry(size_t sampleId);
-
- // Duration is time scale based
- void addOneSttsTableEntry(size_t sampleCount, int32_t timescaledDur);
- void addOneCttsTableEntry(size_t sampleCount, int32_t timescaledDur);
+ void addOneSttsTableEntry(size_t sampleCount, int32_t delta /* media time scale based */);
+ void addOneCttsTableEntry(size_t sampleCount, int32_t sampleOffset);
void addOneElstTableEntry(uint32_t segmentDuration, int32_t mediaTime,
int16_t mediaRate, int16_t mediaRateFraction);
@@ -421,7 +417,7 @@
void sendTrackSummary(bool hasMultipleTracks);
// Write the boxes
- void writeStcoBox(bool use32BitOffset);
+ void writeCo64Box();
void writeStscBox();
void writeStszBox();
void writeStssBox();
@@ -447,7 +443,7 @@
void writeAudioFourCCBox();
void writeVideoFourCCBox();
void writeMetadataFourCCBox();
- void writeStblBox(bool use32BitOffset);
+ void writeStblBox();
void writeEdtsBox();
Track(const Track &);
@@ -489,14 +485,17 @@
mStarted = false;
mWriterThreadStarted = false;
mSendNotify = false;
+ mWriteSeekErr = false;
+ mFallocateErr = false;
// Reset following variables for all the sessions and they will be
// initialized in start(MetaData *param).
mIsRealTimeRecording = true;
mUse4ByteNalLength = true;
- mUse32BitOffset = true;
mOffset = 0;
+ mPreAllocateFileEndOffset = 0;
mMdatOffset = 0;
+ mMdatEndOffset = 0;
mInMemoryCache = NULL;
mInMemoryCacheOffset = 0;
mInMemoryCacheSize = 0;
@@ -505,10 +504,14 @@
mStreamableFile = false;
mTimeScale = -1;
mHasFileLevelMeta = false;
+ mFileLevelMetaDataSize = 0;
mPrimaryItemId = 0;
mAssociationEntryCount = 0;
mNumGrids = 0;
+ mNextItemId = kItemIdBase;
mHasRefs = false;
+ mPreAllocFirstTime = true;
+ mPrevAllTracksTotalMetaDataSizeEstimate = 0;
// Following variables only need to be set for the first recording session.
// And they will stay the same for all the recording sessions.
@@ -530,6 +533,15 @@
ALOGE("cannot seek mFd: %s (%d) %lld", strerror(errno), errno, (long long)mFd);
release();
}
+
+ if (fallocate(mFd, 0, 0, 1) == 0) {
+ ALOGD("PreAllocation enabled");
+ mPreAllocationEnabled = true;
+ } else {
+ ALOGD("PreAllocation disabled. fallocate : %s, %d", strerror(errno), errno);
+ mPreAllocationEnabled = false;
+ }
+
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
(*it)->resetInternal();
@@ -736,9 +748,8 @@
// If the estimation is wrong, we will pay the price of wasting
// some reserved space. This should not happen so often statistically.
- static const int32_t factor = mUse32BitOffset? 1: 2;
- static const int64_t MIN_MOOV_BOX_SIZE = 3 * 1024; // 3 KB
- static const int64_t MAX_MOOV_BOX_SIZE = (180 * 3000000 * 6LL / 8000);
+ static const int64_t MIN_MOOV_BOX_SIZE = 3 * 1024; // 3 KibiBytes
+ static const int64_t MAX_MOOV_BOX_SIZE = (180 * 3000000 * 6LL / 8000); // 395.5 KibiBytes
int64_t size = MIN_MOOV_BOX_SIZE;
// Max file size limit is set
@@ -781,10 +792,7 @@
" estimated moov size %" PRId64 " bytes",
mMaxFileSizeLimitBytes, mMaxFileDurationLimitUs, bitRate, size);
- int64_t estimatedSize = factor * size;
- CHECK_GE(estimatedSize, 8);
-
- return estimatedSize;
+ return size;
}
status_t MPEG4Writer::start(MetaData *param) {
@@ -794,36 +802,24 @@
mStartMeta = param;
/*
- * Check mMaxFileSizeLimitBytes at the beginning
- * since mMaxFileSizeLimitBytes may be implicitly
- * changed later for 32-bit file offset even if
- * user does not ask to set it explicitly.
+ * Check mMaxFileSizeLimitBytes at the beginning since mMaxFileSizeLimitBytes may be implicitly
+ * changed later as per filesizebits of filesystem even if user does not set it explicitly.
*/
if (mMaxFileSizeLimitBytes != 0) {
mIsFileSizeLimitExplicitlyRequested = true;
}
- int32_t use64BitOffset;
- if (param &&
- param->findInt32(kKey64BitFileOffset, &use64BitOffset) &&
- use64BitOffset) {
- mUse32BitOffset = false;
- }
-
- if (mUse32BitOffset) {
- // Implicit 32 bit file size limit
- if (mMaxFileSizeLimitBytes == 0) {
- mMaxFileSizeLimitBytes = kMax32BitFileSize;
- }
-
- // If file size is set to be larger than the 32 bit file
- // size limit, treat it as an error.
- if (mMaxFileSizeLimitBytes > kMax32BitFileSize) {
- ALOGW("32-bit file size limit (%" PRId64 " bytes) too big. "
- "It is changed to %" PRId64 " bytes",
- mMaxFileSizeLimitBytes, kMax32BitFileSize);
- mMaxFileSizeLimitBytes = kMax32BitFileSize;
- }
+ int32_t fileSizeBits = fpathconf(mFd, _PC_FILESIZEBITS);
+ ALOGD("fpathconf _PC_FILESIZEBITS:%" PRId32, fileSizeBits);
+ fileSizeBits = std::min(fileSizeBits, 52 /* cap it below 4 peta bytes */);
+ int64_t maxFileSizeBytes = ((int64_t)1 << fileSizeBits) - 1;
+ if (mMaxFileSizeLimitBytes > maxFileSizeBytes) {
+ mMaxFileSizeLimitBytes = maxFileSizeBytes;
+ ALOGD("File size limit (%" PRId64 " bytes) too big. It is changed to %" PRId64 " bytes",
+ mMaxFileSizeLimitBytes, maxFileSizeBytes);
+ } else if (mMaxFileSizeLimitBytes == 0) {
+ mMaxFileSizeLimitBytes = maxFileSizeBytes;
+ ALOGD("File size limit set to %" PRId64 " bytes implicitly", maxFileSizeBytes);
}
int32_t use2ByteNalLength;
@@ -850,7 +846,8 @@
if (!param ||
!param->findInt32(kKeyTimeScale, &mTimeScale)) {
- mTimeScale = 1000;
+ // Increased by a factor of 10 to improve precision of segment duration in edit list entry.
+ mTimeScale = 10000;
}
CHECK_GT(mTimeScale, 0);
ALOGV("movie time scale: %d", mTimeScale);
@@ -915,7 +912,8 @@
if (mInMemoryCacheSize == 0) {
int32_t bitRate = -1;
if (mHasFileLevelMeta) {
- mInMemoryCacheSize += estimateFileLevelMetaSize(param);
+ mFileLevelMetaDataSize = estimateFileLevelMetaSize(param);
+ mInMemoryCacheSize += mFileLevelMetaDataSize;
}
if (mHasMoovBox) {
if (param) {
@@ -926,7 +924,7 @@
}
if (mStreamableFile) {
// Reserve a 'free' box only for streamable file
- lseek64(mFd, mFreeBoxOffset, SEEK_SET);
+ seekOrPostError(mFd, mFreeBoxOffset, SEEK_SET);
writeInt32(mInMemoryCacheSize);
write("free", 4);
mMdatOffset = mFreeBoxOffset + mInMemoryCacheSize;
@@ -935,18 +933,16 @@
}
mOffset = mMdatOffset;
- lseek64(mFd, mMdatOffset, SEEK_SET);
- if (mUse32BitOffset) {
- write("????mdat", 8);
- } else {
- write("\x00\x00\x00\x01mdat????????", 16);
- }
+ seekOrPostError(mFd, mMdatOffset, SEEK_SET);
+ write("\x00\x00\x00\x01mdat????????", 16);
status_t err = startWriterThread();
if (err != OK) {
return err;
}
+ setupAndStartLooper();
+
err = startTracks(param);
if (err != OK) {
return err;
@@ -956,32 +952,30 @@
return OK;
}
-bool MPEG4Writer::use32BitFileOffset() const {
- return mUse32BitOffset;
-}
-
status_t MPEG4Writer::pause() {
ALOGW("MPEG4Writer: pause is not supported");
return ERROR_UNSUPPORTED;
}
-void MPEG4Writer::stopWriterThread() {
- ALOGD("Stopping writer thread");
+status_t MPEG4Writer::stopWriterThread() {
+ ALOGV("Stopping writer thread");
if (!mWriterThreadStarted) {
- return;
+ return OK;
}
-
{
Mutex::Autolock autolock(mLock);
-
mDone = true;
mChunkReadyCondition.signal();
}
void *dummy;
- pthread_join(mThread, &dummy);
+ status_t err = pthread_join(mThread, &dummy);
+ WARN_UNLESS(err == 0, "stopWriterThread pthread_join err: %d", err);
+
+ err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
mWriterThreadStarted = false;
- ALOGD("Writer thread stopped");
+ WARN_UNLESS(err == 0, "stopWriterThread pthread_join retVal: %d, writer thread stopped", err);
+ return err;
}
/*
@@ -1037,12 +1031,21 @@
}
void MPEG4Writer::release() {
- close(mFd);
+ ALOGD("release()");
+ if (mPreAllocationEnabled) {
+ truncatePreAllocation();
+ }
+ int retVal = fsync(mFd);
+ WARN_UNLESS(retVal == 0, "fsync retVal:%d", retVal);
+ retVal = close(mFd);
+ WARN_UNLESS(retVal == 0, "close mFd retVal :%d", retVal);
mFd = -1;
if (mNextFd != -1) {
- close(mNextFd);
+ retVal = close(mNextFd);
mNextFd = -1;
+ WARN_UNLESS(retVal == 0, "close mNextFd retVal :%d", retVal);
}
+ stopAndReleaseLooper();
mInitCheck = NO_INIT;
mStarted = false;
free(mInMemoryCache);
@@ -1073,16 +1076,19 @@
}
status_t MPEG4Writer::reset(bool stopSource) {
+ ALOGD("reset()");
+ std::lock_guard<std::mutex> l(mResetMutex);
if (mInitCheck != OK) {
return OK;
} else {
if (!mWriterThreadStarted ||
!mStarted) {
+ status_t err = OK;
if (mWriterThreadStarted) {
- stopWriterThread();
+ err = stopWriterThread();
}
release();
- return OK;
+ return err;
}
}
@@ -1092,9 +1098,10 @@
int32_t nonImageTrackCount = 0;
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
- status_t status = (*it)->stop(stopSource);
- if (err == OK && status != OK) {
- err = status;
+ status_t trackErr = (*it)->stop(stopSource);
+ if (err == OK && trackErr != OK) {
+ ALOGW("%s track stopped with an error", (*it)->getTrackType());
+ err = trackErr;
}
// skip image tracks
@@ -1110,12 +1117,18 @@
}
}
+
if (nonImageTrackCount > 1) {
ALOGD("Duration from tracks range is [%" PRId64 ", %" PRId64 "] us",
minDurationUs, maxDurationUs);
}
- stopWriterThread();
+ status_t writerErr = stopWriterThread();
+
+ // TODO: which error to propagage, writerErr or trackErr?
+ if (err == OK && writerErr != OK) {
+ err = writerErr;
+ }
// Do not write out movie header on error.
if (err != OK) {
@@ -1124,17 +1137,12 @@
}
// Fix up the size of the 'mdat' chunk.
- if (mUse32BitOffset) {
- lseek64(mFd, mMdatOffset, SEEK_SET);
- uint32_t size = htonl(static_cast<uint32_t>(mOffset - mMdatOffset));
- ::write(mFd, &size, 4);
- } else {
- lseek64(mFd, mMdatOffset + 8, SEEK_SET);
- uint64_t size = mOffset - mMdatOffset;
- size = hton64(size);
- ::write(mFd, &size, 8);
- }
- lseek64(mFd, mOffset, SEEK_SET);
+ seekOrPostError(mFd, mMdatOffset + 8, SEEK_SET);
+ uint64_t size = mOffset - mMdatOffset;
+ size = hton64(size);
+ writeOrPostError(mFd, &size, 8);
+ seekOrPostError(mFd, mOffset, SEEK_SET);
+ mMdatEndOffset = mOffset;
// Construct file-level meta and moov box now
mInMemoryCacheOffset = 0;
@@ -1198,12 +1206,12 @@
CHECK_LE(mInMemoryCacheOffset + 8, mInMemoryCacheSize);
// Cached box
- lseek64(mFd, mFreeBoxOffset, SEEK_SET);
+ seekOrPostError(mFd, mFreeBoxOffset, SEEK_SET);
mOffset = mFreeBoxOffset;
write(mInMemoryCache, 1, mInMemoryCacheOffset);
// Free box
- lseek64(mFd, mOffset, SEEK_SET);
+ seekOrPostError(mFd, mOffset, SEEK_SET);
mFreeBoxOffset = mOffset;
writeInt32(mInMemoryCacheSize - mInMemoryCacheOffset);
write("free", 4);
@@ -1272,20 +1280,19 @@
std::min(minCttsOffsetTimeUs, (*it)->getMinCttsOffsetTimeUs());
}
}
- ALOGI("Ajust the moov start time from %lld us -> %lld us",
- (long long)mStartTimestampUs,
- (long long)(mStartTimestampUs + minCttsOffsetTimeUs - kMaxCttsOffsetTimeUs));
- // Adjust the global start time.
+ ALOGI("Adjust the moov start time from %lld us -> %lld us", (long long)mStartTimestampUs,
+ (long long)(mStartTimestampUs + minCttsOffsetTimeUs - kMaxCttsOffsetTimeUs));
+ // Adjust movie start time.
mStartTimestampUs += minCttsOffsetTimeUs - kMaxCttsOffsetTimeUs;
- // Add mStartTimeOffsetBFramesUs(-ve or zero) to the duration of first entry in STTS.
+ // Add mStartTimeOffsetBFramesUs(-ve or zero) to the start offset of tracks.
mStartTimeOffsetBFramesUs = minCttsOffsetTimeUs - kMaxCttsOffsetTimeUs;
ALOGV("mStartTimeOffsetBFramesUs :%" PRId32, mStartTimeOffsetBFramesUs);
for (List<Track *>::iterator it = mTracks.begin();
it != mTracks.end(); ++it) {
if (!(*it)->isHeic()) {
- (*it)->writeTrackHeader(mUse32BitOffset);
+ (*it)->writeTrackHeader();
}
}
endBox(); // moov
@@ -1376,17 +1383,15 @@
} else {
if (tiffHdrOffset > 0) {
tiffHdrOffset = htonl(tiffHdrOffset);
- ::write(mFd, &tiffHdrOffset, 4); // exif_tiff_header_offset field
+ writeOrPostError(mFd, &tiffHdrOffset, 4); // exif_tiff_header_offset field
mOffset += 4;
}
- ::write(mFd,
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- buffer->range_length());
+ writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(),
+ buffer->range_length());
mOffset += buffer->range_length();
}
-
*bytesWritten = mOffset - old_offset;
return old_offset;
}
@@ -1431,30 +1436,27 @@
void MPEG4Writer::addLengthPrefixedSample_l(MediaBuffer *buffer) {
size_t length = buffer->range_length();
-
if (mUse4ByteNalLength) {
uint8_t x = length >> 24;
- ::write(mFd, &x, 1);
+ writeOrPostError(mFd, &x, 1);
x = (length >> 16) & 0xff;
- ::write(mFd, &x, 1);
+ writeOrPostError(mFd, &x, 1);
x = (length >> 8) & 0xff;
- ::write(mFd, &x, 1);
+ writeOrPostError(mFd, &x, 1);
x = length & 0xff;
- ::write(mFd, &x, 1);
+ writeOrPostError(mFd, &x, 1);
- ::write(mFd,
- (const uint8_t *)buffer->data() + buffer->range_offset(),
- length);
+ writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(), length);
mOffset += length + 4;
} else {
CHECK_LT(length, 65536u);
uint8_t x = length >> 8;
- ::write(mFd, &x, 1);
+ writeOrPostError(mFd, &x, 1);
x = length & 0xff;
- ::write(mFd, &x, 1);
- ::write(mFd, (const uint8_t *)buffer->data() + buffer->range_offset(), length);
+ writeOrPostError(mFd, &x, 1);
+ writeOrPostError(mFd, (const uint8_t*)buffer->data() + buffer->range_offset(), length);
mOffset += length + 2;
}
}
@@ -1476,9 +1478,9 @@
it != mBoxes.end(); ++it) {
(*it) += mOffset;
}
- lseek64(mFd, mOffset, SEEK_SET);
- ::write(mFd, mInMemoryCache, mInMemoryCacheOffset);
- ::write(mFd, ptr, bytes);
+ seekOrPostError(mFd, mOffset, SEEK_SET);
+ writeOrPostError(mFd, mInMemoryCache, mInMemoryCacheOffset);
+ writeOrPostError(mFd, ptr, bytes);
mOffset += (bytes + mInMemoryCacheOffset);
// All subsequent boxes will be written to the end of the file.
@@ -1488,13 +1490,54 @@
mInMemoryCacheOffset += bytes;
}
} else {
- ::write(mFd, ptr, size * nmemb);
+ writeOrPostError(mFd, ptr, bytes);
mOffset += bytes;
}
return bytes;
}
+void MPEG4Writer::writeOrPostError(int fd, const void* buf, size_t count) {
+ if (mWriteSeekErr == true)
+ return;
+ ssize_t bytesWritten = ::write(fd, buf, count);
+ /* Write as much as possible during stop() execution when there was an error
+ * (mWriteSeekErr == true) in the previous call to write() or lseek64().
+ */
+ if (bytesWritten == count)
+ return;
+ mWriteSeekErr = true;
+ // Note that errno is not changed even when bytesWritten < count.
+ ALOGE("writeOrPostError bytesWritten:%zd, count:%zu, error:%s(%d)", bytesWritten, count,
+ std::strerror(errno), errno);
+
+ // Can't guarantee that file is usable or write would succeed anymore, hence signal to stop.
+ sp<AMessage> msg = new AMessage(kWhatHandleIOError, mReflector);
+ status_t err = msg->post();
+ ALOGE("writeOrPostError post:%d", err);
+}
+
+void MPEG4Writer::seekOrPostError(int fd, off64_t offset, int whence) {
+ if (mWriteSeekErr == true)
+ return;
+ off64_t resOffset = lseek64(fd, offset, whence);
+ /* Allow to seek during stop() execution even when there was an error
+ * (mWriteSeekErr == true) in the previous call to write() or lseek64().
+ */
+ if (resOffset == offset)
+ return;
+ mWriteSeekErr = true;
+ ALOGE("seekOrPostError resOffset:%" PRIu64 ", offset:%" PRIu64 ", error:%s(%d)", resOffset,
+ offset, std::strerror(errno), errno);
+
+ // Can't guarantee that file is usable or seek would succeed anymore, hence signal to stop.
+ sp<AMessage> msg = new AMessage(kWhatHandleIOError, mReflector);
+ status_t err = msg->post();
+ ALOGE("seekOrPostError post:%d", err);
+}
+
void MPEG4Writer::beginBox(uint32_t id) {
+ ALOGV("beginBox:%" PRIu32, id);
+
mBoxes.push_back(mWriteBoxToMemory?
mInMemoryCacheOffset: mOffset);
@@ -1503,6 +1546,7 @@
}
void MPEG4Writer::beginBox(const char *fourcc) {
+ ALOGV("beginBox:%s", fourcc);
CHECK_EQ(strlen(fourcc), 4u);
mBoxes.push_back(mWriteBoxToMemory?
@@ -1522,10 +1566,11 @@
int32_t x = htonl(mInMemoryCacheOffset - offset);
memcpy(mInMemoryCache + offset, &x, 4);
} else {
- lseek64(mFd, offset, SEEK_SET);
+ seekOrPostError(mFd, offset, SEEK_SET);
writeInt32(mOffset - offset);
+ ALOGV("box size:%" PRIu64, mOffset - offset);
mOffset -= 4;
- lseek64(mFd, mOffset, SEEK_SET);
+ seekOrPostError(mFd, mOffset, SEEK_SET);
}
}
@@ -1679,6 +1724,79 @@
return mStreamableFile;
}
+bool MPEG4Writer::preAllocate(uint64_t wantSize) {
+ if (!mPreAllocationEnabled)
+ return true;
+
+ std::lock_guard<std::mutex> l(mFallocMutex);
+
+ if (mFallocateErr == true)
+ return false;
+
+ // approxMOOVHeadersSize has to be changed whenever its needed in the future.
+ uint64_t approxMOOVHeadersSize = 500;
+ // approxTrackHeadersSize has to be changed whenever its needed in the future.
+ const uint64_t approxTrackHeadersSize = 800;
+
+ uint64_t approxMOOVBoxSize = 0;
+ if (mPreAllocFirstTime) {
+ mPreAllocFirstTime = false;
+ approxMOOVBoxSize = approxMOOVHeadersSize + mFileLevelMetaDataSize + mMoovExtraSize +
+ (approxTrackHeadersSize * numTracks());
+ ALOGV("firstTimeAllocation approxMOOVBoxSize:%" PRIu64, approxMOOVBoxSize);
+ }
+
+ uint64_t allTracksTotalMetaDataSizeEstimate = 0;
+ for (List<Track *>::iterator it = mTracks.begin(); it != mTracks.end(); ++it) {
+ allTracksTotalMetaDataSizeEstimate += ((*it)->trackMetaDataSize());
+ }
+ ALOGV(" allTracksTotalMetaDataSizeEstimate:%" PRIu64, allTracksTotalMetaDataSizeEstimate);
+
+ /* MOOVBoxSize will increase whenever a sample gets written to the file. Enough to allocate
+ * the delta increase for each sample after the very first allocation.
+ */
+ uint64_t approxMetaDataSizeIncrease =
+ allTracksTotalMetaDataSizeEstimate - mPrevAllTracksTotalMetaDataSizeEstimate;
+ ALOGV("approxMetaDataSizeIncrease:%" PRIu64 " wantSize:%" PRIu64, approxMetaDataSizeIncrease,
+ wantSize);
+ mPrevAllTracksTotalMetaDataSizeEstimate = allTracksTotalMetaDataSizeEstimate;
+ ALOGV("mPreAllocateFileEndOffset:%" PRIu64 " mOffset:%" PRIu64, mPreAllocateFileEndOffset,
+ mOffset);
+ off64_t lastFileEndOffset = std::max(mPreAllocateFileEndOffset, mOffset);
+ uint64_t preAllocateSize = wantSize + approxMOOVBoxSize + approxMetaDataSizeIncrease;
+ ALOGV("preAllocateSize :%" PRIu64 " lastFileEndOffset:%" PRIu64, preAllocateSize,
+ lastFileEndOffset);
+
+ int res = fallocate(mFd, 0, lastFileEndOffset, preAllocateSize);
+ if (res == -1) {
+ ALOGE("fallocate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
+ sp<AMessage> msg = new AMessage(kWhatHandleFallocateError, mReflector);
+ status_t err = msg->post();
+ mFallocateErr = true;
+ ALOGD("preAllocation post:%d", err);
+ } else {
+ mPreAllocateFileEndOffset = lastFileEndOffset + preAllocateSize;
+ ALOGV("mPreAllocateFileEndOffset:%" PRIu64, mPreAllocateFileEndOffset);
+ }
+ return (res == -1) ? false : true;
+}
+
+bool MPEG4Writer::truncatePreAllocation() {
+ bool status = true;
+ off64_t endOffset = std::max(mMdatEndOffset, mOffset);
+ /* if mPreAllocateFileEndOffset >= endOffset, then preallocation logic works good. (diff >= 0).
+ * Otherwise, the logic needs to be modified.
+ */
+ ALOGD("ftruncate mPreAllocateFileEndOffset:%" PRId64 " mOffset:%" PRIu64
+ " mMdatEndOffset:%" PRIu64 " diff:%" PRId64, mPreAllocateFileEndOffset, mOffset,
+ mMdatEndOffset, mPreAllocateFileEndOffset - endOffset);
+ if(ftruncate(mFd, endOffset) == -1) {
+ ALOGE("ftruncate err:%s, %d, fd:%d", strerror(errno), errno, mFd);
+ status = false;
+ }
+ return status;
+}
+
bool MPEG4Writer::exceedsFileSizeLimit() {
// No limit
if (mMaxFileSizeLimitBytes == 0) {
@@ -1764,6 +1882,9 @@
return mStartTimestampUs;
}
+/* Returns negative when reordering is needed because of BFrames or zero otherwise.
+ * CTTS values for tracks with BFrames offsets this negative value.
+ */
int32_t MPEG4Writer::getStartTimeOffsetBFramesUs() {
Mutex::Autolock autoLock(mLock);
return mStartTimeOffsetBFramesUs;
@@ -1792,7 +1913,6 @@
mEstimatedTrackSizeBytes(0),
mSamplesHaveSameSize(true),
mStszTableEntries(new ListTableEntries<uint32_t, 1>(1000)),
- mStcoTableEntries(new ListTableEntries<uint32_t, 1>(1000)),
mCo64TableEntries(new ListTableEntries<off64_t, 1>(1000)),
mStscTableEntries(new ListTableEntries<uint32_t, 3>(1000)),
mStssTableEntries(new ListTableEntries<uint32_t, 1>(1000)),
@@ -1807,9 +1927,12 @@
mGotAllCodecSpecificData(false),
mReachedEOS(false),
mStartTimestampUs(-1),
+ mFirstSampleTimeRealUs(0),
+ mFirstSampleStartOffsetUs(0),
mRotation(0),
mDimgRefs("dimg"),
mImageItemId(0),
+ mItemIdBase(0),
mIsPrimary(0),
mWidth(0),
mHeight(0),
@@ -1872,15 +1995,11 @@
mIsMalformed = false;
mTrackDurationUs = 0;
mEstimatedTrackSizeBytes = 0;
- mSamplesHaveSameSize = 0;
+ mSamplesHaveSameSize = false;
if (mStszTableEntries != NULL) {
delete mStszTableEntries;
mStszTableEntries = new ListTableEntries<uint32_t, 1>(1000);
}
- if (mStcoTableEntries != NULL) {
- delete mStcoTableEntries;
- mStcoTableEntries = new ListTableEntries<uint32_t, 1>(1000);
- }
if (mCo64TableEntries != NULL) {
delete mCo64TableEntries;
mCo64TableEntries = new ListTableEntries<off64_t, 1>(1000);
@@ -1908,25 +2027,24 @@
mReachedEOS = false;
}
+int64_t MPEG4Writer::Track::trackMetaDataSize() {
+ int64_t co64BoxSizeBytes = mCo64TableEntries->count() * 8;
+ int64_t stszBoxSizeBytes = mStszTableEntries->count() * 4;
+ int64_t trackMetaDataSize = mStscTableEntries->count() * 12 + // stsc box size
+ mStssTableEntries->count() * 4 + // stss box size
+ mSttsTableEntries->count() * 8 + // stts box size
+ mCttsTableEntries->count() * 8 + // ctts box size
+ mElstTableEntries->count() * 12 + // elst box size
+ co64BoxSizeBytes + // stco box size
+ stszBoxSizeBytes; // stsz box size
+ return trackMetaDataSize;
+}
+
+
void MPEG4Writer::Track::updateTrackSizeEstimate() {
mEstimatedTrackSizeBytes = mMdatSizeBytes; // media data size
-
if (!isHeic() && !mOwner->isFileStreamable()) {
- uint32_t stcoBoxCount = (mOwner->use32BitFileOffset()
- ? mStcoTableEntries->count()
- : mCo64TableEntries->count());
- int64_t stcoBoxSizeBytes = stcoBoxCount * 4;
- int64_t stszBoxSizeBytes = mSamplesHaveSameSize? 4: (mStszTableEntries->count() * 4);
-
- // Reserved free space is not large enough to hold
- // all meta data and thus wasted.
- mEstimatedTrackSizeBytes += mStscTableEntries->count() * 12 + // stsc box size
- mStssTableEntries->count() * 4 + // stss box size
- mSttsTableEntries->count() * 8 + // stts box size
- mCttsTableEntries->count() * 8 + // ctts box size
- mElstTableEntries->count() * 12 + // elst box size
- stcoBoxSizeBytes + // stco box size
- stszBoxSizeBytes; // stsz box size
+ mEstimatedTrackSizeBytes += trackMetaDataSize();
}
}
@@ -1941,24 +2059,20 @@
mStssTableEntries->add(htonl(sampleId));
}
-void MPEG4Writer::Track::addOneSttsTableEntry(
- size_t sampleCount, int32_t duration) {
-
- if (duration == 0) {
+void MPEG4Writer::Track::addOneSttsTableEntry(size_t sampleCount, int32_t delta) {
+ if (delta == 0) {
ALOGW("0-duration samples found: %zu", sampleCount);
}
mSttsTableEntries->add(htonl(sampleCount));
- mSttsTableEntries->add(htonl(duration));
+ mSttsTableEntries->add(htonl(delta));
}
-void MPEG4Writer::Track::addOneCttsTableEntry(
- size_t sampleCount, int32_t duration) {
-
+void MPEG4Writer::Track::addOneCttsTableEntry(size_t sampleCount, int32_t sampleOffset) {
if (!mIsVideo) {
return;
}
mCttsTableEntries->add(htonl(sampleCount));
- mCttsTableEntries->add(htonl(duration));
+ mCttsTableEntries->add(htonl(sampleOffset));
}
void MPEG4Writer::Track::addOneElstTableEntry(
@@ -1971,16 +2085,30 @@
mElstTableEntries->add(htonl((((uint32_t)mediaRate) << 16) | (uint32_t)mediaRateFraction));
}
-status_t MPEG4Writer::setNextFd(int fd) {
- ALOGV("addNextFd");
- Mutex::Autolock l(mLock);
- if (mLooper == NULL) {
- mReflector = new AHandlerReflector<MPEG4Writer>(this);
+void MPEG4Writer::setupAndStartLooper() {
+ if (mLooper == nullptr) {
mLooper = new ALooper;
- mLooper->registerHandler(mReflector);
+ mLooper->setName("MP4WriterLooper");
mLooper->start();
+ mReflector = new AHandlerReflector<MPEG4Writer>(this);
+ mLooper->registerHandler(mReflector);
}
+}
+void MPEG4Writer::stopAndReleaseLooper() {
+ if (mLooper != nullptr) {
+ if (mReflector != nullptr) {
+ ALOGD("unregisterHandler");
+ mLooper->unregisterHandler(mReflector->id());
+ mReflector.clear();
+ }
+ mLooper->stop();
+ mLooper.clear();
+ }
+}
+
+status_t MPEG4Writer::setNextFd(int fd) {
+ Mutex::Autolock l(mLock);
if (mNextFd != -1) {
// No need to set a new FD yet.
return INVALID_OPERATION;
@@ -2021,12 +2149,7 @@
void MPEG4Writer::Track::addChunkOffset(off64_t offset) {
CHECK(!mIsHeic);
- if (mOwner->use32BitFileOffset()) {
- uint32_t value = offset;
- mStcoTableEntries->add(htonl(value));
- } else {
- mCo64TableEntries->add(hton64(offset));
- }
+ mCo64TableEntries->add(hton64(offset));
}
void MPEG4Writer::Track::addItemOffsetAndSize(off64_t offset, size_t size, bool isExif) {
@@ -2042,8 +2165,14 @@
}
if (isExif) {
- mExifList.push_back(mOwner->addItem_l({
+ uint16_t exifItemId;
+ if (mOwner->reserveItemId_l(1, &exifItemId) != OK) {
+ return;
+ }
+
+ mExifList.push_back(mOwner->addItem_l({
.itemType = "Exif",
+ .itemId = exifItemId,
.isPrimary = false,
.isHidden = false,
.offset = (uint32_t)offset,
@@ -2092,6 +2221,7 @@
if (hasGrid) {
mDimgRefs.value.push_back(mOwner->addItem_l({
.itemType = "hvc1",
+ .itemId = mItemIdBase++,
.isPrimary = false,
.isHidden = true,
.offset = (uint32_t)offset,
@@ -2114,6 +2244,7 @@
}
mImageItemId = mOwner->addItem_l({
.itemType = "grid",
+ .itemId = mItemIdBase++,
.isPrimary = (mIsPrimary != 0),
.isHidden = false,
.rows = (uint32_t)mGridRows,
@@ -2126,6 +2257,7 @@
} else {
mImageItemId = mOwner->addItem_l({
.itemType = "hvc1",
+ .itemId = mItemIdBase++,
.isPrimary = (mIsPrimary != 0),
.isHidden = false,
.offset = (uint32_t)offset,
@@ -2195,6 +2327,22 @@
notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_NEXT_OUTPUT_FILE_STARTED, 0);
break;
}
+ // ::write() or lseek64() wasn't a success, file could be malformed
+ case kWhatHandleIOError: {
+ ALOGE("kWhatHandleIOError");
+ // Stop tracks' threads and main writer thread.
+ notify(MEDIA_RECORDER_EVENT_ERROR, MEDIA_RECORDER_ERROR_UNKNOWN, ERROR_MALFORMED);
+ stop();
+ break;
+ }
+ // fallocate() failed, hence notify app about it and stop().
+ case kWhatHandleFallocateError: {
+ ALOGE("kWhatHandleFallocateError");
+ //TODO: introduce new MEDIA_RECORDER_INFO_STOPPED instead MEDIA_RECORDER_INFO_UNKNOWN?
+ notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_UNKNOWN, ERROR_IO);
+ stop();
+ break;
+ }
default:
TRESPASS();
}
@@ -2236,7 +2384,6 @@
stop();
delete mStszTableEntries;
- delete mStcoTableEntries;
delete mCo64TableEntries;
delete mStscTableEntries;
delete mSttsTableEntries;
@@ -2245,7 +2392,6 @@
delete mElstTableEntries;
mStszTableEntries = NULL;
- mStcoTableEntries = NULL;
mCo64TableEntries = NULL;
mStscTableEntries = NULL;
mSttsTableEntries = NULL;
@@ -2385,7 +2531,6 @@
if (interChunkTimeUs > it->mPrevChunkTimestampUs) {
it->mMaxInterChunkDurUs = interChunkTimeUs;
}
-
return true;
}
}
@@ -2468,6 +2613,22 @@
params->findInt32(kKeyRotation, &rotationDegrees)) {
mRotation = rotationDegrees;
}
+ if (mIsHeic) {
+ // Reserve the item ids, so that the item ids are ordered in the same
+ // order that the image tracks are added.
+ // If we leave the item ids to be assigned when the sample is written out,
+ // the original track order may not be preserved, if two image tracks
+ // have data around the same time. (This could happen especially when
+ // we're encoding with single tile.) The reordering may be undesirable,
+ // even if the file is well-formed and the primary picture is correct.
+
+ // Reserve item ids for samples + grid
+ size_t numItemsToReserve = mNumTiles + (mNumTiles > 1);
+ status_t err = mOwner->reserveItemId_l(numItemsToReserve, &mItemIdBase);
+ if (err != OK) {
+ return err;
+ }
+ }
initTrackingProgressStatus(params);
@@ -2544,10 +2705,11 @@
mDone = true;
void *dummy;
- pthread_join(mThread, &dummy);
- status_t err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
-
- ALOGD("%s track stopped. %s source", getTrackType(), stopSource ? "Stop" : "Not Stop");
+ status_t err = pthread_join(mThread, &dummy);
+ WARN_UNLESS(err == 0, "track::stop: pthread_join status:%d", err);
+ err = static_cast<status_t>(reinterpret_cast<uintptr_t>(dummy));
+ WARN_UNLESS(err == 0, "%s track stopped. Status :%d. %s source", getTrackType(), err,
+ stopSource ? "Stop" : "Not Stop");
return err;
}
@@ -2959,11 +3121,11 @@
uint32_t lastSamplesPerChunk = 0;
if (mIsAudio) {
- prctl(PR_SET_NAME, (unsigned long)"AudioTrackEncoding", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"AudioTrackWriterThread", 0, 0, 0);
} else if (mIsVideo) {
- prctl(PR_SET_NAME, (unsigned long)"VideoTrackEncoding", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"VideoTrackWriterThread", 0, 0, 0);
} else {
- prctl(PR_SET_NAME, (unsigned long)"MetadataTrackEncoding", 0, 0, 0);
+ prctl(PR_SET_NAME, (unsigned long)"MetadataTrackWriterThread", 0, 0, 0);
}
if (mOwner->isRealTimeRecording()) {
@@ -2983,6 +3145,7 @@
continue;
}
+
// If the codec specific data has not been received yet, delay pause.
// After the codec specific data is received, discard what we received
// when the track is to be paused.
@@ -2993,7 +3156,6 @@
}
++count;
-
int32_t isCodecConfig;
if (buffer->meta_data().findInt32(kKeyIsCodecConfig, &isCodecConfig)
&& isCodecConfig) {
@@ -3060,6 +3222,18 @@
}
}
+ /*
+ * Reserve space in the file for the current sample + to be written MOOV box. If reservation
+ * for a new sample fails, preAllocate(...) stops muxing session completely. Stop() could
+ * write MOOV box successfully as space for the same was reserved in the prior call.
+ * Release the current buffer/sample only here.
+ */
+ if (!mOwner->preAllocate(buffer->range_length())) {
+ buffer->release();
+ buffer = nullptr;
+ break;
+ }
+
++nActualFrames;
// Make a deep copy of the MediaBuffer and Metadata and release
@@ -3071,7 +3245,6 @@
meta_data = new MetaData(buffer->meta_data());
buffer->release();
buffer = NULL;
-
if (isExif) {
copy->meta_data().setInt32(kKeyExifTiffOffset, tiffHdrOffset);
}
@@ -3119,10 +3292,10 @@
if (mOwner->approachingFileSizeLimit()) {
mOwner->notifyApproachingLimit();
}
-
int32_t isSync = false;
meta_data->findInt32(kKeyIsSyncFrame, &isSync);
CHECK(meta_data->findInt64(kKeyTime, ×tampUs));
+ timestampUs += mFirstSampleStartOffsetUs;
// For video, skip the first several non-key frames until getting the first key frame.
if (mIsVideo && !mGotStartKeyFrame && !isSync) {
@@ -3134,10 +3307,13 @@
mGotStartKeyFrame = true;
}
////////////////////////////////////////////////////////////////////////////////
-
if (!mIsHeic) {
if (mStszTableEntries->count() == 0) {
mFirstSampleTimeRealUs = systemTime() / 1000;
+ if (timestampUs < 0 && mFirstSampleStartOffsetUs == 0) {
+ mFirstSampleStartOffsetUs = -timestampUs;
+ timestampUs = 0;
+ }
mOwner->setStartTimestampUs(timestampUs);
mStartTimestampUs = timestampUs;
previousPausedDurationUs = mStartTimestampUs;
@@ -3309,17 +3485,17 @@
}
}
mStszTableEntries->add(htonl(sampleSize));
+
if (mStszTableEntries->count() > 2) {
// Force the first sample to have its own stts entry so that
// we can adjust its value later to maintain the A/V sync.
- if (mStszTableEntries->count() == 3 || currDurationTicks != lastDurationTicks) {
+ if (lastDurationTicks && currDurationTicks != lastDurationTicks) {
addOneSttsTableEntry(sampleCount, lastDurationTicks);
sampleCount = 1;
} else {
++sampleCount;
}
-
}
if (mSamplesHaveSameSize) {
if (mStszTableEntries->count() >= 2 && previousSampleSize != sampleSize) {
@@ -3352,11 +3528,7 @@
if (mIsHeic) {
addItemOffsetAndSize(offset, bytesWritten, isExif);
} else {
- uint32_t count = (mOwner->use32BitFileOffset()
- ? mStcoTableEntries->count()
- : mCo64TableEntries->count());
-
- if (count == 0) {
+ if (mCo64TableEntries->count() == 0) {
addChunkOffset(offset);
}
}
@@ -3392,9 +3564,7 @@
}
}
}
-
}
-
if (isTrackMalFormed()) {
dumpTimeStamps();
err = ERROR_MALFORMED;
@@ -3426,17 +3596,10 @@
++sampleCount; // Count for the last sample
}
- if (mStszTableEntries->count() <= 2) {
- addOneSttsTableEntry(1, lastDurationTicks);
- if (sampleCount - 1 > 0) {
- addOneSttsTableEntry(sampleCount - 1, lastDurationTicks);
- }
- } else {
- addOneSttsTableEntry(sampleCount, lastDurationTicks);
- }
+ addOneSttsTableEntry(sampleCount, lastDurationTicks);
- // The last ctts box may not have been written yet, and this
- // is to make sure that we write out the last ctts box.
+ // The last ctts box entry may not have been written yet, and this
+ // is to make sure that we write out the last ctts box entry.
if (currCttsOffsetTimeTicks == lastCttsOffsetTimeTicks) {
if (cttsSampleCount > 0) {
addOneCttsTableEntry(cttsSampleCount, lastCttsOffsetTimeTicks);
@@ -3687,7 +3850,7 @@
"Metadata";
}
-void MPEG4Writer::Track::writeTrackHeader(bool use32BitOffset) {
+void MPEG4Writer::Track::writeTrackHeader() {
uint32_t now = getMpeg4Time();
mOwner->beginBox("trak");
writeTkhdBox(now);
@@ -3704,7 +3867,7 @@
writeNmhdBox();
}
writeDinfBox();
- writeStblBox(use32BitOffset);
+ writeStblBox();
mOwner->endBox(); // minf
mOwner->endBox(); // mdia
mOwner->endBox(); // trak
@@ -3720,7 +3883,7 @@
return mMinCttsOffsetTimeUs;
}
-void MPEG4Writer::Track::writeStblBox(bool use32BitOffset) {
+void MPEG4Writer::Track::writeStblBox() {
mOwner->beginBox("stbl");
mOwner->beginBox("stsd");
mOwner->writeInt32(0); // version=0, flags=0
@@ -3740,7 +3903,7 @@
}
writeStszBox();
writeStscBox();
- writeStcoBox(use32BitOffset);
+ writeCo64Box();
mOwner->endBox(); // stbl
}
@@ -4099,19 +4262,122 @@
mOwner->endBox();
}
-void MPEG4Writer::Track::writeEdtsBox(){
+void MPEG4Writer::Track::writeEdtsBox() {
ALOGV("%s : getStartTimeOffsetTimeUs of track:%" PRId64 " us", getTrackType(),
getStartTimeOffsetTimeUs());
- // Prepone video playback.
- if (mMinCttsOffsetTicks != mMaxCttsOffsetTicks) {
- int32_t mvhdTimeScale = mOwner->getTimeScale();
- uint32_t tkhdDuration = (getDurationUs() * mvhdTimeScale + 5E5) / 1E6;
- int64_t mediaTime = ((kMaxCttsOffsetTimeUs - getMinCttsOffsetTimeUs())
- * mTimeScale + 5E5) / 1E6;
- if (tkhdDuration > 0 && mediaTime > 0) {
- addOneElstTableEntry(tkhdDuration, mediaTime, 1, 0);
+ int32_t mvhdTimeScale = mOwner->getTimeScale();
+ ALOGV("mvhdTimeScale:%" PRId32, mvhdTimeScale);
+ /* trackStartOffsetUs of this track is the sum of longest offset needed by a track among all
+ * tracks with B frames in this movie and the start offset of this track.
+ */
+ int64_t trackStartOffsetUs = getStartTimeOffsetTimeUs();
+ ALOGV("trackStartOffsetUs:%" PRIu64, trackStartOffsetUs);
+
+ // Longest offset needed by a track among all tracks with B frames.
+ int32_t movieStartOffsetBFramesUs = mOwner->getStartTimeOffsetBFramesUs();
+ ALOGV("movieStartOffsetBFramesUs:%" PRId32, movieStartOffsetBFramesUs);
+
+ // This media/track's real duration (sum of duration of all samples in this track).
+ uint32_t tkhdDurationTicks = (mTrackDurationUs * mvhdTimeScale + 5E5) / 1E6;
+ ALOGV("mTrackDurationUs:%" PRId64 "us", mTrackDurationUs);
+
+ int64_t movieStartTimeUs = mOwner->getStartTimestampUs();
+ ALOGV("movieStartTimeUs:%" PRId64, movieStartTimeUs);
+
+ int64_t trackStartTimeUs = movieStartTimeUs + trackStartOffsetUs;
+ ALOGV("trackStartTimeUs:%" PRId64, trackStartTimeUs);
+
+ if (movieStartOffsetBFramesUs == 0) {
+ // No B frames in any tracks.
+ if (trackStartOffsetUs > 0) {
+ // Track with positive start offset.
+ uint32_t segDuration = (trackStartOffsetUs * mvhdTimeScale + 5E5) / 1E6;
+ ALOGV("segDuration:%" PRIu64 "us", trackStartOffsetUs);
+ /* The first entry is an empty edit (indicated by media_time equal to -1), and its
+ * duration (segment_duration) is equal to the difference of the presentation times of
+ * the earliest media sample among all tracks and the earliest media sample of the track.
+ */
+ ALOGV("Empty edit list entry");
+ addOneElstTableEntry(segDuration, -1, 1, 0);
+ addOneElstTableEntry(tkhdDurationTicks, 0, 1, 0);
+ } else if (mFirstSampleStartOffsetUs > 0) {
+ // Track with start time < 0 / negative start offset.
+ ALOGV("Normal edit list entry");
+ int32_t mediaTime = (mFirstSampleStartOffsetUs * mTimeScale + 5E5) / 1E6;
+ int32_t firstSampleOffsetTicks =
+ (mFirstSampleStartOffsetUs * mvhdTimeScale + 5E5) / 1E6;
+ // samples before 0 don't count in for duration, hence subtract firstSampleOffsetTicks.
+ addOneElstTableEntry(tkhdDurationTicks - firstSampleOffsetTicks, mediaTime, 1, 0);
+ } else {
+ // Track starting at zero.
+ ALOGV("No edit list entry required for this track");
}
+ } else if (movieStartOffsetBFramesUs < 0) {
+ // B frames present in at least one of the tracks.
+ ALOGV("writeEdtsBox - Reordered frames(B frames) present");
+ if (trackStartOffsetUs == std::abs(movieStartOffsetBFramesUs)) {
+ // Track starting at 0, no start offset.
+ // TODO : need to take care of mFirstSampleStartOffsetUs > 0 and trackStartOffsetUs > 0
+ // separately
+ if (mMinCttsOffsetTicks == mMaxCttsOffsetTicks) {
+ // Video with no B frame or non-video track.
+ if (mFirstSampleStartOffsetUs > 0) {
+ // Track with start time < 0 / negative start offset.
+ ALOGV("Normal edit list entry");
+ ALOGV("mFirstSampleStartOffsetUs:%" PRId64 "us", mFirstSampleStartOffsetUs);
+ int32_t mediaTimeTicks = (mFirstSampleStartOffsetUs * mTimeScale + 5E5) / 1E6;
+ int32_t firstSampleOffsetTicks =
+ (mFirstSampleStartOffsetUs * mvhdTimeScale + 5E5) / 1E6;
+ // Samples before 0 don't count for duration, subtract firstSampleOffsetTicks.
+ addOneElstTableEntry(tkhdDurationTicks - firstSampleOffsetTicks, mediaTimeTicks,
+ 1, 0);
+ }
+ } else {
+ // Track with B Frames.
+ int32_t mediaTimeTicks = (trackStartOffsetUs * mTimeScale + 5E5) / 1E6;
+ ALOGV("mediaTime:%" PRId64 "us", trackStartOffsetUs);
+ ALOGV("Normal edit list entry to negate start offset by B Frames in others tracks");
+ addOneElstTableEntry(tkhdDurationTicks, mediaTimeTicks, 1, 0);
+ }
+ } else if (trackStartOffsetUs > std::abs(movieStartOffsetBFramesUs)) {
+ // Track with start offset.
+ ALOGV("Tracks starting > 0");
+ int32_t editDurationTicks = 0;
+ if (mMinCttsOffsetTicks == mMaxCttsOffsetTicks) {
+ // Video with no B frame or non-video track.
+ editDurationTicks =
+ ((trackStartOffsetUs + movieStartOffsetBFramesUs) * mvhdTimeScale + 5E5) /
+ 1E6;
+ ALOGV("editDuration:%" PRId64 "us", (trackStartOffsetUs + movieStartOffsetBFramesUs));
+ } else {
+ // Track with B frame.
+ int32_t trackStartOffsetBFramesUs = getMinCttsOffsetTimeUs() - kMaxCttsOffsetTimeUs;
+ ALOGV("trackStartOffsetBFramesUs:%" PRId32, trackStartOffsetBFramesUs);
+ editDurationTicks =
+ ((trackStartOffsetUs + movieStartOffsetBFramesUs +
+ trackStartOffsetBFramesUs) * mvhdTimeScale + 5E5) / 1E6;
+ ALOGV("editDuration:%" PRId64 "us", (trackStartOffsetUs + movieStartOffsetBFramesUs + trackStartOffsetBFramesUs));
+ }
+ ALOGV("editDurationTicks:%" PRIu32, editDurationTicks);
+ if (editDurationTicks > 0) {
+ ALOGV("Empty edit list entry");
+ addOneElstTableEntry(editDurationTicks, -1, 1, 0);
+ addOneElstTableEntry(tkhdDurationTicks, 0, 1, 0);
+ } else if (editDurationTicks < 0) {
+ // Only video tracks with B Frames would hit this case.
+ ALOGV("Edit list entry to negate start offset by B frames in other tracks");
+ addOneElstTableEntry(tkhdDurationTicks, std::abs(editDurationTicks), 1, 0);
+ } else {
+ ALOGV("No edit list entry needed for this track");
+ }
+ } else {
+ // Not expecting this case as we adjust negative start timestamps to zero.
+ ALOGW("trackStartOffsetUs < std::abs(movieStartOffsetBFramesUs)");
+ }
+ } else {
+ // Neither B frames present nor absent! or any other case?.
+ ALOGW("movieStartOffsetBFramesUs > 0");
}
if (mElstTableEntries->count() == 0) {
@@ -4253,19 +4519,6 @@
void MPEG4Writer::Track::writeSttsBox() {
mOwner->beginBox("stts");
mOwner->writeInt32(0); // version=0, flags=0
- if (mMinCttsOffsetTicks == mMaxCttsOffsetTicks) {
- // For non-vdeio tracks or video tracks without ctts table,
- // adjust duration of first sample for tracks to account for
- // first sample not starting at the media start time.
- // TODO: consider signaling this using some offset
- // as this is not quite correct.
- uint32_t duration;
- CHECK(mSttsTableEntries->get(duration, 1));
- duration = htonl(duration); // Back to host byte order
- int32_t startTimeOffsetScaled = (((getStartTimeOffsetTimeUs() +
- mOwner->getStartTimeOffsetBFramesUs()) * mTimeScale) + 500000LL) / 1000000LL;
- mSttsTableEntries->set(htonl((int32_t)duration + startTimeOffsetScaled), 1);
- }
mSttsTableEntries->write(mOwner);
mOwner->endBox(); // stts
}
@@ -4286,7 +4539,9 @@
mOwner->beginBox("ctts");
mOwner->writeInt32(0); // version=0, flags=0
- int64_t deltaTimeUs = kMaxCttsOffsetTimeUs - getStartTimeOffsetTimeUs();
+ // Adjust ctts entries to have only offset needed for reordering frames.
+ int64_t deltaTimeUs = mMinCttsOffsetTimeUs;
+ ALOGV("ctts deltaTimeUs:%" PRId64, deltaTimeUs);
int64_t delta = (deltaTimeUs * mTimeScale + 500000LL) / 1000000LL;
mCttsTableEntries->adjustEntries([delta](size_t /* ix */, uint32_t (&value)[2]) {
// entries are <count, ctts> pairs; adjust only ctts
@@ -4327,14 +4582,10 @@
mOwner->endBox(); // stsc
}
-void MPEG4Writer::Track::writeStcoBox(bool use32BitOffset) {
- mOwner->beginBox(use32BitOffset? "stco": "co64");
+void MPEG4Writer::Track::writeCo64Box() {
+ mOwner->beginBox("co64");
mOwner->writeInt32(0); // version=0, flags=0
- if (use32BitOffset) {
- mStcoTableEntries->write(mOwner);
- } else {
- mCo64TableEntries->write(mOwner);
- }
+ mCo64TableEntries->write(mOwner);
mOwner->endBox(); // stco or co64
}
@@ -4457,9 +4708,11 @@
}
writeInt16((uint16_t)itemCount);
- for (size_t i = 0; i < itemCount; i++) {
- writeInt16(mItems[i].itemId);
- bool isGrid = mItems[i].isGrid();
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
+
+ writeInt16(item.itemId);
+ bool isGrid = item.isGrid();
writeInt16(isGrid ? 1 : 0); // construction_method
writeInt16(0); // data_reference_index = 0
@@ -4470,8 +4723,8 @@
writeInt32(mNumGrids++ * 8);
writeInt32(8);
} else {
- writeInt32(mItems[i].offset);
- writeInt32(mItems[i].size);
+ writeInt32(item.offset);
+ writeInt32(item.size);
}
}
endBox();
@@ -4500,9 +4753,11 @@
}
writeInt16((uint16_t)itemCount);
- for (size_t i = 0; i < itemCount; i++) {
- writeInfeBox(mItems[i].itemId, mItems[i].itemType,
- (mItems[i].isImage() && mItems[i].isHidden) ? 1 : 0);
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
+
+ writeInfeBox(item.itemId, item.itemType,
+ (item.isImage() && item.isHidden) ? 1 : 0);
}
endBox();
@@ -4511,20 +4766,22 @@
void MPEG4Writer::writeIdatBox() {
beginBox("idat");
- for (size_t i = 0; i < mItems.size(); i++) {
- if (mItems[i].isGrid()) {
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
+
+ if (item.isGrid()) {
writeInt8(0); // version
// flags == 1 means 32-bit width,height
- int8_t flags = (mItems[i].width > 65535 || mItems[i].height > 65535);
+ int8_t flags = (item.width > 65535 || item.height > 65535);
writeInt8(flags);
- writeInt8(mItems[i].rows - 1);
- writeInt8(mItems[i].cols - 1);
+ writeInt8(item.rows - 1);
+ writeInt8(item.cols - 1);
if (flags) {
- writeInt32(mItems[i].width);
- writeInt32(mItems[i].height);
+ writeInt32(item.width);
+ writeInt32(item.height);
} else {
- writeInt16((uint16_t)mItems[i].width);
- writeInt16((uint16_t)mItems[i].height);
+ writeInt16((uint16_t)item.width);
+ writeInt16((uint16_t)item.height);
}
}
}
@@ -4536,11 +4793,13 @@
beginBox("iref");
writeInt32(0); // Version = 0, Flags = 0
{
- for (size_t i = 0; i < mItems.size(); i++) {
- for (size_t r = 0; r < mItems[i].refsList.size(); r++) {
- const ItemRefs &refs = mItems[i].refsList[r];
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
+
+ for (size_t r = 0; r < item.refsList.size(); r++) {
+ const ItemRefs &refs = item.refsList[r];
beginBox(refs.key);
- writeInt16(mItems[i].itemId);
+ writeInt16(item.itemId);
size_t refCount = refs.value.size();
if (refCount > 65535) {
ALOGW("too many entries in %s", refs.key);
@@ -4615,12 +4874,14 @@
writeInt32(flags); // Version = 0
writeInt32(mAssociationEntryCount);
- for (size_t itemIndex = 0; itemIndex < mItems.size(); itemIndex++) {
- const Vector<uint16_t> &properties = mItems[itemIndex].properties;
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
+
+ const Vector<uint16_t> &properties = item.properties;
if (properties.empty()) {
continue;
}
- writeInt16(mItems[itemIndex].itemId);
+ writeInt16(item.itemId);
size_t entryCount = properties.size();
if (entryCount > 255) {
@@ -4650,19 +4911,21 @@
// patch up the mPrimaryItemId and count items with prop associations
uint16_t firstVisibleItemId = 0;
uint16_t firstImageItemId = 0;
- for (size_t index = 0; index < mItems.size(); index++) {
- if (!mItems[index].isImage()) continue;
+ for (auto it = mItems.begin(); it != mItems.end(); it++) {
+ ItemInfo &item = it->second;
- if (mItems[index].isPrimary) {
- mPrimaryItemId = mItems[index].itemId;
+ if (!item.isImage()) continue;
+
+ if (item.isPrimary) {
+ mPrimaryItemId = item.itemId;
}
if (!firstImageItemId) {
- firstImageItemId = mItems[index].itemId;
+ firstImageItemId = item.itemId;
}
- if (!firstVisibleItemId && !mItems[index].isHidden) {
- firstVisibleItemId = mItems[index].itemId;
+ if (!firstVisibleItemId && !item.isHidden) {
+ firstVisibleItemId = item.itemId;
}
- if (!mItems[index].properties.empty()) {
+ if (!item.properties.empty()) {
mAssociationEntryCount++;
}
}
@@ -4716,15 +4979,25 @@
return mProperties.size();
}
+status_t MPEG4Writer::reserveItemId_l(size_t numItems, uint16_t *itemIdBase) {
+ if (numItems > UINT16_MAX - mNextItemId) {
+ ALOGE("couldn't reserve item ids for %zu items", numItems);
+ return ERROR_OUT_OF_RANGE;
+ }
+ *itemIdBase = mNextItemId;
+ mNextItemId += numItems;
+ return OK;
+}
+
uint16_t MPEG4Writer::addItem_l(const ItemInfo &info) {
ALOGV("addItem_l: type %s, offset %u, size %u",
info.itemType, info.offset, info.size);
- size_t index = mItems.size();
- mItems.push_back(info);
+ if (info.itemId < kItemIdBase || info.itemId >= mNextItemId) {
+ ALOGW("Item id %u is used without reservation!", info.itemId);
+ }
- // make the item id start at kItemIdBase
- mItems.editItemAt(index).itemId = index + kItemIdBase;
+ mItems[info.itemId] = info;
#if (LOG_NDEBUG==0)
if (!info.properties.empty()) {
@@ -4735,24 +5008,28 @@
}
str.append(info.properties[i]);
}
- ALOGV("addItem_l: id %d, properties: %s", mItems[index].itemId, str.c_str());
+ ALOGV("addItem_l: id %d, properties: %s", info.itemId, str.c_str());
}
#endif // (LOG_NDEBUG==0)
- return mItems[index].itemId;
+ return info.itemId;
}
void MPEG4Writer::addRefs_l(uint16_t itemId, const ItemRefs &refs) {
if (refs.value.empty()) {
return;
}
- if (itemId < kItemIdBase) {
- ALOGW("itemId shouldn't be smaller than kItemIdBase");
+ if (itemId < kItemIdBase || itemId >= mNextItemId) {
+ ALOGW("itemId %u for ref is invalid!", itemId);
return;
}
- size_t index = itemId - kItemIdBase;
- mItems.editItemAt(index).refsList.push_back(refs);
+ auto it = mItems.find(itemId);
+ if (it == mItems.end()) {
+ ALOGW("itemId %u was not added yet", itemId);
+ return;
+ }
+ it->second.refsList.push_back(refs);
mHasRefs = true;
}
diff --git a/media/libstagefright/MediaCodec.cpp b/media/libstagefright/MediaCodec.cpp
index 257dd0f..712be41 100644
--- a/media/libstagefright/MediaCodec.cpp
+++ b/media/libstagefright/MediaCodec.cpp
@@ -22,7 +22,6 @@
#include <stdlib.h>
#include "include/SoftwareRenderer.h"
-#include "StagefrightPluginLoader.h"
#include <android/hardware/cas/native/1.0/IDescrambler.h>
#include <android/hardware/media/omx/1.0/IGraphicBufferSource.h>
@@ -39,7 +38,7 @@
#include <mediadrm/ICrypto.h>
#include <media/IOMX.h>
#include <media/MediaCodecBuffer.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/MediaResource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
@@ -51,6 +50,7 @@
#include <media/stagefright/ACodec.h>
#include <media/stagefright/BatteryChecker.h>
#include <media/stagefright/BufferProducerWrapper.h>
+#include <media/stagefright/CCodec.h>
#include <media/stagefright/MediaCodec.h>
#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaDefs.h>
@@ -101,6 +101,10 @@
static const char *kCodecLatencyHist = "android.media.mediacodec.latency.hist"; /* in us */
static const char *kCodecLatencyUnknown = "android.media.mediacodec.latency.unknown";
+static const char *kCodecNumLowLatencyModeOn = "android.media.mediacodec.low-latency.on"; /* 0..n */
+static const char *kCodecNumLowLatencyModeOff = "android.media.mediacodec.low-latency.off"; /* 0..n */
+static const char *kCodecFirstFrameIndexLowLatencyModeOn = "android.media.mediacodec.low-latency.first-frame"; /* 0..n */
+
// the kCodecRecent* fields appear only in getMetrics() results
static const char *kCodecRecentLatencyMax = "android.media.mediacodec.recent.max"; /* in us */
static const char *kCodecRecentLatencyMin = "android.media.mediacodec.recent.min"; /* in us */
@@ -534,9 +538,7 @@
// static
sp<PersistentSurface> MediaCodec::CreatePersistentInputSurface() {
- // allow plugin to create surface
- sp<PersistentSurface> pluginSurface =
- StagefrightPluginLoader::GetCCodecInstance()->createInputSurface();
+ sp<PersistentSurface> pluginSurface = CCodec::CreateInputSurface();
if (pluginSurface != nullptr) {
return pluginSurface;
}
@@ -583,7 +585,12 @@
mHaveInputSurface(false),
mHavePendingInputBuffers(false),
mCpuBoostRequested(false),
- mLatencyUnknown(0) {
+ mLatencyUnknown(0),
+ mNumLowLatencyEnables(0),
+ mNumLowLatencyDisables(0),
+ mIsLowLatencyModeOn(false),
+ mIndexOfFirstFrameWhenLowLatencyOn(-1),
+ mInputBufferCounter(0) {
if (uid == kNoUid) {
mUid = AIBinder_getCallingUid();
} else {
@@ -616,6 +623,16 @@
}
mRecentHead = 0;
}
+
+ {
+ Mutex::Autolock al(mLatencyLock);
+ mBuffersInFlight.clear();
+ mNumLowLatencyEnables = 0;
+ mNumLowLatencyDisables = 0;
+ mIsLowLatencyModeOn = false;
+ mIndexOfFirstFrameWhenLowLatencyOn = -1;
+ mInputBufferCounter = 0;
+ }
}
void MediaCodec::updateMediametrics() {
@@ -641,6 +658,13 @@
mediametrics_setInt64(mMetricsHandle, kCodecLatencyUnknown, mLatencyUnknown);
}
+ {
+ Mutex::Autolock al(mLatencyLock);
+ mediametrics_setInt64(mMetricsHandle, kCodecNumLowLatencyModeOn, mNumLowLatencyEnables);
+ mediametrics_setInt64(mMetricsHandle, kCodecNumLowLatencyModeOff, mNumLowLatencyDisables);
+ mediametrics_setInt64(mMetricsHandle, kCodecFirstFrameIndexLowLatencyModeOn,
+ mIndexOfFirstFrameWhenLowLatencyOn);
+ }
#if 0
// enable for short term, only while debugging
updateEphemeralMediametrics(mMetricsHandle);
@@ -697,6 +721,22 @@
}
}
+void MediaCodec::updateLowLatency(const sp<AMessage> &msg) {
+ int32_t lowLatency = 0;
+ if (msg->findInt32("low-latency", &lowLatency)) {
+ Mutex::Autolock al(mLatencyLock);
+ if (lowLatency > 0) {
+ ++mNumLowLatencyEnables;
+ // This is just an estimate since low latency mode change happens ONLY at key frame
+ mIsLowLatencyModeOn = true;
+ } else if (lowLatency == 0) {
+ ++mNumLowLatencyDisables;
+ // This is just an estimate since low latency mode change happens ONLY at key frame
+ mIsLowLatencyModeOn = false;
+ }
+ }
+}
+
bool MediaCodec::Histogram::setup(int nbuckets, int64_t width, int64_t floor)
{
if (nbuckets <= 0 || width <= 0) {
@@ -813,6 +853,11 @@
// XXX: we *could* make sure that the time is later than the end of queue
// as part of a consistency check...
mBuffersInFlight.push_back(startdata);
+
+ if (mIsLowLatencyModeOn && mIndexOfFirstFrameWhenLowLatencyOn < 0) {
+ mIndexOfFirstFrameWhenLowLatencyOn = mInputBufferCounter;
+ }
+ ++mInputBufferCounter;
}
}
@@ -922,7 +967,7 @@
}
static CodecBase *CreateCCodec() {
- return StagefrightPluginLoader::GetCCodecInstance()->createCodec();
+ return new CCodec;
}
//static
@@ -1133,6 +1178,8 @@
}
}
+ updateLowLatency(format);
+
msg->setMessage("format", format);
msg->setInt32("flags", flags);
msg->setObject("surface", surface);
@@ -3714,6 +3761,7 @@
}
status_t MediaCodec::onSetParameters(const sp<AMessage> ¶ms) {
+ updateLowLatency(params);
mCodec->signalSetParameters(params);
return OK;
diff --git a/media/libstagefright/MediaCodecList.cpp b/media/libstagefright/MediaCodecList.cpp
index a267f7e..ac54fa1 100644
--- a/media/libstagefright/MediaCodecList.cpp
+++ b/media/libstagefright/MediaCodecList.cpp
@@ -19,7 +19,6 @@
#include <utils/Log.h>
#include "MediaCodecListOverrides.h"
-#include "StagefrightPluginLoader.h"
#include <binder/IServiceManager.h>
@@ -30,11 +29,14 @@
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/MediaDefs.h>
+#include <media/stagefright/omx/OMXUtils.h>
+#include <media/stagefright/xmlparser/MediaCodecsXmlParser.h>
+#include <media/stagefright/CCodec.h>
+#include <media/stagefright/Codec2InfoBuilder.h>
#include <media/stagefright/MediaCodecList.h>
#include <media/stagefright/MediaErrors.h>
#include <media/stagefright/OmxInfoBuilder.h>
-#include <media/stagefright/omx/OMXUtils.h>
-#include <xmlparser/include/media/stagefright/xmlparser/MediaCodecsXmlParser.h>
+#include <media/stagefright/PersistentSurface.h>
#include <sys/stat.h>
#include <utils/threads.h>
@@ -86,8 +88,7 @@
MediaCodecListBuilderBase *GetCodec2InfoBuilder() {
Mutex::Autolock _l(sCodec2InfoBuilderMutex);
if (!sCodec2InfoBuilder) {
- sCodec2InfoBuilder.reset(
- StagefrightPluginLoader::GetCCodecInstance()->createBuilder());
+ sCodec2InfoBuilder.reset(new Codec2InfoBuilder);
}
return sCodec2InfoBuilder.get();
}
@@ -96,8 +97,7 @@
std::vector<MediaCodecListBuilderBase *> builders;
// if plugin provides the input surface, we cannot use OMX video encoders.
// In this case, rely on plugin to provide list of OMX codecs that are usable.
- sp<PersistentSurface> surfaceTest =
- StagefrightPluginLoader::GetCCodecInstance()->createInputSurface();
+ sp<PersistentSurface> surfaceTest = CCodec::CreateInputSurface();
if (surfaceTest == nullptr) {
ALOGD("Allowing all OMX codecs");
builders.push_back(&sOmxInfoBuilder);
diff --git a/media/libstagefright/MediaCodecSource.cpp b/media/libstagefright/MediaCodecSource.cpp
index 7243b82..7fec072 100644
--- a/media/libstagefright/MediaCodecSource.cpp
+++ b/media/libstagefright/MediaCodecSource.cpp
@@ -25,7 +25,7 @@
#include <mediadrm/ICrypto.h>
#include <media/MediaBufferHolder.h>
#include <media/MediaCodecBuffer.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
diff --git a/media/libstagefright/MediaMuxer.cpp b/media/libstagefright/MediaMuxer.cpp
index 7ebdb1a..3808d7e 100644
--- a/media/libstagefright/MediaMuxer.cpp
+++ b/media/libstagefright/MediaMuxer.cpp
@@ -24,7 +24,7 @@
#include <media/stagefright/MediaMuxer.h>
#include <media/mediarecorder.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
@@ -48,7 +48,8 @@
MediaMuxer::MediaMuxer(int fd, OutputFormat format)
: mFormat(format),
- mState(UNINITIALIZED) {
+ mState(UNINITIALIZED),
+ mError(OK) {
if (isMp4Format(format)) {
mWriter = new MPEG4Writer(fd);
} else if (format == OUTPUT_FORMAT_WEBM) {
@@ -58,6 +59,7 @@
}
if (mWriter != NULL) {
+ mWriter->setMuxerListener(this);
mFileMeta = new MetaData;
if (format == OUTPUT_FORMAT_HEIF) {
// Note that the key uses recorder file types.
@@ -156,14 +158,22 @@
status_t MediaMuxer::stop() {
Mutex::Autolock autoLock(mMuxerLock);
- if (mState == STARTED) {
+ if (mState == STARTED || mState == ERROR) {
mState = STOPPED;
for (size_t i = 0; i < mTrackList.size(); i++) {
if (mTrackList[i]->stop() != OK) {
return INVALID_OPERATION;
}
}
- return mWriter->stop();
+ status_t err = mWriter->stop();
+ if (err != OK || mError != OK) {
+ ALOGE("stop err: %d, mError:%d", err, mError);
+ }
+ // Prioritize mError over err.
+ if (mError != OK) {
+ err = mError;
+ }
+ return err;
} else {
ALOGE("stop() is called in invalid state %d", mState);
return INVALID_OPERATION;
@@ -212,4 +222,29 @@
return currentTrack->pushBuffer(mediaBuffer);
}
+void MediaMuxer::notify(int msg, int ext1, int ext2) {
+ switch (msg) {
+ case MEDIA_RECORDER_EVENT_ERROR:
+ case MEDIA_RECORDER_TRACK_EVENT_ERROR: {
+ Mutex::Autolock autoLock(mMuxerLock);
+ mState = ERROR;
+ mError = ext2;
+ ALOGW("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
+ break;
+ }
+ case MEDIA_RECORDER_EVENT_INFO: {
+ if (ext1 == MEDIA_RECORDER_INFO_UNKNOWN) {
+ Mutex::Autolock autoLock(mMuxerLock);
+ mState = ERROR;
+ mError = ext2;
+ ALOGW("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
+ }
+ break;
+ }
+ default:
+ // Ignore INFO and other notifications for now.
+ break;
+ }
+}
+
} // namespace android
diff --git a/media/libstagefright/MediaSource.cpp b/media/libstagefright/MediaSource.cpp
index 5bbd3d8..ee0031f 100644
--- a/media/libstagefright/MediaSource.cpp
+++ b/media/libstagefright/MediaSource.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/IMediaSource.h>
namespace android {
diff --git a/media/libstagefright/NuMediaExtractor.cpp b/media/libstagefright/NuMediaExtractor.cpp
index 66fb4b0..9fe09fa 100644
--- a/media/libstagefright/NuMediaExtractor.cpp
+++ b/media/libstagefright/NuMediaExtractor.cpp
@@ -25,7 +25,7 @@
#include <datasource/DataSourceFactory.h>
#include <datasource/FileSource.h>
#include <media/DataSource.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
diff --git a/media/libstagefright/OggWriter.cpp b/media/libstagefright/OggWriter.cpp
index b738fef..0bc5976 100644
--- a/media/libstagefright/OggWriter.cpp
+++ b/media/libstagefright/OggWriter.cpp
@@ -22,7 +22,7 @@
#include <sys/stat.h>
#include <sys/types.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/mediarecorder.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MediaDefs.h>
diff --git a/media/libstagefright/RemoteMediaExtractor.cpp b/media/libstagefright/RemoteMediaExtractor.cpp
index 29c3a35..c841d10 100644
--- a/media/libstagefright/RemoteMediaExtractor.cpp
+++ b/media/libstagefright/RemoteMediaExtractor.cpp
@@ -20,8 +20,8 @@
#include <binder/IPCThreadState.h>
#include <media/stagefright/InterfaceUtils.h>
-#include <media/MediaAnalyticsItem.h>
-#include <media/MediaSource.h>
+#include <media/MediaMetricsItem.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/RemoteMediaExtractor.h>
// still doing some on/off toggling here.
@@ -48,20 +48,20 @@
mSource(source),
mExtractorPlugin(plugin) {
- mAnalyticsItem = nullptr;
+ mMetricsItem = nullptr;
if (MEDIA_LOG) {
- mAnalyticsItem = MediaAnalyticsItem::create(kKeyExtractor);
+ mMetricsItem = mediametrics::Item::create(kKeyExtractor);
// we're in the extractor service, we want to attribute to the app
// that invoked us.
int uid = IPCThreadState::self()->getCallingUid();
- mAnalyticsItem->setUid(uid);
+ mMetricsItem->setUid(uid);
// track the container format (mpeg, aac, wvm, etc)
size_t ntracks = extractor->countTracks();
- mAnalyticsItem->setCString(kExtractorFormat, extractor->name());
+ mMetricsItem->setCString(kExtractorFormat, extractor->name());
// tracks (size_t)
- mAnalyticsItem->setInt32(kExtractorTracks, ntracks);
+ mMetricsItem->setInt32(kExtractorTracks, ntracks);
// metadata
MetaDataBase pMetaData;
if (extractor->getMetaData(pMetaData) == OK) {
@@ -70,7 +70,7 @@
// 'mime'
const char *mime = nullptr;
if (pMetaData.findCString(kKeyMIMEType, &mime)) {
- mAnalyticsItem->setCString(kExtractorMime, mime);
+ mMetricsItem->setCString(kExtractorMime, mime);
}
// what else is interesting and not already available?
}
@@ -84,15 +84,15 @@
mExtractorPlugin = nullptr;
// log the current record, provided it has some information worth recording
if (MEDIA_LOG) {
- if (mAnalyticsItem != nullptr) {
- if (mAnalyticsItem->count() > 0) {
- mAnalyticsItem->selfrecord();
+ if (mMetricsItem != nullptr) {
+ if (mMetricsItem->count() > 0) {
+ mMetricsItem->selfrecord();
}
}
}
- if (mAnalyticsItem != nullptr) {
- delete mAnalyticsItem;
- mAnalyticsItem = nullptr;
+ if (mMetricsItem != nullptr) {
+ delete mMetricsItem;
+ mMetricsItem = nullptr;
}
}
@@ -123,11 +123,11 @@
}
status_t RemoteMediaExtractor::getMetrics(Parcel *reply) {
- if (mAnalyticsItem == nullptr || reply == nullptr) {
+ if (mMetricsItem == nullptr || reply == nullptr) {
return UNKNOWN_ERROR;
}
- mAnalyticsItem->writeToParcel(reply);
+ mMetricsItem->writeToParcel(reply);
return OK;
}
diff --git a/media/libstagefright/StagefrightPluginLoader.cpp b/media/libstagefright/StagefrightPluginLoader.cpp
deleted file mode 100644
index fb03c5e..0000000
--- a/media/libstagefright/StagefrightPluginLoader.cpp
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright 2018, The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//#define LOG_NDEBUG 0
-#define LOG_TAG "StagefrightPluginLoader"
-#include <utils/Log.h>
-
-#include <android-base/properties.h>
-#include <dlfcn.h>
-
-#include "StagefrightPluginLoader.h"
-
-namespace android {
-
-/* static */ Mutex StagefrightPluginLoader::sMutex;
-/* static */ std::unique_ptr<StagefrightPluginLoader> StagefrightPluginLoader::sInstance;
-
-namespace /* unnamed */ {
-
-constexpr const char kCCodecPluginPath[] = "libsfplugin_ccodec.so";
-
-} // unnamed namespace
-
-StagefrightPluginLoader::StagefrightPluginLoader(const char *libPath) {
- if (android::base::GetIntProperty("debug.stagefright.ccodec", 1) == 0) {
- ALOGD("CCodec is disabled.");
- return;
- }
- mLibHandle = dlopen(libPath, RTLD_NOW | RTLD_NODELETE);
- if (mLibHandle == nullptr) {
- ALOGD("Failed to load library: %s (%s)", libPath, dlerror());
- return;
- }
- mCreateCodec = (CodecBase::CreateCodecFunc)dlsym(mLibHandle, "CreateCodec");
- if (mCreateCodec == nullptr) {
- ALOGD("Failed to find symbol: CreateCodec (%s)", dlerror());
- }
- mCreateBuilder = (MediaCodecListBuilderBase::CreateBuilderFunc)dlsym(
- mLibHandle, "CreateBuilder");
- if (mCreateBuilder == nullptr) {
- ALOGD("Failed to find symbol: CreateBuilder (%s)", dlerror());
- }
- mCreateInputSurface = (CodecBase::CreateInputSurfaceFunc)dlsym(
- mLibHandle, "CreateInputSurface");
- if (mCreateInputSurface == nullptr) {
- ALOGD("Failed to find symbol: CreateInputSurface (%s)", dlerror());
- }
-}
-
-StagefrightPluginLoader::~StagefrightPluginLoader() {
- if (mLibHandle != nullptr) {
- ALOGV("Closing handle");
- dlclose(mLibHandle);
- }
-}
-
-CodecBase *StagefrightPluginLoader::createCodec() {
- if (mLibHandle == nullptr || mCreateCodec == nullptr) {
- ALOGD("Handle or CreateCodec symbol is null");
- return nullptr;
- }
- return mCreateCodec();
-}
-
-MediaCodecListBuilderBase *StagefrightPluginLoader::createBuilder() {
- if (mLibHandle == nullptr || mCreateBuilder == nullptr) {
- ALOGD("Handle or CreateBuilder symbol is null");
- return nullptr;
- }
- return mCreateBuilder();
-}
-
-PersistentSurface *StagefrightPluginLoader::createInputSurface() {
- if (mLibHandle == nullptr || mCreateInputSurface == nullptr) {
- ALOGD("Handle or CreateInputSurface symbol is null");
- return nullptr;
- }
- return mCreateInputSurface();
-}
-
-//static
-const std::unique_ptr<StagefrightPluginLoader> &StagefrightPluginLoader::GetCCodecInstance() {
- Mutex::Autolock _l(sMutex);
- if (!sInstance) {
- ALOGV("Loading library");
- sInstance.reset(new StagefrightPluginLoader(kCCodecPluginPath));
- }
- return sInstance;
-}
-
-} // namespace android
diff --git a/media/libstagefright/StagefrightPluginLoader.h b/media/libstagefright/StagefrightPluginLoader.h
deleted file mode 100644
index 78effbf..0000000
--- a/media/libstagefright/StagefrightPluginLoader.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright 2018, 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 STAGEFRIGHT_PLUGIN_LOADER_H_
-
-#define STAGEFRIGHT_PLUGIN_LOADER_H_
-
-#include <media/stagefright/CodecBase.h>
-#include <media/stagefright/MediaCodecListWriter.h>
-#include <media/stagefright/PersistentSurface.h>
-#include <utils/Mutex.h>
-
-namespace android {
-
-class StagefrightPluginLoader {
-public:
- static const std::unique_ptr<StagefrightPluginLoader> &GetCCodecInstance();
- ~StagefrightPluginLoader();
-
- CodecBase *createCodec();
- MediaCodecListBuilderBase *createBuilder();
- PersistentSurface *createInputSurface();
-
-private:
- explicit StagefrightPluginLoader(const char *libPath);
-
- static Mutex sMutex;
- static std::unique_ptr<StagefrightPluginLoader> sInstance;
-
- void *mLibHandle{nullptr};
- CodecBase::CreateCodecFunc mCreateCodec{nullptr};
- MediaCodecListBuilderBase::CreateBuilderFunc mCreateBuilder{nullptr};
- CodecBase::CreateInputSurfaceFunc mCreateInputSurface{nullptr};
-};
-
-} // namespace android
-
-#endif // STAGEFRIGHT_PLUGIN_LOADER_H_
diff --git a/media/libstagefright/codecs/amrnb/common/Android.bp b/media/libstagefright/codecs/amrnb/common/Android.bp
index 772ebf9..ea8b073 100644
--- a/media/libstagefright/codecs/amrnb/common/Android.bp
+++ b/media/libstagefright/codecs/amrnb/common/Android.bp
@@ -1,4 +1,4 @@
-cc_library_shared {
+cc_library {
name: "libstagefright_amrnb_common",
vendor_available: true,
diff --git a/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecTestEnvironment.h b/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecTestEnvironment.h
new file mode 100644
index 0000000..0344ac5
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecTestEnvironment.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __AMRNBDEC_TEST_ENVIRONMENT_H__
+#define __AMRNBDEC_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class AmrnbDecTestEnvironment : public ::testing::Environment {
+ public:
+ AmrnbDecTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int AmrnbDecTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P':
+ setRes(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __AMRNBDEC_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecoderTest.cpp b/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecoderTest.cpp
new file mode 100644
index 0000000..af62074
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/test/AmrnbDecoderTest.cpp
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AmrnbDecoderTest"
+#define OUTPUT_FILE "/data/local/tmp/amrnbDecode.out"
+
+#include <utils/Log.h>
+
+#include <audio_utils/sndfile.h>
+#include <stdio.h>
+
+#include "gsmamr_dec.h"
+
+#include "AmrnbDecTestEnvironment.h"
+
+// Constants for AMR-NB
+constexpr int32_t kInputBufferSize = 64;
+constexpr int32_t kSamplesPerFrame = L_FRAME;
+constexpr int32_t kBitsPerSample = 16;
+constexpr int32_t kSampleRate = 8000;
+constexpr int32_t kChannels = 1;
+constexpr int32_t kOutputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
+const int32_t kFrameSizes[] = {12, 13, 15, 17, 19, 20, 26, 31, -1, -1, -1, -1, -1, -1, -1, -1};
+
+constexpr int32_t kNumFrameReset = 150;
+
+static AmrnbDecTestEnvironment *gEnv = nullptr;
+
+class AmrnbDecoderTest : public ::testing::TestWithParam<string> {
+ public:
+ AmrnbDecoderTest() : mFpInput(nullptr) {}
+
+ ~AmrnbDecoderTest() {
+ if (mFpInput) {
+ fclose(mFpInput);
+ mFpInput = nullptr;
+ }
+ }
+
+ FILE *mFpInput;
+ SNDFILE *openOutputFile(SF_INFO *sfInfo);
+ int32_t DecodeFrames(void *amrHandle, SNDFILE *outFileHandle, int32_t frameCount = INT32_MAX);
+};
+
+SNDFILE *AmrnbDecoderTest::openOutputFile(SF_INFO *sfInfo) {
+ memset(sfInfo, 0, sizeof(SF_INFO));
+ sfInfo->channels = kChannels;
+ sfInfo->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+ sfInfo->samplerate = kSampleRate;
+ SNDFILE *outFileHandle = sf_open(OUTPUT_FILE, SFM_WRITE, sfInfo);
+ return outFileHandle;
+}
+
+int32_t AmrnbDecoderTest::DecodeFrames(void *amrHandle, SNDFILE *outFileHandle,
+ int32_t frameCount) {
+ uint8_t inputBuf[kInputBufferSize];
+ int16_t outputBuf[kOutputBufferSize];
+
+ while (frameCount > 0) {
+ uint8_t mode;
+ int32_t bytesRead = fread(&mode, 1, 1, mFpInput);
+ if (bytesRead != 1) break;
+
+ // Find frame type
+ Frame_Type_3GPP frameType = (Frame_Type_3GPP)((mode >> 3) & 0x0f);
+ int32_t frameSize = kFrameSizes[frameType];
+ if (frameSize < 0) {
+ ALOGE("Illegal frame type");
+ return -1;
+ }
+ bytesRead = fread(inputBuf, 1, frameSize, mFpInput);
+ if (bytesRead != frameSize) break;
+
+ int32_t bytesDecoded = AMRDecode(amrHandle, frameType, inputBuf, outputBuf, MIME_IETF);
+ if (bytesDecoded == -1) {
+ ALOGE("Failed to decode the input file");
+ return -1;
+ }
+
+ sf_writef_short(outFileHandle, outputBuf, kSamplesPerFrame);
+ frameCount--;
+ }
+ return 0;
+}
+
+TEST_F(AmrnbDecoderTest, CreateAmrnbDecoderTest) {
+ void *amrHandle;
+ int32_t status = GSMInitDecode(&amrHandle, (Word8 *)"AMRNBDecoder");
+ ASSERT_EQ(status, 0) << "Error creating AMR-NB decoder";
+ GSMDecodeFrameExit(&amrHandle);
+ ASSERT_EQ(amrHandle, nullptr) << "Error deleting AMR-NB decoder";
+}
+
+TEST_P(AmrnbDecoderTest, DecodeTest) {
+ string inputFile = gEnv->getRes() + GetParam();
+ mFpInput = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mFpInput, nullptr) << "Error opening input file " << inputFile;
+
+ // Open the output file.
+ SF_INFO sfInfo;
+ SNDFILE *outFileHandle = openOutputFile(&sfInfo);
+ ASSERT_NE(outFileHandle, nullptr) << "Error opening output file for writing decoded output";
+
+ void *amrHandle;
+ int32_t status = GSMInitDecode(&amrHandle, (Word8 *)"AMRNBDecoder");
+ ASSERT_EQ(status, 0) << "Error creating AMR-NB decoder";
+
+ // Decode
+ int32_t decoderErr = DecodeFrames(amrHandle, outFileHandle);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ sf_close(outFileHandle);
+ GSMDecodeFrameExit(&amrHandle);
+ ASSERT_EQ(amrHandle, nullptr) << "Error deleting AMR-NB decoder";
+}
+
+TEST_P(AmrnbDecoderTest, ResetDecodeTest) {
+ string inputFile = gEnv->getRes() + GetParam();
+ mFpInput = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mFpInput, nullptr) << "Error opening input file " << inputFile;
+
+ // Open the output file.
+ SF_INFO sfInfo;
+ SNDFILE *outFileHandle = openOutputFile(&sfInfo);
+ ASSERT_NE(outFileHandle, nullptr) << "Error opening output file for writing decoded output";
+
+ void *amrHandle;
+ int32_t status = GSMInitDecode(&amrHandle, (Word8 *)"AMRNBDecoder");
+ ASSERT_EQ(status, 0) << "Error creating AMR-NB decoder";
+
+ // Decode kNumFrameReset first
+ int32_t decoderErr = DecodeFrames(amrHandle, outFileHandle, kNumFrameReset);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ status = Speech_Decode_Frame_reset(amrHandle);
+ ASSERT_EQ(status, 0) << "Error resting AMR-NB decoder";
+
+ // Start decoding again
+ decoderErr = DecodeFrames(amrHandle, outFileHandle);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ sf_close(outFileHandle);
+ GSMDecodeFrameExit(&amrHandle);
+ ASSERT_EQ(amrHandle, nullptr) << "Error deleting AMR-NB decoder";
+}
+
+INSTANTIATE_TEST_SUITE_P(AmrnbDecoderTestAll, AmrnbDecoderTest,
+ ::testing::Values(("bbb_8000hz_1ch_8kbps_amrnb_30sec.amrnb"),
+ ("sine_amrnb_1ch_12kbps_8000hz.amrnb")));
+
+int main(int argc, char **argv) {
+ gEnv = new AmrnbDecTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/codecs/amrnb/dec/test/Android.bp b/media/libstagefright/codecs/amrnb/dec/test/Android.bp
new file mode 100644
index 0000000..7a95cfa
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/test/Android.bp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_test {
+ name: "AmrnbDecoderTest",
+ gtest: true,
+
+ srcs: [
+ "AmrnbDecoderTest.cpp",
+ ],
+
+ static_libs: [
+ "libstagefright_amrnb_common",
+ "libstagefright_amrnbdec",
+ "libaudioutils",
+ "libsndfile",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/codecs/amrnb/dec/test/README.md b/media/libstagefright/codecs/amrnb/dec/test/README.md
new file mode 100644
index 0000000..62e13ae
--- /dev/null
+++ b/media/libstagefright/codecs/amrnb/dec/test/README.md
@@ -0,0 +1,34 @@
+## Media Testing ##
+---
+#### AMR-NB Decoder :
+The Amr-Nb Decoder Test Suite validates the amrnb decoder available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m AmrnbDecoderTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/AmrnbDecoderTest/AmrnbDecoderTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/AmrnbDecoderTest/AmrnbDecoderTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/13cM4tAaVFrmr-zGFqaAzFBbKs75pnm9b). Push these files into device for testing.
+Download amr-nb folder and push all the files in this folder to /data/local/tmp/ on the device.
+```
+adb push amr-nb/. /data/local/tmp/
+```
+
+usage: AmrnbDecoderTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/AmrnbDecoderTest -P /data/local/tmp/
+```
diff --git a/media/libstagefright/codecs/amrwb/test/AmrwbDecTestEnvironment.h b/media/libstagefright/codecs/amrwb/test/AmrwbDecTestEnvironment.h
new file mode 100644
index 0000000..84d337d
--- /dev/null
+++ b/media/libstagefright/codecs/amrwb/test/AmrwbDecTestEnvironment.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __AMRWBDEC_TEST_ENVIRONMENT_H__
+#define __AMRWBDEC_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class AmrwbDecTestEnvironment : public ::testing::Environment {
+ public:
+ AmrwbDecTestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int AmrwbDecTestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"res", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P':
+ setRes(optarg);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __AMRWBDEC_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp b/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp
new file mode 100644
index 0000000..2aad81b
--- /dev/null
+++ b/media/libstagefright/codecs/amrwb/test/AmrwbDecoderTest.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "AmrwbDecoderTest"
+#define OUTPUT_FILE "/data/local/tmp/amrwbDecode.out"
+
+#include <utils/Log.h>
+
+#include <audio_utils/sndfile.h>
+#include <stdio.h>
+
+#include "pvamrwbdecoder.h"
+#include "pvamrwbdecoder_api.h"
+
+#include "AmrwbDecTestEnvironment.h"
+
+// Constants for AMR-WB.
+constexpr int32_t kInputBufferSize = 64;
+constexpr int32_t kSamplesPerFrame = 320;
+constexpr int32_t kBitsPerSample = 16;
+constexpr int32_t kSampleRate = 16000;
+constexpr int32_t kChannels = 1;
+constexpr int32_t kMaxSourceDataUnitSize = KAMRWB_NB_BITS_MAX * sizeof(int16_t);
+constexpr int32_t kOutputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
+const int32_t kFrameSizes[16] = {17, 23, 32, 36, 40, 46, 50, 58, 60, -1, -1, -1, -1, -1, -1, -1};
+constexpr int32_t kNumFrameReset = 150;
+
+constexpr int32_t kMaxCount = 10;
+
+static AmrwbDecTestEnvironment *gEnv = nullptr;
+
+class AmrwbDecoderTest : public ::testing::TestWithParam<string> {
+ public:
+ AmrwbDecoderTest() : mFpInput(nullptr) {}
+
+ ~AmrwbDecoderTest() {
+ if (mFpInput) {
+ fclose(mFpInput);
+ mFpInput = nullptr;
+ }
+ }
+
+ FILE *mFpInput;
+ int32_t DecodeFrames(int16_t *decoderCookie, void *decoderBuf, SNDFILE *outFileHandle,
+ int32_t frameCount = INT32_MAX);
+ SNDFILE *openOutputFile(SF_INFO *sfInfo);
+};
+
+SNDFILE *AmrwbDecoderTest::openOutputFile(SF_INFO *sfInfo) {
+ memset(sfInfo, 0, sizeof(SF_INFO));
+ sfInfo->channels = kChannels;
+ sfInfo->format = SF_FORMAT_WAV | SF_FORMAT_PCM_16;
+ sfInfo->samplerate = kSampleRate;
+ SNDFILE *outFileHandle = sf_open(OUTPUT_FILE, SFM_WRITE, sfInfo);
+ return outFileHandle;
+}
+
+int32_t AmrwbDecoderTest::DecodeFrames(int16_t *decoderCookie, void *decoderBuf,
+ SNDFILE *outFileHandle, int32_t frameCount) {
+ uint8_t inputBuf[kInputBufferSize];
+ int16_t inputSampleBuf[kMaxSourceDataUnitSize];
+ int16_t outputBuf[kOutputBufferSize];
+
+ while (frameCount > 0) {
+ uint8_t modeByte;
+ int32_t bytesRead = fread(&modeByte, 1, 1, mFpInput);
+ if (bytesRead != 1) break;
+
+ int16 mode = ((modeByte >> 3) & 0x0f);
+ if (mode >= 9) {
+ // Produce silence for comfort noise, speech lost and no data.
+ int32_t outputBufferSize = kSamplesPerFrame * kBitsPerSample / 8;
+ memset(outputBuf, 0, outputBufferSize);
+ } else {
+ // Read rest of the frame.
+ int32_t frameSize = kFrameSizes[mode];
+ // AMR-WB file format cannot have mode 10, 11, 12 and 13.
+ if (frameSize < 0) {
+ ALOGE("Illegal frame mode");
+ return -1;
+ }
+ bytesRead = fread(inputBuf, 1, frameSize, mFpInput);
+ if (bytesRead != frameSize) break;
+
+ int16 frameMode = mode;
+ int16 frameType;
+ RX_State_wb rx_state;
+ mime_unsorting(inputBuf, inputSampleBuf, &frameType, &frameMode, 1, &rx_state);
+
+ int16_t numSamplesOutput;
+ pvDecoder_AmrWb(frameMode, inputSampleBuf, outputBuf, &numSamplesOutput, decoderBuf,
+ frameType, decoderCookie);
+ if (numSamplesOutput != kSamplesPerFrame) {
+ ALOGE("Failed to decode the input file");
+ return -1;
+ }
+ for (int count = 0; count < kSamplesPerFrame; ++count) {
+ /* Delete the 2 LSBs (14-bit output) */
+ outputBuf[count] &= 0xfffc;
+ }
+ }
+ sf_writef_short(outFileHandle, outputBuf, kSamplesPerFrame / kChannels);
+ frameCount--;
+ }
+ return 0;
+}
+
+TEST_F(AmrwbDecoderTest, MultiCreateAmrwbDecoderTest) {
+ uint32_t memRequirements = pvDecoder_AmrWbMemRequirements();
+ void *decoderBuf = malloc(memRequirements);
+ ASSERT_NE(decoderBuf, nullptr)
+ << "Failed to allocate decoder memory of size " << memRequirements;
+
+ // Create AMR-WB decoder instance.
+ void *amrHandle = nullptr;
+ int16_t *decoderCookie;
+ for (int count = 0; count < kMaxCount; count++) {
+ pvDecoder_AmrWb_Init(&amrHandle, decoderBuf, &decoderCookie);
+ ASSERT_NE(amrHandle, nullptr) << "Failed to initialize decoder";
+ ALOGV("Decoder created successfully");
+ }
+ if (decoderBuf) {
+ free(decoderBuf);
+ decoderBuf = nullptr;
+ }
+}
+
+TEST_P(AmrwbDecoderTest, DecodeTest) {
+ uint32_t memRequirements = pvDecoder_AmrWbMemRequirements();
+ void *decoderBuf = malloc(memRequirements);
+ ASSERT_NE(decoderBuf, nullptr)
+ << "Failed to allocate decoder memory of size " << memRequirements;
+
+ void *amrHandle = nullptr;
+ int16_t *decoderCookie;
+ pvDecoder_AmrWb_Init(&amrHandle, decoderBuf, &decoderCookie);
+ ASSERT_NE(amrHandle, nullptr) << "Failed to initialize decoder";
+
+ string inputFile = gEnv->getRes() + GetParam();
+ mFpInput = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mFpInput, nullptr) << "Error opening input file " << inputFile;
+
+ // Open the output file.
+ SF_INFO sfInfo;
+ SNDFILE *outFileHandle = openOutputFile(&sfInfo);
+ ASSERT_NE(outFileHandle, nullptr) << "Error opening output file for writing decoded output";
+
+ int32_t decoderErr = DecodeFrames(decoderCookie, decoderBuf, outFileHandle);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ sf_close(outFileHandle);
+ if (decoderBuf) {
+ free(decoderBuf);
+ decoderBuf = nullptr;
+ }
+}
+
+TEST_P(AmrwbDecoderTest, ResetDecoderTest) {
+ uint32_t memRequirements = pvDecoder_AmrWbMemRequirements();
+ void *decoderBuf = malloc(memRequirements);
+ ASSERT_NE(decoderBuf, nullptr)
+ << "Failed to allocate decoder memory of size " << memRequirements;
+
+ void *amrHandle = nullptr;
+ int16_t *decoderCookie;
+ pvDecoder_AmrWb_Init(&amrHandle, decoderBuf, &decoderCookie);
+ ASSERT_NE(amrHandle, nullptr) << "Failed to initialize decoder";
+
+ string inputFile = gEnv->getRes() + GetParam();
+ mFpInput = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(mFpInput, nullptr) << "Error opening input file " << inputFile;
+
+ // Open the output file.
+ SF_INFO sfInfo;
+ SNDFILE *outFileHandle = openOutputFile(&sfInfo);
+ ASSERT_NE(outFileHandle, nullptr) << "Error opening output file for writing decoded output";
+
+ // Decode 150 frames first
+ int32_t decoderErr = DecodeFrames(decoderCookie, decoderBuf, outFileHandle, kNumFrameReset);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ // Reset Decoder
+ pvDecoder_AmrWb_Reset(decoderBuf, 1);
+
+ // Start decoding again
+ decoderErr = DecodeFrames(decoderCookie, decoderBuf, outFileHandle);
+ ASSERT_EQ(decoderErr, 0) << "DecodeFrames returned error";
+
+ sf_close(outFileHandle);
+ if (decoderBuf) {
+ free(decoderBuf);
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(AmrwbDecoderTestAll, AmrwbDecoderTest,
+ ::testing::Values(("bbb_amrwb_1ch_14kbps_16000hz.amrwb"),
+ ("bbb_16000hz_1ch_9kbps_amrwb_30sec.amrwb")));
+
+int main(int argc, char **argv) {
+ gEnv = new AmrwbDecTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/codecs/amrwb/test/Android.bp b/media/libstagefright/codecs/amrwb/test/Android.bp
new file mode 100644
index 0000000..968215a
--- /dev/null
+++ b/media/libstagefright/codecs/amrwb/test/Android.bp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_test {
+ name: "AmrwbDecoderTest",
+ gtest: true,
+
+ srcs: [
+ "AmrwbDecoderTest.cpp",
+ ],
+
+ static_libs: [
+ "libstagefright_amrwbdec",
+ "libsndfile",
+ "libaudioutils",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/codecs/amrwb/test/README.md b/media/libstagefright/codecs/amrwb/test/README.md
new file mode 100644
index 0000000..502a20d
--- /dev/null
+++ b/media/libstagefright/codecs/amrwb/test/README.md
@@ -0,0 +1,34 @@
+## Media Testing ##
+---
+#### AMR-WB Decoder :
+The Amr-Wb Decoder Test Suite validates the amrwb decoder available in libstagefright.
+
+Run the following steps to build the test suite:
+```
+m AmrwbDecoderTest
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/AmrwbDecoderTest/AmrwbDecoderTest /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/AmrwbDecoderTest/AmrwbDecoderTest /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/13cM4tAaVFrmr-zGFqaAzFBbKs75pnm9b). Push these files into device for testing.
+Download amr-wb folder and push all the files in this folder to /data/local/tmp/ on the device.
+```
+adb push amr-wb/. /data/local/tmp/
+```
+
+usage: AmrwbDecoderTest -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/AmrwbDecoderTest -P /data/local/tmp/
+```
diff --git a/media/libstagefright/colorconversion/Android.bp b/media/libstagefright/colorconversion/Android.bp
index ba57497..6b08b08 100644
--- a/media/libstagefright/colorconversion/Android.bp
+++ b/media/libstagefright/colorconversion/Android.bp
@@ -15,6 +15,11 @@
"libnativewindow",
],
+ header_libs: [
+ "libstagefright_headers",
+ "libstagefright_foundation_headers",
+ ],
+
static_libs: ["libyuv_static"],
cflags: ["-Werror"],
diff --git a/media/libstagefright/foundation/Android.bp b/media/libstagefright/foundation/Android.bp
index 5485f6d..7398690 100644
--- a/media/libstagefright/foundation/Android.bp
+++ b/media/libstagefright/foundation/Android.bp
@@ -2,6 +2,7 @@
name: "libstagefright_foundation_headers",
export_include_dirs: ["include"],
vendor_available: true,
+ host_supported: true,
}
cc_defaults {
diff --git a/media/libstagefright/id3/test/Android.bp b/media/libstagefright/id3/test/Android.bp
new file mode 100644
index 0000000..9d26eec
--- /dev/null
+++ b/media/libstagefright/id3/test/Android.bp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+cc_test {
+ name: "ID3Test",
+ gtest: true,
+
+ srcs: ["ID3Test.cpp"],
+
+ static_libs: [
+ "libdatasource",
+ "libstagefright_id3",
+ "libstagefright",
+ "libstagefright_foundation",
+ ],
+
+ shared_libs: [
+ "libutils",
+ "liblog",
+ "libbinder",
+ ],
+
+ cflags: [
+ "-Werror",
+ "-Wall",
+ ],
+
+ sanitize: {
+ cfi: true,
+ misc_undefined: [
+ "unsigned-integer-overflow",
+ "signed-integer-overflow",
+ ],
+ },
+}
diff --git a/media/libstagefright/id3/test/ID3Test.cpp b/media/libstagefright/id3/test/ID3Test.cpp
new file mode 100644
index 0000000..a8f1470
--- /dev/null
+++ b/media/libstagefright/id3/test/ID3Test.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "ID3Test"
+#include <utils/Log.h>
+
+#include <ctype.h>
+#include <string>
+#include <sys/stat.h>
+#include <datasource/FileSource.h>
+
+#include <media/stagefright/foundation/hexdump.h>
+#include <ID3.h>
+
+#include "ID3TestEnvironment.h"
+
+using namespace android;
+
+static ID3TestEnvironment *gEnv = nullptr;
+
+class ID3tagTest : public ::testing::TestWithParam<string> {};
+class ID3versionTest : public ::testing::TestWithParam<pair<string, int>> {};
+class ID3textTagTest : public ::testing::TestWithParam<pair<string, int>> {};
+class ID3albumArtTest : public ::testing::TestWithParam<pair<string, bool>> {};
+class ID3multiAlbumArtTest : public ::testing::TestWithParam<pair<string, int>> {};
+
+TEST_P(ID3tagTest, TagTest) {
+ string path = gEnv->getRes() + GetParam();
+ sp<FileSource> file = new FileSource(path.c_str());
+ ASSERT_EQ(file->initCheck(), (status_t)OK) << "File initialization failed! \n";
+ ID3 tag(file.get());
+ ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
+
+ ID3::Iterator it(tag, nullptr);
+ while (!it.done()) {
+ String8 id;
+ it.getID(&id);
+ ASSERT_GT(id.length(), 0) << "No ID tag found! \n";
+ ALOGV("Found ID tag: %s\n", String8(id).c_str());
+ it.next();
+ }
+}
+
+TEST_P(ID3versionTest, VersionTest) {
+ int versionNumber = GetParam().second;
+ string path = gEnv->getRes() + GetParam().first;
+ sp<android::FileSource> file = new FileSource(path.c_str());
+ ASSERT_EQ(file->initCheck(), (status_t)OK) << "File initialization failed! \n";
+
+ ID3 tag(file.get());
+ ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
+ ASSERT_TRUE(tag.version() >= versionNumber)
+ << "Expected version: " << tag.version() << " Found version: " << versionNumber;
+}
+
+TEST_P(ID3textTagTest, TextTagTest) {
+ int numTextFrames = GetParam().second;
+ string path = gEnv->getRes() + GetParam().first;
+ sp<android::FileSource> file = new FileSource(path.c_str());
+ ASSERT_EQ(file->initCheck(), (status_t)OK) << "File initialization failed! \n";
+
+ ID3 tag(file.get());
+ ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
+ int countTextFrames = 0;
+ ID3::Iterator it(tag, nullptr);
+ while (!it.done()) {
+ String8 id;
+ it.getID(&id);
+ ASSERT_GT(id.length(), 0);
+ if (id[0] == 'T') {
+ String8 text;
+ countTextFrames++;
+ it.getString(&text);
+ ALOGV("Found text frame %s : %s \n", id.string(), text.string());
+ }
+ it.next();
+ }
+ ASSERT_EQ(countTextFrames, numTextFrames)
+ << "Expected " << numTextFrames << " text frames, found " << countTextFrames;
+}
+
+TEST_P(ID3albumArtTest, AlbumArtTest) {
+ bool albumArtPresent = GetParam().second;
+ string path = gEnv->getRes() + GetParam().first;
+ sp<android::FileSource> file = new FileSource(path.c_str());
+ ASSERT_EQ(file->initCheck(), (status_t)OK) << "File initialization failed! \n";
+
+ ID3 tag(file.get());
+ ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
+ size_t dataSize;
+ String8 mime;
+ const void *data = tag.getAlbumArt(&dataSize, &mime);
+
+ if (albumArtPresent) {
+ if (data) {
+ ALOGV("Found album art: size = %zu mime = %s \n", dataSize, mime.string());
+ }
+ ASSERT_NE(data, nullptr) << "Expected album art, found none!" << path;
+ } else {
+ ASSERT_EQ(data, nullptr) << "Found album art when expected none!";
+ }
+#if (LOG_NDEBUG == 0)
+ hexdump(data, dataSize > 128 ? 128 : dataSize);
+#endif
+}
+
+TEST_P(ID3multiAlbumArtTest, MultiAlbumArtTest) {
+ int numAlbumArt = GetParam().second;
+ string path = gEnv->getRes() + GetParam().first;
+ sp<android::FileSource> file = new FileSource(path.c_str());
+ ASSERT_EQ(file->initCheck(), (status_t)OK) << "File initialization failed! \n";
+
+ ID3 tag(file.get());
+ ASSERT_TRUE(tag.isValid()) << "No valid ID3 tag found for " << path.c_str() << "\n";
+ int count = 0;
+ ID3::Iterator it(tag, nullptr);
+ while (!it.done()) {
+ String8 id;
+ it.getID(&id);
+ ASSERT_GT(id.length(), 0);
+ // Check if the tag is an "APIC/PIC" tag.
+ if (String8(id) == "APIC" || String8(id) == "PIC") {
+ count++;
+ size_t dataSize;
+ String8 mime;
+ const void *data = tag.getAlbumArt(&dataSize, &mime);
+ if (data) {
+ ALOGV("Found album art: size = %zu mime = %s \n", dataSize, mime.string());
+#if (LOG_NDEBUG == 0)
+ hexdump(data, dataSize > 128 ? 128 : dataSize);
+#endif
+ }
+ ASSERT_NE(data, nullptr) << "Expected album art, found none!" << path;
+ }
+ it.next();
+ }
+ ASSERT_EQ(count, numAlbumArt) << "Found " << count << " album arts, expected " << numAlbumArt
+ << " album arts! \n";
+}
+
+INSTANTIATE_TEST_SUITE_P(id3TestAll, ID3tagTest,
+ ::testing::Values("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_5mins.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3",
+ "bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3"));
+
+INSTANTIATE_TEST_SUITE_P(
+ id3TestAll, ID3versionTest,
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 4),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3", 4)));
+
+INSTANTIATE_TEST_SUITE_P(
+ id3TestAll, ID3textTagTest,
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_moreTextFrames.mp3", 5)));
+
+INSTANTIATE_TEST_SUITE_P(
+ id3TestAll, ID3albumArtTest,
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", false),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", true),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", true),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", false),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", true),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", true),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", true)));
+
+INSTANTIATE_TEST_SUITE_P(
+ id3TestAll, ID3multiAlbumArtTest,
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", 0),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins.mp3", 0),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_1_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_1_image.mp3", 1),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec_2_image.mp3", 2),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_2_image.mp3", 2),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_5mins_largeSize.mp3", 3)));
+
+int main(int argc, char **argv) {
+ gEnv = new ID3TestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGI("ID3 Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/libstagefright/id3/test/ID3TestEnvironment.h b/media/libstagefright/id3/test/ID3TestEnvironment.h
new file mode 100644
index 0000000..2229718
--- /dev/null
+++ b/media/libstagefright/id3/test/ID3TestEnvironment.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __ID3_TEST_ENVIRONMENT_H__
+#define __ID3_TEST_ENVIRONMENT_H__
+
+#include <gtest/gtest.h>
+
+#include <getopt.h>
+
+using namespace std;
+
+class ID3TestEnvironment : public::testing::Environment {
+ public:
+ ID3TestEnvironment() : res("/data/local/tmp/") {}
+
+ // Parses the command line arguments
+ int initFromOptions(int argc, char **argv);
+
+ void setRes(const char *_res) { res = _res; }
+
+ const string getRes() const { return res; }
+
+ private:
+ string res;
+};
+
+int ID3TestEnvironment::initFromOptions(int argc, char **argv) {
+ static struct option options[] = {{"path", required_argument, 0, 'P'}, {0, 0, 0, 0}};
+
+ while (true) {
+ int index = 0;
+ int c = getopt_long(argc, argv, "P:", options, &index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'P': {
+ setRes(optarg);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (optind < argc) {
+ fprintf(stderr,
+ "unrecognized option: %s\n\n"
+ "usage: %s <gtest options> <test options>\n\n"
+ "test options are:\n\n"
+ "-P, --path: Resource files directory location\n",
+ argv[optind ?: 1], argv[0]);
+ return 2;
+ }
+ return 0;
+}
+
+#endif // __ID3_TEST_ENVIRONMENT_H__
diff --git a/media/libstagefright/id3/test/README.md b/media/libstagefright/id3/test/README.md
new file mode 100644
index 0000000..72f730c
--- /dev/null
+++ b/media/libstagefright/id3/test/README.md
@@ -0,0 +1,34 @@
+## Media Testing ##
+---
+#### ID3 Test :
+The ID3 Test Suite validates the ID3 parser available in libstagefright.
+
+Run the following command in the id3 folder to build the test suite:
+```
+m ID3Test
+```
+
+The 32-bit binaries will be created in the following path : ${OUT}/data/nativetest/
+
+The 64-bit binaries will be created in the following path : ${OUT}/data/nativetest64/
+
+To test 64-bit binary push binaries from nativetest64.
+```
+adb push ${OUT}/data/nativetest64/ID3Test/ID3Test /data/local/tmp/
+```
+
+To test 32-bit binary push binaries from nativetest.
+```
+adb push ${OUT}/data/nativetest/ID3Test/ID3Test /data/local/tmp/
+```
+
+The resource file for the tests is taken from [here](https://drive.google.com/drive/folders/1pt5HFVSysbqfyqY1sVJ9MTupZKCdqjYZ). Push these files into device for testing.
+Download ID3 folder and push all the files in this folder to /data/local/tmp/ID3 on the device.
+```
+adb push ID3/. /data/local/tmp/ID3
+```
+
+usage: ID3Test -P \<path_to_folder\>
+```
+adb shell /data/local/tmp/ID3Test -P /data/local/tmp/ID3/
+```
diff --git a/media/libstagefright/include/FrameDecoder.h b/media/libstagefright/include/FrameDecoder.h
index 353c957..19ae0e3 100644
--- a/media/libstagefright/include/FrameDecoder.h
+++ b/media/libstagefright/include/FrameDecoder.h
@@ -22,7 +22,7 @@
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/foundation/ABase.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/openmax/OMX_Video.h>
#include <ui/GraphicTypes.h>
diff --git a/media/libstagefright/include/media/stagefright/AACWriter.h b/media/libstagefright/include/media/stagefright/AACWriter.h
index 7c63ddd..2671138 100644
--- a/media/libstagefright/include/media/stagefright/AACWriter.h
+++ b/media/libstagefright/include/media/stagefright/AACWriter.h
@@ -17,7 +17,7 @@
#ifndef AAC_WRITER_H_
#define AAC_WRITER_H_
-#include "foundation/ABase.h"
+#include "media/stagefright/foundation/ABase.h"
#include <media/stagefright/MediaWriter.h>
#include <utils/threads.h>
diff --git a/media/libstagefright/include/media/stagefright/AudioSource.h b/media/libstagefright/include/media/stagefright/AudioSource.h
index ad4ab47..451aa57 100644
--- a/media/libstagefright/include/media/stagefright/AudioSource.h
+++ b/media/libstagefright/include/media/stagefright/AudioSource.h
@@ -20,7 +20,7 @@
#include <media/AudioRecord.h>
#include <media/AudioSystem.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/MicrophoneInfo.h>
#include <media/stagefright/MediaBuffer.h>
#include <utils/List.h>
diff --git a/media/libstagefright/include/media/stagefright/CallbackMediaSource.h b/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
index 33453fa..d2adbb9 100644
--- a/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
+++ b/media/libstagefright/include/media/stagefright/CallbackMediaSource.h
@@ -17,7 +17,7 @@
#ifndef CALLBACK_MEDIA_SOURCE_H_
#define CALLBACK_MEDIA_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
namespace android {
diff --git a/media/libstagefright/include/media/stagefright/CameraSource.h b/media/libstagefright/include/media/stagefright/CameraSource.h
index 3037b72..6f0d3b5 100644
--- a/media/libstagefright/include/media/stagefright/CameraSource.h
+++ b/media/libstagefright/include/media/stagefright/CameraSource.h
@@ -19,7 +19,7 @@
#define CAMERA_SOURCE_H_
#include <deque>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MediaBuffer.h>
#include <camera/android/hardware/ICamera.h>
#include <camera/ICameraRecordingProxy.h>
diff --git a/media/libstagefright/include/media/stagefright/InterfaceUtils.h b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
index 671f2ff..92ef543 100644
--- a/media/libstagefright/include/media/stagefright/InterfaceUtils.h
+++ b/media/libstagefright/include/media/stagefright/InterfaceUtils.h
@@ -19,7 +19,7 @@
#include <utils/RefBase.h>
#include <media/stagefright/RemoteMediaExtractor.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <android/IMediaExtractor.h>
#include <media/IMediaSource.h>
diff --git a/media/libstagefright/include/media/stagefright/JPEGSource.h b/media/libstagefright/include/media/stagefright/JPEGSource.h
index 8ab3d11..53cb344 100644
--- a/media/libstagefright/include/media/stagefright/JPEGSource.h
+++ b/media/libstagefright/include/media/stagefright/JPEGSource.h
@@ -18,7 +18,7 @@
#define JPEG_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
namespace android {
diff --git a/media/libstagefright/include/media/stagefright/MPEG4Writer.h b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
index 6f19023..34a7d55 100644
--- a/media/libstagefright/include/media/stagefright/MPEG4Writer.h
+++ b/media/libstagefright/include/media/stagefright/MPEG4Writer.h
@@ -23,8 +23,10 @@
#include <media/stagefright/MediaWriter.h>
#include <utils/List.h>
#include <utils/threads.h>
+#include <map>
#include <media/stagefright/foundation/AHandlerReflector.h>
#include <media/stagefright/foundation/ALooper.h>
+#include <mutex>
namespace android {
@@ -58,6 +60,10 @@
void writeFourcc(const char *fourcc);
void write(const void *data, size_t size);
inline size_t write(const void *ptr, size_t size, size_t nmemb);
+ // Write to file system by calling ::write() or post error message to looper on failure.
+ void writeOrPostError(int fd, const void *buf, size_t count);
+ // Seek in the file by calling ::lseek64() or post error message to looper on failure.
+ void seekOrPostError(int fd, off64_t offset, int whence);
void endBox();
uint32_t interleaveDuration() const { return mInterleaveDurationUs; }
status_t setInterleaveDuration(uint32_t duration);
@@ -80,6 +86,8 @@
enum {
kWhatSwitch = 'swch',
+ kWhatHandleIOError = 'ioer',
+ kWhatHandleFallocateError = 'faer'
};
int mFd;
@@ -88,14 +96,15 @@
status_t mInitCheck;
bool mIsRealTimeRecording;
bool mUse4ByteNalLength;
- bool mUse32BitOffset;
bool mIsFileSizeLimitExplicitlyRequested;
bool mPaused;
bool mStarted; // Writer thread + track threads started successfully
bool mWriterThreadStarted; // Only writer thread started successfully
bool mSendNotify;
off64_t mOffset;
- off_t mMdatOffset;
+ off64_t mPreAllocateFileEndOffset; //End of file offset during preallocation.
+ off64_t mMdatOffset;
+ off64_t mMdatEndOffset; // End offset of mdat atom.
uint8_t *mInMemoryCache;
off64_t mInMemoryCacheOffset;
off64_t mInMemoryCacheSize;
@@ -106,17 +115,24 @@
uint32_t mInterleaveDurationUs;
int32_t mTimeScale;
int64_t mStartTimestampUs;
- int32_t mStartTimeOffsetBFramesUs; // Start time offset when B Frames are present
+ int32_t mStartTimeOffsetBFramesUs; // Longest offset needed for reordering tracks with B Frames
int mLatitudex10000;
int mLongitudex10000;
bool mAreGeoTagsAvailable;
int32_t mStartTimeOffsetMs;
bool mSwitchPending;
+ bool mWriteSeekErr;
+ bool mFallocateErr;
+ bool mPreAllocationEnabled;
sp<ALooper> mLooper;
sp<AHandlerReflector<MPEG4Writer> > mReflector;
Mutex mLock;
+ std::mutex mResetMutex;
+ std::mutex mFallocMutex;
+ bool mPreAllocFirstTime; // Pre-allocate space for file and track headers only once per file.
+ uint64_t mPrevAllTracksTotalMetaDataSizeEstimate;
List<Track *> mTracks;
@@ -200,19 +216,23 @@
} ItemProperty;
bool mHasFileLevelMeta;
+ uint64_t mFileLevelMetaDataSize;
bool mHasMoovBox;
uint32_t mPrimaryItemId;
uint32_t mAssociationEntryCount;
uint32_t mNumGrids;
+ uint16_t mNextItemId;
bool mHasRefs;
- Vector<ItemInfo> mItems;
+ std::map<uint32_t, ItemInfo> mItems;
Vector<ItemProperty> mProperties;
// Writer thread handling
status_t startWriterThread();
- void stopWriterThread();
+ status_t stopWriterThread();
static void *ThreadWrapper(void *me);
void threadFunc();
+ void setupAndStartLooper();
+ void stopAndReleaseLooper();
// Buffer a single chunk to be written out later.
void bufferChunk(const Chunk& chunk);
@@ -259,11 +279,11 @@
void addLengthPrefixedSample_l(MediaBuffer *buffer);
void addMultipleLengthPrefixedSamples_l(MediaBuffer *buffer);
uint16_t addProperty_l(const ItemProperty &);
+ status_t reserveItemId_l(size_t numItems, uint16_t *itemIdBase);
uint16_t addItem_l(const ItemInfo &);
void addRefs_l(uint16_t itemId, const ItemRefs &);
bool exceedsFileSizeLimit();
- bool use32BitFileOffset() const;
bool exceedsFileDurationLimit();
bool approachingFileSizeLimit();
bool isFileStreamable() const;
@@ -284,6 +304,16 @@
void writeIlst();
void writeMoovLevelMetaBox();
+ /*
+ * Allocate space needed for MOOV atom in advance and maintain just enough before write
+ * of any data. Stop writing and save MOOV atom if there was any error.
+ */
+ bool preAllocate(uint64_t wantSize);
+ /*
+ * Truncate file as per the size used for meta data and actual data in a session.
+ */
+ bool truncatePreAllocation();
+
// HEIF writing
void writeIlocBox();
void writeInfeBox(uint16_t itemId, const char *type, uint32_t flags);
diff --git a/media/libstagefright/include/media/stagefright/MediaAdapter.h b/media/libstagefright/include/media/stagefright/MediaAdapter.h
index 589c827..177a9e9 100644
--- a/media/libstagefright/include/media/stagefright/MediaAdapter.h
+++ b/media/libstagefright/include/media/stagefright/MediaAdapter.h
@@ -17,7 +17,7 @@
#ifndef MEDIA_ADAPTER_H
#define MEDIA_ADAPTER_H
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/MediaBuffer.h>
#include <media/stagefright/MetaData.h>
diff --git a/media/libstagefright/include/media/stagefright/MediaCodec.h b/media/libstagefright/include/media/stagefright/MediaCodec.h
index 78cb01c..5c5c23a 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodec.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodec.h
@@ -313,6 +313,7 @@
void updateMediametrics();
void flushMediametrics();
void updateEphemeralMediametrics(mediametrics_handle_t item);
+ void updateLowLatency(const sp<AMessage> &msg);
sp<AMessage> mOutputFormat;
sp<AMessage> mInputFormat;
@@ -440,6 +441,12 @@
std::deque<BufferFlightTiming_t> mBuffersInFlight;
Mutex mLatencyLock;
int64_t mLatencyUnknown; // buffers for which we couldn't calculate latency
+ int64_t mNumLowLatencyEnables; // how many times low latency mode is enabled
+ int64_t mNumLowLatencyDisables; // how many times low latency mode is disabled
+ bool mIsLowLatencyModeOn; // is low latency mode on currently
+ int64_t mIndexOfFirstFrameWhenLowLatencyOn; // index of the first frame queued
+ // when low latency is on
+ int64_t mInputBufferCounter; // number of input buffers queued since last reset/flush
sp<BatteryChecker> mBatteryChecker;
diff --git a/media/libstagefright/include/media/stagefright/MediaCodecSource.h b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
index a68cc19..2f98af1 100644
--- a/media/libstagefright/include/media/stagefright/MediaCodecSource.h
+++ b/media/libstagefright/include/media/stagefright/MediaCodecSource.h
@@ -17,7 +17,7 @@
#ifndef MediaCodecSource_H_
#define MediaCodecSource_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AHandlerReflector.h>
#include <media/stagefright/foundation/Mutexed.h>
diff --git a/media/libstagefright/include/media/stagefright/MediaMuxer.h b/media/libstagefright/include/media/stagefright/MediaMuxer.h
index 69d6cde..7c75f74 100644
--- a/media/libstagefright/include/media/stagefright/MediaMuxer.h
+++ b/media/libstagefright/include/media/stagefright/MediaMuxer.h
@@ -22,7 +22,7 @@
#include <utils/Vector.h>
#include <utils/threads.h>
-#include "foundation/ABase.h"
+#include "media/stagefright/foundation/ABase.h"
namespace android {
@@ -117,21 +117,24 @@
status_t writeSampleData(const sp<ABuffer> &buffer, size_t trackIndex,
int64_t timeUs, uint32_t flags) ;
+ void notify(int msg, int ext1, int ext2);
+
private:
const OutputFormat mFormat;
sp<MediaWriter> mWriter;
Vector< sp<MediaAdapter> > mTrackList; // Each track has its MediaAdapter.
sp<MetaData> mFileMeta; // Metadata for the whole file.
-
Mutex mMuxerLock;
enum State {
UNINITIALIZED,
INITIALIZED,
STARTED,
- STOPPED
+ STOPPED,
+ ERROR
};
State mState;
+ status_t mError;
DISALLOW_EVIL_CONSTRUCTORS(MediaMuxer);
};
diff --git a/media/libstagefright/include/media/stagefright/MediaWriter.h b/media/libstagefright/include/media/stagefright/MediaWriter.h
index 972ae1d..08e54b3 100644
--- a/media/libstagefright/include/media/stagefright/MediaWriter.h
+++ b/media/libstagefright/include/media/stagefright/MediaWriter.h
@@ -19,8 +19,9 @@
#define MEDIA_WRITER_H_
#include <utils/RefBase.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/IMediaRecorderClient.h>
+#include <media/stagefright/MediaMuxer.h>
namespace android {
@@ -36,7 +37,7 @@
virtual status_t stop() = 0;
virtual status_t pause() = 0;
virtual status_t setCaptureRate(float /* captureFps */) {
- ALOGW("setCaptureRate unsupported");
+ ALOG(LOG_WARN, "MediaWriter", "setCaptureRate unsupported");
return ERROR_UNSUPPORTED;
}
@@ -45,6 +46,7 @@
virtual void setListener(const sp<IMediaRecorderClient>& listener) {
mListener = listener;
}
+ virtual void setMuxerListener(const wp<MediaMuxer>& muxer) { mMuxer = muxer; }
virtual status_t dump(int /*fd*/, const Vector<String16>& /*args*/) {
return OK;
@@ -59,11 +61,17 @@
int64_t mMaxFileSizeLimitBytes;
int64_t mMaxFileDurationLimitUs;
sp<IMediaRecorderClient> mListener;
+ wp<MediaMuxer> mMuxer;
void notify(int msg, int ext1, int ext2) {
- if (mListener != NULL) {
+ ALOG(LOG_VERBOSE, "MediaWriter", "notify msg:%d, ext1:%d, ext2:%d", msg, ext1, ext2);
+ if (mListener != nullptr) {
mListener->notify(msg, ext1, ext2);
}
+ sp<MediaMuxer> muxer = mMuxer.promote();
+ if (muxer != nullptr) {
+ muxer->notify(msg, ext1, ext2);
+ }
}
private:
MediaWriter(const MediaWriter &);
diff --git a/media/libstagefright/include/media/stagefright/MetaDataBase.h b/media/libstagefright/include/media/stagefright/MetaDataBase.h
index e17093a..b9cd18a 100644
--- a/media/libstagefright/include/media/stagefright/MetaDataBase.h
+++ b/media/libstagefright/include/media/stagefright/MetaDataBase.h
@@ -113,8 +113,6 @@
kKeyVideoProfile = 'vprf', // int32_t
kKeyVideoLevel = 'vlev', // int32_t
- // Set this key to enable authoring files in 64-bit offset
- kKey64BitFileOffset = 'fobt', // int32_t (bool)
kKey2ByteNalLength = '2NAL', // int32_t (bool)
// Identify the file output format for authoring
diff --git a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
index 98e4b22..227cead 100644
--- a/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/NuMediaExtractor.h
@@ -22,7 +22,7 @@
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AudioPresentationInfo.h>
#include <android/IMediaExtractor.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/RefBase.h>
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
index 2bf5a8b..e33e303 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaExtractor.h
@@ -18,12 +18,12 @@
#define REMOTE_MEDIA_EXTRACTOR_H_
#include <android/IMediaExtractor.h>
+#include <media/MediaMetricsItem.h>
#include <media/stagefright/MediaExtractor.h>
#include <media/stagefright/foundation/ABase.h>
namespace android {
-class MediaAnalyticsItem;
// IMediaExtractor wrapper to the MediaExtractor.
class RemoteMediaExtractor : public BnMediaExtractor {
@@ -48,7 +48,7 @@
sp<DataSource> mSource;
sp<RefBase> mExtractorPlugin;
- MediaAnalyticsItem *mAnalyticsItem;
+ mediametrics::Item *mMetricsItem;
explicit RemoteMediaExtractor(
MediaExtractor *extractor,
diff --git a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
index 03d3869..2cd23f0 100644
--- a/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
+++ b/media/libstagefright/include/media/stagefright/RemoteMediaSource.h
@@ -18,7 +18,7 @@
#define REMOTE_MEDIA_SOURCE_H_
#include <media/IMediaSource.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
namespace android {
diff --git a/media/libstagefright/include/media/stagefright/SimpleDecodingSource.h b/media/libstagefright/include/media/stagefright/SimpleDecodingSource.h
index 23defb4..a97ae23 100644
--- a/media/libstagefright/include/media/stagefright/SimpleDecodingSource.h
+++ b/media/libstagefright/include/media/stagefright/SimpleDecodingSource.h
@@ -17,7 +17,7 @@
#ifndef SIMPLE_DECODING_SOURCE_H_
#define SIMPLE_DECODING_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/AString.h>
#include <media/stagefright/foundation/Mutexed.h>
diff --git a/media/libstagefright/include/media/stagefright/foundation b/media/libstagefright/include/media/stagefright/foundation
deleted file mode 120000
index b9fd3b3..0000000
--- a/media/libstagefright/include/media/stagefright/foundation
+++ /dev/null
@@ -1 +0,0 @@
-../../../foundation/include/media/stagefright/foundation/
\ No newline at end of file
diff --git a/media/libstagefright/mpeg2ts/ATSParser.h b/media/libstagefright/mpeg2ts/ATSParser.h
index 0ff2d7e..49578d3 100644
--- a/media/libstagefright/mpeg2ts/ATSParser.h
+++ b/media/libstagefright/mpeg2ts/ATSParser.h
@@ -20,7 +20,7 @@
#include <sys/types.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
#include <media/stagefright/foundation/AMessage.h>
#include <media/stagefright/foundation/AudioPresentationInfo.h>
diff --git a/media/libstagefright/mpeg2ts/AnotherPacketSource.h b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
index f4a6acb..44fe2c8 100644
--- a/media/libstagefright/mpeg2ts/AnotherPacketSource.h
+++ b/media/libstagefright/mpeg2ts/AnotherPacketSource.h
@@ -18,7 +18,7 @@
#define ANOTHER_PACKET_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABase.h>
#include <utils/threads.h>
#include <utils/List.h>
diff --git a/media/libstagefright/omx/Android.bp b/media/libstagefright/omx/Android.bp
index 7d612b4..78b4f19 100644
--- a/media/libstagefright/omx/Android.bp
+++ b/media/libstagefright/omx/Android.bp
@@ -4,6 +4,7 @@
vndk: {
enabled: true,
},
+ double_loadable: true,
srcs: [
"OMXMaster.cpp",
diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp
index 6848a83..7893148 100644
--- a/media/libstagefright/omx/tests/OMXHarness.cpp
+++ b/media/libstagefright/omx/tests/OMXHarness.cpp
@@ -30,7 +30,7 @@
#include <datasource/DataSourceFactory.h>
#include <media/DataSource.h>
#include <media/IMediaHTTPService.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/OMXBuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/ALooper.h>
diff --git a/media/libstagefright/rtsp/ARTPWriter.cpp b/media/libstagefright/rtsp/ARTPWriter.cpp
index 4f86773..58d6086 100644
--- a/media/libstagefright/rtsp/ARTPWriter.cpp
+++ b/media/libstagefright/rtsp/ARTPWriter.cpp
@@ -22,7 +22,7 @@
#include <fcntl.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/foundation/ABuffer.h>
#include <media/stagefright/foundation/ADebug.h>
#include <media/stagefright/foundation/AMessage.h>
diff --git a/media/libstagefright/rtsp/VideoSource.h b/media/libstagefright/rtsp/VideoSource.h
index 4be9bf6..f29db57 100644
--- a/media/libstagefright/rtsp/VideoSource.h
+++ b/media/libstagefright/rtsp/VideoSource.h
@@ -18,7 +18,7 @@
#define VIDEO_SOURCE_H_
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
diff --git a/media/libstagefright/webm/WebmFrameThread.h b/media/libstagefright/webm/WebmFrameThread.h
index 2dde20a..5aa6feb 100644
--- a/media/libstagefright/webm/WebmFrameThread.h
+++ b/media/libstagefright/webm/WebmFrameThread.h
@@ -21,7 +21,7 @@
#include "LinkedBlockingQueue.h"
#include <datasource/FileSource.h>
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <utils/List.h>
#include <utils/Errors.h>
diff --git a/media/libstagefright/webm/WebmWriter.h b/media/libstagefright/webm/WebmWriter.h
index ffe4c79..ed5bc4c 100644
--- a/media/libstagefright/webm/WebmWriter.h
+++ b/media/libstagefright/webm/WebmWriter.h
@@ -21,7 +21,7 @@
#include "WebmFrameThread.h"
#include "LinkedBlockingQueue.h"
-#include <media/MediaSource.h>
+#include <media/stagefright/MediaSource.h>
#include <media/stagefright/MediaWriter.h>
#include <utils/Errors.h>
diff --git a/media/libstagefright/xmlparser/Android.bp b/media/libstagefright/xmlparser/Android.bp
index 38de831..7ed0e88 100644
--- a/media/libstagefright/xmlparser/Android.bp
+++ b/media/libstagefright/xmlparser/Android.bp
@@ -10,6 +10,7 @@
vndk: {
enabled: true,
},
+ double_loadable: true,
srcs: [
"MediaCodecsXmlParser.cpp",
diff --git a/media/mtp/IMtpHandle.h b/media/mtp/IMtpHandle.h
index fd14b18..0435e82 100644
--- a/media/mtp/IMtpHandle.h
+++ b/media/mtp/IMtpHandle.h
@@ -16,7 +16,7 @@
#ifndef _IMTP_HANDLE_H
#define _IMTP_HANDLE_H
-#include <linux/usb/f_mtp.h>
+#include "f_mtp.h"
namespace android {
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index af69a10..fdf51b1 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -84,8 +84,8 @@
// MTP_OPERATION_SET_OBJECT_PROP_LIST,
// MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
// MTP_OPERATION_SEND_OBJECT_PROP_LIST,
- MTP_OPERATION_GET_OBJECT_REFERENCES,
- MTP_OPERATION_SET_OBJECT_REFERENCES,
+// MTP_OPERATION_GET_OBJECT_REFERENCES,
+// MTP_OPERATION_SET_OBJECT_REFERENCES,
// MTP_OPERATION_SKIP,
// Android extension for direct file IO
MTP_OPERATION_GET_PARTIAL_OBJECT_64,
diff --git a/media/mtp/f_mtp.h b/media/mtp/f_mtp.h
new file mode 100644
index 0000000..22ec771
--- /dev/null
+++ b/media/mtp/f_mtp.h
@@ -0,0 +1,43 @@
+/****************************************************************************
+ ****************************************************************************
+ ***
+ *** This header was automatically generated from a Linux kernel header
+ *** of the same name, to make information necessary for userspace to
+ *** call into the kernel available to libc. It contains only constants,
+ *** structures, and macros generated from the original header, and thus,
+ *** contains no copyrightable information.
+ ***
+ *** To edit the content of this header, modify the corresponding
+ *** source file (e.g. under external/kernel-headers/original/) then
+ *** run bionic/libc/kernel/tools/update_all.py
+ ***
+ *** Any manual change here will be lost the next time this script will
+ *** be run. You've been warned!
+ ***
+ ****************************************************************************
+ ****************************************************************************/
+#ifndef _UAPI_LINUX_USB_F_MTP_H
+#define _UAPI_LINUX_USB_F_MTP_H
+#include <linux/ioctl.h>
+#include <linux/types.h>
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+struct mtp_file_range {
+ int fd;
+ loff_t offset;
+ int64_t length;
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ uint16_t command;
+ uint32_t transaction_id;
+};
+struct mtp_event {
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+ size_t length;
+ void * data;
+};
+#define MTP_SEND_FILE _IOW('M', 0, struct mtp_file_range)
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
+#define MTP_RECEIVE_FILE _IOW('M', 1, struct mtp_file_range)
+#define MTP_SEND_EVENT _IOW('M', 3, struct mtp_event)
+#define MTP_SEND_FILE_WITH_HEADER _IOW('M', 4, struct mtp_file_range)
+#endif
+/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
diff --git a/media/ndk/Android.bp b/media/ndk/Android.bp
index f83d530..7b285a6 100644
--- a/media/ndk/Android.bp
+++ b/media/ndk/Android.bp
@@ -179,6 +179,10 @@
"libcutils",
"android.hardware.graphics.bufferqueue@1.0",
],
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
+
cflags: [
"-D__ANDROID_VNDK__",
],
diff --git a/media/tests/benchmark/MediaBenchmarkTest/Android.bp b/media/tests/benchmark/MediaBenchmarkTest/Android.bp
index 91b03f1..d80d9a5 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/Android.bp
+++ b/media/tests/benchmark/MediaBenchmarkTest/Android.bp
@@ -17,11 +17,13 @@
android_test {
name: "MediaBenchmarkTest",
+ defaults: [
+ "MediaBenchmark-defaults",
+ ],
+
// Include all the test code
srcs: ["src/androidTest/**/*.java"],
- sdk_version: "system_current",
-
resource_dirs: ["res"],
libs: [
@@ -29,6 +31,10 @@
"android.test.base",
],
+ jni_libs: [
+ "libmediabenchmark_jni",
+ ],
+
static_libs: [
"libMediaBenchmark",
"junit",
@@ -39,12 +45,22 @@
android_library {
name: "libMediaBenchmark",
+ defaults: [
+ "MediaBenchmark-defaults",
+ ],
+
// Include all the libraries
srcs: ["src/main/**/*.java"],
- sdk_version: "system_current",
-
static_libs: [
"androidx.test.core",
],
}
+
+java_defaults {
+ name: "MediaBenchmark-defaults",
+
+ sdk_version: "system_current",
+ min_sdk_version: "28",
+ target_sdk_version: "29",
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/AndroidTest.xml b/media/tests/benchmark/MediaBenchmarkTest/AndroidTest.xml
index 89d6ce2..1179d6c 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/AndroidTest.xml
+++ b/media/tests/benchmark/MediaBenchmarkTest/AndroidTest.xml
@@ -15,6 +15,7 @@
-->
<configuration description="Runs Media Benchmark Tests">
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="false" />
<option name="test-file-name" value="MediaBenchmarkTest.apk" />
</target_preparer>
diff --git a/media/tests/benchmark/MediaBenchmarkTest/build.gradle b/media/tests/benchmark/MediaBenchmarkTest/build.gradle
index b0ee692..b2aee1a 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/build.gradle
+++ b/media/tests/benchmark/MediaBenchmarkTest/build.gradle
@@ -30,7 +30,7 @@
compileSdkVersion 29
defaultConfig {
applicationId "com.android.media.benchmark"
- minSdkVersion 21
+ minSdkVersion 28
targetSdkVersion 29
versionCode 1
versionName "1.0"
@@ -48,6 +48,18 @@
manifest.srcFile 'AndroidManifest.xml'
}
}
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+ externalNativeBuild {
+ cmake {
+ path "src/main/cpp/CMakeLists.txt"
+ version "3.10.2"
+ }
+ }
}
repositories {
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java
index be2633d..c41f198 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/DecoderTest.java
@@ -27,7 +27,10 @@
import com.android.media.benchmark.library.CodecUtils;
import com.android.media.benchmark.library.Decoder;
import com.android.media.benchmark.library.Extractor;
+import com.android.media.benchmark.library.Native;
+import com.android.media.benchmark.library.Stats;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -44,12 +47,17 @@
import java.util.Arrays;
import java.util.Collection;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
@RunWith(Parameterized.class)
public class DecoderTest {
private static final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
private static final String mOutputFilePath = mContext.getString(R.string.output_file_path);
+ private static final String mStatsFile =
+ mContext.getExternalFilesDir(null) + "/Decoder." + System.currentTimeMillis() + ".csv";
private static final String TAG = "DecoderTest";
private static final long PER_TEST_TIMEOUT_MS = 60000;
private static final boolean DEBUG = false;
@@ -101,97 +109,112 @@
{"crowd_1920x1080_25fps_4000kbps_h265.mkv", true}});
}
+ @BeforeClass
+ public static void writeStatsHeaderToFile() throws IOException {
+ Stats mStats = new Stats();
+ boolean status = mStats.writeStatsHeader(mStatsFile);
+ assertTrue("Unable to open stats file for writing!", status);
+ Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
+ }
+
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void testDecoder() throws IOException {
File inputFile = new File(mInputFilePath + mInputFile);
- if (inputFile.exists()) {
- FileInputStream fileInput = new FileInputStream(inputFile);
- FileDescriptor fileDescriptor = fileInput.getFD();
- Extractor extractor = new Extractor();
- int trackCount = extractor.setUpExtractor(fileDescriptor);
- ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
- ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
- if (trackCount <= 0) {
- Log.e(TAG, "Extraction failed. No tracks for file: " + mInputFile);
- return;
- }
- for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
- extractor.selectExtractorTrack(currentTrack);
- MediaFormat format = extractor.getFormat(currentTrack);
- String mime = format.getString(MediaFormat.KEY_MIME);
- ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, false);
- if (mediaCodecs.size() <= 0) {
- Log.e(TAG,
- "No suitable codecs found for file: " + mInputFile
- + " track : " + currentTrack + " mime: " + mime);
- continue;
+ assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ assertTrue("Extraction failed. No tracks for file: " + mInputFile, (trackCount > 0));
+ ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, false);
+ assertTrue("No suitable codecs found for file: " + mInputFile + " track : " +
+ currentTrack + " mime: " + mime, (mediaCodecs.size() > 0));
+
+ // Get samples from extractor
+ int sampleSize;
+ do {
+ sampleSize = extractor.getFrameSample();
+ MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
+ MediaCodec.BufferInfo info = extractor.getBufferInfo();
+ ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
+ dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
+ bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
+ inputBuffer.add(dataBuffer);
+ frameInfo.add(bufInfo);
+ if (DEBUG) {
+ Log.d(TAG, "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = " +
+ bufInfo.presentationTimeUs + " size = " + bufInfo.size);
}
- // Get samples from extractor
- int sampleSize;
- do {
- sampleSize = extractor.getFrameSample();
- MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
- MediaCodec.BufferInfo info = extractor.getBufferInfo();
- ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
- dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
- bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
- inputBuffer.add(dataBuffer);
- frameInfo.add(bufInfo);
- if (DEBUG) {
- Log.d(TAG,
- "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = "
- + bufInfo.presentationTimeUs + " size = " + bufInfo.size);
+ } while (sampleSize > 0);
+ for (String codecName : mediaCodecs) {
+ FileOutputStream decodeOutputStream = null;
+ if (WRITE_OUTPUT) {
+ if (!Paths.get(mOutputFilePath).toFile().exists()) {
+ Files.createDirectories(Paths.get(mOutputFilePath));
}
- } while (sampleSize > 0);
- for (String codecName : mediaCodecs) {
- FileOutputStream decodeOutputStream = null;
- if (WRITE_OUTPUT) {
- if (!Paths.get(mOutputFilePath).toFile().exists()) {
- Files.createDirectories(Paths.get(mOutputFilePath));
- }
- File outFile = new File(mOutputFilePath + "decoder.out");
- if (outFile.exists()) {
- if (!outFile.delete()) {
- Log.e(TAG, " Unable to delete existing file" + outFile.toString());
- }
- }
- if (outFile.createNewFile()) {
- decodeOutputStream = new FileOutputStream(outFile);
- } else {
- Log.e(TAG, "Unable to create file: " + outFile.toString());
- }
+ File outFile = new File(mOutputFilePath + "decoder.out");
+ if (outFile.exists()) {
+ assertTrue(" Unable to delete existing file" + outFile.toString(),
+ outFile.delete());
}
- Decoder decoder = new Decoder();
- decoder.setupDecoder(decodeOutputStream);
- int status =
- decoder.decode(inputBuffer, frameInfo, mAsyncMode, format, codecName);
- decoder.deInitCodec();
- if (status == 0) {
- decoder.dumpStatistics(
- mInputFile + " " + codecName, extractor.getClipDuration());
- Log.i(TAG,
- "Decoding Successful for file: " + mInputFile
- + " with codec: " + codecName);
- } else {
- Log.e(TAG,
- "Decoder returned error " + status + " for file: " + mInputFile
- + " with codec: " + codecName);
- }
- decoder.resetDecoder();
- if (decodeOutputStream != null) {
- decodeOutputStream.close();
- }
+ assertTrue("Unable to create file: " + outFile.toString(),
+ outFile.createNewFile());
+ decodeOutputStream = new FileOutputStream(outFile);
}
- extractor.unselectExtractorTrack(currentTrack);
- inputBuffer.clear();
- frameInfo.clear();
+ Decoder decoder = new Decoder();
+ decoder.setupDecoder(decodeOutputStream);
+ int status = decoder.decode(inputBuffer, frameInfo, mAsyncMode, format, codecName);
+ decoder.deInitCodec();
+ assertEquals("Decoder returned error " + status + " for file: " + mInputFile +
+ " with codec: " + codecName, 0, status);
+ decoder.dumpStatistics(mInputFile, codecName, (mAsyncMode ? "async" : "sync"),
+ extractor.getClipDuration(), mStatsFile);
+ Log.i(TAG, "Decoding Successful for file: " + mInputFile + " with codec: " +
+ codecName);
+ decoder.resetDecoder();
+ if (decodeOutputStream != null) {
+ decodeOutputStream.close();
+ }
}
- extractor.deinitExtractor();
- fileInput.close();
- } else {
- Log.w(TAG,
- "Warning: Test Skipped. Cannot find " + mInputFile + " in directory "
- + mInputFilePath);
+ extractor.unselectExtractorTrack(currentTrack);
+ inputBuffer.clear();
+ frameInfo.clear();
}
+ extractor.deinitExtractor();
+ fileInput.close();
+ }
+
+ @Test
+ public void testNativeDecoder() throws IOException {
+ File inputFile = new File(mInputFilePath + mInputFile);
+ assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ assertTrue("Extraction failed. No tracks for file: ", trackCount > 0);
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, false);
+ for (String codecName : mediaCodecs) {
+ Log.i("Test: %s\n", mInputFile);
+ Native nativeDecoder = new Native();
+ int status = nativeDecoder.Decode(
+ mInputFilePath, mInputFile, mStatsFile, codecName, mAsyncMode);
+ assertEquals("Decoder returned error " + status + " for file: " + mInputFile, 0,
+ status);
+ }
+ }
+ fileInput.close();
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
index 9db9c84..831467a 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/EncoderTest.java
@@ -28,7 +28,10 @@
import com.android.media.benchmark.library.Decoder;
import com.android.media.benchmark.library.Encoder;
import com.android.media.benchmark.library.Extractor;
+import com.android.media.benchmark.library.Native;
+import com.android.media.benchmark.library.Stats;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -37,18 +40,24 @@
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
@RunWith(Parameterized.class)
public class EncoderTest {
private static final Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
private static final String mOutputFilePath = mContext.getString(R.string.output_file_path);
+ private static final String mStatsFile =
+ mContext.getExternalFilesDir(null) + "/Encoder." + System.currentTimeMillis() + ".csv";
private static final String TAG = "EncoderTest";
private static final long PER_TEST_TIMEOUT_MS = 120000;
private static final boolean DEBUG = false;
@@ -56,7 +65,6 @@
private static final int ENCODE_DEFAULT_FRAME_RATE = 25;
private static final int ENCODE_DEFAULT_BIT_RATE = 8000000 /* 8 Mbps */;
private static final int ENCODE_MIN_BIT_RATE = 600000 /* 600 Kbps */;
-
private String mInputFile;
@Parameterized.Parameters
@@ -81,192 +89,205 @@
this.mInputFile = inputFileName;
}
+ @BeforeClass
+ public static void writeStatsHeaderToFile() throws IOException {
+ Stats mStats = new Stats();
+ boolean status = mStats.writeStatsHeader(mStatsFile);
+ assertTrue("Unable to open stats file for writing!", status);
+ Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
+ }
+
@Test(timeout = PER_TEST_TIMEOUT_MS)
public void sampleEncoderTest() throws Exception {
int status;
int frameSize;
-
//Parameters for video
int width = 0;
int height = 0;
int profile = 0;
int level = 0;
int frameRate = 0;
-
//Parameters for audio
int bitRate = 0;
int sampleRate = 0;
int numChannels = 0;
-
File inputFile = new File(mInputFilePath + mInputFile);
- if (inputFile.exists()) {
- FileInputStream fileInput = new FileInputStream(inputFile);
- FileDescriptor fileDescriptor = fileInput.getFD();
- Extractor extractor = new Extractor();
- int trackCount = extractor.setUpExtractor(fileDescriptor);
- if (trackCount <= 0) {
- Log.e(TAG, "Extraction failed. No tracks for file: " + mInputFile);
- return;
+ assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ assertTrue("Extraction failed. No tracks for file: " + mInputFile, (trackCount > 0));
+ ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ // Get samples from extractor
+ int sampleSize;
+ do {
+ sampleSize = extractor.getFrameSample();
+ MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
+ MediaCodec.BufferInfo info = extractor.getBufferInfo();
+ ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
+ dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
+ bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
+ inputBuffer.add(dataBuffer);
+ frameInfo.add(bufInfo);
+ if (DEBUG) {
+ Log.d(TAG, "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = " +
+ bufInfo.presentationTimeUs + " size = " + bufInfo.size);
+ }
+ } while (sampleSize > 0);
+ int tid = android.os.Process.myTid();
+ File decodedFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
+ FileOutputStream decodeOutputStream = new FileOutputStream(decodedFile);
+ Decoder decoder = new Decoder();
+ decoder.setupDecoder(decodeOutputStream);
+ status = decoder.decode(inputBuffer, frameInfo, false, format, "");
+ assertEquals("Decoder returned error " + status + " for file: " + mInputFile, 0,
+ status);
+ decoder.deInitCodec();
+ extractor.unselectExtractorTrack(currentTrack);
+ inputBuffer.clear();
+ frameInfo.clear();
+ if (decodeOutputStream != null) {
+ decodeOutputStream.close();
}
- ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
- ArrayList<MediaCodec.BufferInfo> frameInfo = new ArrayList<>();
- for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
- extractor.selectExtractorTrack(currentTrack);
- MediaFormat format = extractor.getFormat(currentTrack);
- // Get samples from extractor
- int sampleSize;
- do {
- sampleSize = extractor.getFrameSample();
- MediaCodec.BufferInfo bufInfo = new MediaCodec.BufferInfo();
- MediaCodec.BufferInfo info = extractor.getBufferInfo();
- ByteBuffer dataBuffer = ByteBuffer.allocate(info.size);
- dataBuffer.put(extractor.getFrameBuffer().array(), 0, info.size);
- bufInfo.set(info.offset, info.size, info.presentationTimeUs, info.flags);
- inputBuffer.add(dataBuffer);
- frameInfo.add(bufInfo);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
+ assertTrue("No suitable codecs found for file: " + mInputFile + " track : " +
+ currentTrack + " mime: " + mime, (mediaCodecs.size() > 0));
+ Boolean[] encodeMode = {true, false};
+ /* Encoding the decoder's output */
+ for (Boolean asyncMode : encodeMode) {
+ for (String codecName : mediaCodecs) {
+ FileOutputStream encodeOutputStream = null;
+ if (WRITE_OUTPUT) {
+ File outEncodeFile = new File(mOutputFilePath + "encoder.out");
+ if (outEncodeFile.exists()) {
+ assertTrue(" Unable to delete existing file" + outEncodeFile.toString(),
+ outEncodeFile.delete());
+ }
+ assertTrue("Unable to create file to write encoder output: " +
+ outEncodeFile.toString(), outEncodeFile.createNewFile());
+ encodeOutputStream = new FileOutputStream(outEncodeFile);
+ }
+ File rawFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
+ assertTrue("Cannot open file to write decoded output", rawFile.exists());
if (DEBUG) {
- Log.d(TAG, "Extracted bufInfo: flag = " + bufInfo.flags + " timestamp = " +
- bufInfo.presentationTimeUs + " size = " + bufInfo.size);
+ Log.i(TAG, "Path of decoded input file: " + rawFile.toString());
}
- } while (sampleSize > 0);
-
- int tid = android.os.Process.myTid();
- File decodedFile = new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
- FileOutputStream decodeOutputStream = new FileOutputStream(decodedFile);
- Decoder decoder = new Decoder();
- decoder.setupDecoder(decodeOutputStream);
- status = decoder.decode(inputBuffer, frameInfo, false, format, "");
- if (status == 0) {
- Log.i(TAG, "Decoding complete.");
- } else {
- Log.e(TAG, "Decode returned error. Encoding did not take place." + status);
- return;
- }
- decoder.deInitCodec();
- extractor.unselectExtractorTrack(currentTrack);
- inputBuffer.clear();
- frameInfo.clear();
- if (decodeOutputStream != null) {
- decodeOutputStream.close();
- }
- String mime = format.getString(MediaFormat.KEY_MIME);
- ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
- if (mediaCodecs.size() <= 0) {
- Log.e(TAG, "No suitable codecs found for file: " + mInputFile + " track : " +
- currentTrack + " mime: " + mime);
- return;
- }
- Boolean[] encodeMode = {true, false};
- /* Encoding the decoder's output */
- for (Boolean asyncMode : encodeMode) {
- for (String codecName : mediaCodecs) {
- FileOutputStream encodeOutputStream = null;
- if (WRITE_OUTPUT) {
- File outEncodeFile = new File(mOutputFilePath + "encoder.out");
- if (outEncodeFile.exists()) {
- if (!outEncodeFile.delete()) {
- Log.e(TAG, "Unable to delete existing file" +
- decodedFile.toString());
- }
- }
- if (outEncodeFile.createNewFile()) {
- encodeOutputStream = new FileOutputStream(outEncodeFile);
+ FileInputStream eleStream = new FileInputStream(rawFile);
+ if (mime.startsWith("video/")) {
+ width = format.getInteger(MediaFormat.KEY_WIDTH);
+ height = format.getInteger(MediaFormat.KEY_HEIGHT);
+ if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
+ frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
+ } else if (frameRate <= 0) {
+ frameRate = ENCODE_DEFAULT_FRAME_RATE;
+ }
+ if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
+ bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+ } else if (bitRate <= 0) {
+ if (mime.contains("video/3gpp") || mime.contains("video/mp4v-es")) {
+ bitRate = ENCODE_MIN_BIT_RATE;
} else {
- Log.e(TAG, "Unable to create file to write encoder output: " +
- outEncodeFile.toString());
+ bitRate = ENCODE_DEFAULT_BIT_RATE;
}
}
- File rawFile =
- new File(mContext.getFilesDir() + "/decoder_" + tid + ".out");
- if (rawFile.exists()) {
- if (DEBUG) {
- Log.i(TAG, "Path of decoded input file: " + rawFile.toString());
- }
- FileInputStream eleStream = new FileInputStream(rawFile);
- if (mime.startsWith("video/")) {
- width = format.getInteger(MediaFormat.KEY_WIDTH);
- height = format.getInteger(MediaFormat.KEY_HEIGHT);
- if (format.containsKey(MediaFormat.KEY_FRAME_RATE)) {
- frameRate = format.getInteger(MediaFormat.KEY_FRAME_RATE);
- } else if (frameRate <= 0) {
- frameRate = ENCODE_DEFAULT_FRAME_RATE;
- }
- if (format.containsKey(MediaFormat.KEY_BIT_RATE)) {
- bitRate = format.getInteger(MediaFormat.KEY_BIT_RATE);
- } else if (bitRate <= 0) {
- if (mime.contains("video/3gpp") ||
- mime.contains("video/mp4v-es")) {
- bitRate = ENCODE_MIN_BIT_RATE;
- } else {
- bitRate = ENCODE_DEFAULT_BIT_RATE;
- }
- }
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- profile = format.getInteger(MediaFormat.KEY_PROFILE);
- }
- if (format.containsKey(MediaFormat.KEY_PROFILE)) {
- level = format.getInteger(MediaFormat.KEY_LEVEL);
- }
- } else {
- sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
- numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
- bitRate = sampleRate * numChannels * 16;
- }
- /*Setup Encode Format*/
- MediaFormat encodeFormat;
- if (mime.startsWith("video/")) {
- frameSize = width * height * 3 / 2;
- encodeFormat = MediaFormat.createVideoFormat(mime, width, height);
- encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- encodeFormat.setInteger(MediaFormat.KEY_PROFILE, profile);
- encodeFormat.setInteger(MediaFormat.KEY_LEVEL, level);
- encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
- encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, frameSize);
- } else {
- encodeFormat = MediaFormat
- .createAudioFormat(mime, sampleRate, numChannels);
- encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
- frameSize = 4096;
- }
- Encoder encoder = new Encoder();
- encoder.setupEncoder(encodeOutputStream, eleStream);
- status = encoder.encode(codecName, encodeFormat, mime, frameRate,
- sampleRate, frameSize, asyncMode);
- encoder.deInitEncoder();
- if (status == 0) {
- encoder.dumpStatistics(mInputFile + "with " + codecName + " for " +
- "aSyncMode = " + asyncMode, extractor.getClipDuration());
- Log.i(TAG, "Encoding complete for file: " + mInputFile +
- " with codec: " + codecName + " for aSyncMode = " +
- asyncMode);
- } else {
- Log.e(TAG,
- codecName + " encoder returned error " + status + " for " +
- "file:" + " " + mInputFile);
- }
- encoder.resetEncoder();
- eleStream.close();
- if (encodeOutputStream != null) {
- encodeOutputStream.close();
- }
+ if (format.containsKey(MediaFormat.KEY_PROFILE)) {
+ profile = format.getInteger(MediaFormat.KEY_PROFILE);
}
- }
- }
- //Cleanup temporary input file
- if (decodedFile.exists()) {
- if (decodedFile.delete()) {
- Log.i(TAG, "Successfully deleted decoded file");
+ if (format.containsKey(MediaFormat.KEY_PROFILE)) {
+ level = format.getInteger(MediaFormat.KEY_LEVEL);
+ }
} else {
- Log.e(TAG, "Unable to delete decoded file");
+ sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+ numChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+ bitRate = sampleRate * numChannels * 16;
}
+ /*Setup Encode Format*/
+ MediaFormat encodeFormat;
+ if (mime.startsWith("video/")) {
+ frameSize = width * height * 3 / 2;
+ encodeFormat = MediaFormat.createVideoFormat(mime, width, height);
+ encodeFormat.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+ encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ encodeFormat.setInteger(MediaFormat.KEY_PROFILE, profile);
+ encodeFormat.setInteger(MediaFormat.KEY_LEVEL, level);
+ encodeFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+ encodeFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, frameSize);
+ } else {
+ encodeFormat = MediaFormat.createAudioFormat(mime, sampleRate, numChannels);
+ encodeFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+ frameSize = 4096;
+ }
+ Encoder encoder = new Encoder();
+ encoder.setupEncoder(encodeOutputStream, eleStream);
+ status = encoder.encode(codecName, encodeFormat, mime, frameRate, sampleRate,
+ frameSize, asyncMode);
+ encoder.deInitEncoder();
+ assertEquals(
+ codecName + " encoder returned error " + status + " for " + "file:" +
+ " " + mInputFile, 0, status);
+ encoder.dumpStatistics(mInputFile, codecName, (asyncMode ? "async" : "sync"),
+ extractor.getClipDuration(), mStatsFile);
+ Log.i(TAG, "Encoding complete for file: " + mInputFile + " with codec: " +
+ codecName + " for aSyncMode = " + asyncMode);
+ encoder.resetEncoder();
+ eleStream.close();
+ if (encodeOutputStream != null) {
+ encodeOutputStream.close();
+ }
+
}
}
- extractor.deinitExtractor();
- fileInput.close();
- } else {
- Log.w(TAG, "Warning: Test Skipped. Cannot find " + mInputFile + " in directory " +
- mInputFilePath);
+ //Cleanup temporary input file
+ if (decodedFile.exists()) {
+ assertTrue(" Unable to delete decoded file" + decodedFile.toString(),
+ decodedFile.delete());
+ Log.i(TAG, "Successfully deleted decoded file");
+ }
}
+ extractor.deinitExtractor();
+ fileInput.close();
+ }
+
+ @Test
+ public void testNativeEncoder() throws Exception {
+ File inputFile = new File(mInputFilePath + mInputFile);
+ assertTrue("Cannot find " + mInputFile + " in directory " + mInputFilePath,
+ inputFile.exists());
+ int tid = android.os.Process.myTid();
+ final String mDecodedFile = mContext.getFilesDir() + "/decoder_" + tid + ".out";
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ assertTrue("Extraction failed. No tracks for file: ", trackCount > 0);
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ ArrayList<String> mediaCodecs = CodecUtils.selectCodecs(mime, true);
+ // Encoding the decoder's output
+ for (String codecName : mediaCodecs) {
+ Native nativeEncoder = new Native();
+ int status = nativeEncoder.Encode(
+ mInputFilePath, mInputFile, mDecodedFile, mStatsFile, codecName);
+ assertEquals(
+ codecName + " encoder returned error " + status + " for " + "file:" + " " +
+ mInputFile, 0, status);
+ }
+ }
+ File decodedFile = new File(mDecodedFile);
+ // Cleanup temporary input file
+ if (decodedFile.exists()) {
+ assertTrue("Unable to delete - " + mDecodedFile, decodedFile.delete());
+ Log.i(TAG, "Successfully deleted - " + mDecodedFile);
+ }
+ fileInput.close();
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/ExtractorTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/ExtractorTest.java
index a02011c..6b7aad1 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/ExtractorTest.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/ExtractorTest.java
@@ -18,12 +18,16 @@
import com.android.media.benchmark.R;
import com.android.media.benchmark.library.Extractor;
+import com.android.media.benchmark.library.Native;
+import com.android.media.benchmark.library.Stats;
import android.content.Context;
+import android.media.MediaFormat;
import android.util.Log;
import androidx.test.platform.app.InstrumentationRegistry;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -31,19 +35,23 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class ExtractorTest {
private static Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
+ private static final String mStatsFile = mContext.getExternalFilesDir(null) + "/Extractor."
+ + System.currentTimeMillis() + ".csv";
private static final String TAG = "ExtractorTest";
private String mInputFileName;
private int mTrackId;
@@ -71,22 +79,43 @@
this.mTrackId = track;
}
+ @BeforeClass
+ public static void writeStatsHeaderToFile() throws IOException {
+ Stats mStats = new Stats();
+ boolean status = mStats.writeStatsHeader(mStatsFile);
+ assertTrue("Unable to open stats file for writing!", status);
+ Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
+ }
+
@Test
public void sampleExtractTest() throws IOException {
- int status = -1;
File inputFile = new File(mInputFilePath + mInputFileName);
- if (inputFile.exists()) {
- FileInputStream fileInput = new FileInputStream(inputFile);
- FileDescriptor fileDescriptor = fileInput.getFD();
- Extractor extractor = new Extractor();
- extractor.setUpExtractor(fileDescriptor);
- status = extractor.extractSample(mTrackId);
- extractor.deinitExtractor();
- extractor.dumpStatistics(mInputFileName);
- fileInput.close();
- } else {
- Log.e(TAG, "Cannot find " + mInputFileName + " in directory " + mInputFilePath);
- }
- assertThat(status, is(equalTo(0)));
+ assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ Extractor extractor = new Extractor();
+ extractor.setUpExtractor(fileDescriptor);
+ MediaFormat format = extractor.getFormat(mTrackId);
+ String mime = format.getString(MediaFormat.KEY_MIME);
+ int status = extractor.extractSample(mTrackId);
+ assertEquals("Extraction failed for " + mInputFileName, 0, status);
+ Log.i(TAG, "Extracted " + mInputFileName + " successfully.");
+ extractor.deinitExtractor();
+ extractor.dumpStatistics(mInputFileName, mime, mStatsFile);
+ fileInput.close();
+ }
+
+ @Test
+ public void sampleExtractNativeTest() throws IOException {
+ Native nativeExtractor = new Native();
+ File inputFile = new File(mInputFilePath + mInputFileName);
+ assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ int status = nativeExtractor.Extract(mInputFilePath, mInputFileName, mStatsFile);
+ fileInput.close();
+ assertEquals("Extraction failed for " + mInputFileName, 0, status);
+ Log.i(TAG, "Extracted " + mInputFileName + " successfully.");
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/MuxerTest.java b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/MuxerTest.java
index 8c3080c..2efdba2 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/MuxerTest.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/androidTest/java/com/android/media/benchmark/tests/MuxerTest.java
@@ -18,6 +18,8 @@
import com.android.media.benchmark.R;
import com.android.media.benchmark.library.Extractor;
import com.android.media.benchmark.library.Muxer;
+import com.android.media.benchmark.library.Native;
+import com.android.media.benchmark.library.Stats;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -27,6 +29,7 @@
import android.media.MediaMuxer;
import android.util.Log;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -34,6 +37,7 @@
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@@ -42,15 +46,19 @@
import java.util.Hashtable;
import java.util.Map;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import static org.junit.Assert.assertTrue;
@RunWith(Parameterized.class)
public class MuxerTest {
private static Context mContext =
InstrumentationRegistry.getInstrumentation().getTargetContext();
private static final String mInputFilePath = mContext.getString(R.string.input_file_path);
+ private static final String mStatsFile =
+ mContext.getExternalFilesDir(null) + "/Muxer." + System.currentTimeMillis() + ".csv";
private static final String TAG = "MuxerTest";
private static final Map<String, Integer> mMapFormat = new Hashtable<String, Integer>() {
{
@@ -93,60 +101,80 @@
this.mFormat = outputFormat;
}
+ @BeforeClass
+ public static void writeStatsHeaderToFile() throws IOException {
+ Stats mStats = new Stats();
+ boolean status = mStats.writeStatsHeader(mStatsFile);
+ assertTrue("Unable to open stats file for writing!", status);
+ Log.d(TAG, "Saving Benchmark results in: " + mStatsFile);
+ }
+
@Test
public void sampleMuxerTest() throws IOException {
- int status = -1;
File inputFile = new File(mInputFilePath + mInputFileName);
- if (inputFile.exists()) {
- FileInputStream fileInput = new FileInputStream(inputFile);
- FileDescriptor fileDescriptor = fileInput.getFD();
- ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
- ArrayList<MediaCodec.BufferInfo> inputBufferInfo = new ArrayList<>();
- Extractor extractor = new Extractor();
- int trackCount = extractor.setUpExtractor(fileDescriptor);
- for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
- extractor.selectExtractorTrack(currentTrack);
- while (true) {
- int sampleSize = extractor.getFrameSample();
- MediaCodec.BufferInfo bufferInfo = extractor.getBufferInfo();
- MediaCodec.BufferInfo tempBufferInfo = new MediaCodec.BufferInfo();
- tempBufferInfo
- .set(bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs,
- bufferInfo.flags);
- inputBufferInfo.add(tempBufferInfo);
- ByteBuffer tempSampleBuffer = ByteBuffer.allocate(tempBufferInfo.size);
- tempSampleBuffer.put(extractor.getFrameBuffer().array(), 0, bufferInfo.size);
- inputBuffer.add(tempSampleBuffer);
- if (sampleSize < 0) {
- break;
- }
- }
- MediaFormat format = extractor.getFormat(currentTrack);
- int outputFormat = mMapFormat.getOrDefault(mFormat, -1);
- if (outputFormat != -1) {
- Muxer muxer = new Muxer();
- int trackIndex = muxer.setUpMuxer(mContext, outputFormat, format);
- status = muxer.mux(trackIndex, inputBuffer, inputBufferInfo);
- if (status != 0) {
- Log.e(TAG, "Cannot perform write operation for " + mInputFileName);
- }
- muxer.deInitMuxer();
- muxer.dumpStatistics(mInputFileName, extractor.getClipDuration());
- muxer.resetMuxer();
- extractor.unselectExtractorTrack(currentTrack);
- inputBufferInfo.clear();
- inputBuffer.clear();
- } else {
- Log.e(TAG, "Test failed for " + mInputFileName + ". Returned invalid " +
- "output format for given " + mFormat + " format.");
+ assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
+ inputFile.exists());
+ FileInputStream fileInput = new FileInputStream(inputFile);
+ FileDescriptor fileDescriptor = fileInput.getFD();
+ ArrayList<ByteBuffer> inputBuffer = new ArrayList<>();
+ ArrayList<MediaCodec.BufferInfo> inputBufferInfo = new ArrayList<>();
+ Extractor extractor = new Extractor();
+ int trackCount = extractor.setUpExtractor(fileDescriptor);
+ for (int currentTrack = 0; currentTrack < trackCount; currentTrack++) {
+ extractor.selectExtractorTrack(currentTrack);
+ while (true) {
+ int sampleSize = extractor.getFrameSample();
+ MediaCodec.BufferInfo bufferInfo = extractor.getBufferInfo();
+ MediaCodec.BufferInfo tempBufferInfo = new MediaCodec.BufferInfo();
+ tempBufferInfo
+ .set(bufferInfo.offset, bufferInfo.size, bufferInfo.presentationTimeUs,
+ bufferInfo.flags);
+ inputBufferInfo.add(tempBufferInfo);
+ ByteBuffer tempSampleBuffer = ByteBuffer.allocate(tempBufferInfo.size);
+ tempSampleBuffer.put(extractor.getFrameBuffer().array(), 0, bufferInfo.size);
+ inputBuffer.add(tempSampleBuffer);
+ if (sampleSize < 0) {
+ break;
}
}
- extractor.deinitExtractor();
- fileInput.close();
- } else {
- Log.w(TAG, "Warning: Test Skipped. Cannot find " + mInputFileName + " in directory " +
- mInputFilePath);
+ MediaFormat format = extractor.getFormat(currentTrack);
+ int outputFormat = mMapFormat.getOrDefault(mFormat, -1);
+ assertNotEquals("Test failed for " + mInputFileName + ". Returned invalid " +
+ "output format for given " + mFormat + " format.", -1, outputFormat);
+ Muxer muxer = new Muxer();
+ int trackIndex = muxer.setUpMuxer(mContext, outputFormat, format);
+ int status = muxer.mux(trackIndex, inputBuffer, inputBufferInfo);
+ assertEquals("Cannot perform write operation for " + mInputFileName, 0, status);
+ Log.i(TAG, "Muxed " + mInputFileName + " successfully.");
+ muxer.deInitMuxer();
+ muxer.dumpStatistics(mInputFileName, mFormat, extractor.getClipDuration(), mStatsFile);
+ muxer.resetMuxer();
+ extractor.unselectExtractorTrack(currentTrack);
+ inputBufferInfo.clear();
+ inputBuffer.clear();
+
}
- assertThat(status, is(equalTo(0)));
+ extractor.deinitExtractor();
+ fileInput.close();
+ }
+
+ @Test
+ public void sampleMuxerNativeTest() {
+ Native nativeMuxer = new Native();
+ File inputFile = new File(mInputFilePath + mInputFileName);
+ assertTrue("Cannot find " + mInputFileName + " in directory " + mInputFilePath,
+ inputFile.exists());
+ int tid = android.os.Process.myTid();
+ String mMuxOutputFile = (mContext.getFilesDir() + "/mux_" + tid + ".out");
+ int status = nativeMuxer.Mux(
+ mInputFilePath, mInputFileName, mMuxOutputFile, mStatsFile, mFormat);
+ assertEquals("Cannot perform write operation for " + mInputFileName, 0, status);
+ Log.i(TAG, "Muxed " + mInputFileName + " successfully.");
+ File muxedFile = new File(mMuxOutputFile);
+ // Cleanup temporary output file
+ if (muxedFile.exists()) {
+ assertTrue("Unable to delete" + mMuxOutputFile + " file.",
+ muxedFile.delete());
+ }
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/Android.bp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/Android.bp
new file mode 100644
index 0000000..c72eb55
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/Android.bp
@@ -0,0 +1,32 @@
+cc_test_library {
+ name: "libmediabenchmark_jni",
+
+ defaults: [
+ "libmediabenchmark_common-defaults",
+ "libmediabenchmark_soft_sanitize_all-defaults",
+ ],
+
+ srcs: [
+ "NativeExtractor.cpp",
+ "NativeMuxer.cpp",
+ "NativeEncoder.cpp",
+ "NativeDecoder.cpp",
+ ],
+
+ shared_libs: [
+ "liblog",
+ ],
+
+ static_libs: [
+ "libmediabenchmark_common",
+ "libmediabenchmark_extractor",
+ "libmediabenchmark_muxer",
+ "libmediabenchmark_decoder",
+ "libmediabenchmark_encoder",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/CMakeLists.txt b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/CMakeLists.txt
new file mode 100644
index 0000000..5823883
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/CMakeLists.txt
@@ -0,0 +1,44 @@
+#
+# Copyright (C) 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+#
+
+cmake_minimum_required(VERSION 3.4.1)
+
+set(native_source_path "../../../../src/native")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
+
+add_library(
+ mediabenchmark_jni SHARED
+ NativeExtractor.cpp
+ NativeMuxer.cpp
+ NativeDecoder.cpp
+ NativeEncoder.cpp
+ ${native_source_path}/common/BenchmarkCommon.cpp
+ ${native_source_path}/common/Stats.cpp
+ ${native_source_path}/common/utils/Timers.cpp
+ ${native_source_path}/extractor/Extractor.cpp
+ ${native_source_path}/muxer/Muxer.cpp
+ ${native_source_path}/decoder/Decoder.cpp
+ ${native_source_path}/encoder/Encoder.cpp)
+
+include_directories(${native_source_path}/common)
+include_directories(${native_source_path}/extractor)
+include_directories(${native_source_path}/muxer)
+include_directories(${native_source_path}/decoder)
+include_directories(${native_source_path}/encoder)
+
+find_library(log-lib log)
+
+target_link_libraries(mediabenchmark_jni mediandk ${log-lib})
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeDecoder.cpp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeDecoder.cpp
new file mode 100644
index 0000000..043bc9e
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeDecoder.cpp
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeDecoder"
+
+#include <jni.h>
+#include <fstream>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <android/log.h>
+
+#include "Decoder.h"
+
+extern "C" JNIEXPORT int JNICALL Java_com_android_media_benchmark_library_Native_Decode(
+ JNIEnv *env, jobject thiz, jstring jFilePath, jstring jFileName, jstring jStatsFile,
+ jstring jCodecName, jboolean asyncMode) {
+ const char *filePath = env->GetStringUTFChars(jFilePath, nullptr);
+ const char *fileName = env->GetStringUTFChars(jFileName, nullptr);
+ string sFilePath = string(filePath) + string(fileName);
+ UNUSED(thiz);
+ FILE *inputFp = fopen(sFilePath.c_str(), "rb");
+ env->ReleaseStringUTFChars(jFileName, fileName);
+ env->ReleaseStringUTFChars(jFilePath, filePath);
+ if (!inputFp) {
+ ALOGE("Unable to open input file for reading");
+ return -1;
+ }
+
+ Decoder *decoder = new Decoder();
+ Extractor *extractor = decoder->getExtractor();
+ if (!extractor) {
+ ALOGE("Extractor creation failed");
+ return -1;
+ }
+
+ // Read file properties
+ struct stat buf;
+ stat(sFilePath.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ if (fileSize > kMaxBufferSize) {
+ ALOGE("File size greater than maximum buffer size");
+ return -1;
+ }
+ int32_t fd = fileno(inputFp);
+ int32_t trackCount = extractor->initExtractor(fd, fileSize);
+ if (trackCount <= 0) {
+ ALOGE("initExtractor failed");
+ return -1;
+ }
+ for (int curTrack = 0; curTrack < trackCount; curTrack++) {
+ int32_t status = extractor->setupTrackFormat(curTrack);
+ if (status != 0) {
+ ALOGE("Track Format invalid");
+ return -1;
+ }
+
+ uint8_t *inputBuffer = (uint8_t *) malloc(fileSize);
+ if (!inputBuffer) {
+ ALOGE("Insufficient memory");
+ return -1;
+ }
+
+ vector<AMediaCodecBufferInfo> frameInfo;
+ AMediaCodecBufferInfo info;
+ uint32_t inputBufferOffset = 0;
+
+ // Get frame data
+ while (1) {
+ status = extractor->getFrameSample(info);
+ if (status || !info.size) break;
+ // copy the meta data and buffer to be passed to decoder
+ if (inputBufferOffset + info.size > kMaxBufferSize) {
+ ALOGE("Memory allocated not sufficient");
+ free(inputBuffer);
+ return -1;
+ }
+ memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
+ frameInfo.push_back(info);
+ inputBufferOffset += info.size;
+ }
+
+ const char *codecName = env->GetStringUTFChars(jCodecName, nullptr);
+ string sCodecName = string(codecName);
+ decoder->setupDecoder();
+ status = decoder->decode(inputBuffer, frameInfo, sCodecName, asyncMode);
+ if (status != AMEDIA_OK) {
+ ALOGE("Decode returned error");
+ free(inputBuffer);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
+ return -1;
+ }
+ decoder->deInitCodec();
+ const char *inputReference = env->GetStringUTFChars(jFileName, nullptr);
+ const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
+ string sInputReference = string(inputReference);
+ decoder->dumpStatistics(sInputReference, sCodecName, (asyncMode ? "async" : "sync"),
+ statsFile);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
+ env->ReleaseStringUTFChars(jStatsFile, statsFile);
+ env->ReleaseStringUTFChars(jFileName, inputReference);
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ decoder->resetDecoder();
+ }
+ if (inputFp) {
+ fclose(inputFp);
+ inputFp = nullptr;
+ }
+ extractor->deInitExtractor();
+ delete decoder;
+ return 0;
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp
new file mode 100644
index 0000000..271b852
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeEncoder.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeEncoder"
+
+#include <jni.h>
+#include <fstream>
+#include <iostream>
+#include <sys/stat.h>
+
+#include <android/log.h>
+
+#include "Decoder.h"
+#include "Encoder.h"
+
+#include <stdio.h>
+
+extern "C" JNIEXPORT int JNICALL Java_com_android_media_benchmark_library_Native_Encode(
+ JNIEnv *env, jobject thiz, jstring jFilePath, jstring jFileName, jstring jOutFilePath,
+ jstring jStatsFile, jstring jCodecName) {
+ const char *filePath = env->GetStringUTFChars(jFilePath, nullptr);
+ const char *fileName = env->GetStringUTFChars(jFileName, nullptr);
+ string sFilePath = string(filePath) + string(fileName);
+ UNUSED(thiz);
+ FILE *inputFp = fopen(sFilePath.c_str(), "rb");
+ env->ReleaseStringUTFChars(jFileName, fileName);
+ env->ReleaseStringUTFChars(jFilePath, filePath);
+ if (!inputFp) {
+ ALOGE("Unable to open input file for reading");
+ return -1;
+ }
+
+ Decoder *decoder = new Decoder();
+ Extractor *extractor = decoder->getExtractor();
+ if (!extractor) {
+ ALOGE("Extractor creation failed");
+ return -1;
+ }
+
+ // Read file properties
+ struct stat buf;
+ stat(sFilePath.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ if (fileSize > kMaxBufferSize) {
+ ALOGE("File size greater than maximum buffer size");
+ return -1;
+ }
+ int32_t fd = fileno(inputFp);
+ int32_t trackCount = extractor->initExtractor(fd, fileSize);
+ if (trackCount <= 0) {
+ ALOGE("initExtractor failed");
+ return -1;
+ }
+
+ for (int curTrack = 0; curTrack < trackCount; curTrack++) {
+ int32_t status = extractor->setupTrackFormat(curTrack);
+ if (status != 0) {
+ ALOGE("Track Format invalid");
+ return -1;
+ }
+ uint8_t *inputBuffer = (uint8_t *) malloc(fileSize);
+ if (!inputBuffer) {
+ ALOGE("Insufficient memory");
+ return -1;
+ }
+ vector<AMediaCodecBufferInfo> frameInfo;
+ AMediaCodecBufferInfo info;
+ uint32_t inputBufferOffset = 0;
+
+ // Get frame data
+ while (1) {
+ status = extractor->getFrameSample(info);
+ if (status || !info.size) break;
+ // copy the meta data and buffer to be passed to decoder
+ if (inputBufferOffset + info.size > kMaxBufferSize) {
+ ALOGE("Memory allocated not sufficient");
+ free(inputBuffer);
+ return -1;
+ }
+ memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
+ frameInfo.push_back(info);
+ inputBufferOffset += info.size;
+ }
+ string decName = "";
+ const char *outputFilePath = env->GetStringUTFChars(jOutFilePath, nullptr);
+ FILE *outFp = fopen(outputFilePath, "wb");
+ if (outFp == nullptr) {
+ ALOGE("%s - File failed to open for writing!", outputFilePath);
+ free(inputBuffer);
+ return -1;
+ }
+ decoder->setupDecoder();
+ status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp);
+ if (status != AMEDIA_OK) {
+ ALOGE("Decode returned error");
+ free(inputBuffer);
+ return -1;
+ }
+ AMediaFormat *format = extractor->getFormat();
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ const char *mime = nullptr;
+ AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
+ if (!mime) {
+ ALOGE("Error in AMediaFormat_getString");
+ return -1;
+ }
+ ifstream eleStream;
+ eleStream.open(outputFilePath, ifstream::binary | ifstream::ate);
+ if (!eleStream.is_open()) {
+ ALOGE("%s - File failed to open for reading!", outputFilePath);
+ env->ReleaseStringUTFChars(jOutFilePath, outputFilePath);
+ return -1;
+ }
+ const char *codecName = env->GetStringUTFChars(jCodecName, NULL);
+ const char *inputReference = env->GetStringUTFChars(jFileName, nullptr);
+ string sCodecName = string(codecName);
+ string sInputReference = string(inputReference);
+
+ bool asyncMode[2] = {true, false};
+ for (int i = 0; i < 2; i++) {
+ size_t eleSize = eleStream.tellg();
+ eleStream.seekg(0, ifstream::beg);
+
+ // Get encoder params
+ encParameter encParams;
+ if (!strncmp(mime, "video/", 6)) {
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &encParams.width);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &encParams.height);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &encParams.frameRate);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &encParams.bitrate);
+ if (encParams.bitrate <= 0 || encParams.frameRate <= 0) {
+ encParams.frameRate = 25;
+ if (!strcmp(mime, "video/3gpp") || !strcmp(mime, "video/mp4v-es")) {
+ encParams.bitrate = 600000 /* 600 Kbps */;
+ } else {
+ encParams.bitrate = 8000000 /* 8 Mbps */;
+ }
+ }
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_PROFILE, &encParams.profile);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_LEVEL, &encParams.level);
+ } else {
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &encParams.sampleRate);
+ AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
+ &encParams.numChannels);
+ encParams.bitrate =
+ encParams.sampleRate * encParams.numChannels * 16 /* bitsPerSample */;
+ }
+ Encoder *encoder = new Encoder();
+ encoder->setupEncoder();
+ status = encoder->encode(sCodecName, eleStream, eleSize, asyncMode[i], encParams,
+ (char *) mime);
+ encoder->deInitCodec();
+ cout << "codec : " << codecName << endl;
+ ALOGV(" asyncMode = %d \n", asyncMode[i]);
+ const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
+ encoder->dumpStatistics(sInputReference, extractor->getClipDuration(), sCodecName,
+ (asyncMode[i] ? "async" : "sync"), statsFile);
+ env->ReleaseStringUTFChars(jStatsFile, statsFile);
+ encoder->resetEncoder();
+ delete encoder;
+ encoder = nullptr;
+ }
+ eleStream.close();
+ if (outFp) {
+ fclose(outFp);
+ outFp = nullptr;
+ }
+ env->ReleaseStringUTFChars(jFileName, inputReference);
+ env->ReleaseStringUTFChars(jCodecName, codecName);
+ env->ReleaseStringUTFChars(jOutFilePath, outputFilePath);
+ if (format) {
+ AMediaFormat_delete(format);
+ format = nullptr;
+ }
+ decoder->deInitCodec();
+ decoder->resetDecoder();
+ }
+ if (inputFp) {
+ fclose(inputFp);
+ inputFp = nullptr;
+ }
+ extractor->deInitExtractor();
+ delete decoder;
+ return 0;
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeExtractor.cpp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeExtractor.cpp
new file mode 100644
index 0000000..a762760
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeExtractor.cpp
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeExtractor"
+
+#include <jni.h>
+#include <fstream>
+#include <string>
+#include <sys/stat.h>
+
+#include "Extractor.h"
+
+extern "C" JNIEXPORT int32_t JNICALL Java_com_android_media_benchmark_library_Native_Extract(
+ JNIEnv *env, jobject thiz, jstring jInputFilePath, jstring jInputFileName,
+ jstring jStatsFile) {
+ UNUSED(thiz);
+ const char *inputFilePath = env->GetStringUTFChars(jInputFilePath, nullptr);
+ const char *inputFileName = env->GetStringUTFChars(jInputFileName, nullptr);
+ string sFilePath = string(inputFilePath) + string(inputFileName);
+ FILE *inputFp = fopen(sFilePath.c_str(), "rb");
+
+ // Read file properties
+ struct stat buf;
+ stat(sFilePath.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ int32_t fd = fileno(inputFp);
+
+ Extractor *extractObj = new Extractor();
+ int32_t trackCount = extractObj->initExtractor((long) fd, fileSize);
+ if (trackCount <= 0) {
+ ALOGE("initExtractor failed");
+ return -1;
+ }
+
+ int32_t trackID = 0;
+ const char *mime = nullptr;
+ int32_t status = extractObj->extract(trackID);
+ if (status != AMEDIA_OK) {
+ ALOGE("Extraction failed");
+ return -1;
+ }
+
+ if (inputFp) {
+ fclose(inputFp);
+ inputFp = nullptr;
+ }
+ status = extractObj->setupTrackFormat(trackID);
+ AMediaFormat *format = extractObj->getFormat();
+ if (!format) {
+ ALOGE("format is null!");
+ return -1;
+ }
+ AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
+ if (!mime) {
+ ALOGE("mime is null!");
+ return -1;
+ }
+ extractObj->deInitExtractor();
+ const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
+ extractObj->dumpStatistics(string(inputFileName), string(mime), statsFile);
+ env->ReleaseStringUTFChars(jStatsFile, statsFile);
+ env->ReleaseStringUTFChars(jInputFilePath, inputFilePath);
+ env->ReleaseStringUTFChars(jInputFileName, inputFileName);
+
+ delete extractObj;
+ return status;
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeMuxer.cpp b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeMuxer.cpp
new file mode 100644
index 0000000..a5ef5b8
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/cpp/NativeMuxer.cpp
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMuxer"
+
+#include <jni.h>
+#include <fstream>
+#include <string>
+#include <sys/stat.h>
+
+#include "Muxer.h"
+
+MUXER_OUTPUT_T getMuxerOutFormat(const char *fmt);
+
+extern "C" JNIEXPORT int32_t JNICALL Java_com_android_media_benchmark_library_Native_Mux(
+ JNIEnv *env, jobject thiz, jstring jInputFilePath, jstring jInputFileName,
+ jstring jOutputFilePath, jstring jStatsFile, jstring jFormat) {
+ UNUSED(thiz);
+ ALOGV("Mux the samples given by extractor");
+ const char *inputFilePath = env->GetStringUTFChars(jInputFilePath, nullptr);
+ const char *inputFileName = env->GetStringUTFChars(jInputFileName, nullptr);
+ string sInputFile = string(inputFilePath) + string(inputFileName);
+ FILE *inputFp = fopen(sInputFile.c_str(), "rb");
+ if (!inputFp) {
+ ALOGE("Unable to open input file for reading");
+ return -1;
+ }
+
+ const char *fmt = env->GetStringUTFChars(jFormat, nullptr);
+ MUXER_OUTPUT_T outputFormat = getMuxerOutFormat(fmt);
+ if (outputFormat == MUXER_OUTPUT_FORMAT_INVALID) {
+ ALOGE("output format is MUXER_OUTPUT_FORMAT_INVALID");
+ return MUXER_OUTPUT_FORMAT_INVALID;
+ }
+
+ Muxer *muxerObj = new Muxer();
+ Extractor *extractor = muxerObj->getExtractor();
+ if (!extractor) {
+ ALOGE("Extractor creation failed");
+ return -1;
+ }
+
+ // Read file properties
+ struct stat buf;
+ stat(sInputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ int32_t fd = fileno(inputFp);
+
+ int32_t trackCount = extractor->initExtractor(fd, fileSize);
+ if (trackCount <= 0) {
+ ALOGE("initExtractor failed");
+ return -1;
+ }
+
+ for (int curTrack = 0; curTrack < trackCount; curTrack++) {
+ int32_t status = extractor->setupTrackFormat(curTrack);
+ if (status != 0) {
+ ALOGE("Track Format invalid");
+ return -1;
+ }
+
+ uint8_t *inputBuffer = (uint8_t *) malloc(fileSize);
+ if (!inputBuffer) {
+ ALOGE("Allocation Failed");
+ return -1;
+ }
+ vector<AMediaCodecBufferInfo> frameInfos;
+ AMediaCodecBufferInfo info;
+ uint32_t inputBufferOffset = 0;
+
+ // Get Frame Data
+ while (1) {
+ status = extractor->getFrameSample(info);
+ if (status || !info.size) break;
+ // copy the meta data and buffer to be passed to muxer
+ if (inputBufferOffset + info.size > fileSize) {
+ ALOGE("Memory allocated not sufficient");
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ return -1;
+ }
+ memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(),
+ static_cast<size_t>(info.size));
+ info.offset = inputBufferOffset;
+ frameInfos.push_back(info);
+ inputBufferOffset += info.size;
+ }
+
+ const char *outputFilePath = env->GetStringUTFChars(jOutputFilePath, nullptr);
+ FILE *outputFp = fopen(((string) outputFilePath).c_str(), "w+b");
+ env->ReleaseStringUTFChars(jOutputFilePath, outputFilePath);
+
+ if (!outputFp) {
+ ALOGE("Unable to open output file for writing");
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ return -1;
+ }
+ int32_t outFd = fileno(outputFp);
+
+ status = muxerObj->initMuxer(outFd, (MUXER_OUTPUT_T) outputFormat);
+ if (status != 0) {
+ ALOGE("initMuxer failed");
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ return -1;
+ }
+
+ status = muxerObj->mux(inputBuffer, frameInfos);
+ if (status != 0) {
+ ALOGE("Mux failed");
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ return -1;
+ }
+ muxerObj->deInitMuxer();
+ const char *statsFile = env->GetStringUTFChars(jStatsFile, nullptr);
+ string muxFormat(fmt);
+ muxerObj->dumpStatistics(string(inputFileName), muxFormat, statsFile);
+ env->ReleaseStringUTFChars(jStatsFile, statsFile);
+ env->ReleaseStringUTFChars(jInputFilePath, inputFilePath);
+ env->ReleaseStringUTFChars(jInputFileName, inputFileName);
+
+ if (inputBuffer) {
+ free(inputBuffer);
+ inputBuffer = nullptr;
+ }
+ if (outputFp) {
+ fclose(outputFp);
+ outputFp = nullptr;
+ }
+ muxerObj->resetMuxer();
+ }
+ if (inputFp) {
+ fclose(inputFp);
+ inputFp = nullptr;
+ }
+ env->ReleaseStringUTFChars(jFormat, fmt);
+ extractor->deInitExtractor();
+ delete muxerObj;
+
+ return 0;
+}
+
+MUXER_OUTPUT_T getMuxerOutFormat(const char *fmt) {
+ static const struct {
+ const char *name;
+ int value;
+ } kFormatMaps[] = {{"mp4", MUXER_OUTPUT_FORMAT_MPEG_4},
+ {"webm", MUXER_OUTPUT_FORMAT_WEBM},
+ {"3gpp", MUXER_OUTPUT_FORMAT_3GPP},
+ {"ogg", MUXER_OUTPUT_FORMAT_OGG}};
+
+ int32_t muxOutputFormat = MUXER_OUTPUT_FORMAT_INVALID;
+ for (auto kFormatMap : kFormatMaps) {
+ if (!strcmp(fmt, kFormatMap.name)) {
+ muxOutputFormat = kFormatMap.value;
+ break;
+ }
+ }
+ return (MUXER_OUTPUT_T) muxOutputFormat;
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java
index 2cd27c2..3b1eed4 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Decoder.java
@@ -239,11 +239,16 @@
* Prints out the statistics in the information log
*
* @param inputReference The operation being performed, in this case decode
+ * @param componentName Name of the component/codec
+ * @param mode The operating mode: Sync/Async
* @param durationUs Duration of the clip in microseconds
+ * @param statsFile The output file where the stats data is written
*/
- public void dumpStatistics(String inputReference, long durationUs) {
+ public void dumpStatistics(String inputReference, String componentName, String mode,
+ long durationUs, String statsFile) throws IOException {
String operation = "decode";
- mStats.dumpStatistics(operation, inputReference, durationUs);
+ mStats.dumpStatistics(
+ inputReference, operation, componentName, mode, durationUs, statsFile);
}
/**
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
index 03db294..40cf8bd 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Encoder.java
@@ -326,12 +326,17 @@
/**
* Prints out the statistics in the information log
*
- * @param inputReference The operation being performed, in this case encode
+ * @param inputReference The operation being performed, in this case decode
+ * @param componentName Name of the component/codec
+ * @param mode The operating mode: Sync/Async
* @param durationUs Duration of the clip in microseconds
+ * @param statsFile The output file where the stats data is written
*/
- public void dumpStatistics(String inputReference, long durationUs) {
+ public void dumpStatistics(String inputReference, String componentName, String mode,
+ long durationUs, String statsFile) throws IOException {
String operation = "encode";
- mStats.dumpStatistics(operation, inputReference, durationUs);
+ mStats.dumpStatistics(
+ inputReference, operation, componentName, mode, durationUs, statsFile);
}
/**
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Extractor.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Extractor.java
index 459e2a9..f3024e7 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Extractor.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Extractor.java
@@ -167,9 +167,12 @@
* Write the benchmark logs for the given input file
*
* @param inputReference Name of the input file
+ * @param mimeType Mime type of the muxed file
+ * @param statsFile The output file where the stats data is written
*/
- public void dumpStatistics(String inputReference) {
+ public void dumpStatistics(String inputReference, String mimeType, String statsFile)
+ throws IOException {
String operation = "extract";
- mStats.dumpStatistics(operation, inputReference, mDurationUs);
+ mStats.dumpStatistics(inputReference, operation, mimeType, "", mDurationUs, statsFile);
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Muxer.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Muxer.java
index 49eaa1c..340b539 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Muxer.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Muxer.java
@@ -101,10 +101,13 @@
* Write the benchmark logs for the given input file
*
* @param inputReference Name of the input file
+ * @param muxFormat Format of the muxed output
* @param clipDuration Duration of the given inputReference file
+ * @param statsFile The output file where the stats data is written
*/
- public void dumpStatistics(String inputReference, long clipDuration) {
+ public void dumpStatistics(String inputReference, String muxFormat, long clipDuration,
+ String statsFile) throws IOException {
String operation = "mux";
- mStats.dumpStatistics(operation, inputReference, clipDuration);
+ mStats.dumpStatistics(inputReference, operation, muxFormat, "", clipDuration, statsFile);
}
}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java
new file mode 100644
index 0000000..38b608a
--- /dev/null
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Native.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.media.benchmark.library;
+
+public class Native {
+ static { System.loadLibrary("mediabenchmark_jni"); }
+
+ public native int Extract(String inputFilePath, String inputFileName, String statsFile);
+
+ public native int Mux(String inputFilePath, String inputFileName, String outputFilePath,
+ String statsFile, String format);
+
+ public native int Decode(String inputFilePath, String inputFileName, String statsFile,
+ String codecName, boolean asyncMode);
+
+ public native int Encode(String inputFilePath, String inputFileName, String outputFilePath,
+ String statsFile, String codecName);
+}
diff --git a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Stats.java b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Stats.java
index 18ab5be..7245a3a 100644
--- a/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Stats.java
+++ b/media/tests/benchmark/MediaBenchmarkTest/src/main/java/com/android/media/benchmark/library/Stats.java
@@ -18,6 +18,10 @@
import android.util.Log;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
import java.util.ArrayList;
/**
@@ -91,14 +95,38 @@
}
/**
+ * Writes the stats header to a file
+ * <p>
+ * \param statsFile file where the stats data is to be written
+ **/
+ public boolean writeStatsHeader(String statsFile) throws IOException {
+ File outputFile = new File(statsFile);
+ FileOutputStream out = new FileOutputStream(outputFile, true);
+ if (!outputFile.exists())
+ return false;
+ String statsHeader =
+ "currentTime, fileName, operation, componentName, NDK/SDK, sync/async, setupTime, "
+ + "destroyTime, minimumTime, maximumTime, "
+ + "averageTime, timeToProcess1SecContent, totalBytesProcessedPerSec, "
+ + "timeToFirstFrame, totalSizeInBytes, totalTime\n";
+ out.write(statsHeader.getBytes());
+ out.close();
+ return true;
+ }
+
+ /**
* Dumps the stats of the operation for a given input media.
* <p>
+ * \param inputReference input media
* \param operation describes the operation performed on the input media
* (i.e. extract/mux/decode/encode)
- * \param inputReference input media
- * \param durationUs is a duration of the input media in microseconds.
+ * \param componentName name of the codec/muxFormat/mime
+ * \param mode the operating mode: sync/async.
+ * \param durationUs is a duration of the input media in microseconds.
+ * \param statsFile the file where the stats data is to be written.
*/
- public void dumpStatistics(String operation, String inputReference, long durationUs) {
+ public void dumpStatistics(String inputReference, String operation, String componentName,
+ String mode, long durationUs, String statsFile) throws IOException {
if (mOutputTimer.size() == 0) {
Log.e(TAG, "No output produced");
return;
@@ -121,18 +149,30 @@
maxTimeTakenNs = intervalNs;
}
}
- // Print the Stats
- Log.i(TAG, "Input Reference : " + inputReference);
- Log.i(TAG, "Setup Time in nano sec : " + mInitTimeNs);
- Log.i(TAG, "Average Time in nano sec : " + totalTimeTakenNs / mOutputTimer.size());
- Log.i(TAG, "Time to first frame in nano sec : " + timeToFirstFrameNs);
- Log.i(TAG, "Time taken (in nano sec) to " + operation + " 1 sec of content : " +
- timeTakenPerSec);
- Log.i(TAG, "Total bytes " + operation + "ed : " + size);
- Log.i(TAG, "Number of bytes " + operation + "ed per second : " +
- (size * 1000000000) / totalTimeTakenNs);
- Log.i(TAG, "Minimum Time in nano sec : " + minTimeTakenNs);
- Log.i(TAG, "Maximum Time in nano sec : " + maxTimeTakenNs);
- Log.i(TAG, "Destroy Time in nano sec : " + mDeInitTimeNs);
+
+ // Write the stats row data to file
+ String rowData = "";
+ rowData += System.nanoTime() + ", ";
+ rowData += inputReference + ", ";
+ rowData += operation + ", ";
+ rowData += componentName + ", ";
+ rowData += "SDK, ";
+ rowData += mode + ", ";
+ rowData += mInitTimeNs + ", ";
+ rowData += mDeInitTimeNs + ", ";
+ rowData += minTimeTakenNs + ", ";
+ rowData += maxTimeTakenNs + ", ";
+ rowData += totalTimeTakenNs / mOutputTimer.size() + ", ";
+ rowData += timeTakenPerSec + ", ";
+ rowData += (size * 1000000000) / totalTimeTakenNs + ", ";
+ rowData += timeToFirstFrameNs + ", ";
+ rowData += size + ", ";
+ rowData += totalTimeTakenNs + "\n";
+
+ File outputFile = new File(statsFile);
+ FileOutputStream out = new FileOutputStream(outputFile, true);
+ assert outputFile.exists() : "Failed to open the stats file for writing!";
+ out.write(rowData.getBytes());
+ out.close();
}
-}
\ No newline at end of file
+}
diff --git a/media/tests/benchmark/README.md b/media/tests/benchmark/README.md
index 6627462..05fbe6f 100644
--- a/media/tests/benchmark/README.md
+++ b/media/tests/benchmark/README.md
@@ -145,3 +145,12 @@
```
adb shell /data/local/tmp/C2DecoderTest -P /data/local/tmp/MediaBenchmark/res/
```
+## C2 Encoder
+
+The test encodes input stream and benchmarks the codec2 encoders available in device.
+
+Setup steps are same as [extractor](#extractor).
+
+```
+adb shell /data/local/tmp/C2EncoderTest -P /data/local/tmp/MediaBenchmark/res/
+```
diff --git a/media/tests/benchmark/src/native/common/Android.bp b/media/tests/benchmark/src/native/common/Android.bp
index babc329..f8ea25c 100644
--- a/media/tests/benchmark/src/native/common/Android.bp
+++ b/media/tests/benchmark/src/native/common/Android.bp
@@ -29,7 +29,7 @@
export_include_dirs: ["."],
- ldflags: ["-Wl,-Bsymbolic"]
+ ldflags: ["-Wl,-Bsymbolic"],
}
cc_defaults {
@@ -55,7 +55,7 @@
cflags: [
"-Wall",
"-Werror",
- ]
+ ],
}
cc_library_static {
@@ -66,18 +66,20 @@
srcs: [
"BenchmarkC2Common.cpp",
+ "BenchmarkCommon.cpp",
+ "Stats.cpp",
+ "utils/Timers.cpp",
],
export_include_dirs: ["."],
- ldflags: ["-Wl,-Bsymbolic"]
+ ldflags: ["-Wl,-Bsymbolic"],
}
cc_defaults {
name: "libmediabenchmark_codec2_common-defaults",
defaults: [
- "libmediabenchmark_common-defaults",
"libcodec2-hidl-client-defaults",
"libmediabenchmark_soft_sanitize_all-defaults",
],
@@ -88,7 +90,14 @@
shared_libs: [
"libcodec2_client",
- ]
+ "libmediandk",
+ "liblog",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
}
// public dependency for native implementation
@@ -102,5 +111,5 @@
"signed-integer-overflow",
],
cfi: true,
- }
+ },
}
diff --git a/media/tests/benchmark/src/native/common/BenchmarkCommon.cpp b/media/tests/benchmark/src/native/common/BenchmarkCommon.cpp
index ab74508..cb49b8e 100644
--- a/media/tests/benchmark/src/native/common/BenchmarkCommon.cpp
+++ b/media/tests/benchmark/src/native/common/BenchmarkCommon.cpp
@@ -56,6 +56,7 @@
void OnErrorCB(AMediaCodec *codec, void *userdata, media_status_t err, int32_t actionCode,
const char *detail) {
+ (void)codec;
ALOGE("OnErrorCB: err(%d), actionCode(%d), detail(%s)", err, actionCode, detail);
CallBackHandle *self = (CallBackHandle *)userdata;
self->mSawError = true;
@@ -91,7 +92,7 @@
/* Configure codec with the given format*/
const char *s = AMediaFormat_toString(format);
- ALOGV("Input format: %s\n", s);
+ ALOGI("Input format: %s\n", s);
media_status_t status = AMediaCodec_configure(codec, format, nullptr, nullptr, isEncoder);
if (status != AMEDIA_OK) {
@@ -99,4 +100,4 @@
return nullptr;
}
return codec;
-}
\ No newline at end of file
+}
diff --git a/media/tests/benchmark/src/native/common/BenchmarkCommon.h b/media/tests/benchmark/src/native/common/BenchmarkCommon.h
index 8153a86..c11fe36 100644
--- a/media/tests/benchmark/src/native/common/BenchmarkCommon.h
+++ b/media/tests/benchmark/src/native/common/BenchmarkCommon.h
@@ -17,15 +17,18 @@
#ifndef __BENCHMARK_COMMON_H__
#define __BENCHMARK_COMMON_H__
+#include <sys/stat.h>
#include <inttypes.h>
#include <mutex>
#include <queue>
#include <thread>
+#include <iostream>
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaError.h>
#include "Stats.h"
+#define UNUSED(x) (void)(x)
using namespace std;
diff --git a/media/tests/benchmark/src/native/common/Stats.cpp b/media/tests/benchmark/src/native/common/Stats.cpp
index 2d9bb31..bfde125 100644
--- a/media/tests/benchmark/src/native/common/Stats.cpp
+++ b/media/tests/benchmark/src/native/common/Stats.cpp
@@ -17,8 +17,10 @@
//#define LOG_NDEBUG 0
#define LOG_TAG "Stats"
+#include <ctime>
#include <iostream>
#include <stdint.h>
+#include <fstream>
#include "Stats.h"
@@ -28,16 +30,20 @@
* \param operation describes the operation performed on the input media
* (i.e. extract/mux/decode/encode)
* \param inputReference input media
- * \param duarationUs is a duration of the input media in microseconds.
+ * \param durationUs is a duration of the input media in microseconds.
+ * \param componentName describes the codecName/muxFormat/mimeType.
+ * \param mode the operating mode: sync/async.
+ * \param statsFile the file where the stats data is to be written.
*/
-void Stats::dumpStatistics(std::string operation, std::string inputReference, int64_t duarationUs) {
+void Stats::dumpStatistics(string operation, string inputReference, int64_t durationUs,
+ string componentName, string mode, string statsFile) {
ALOGV("In %s", __func__);
if (!mOutputTimer.size()) {
ALOGE("No output produced");
return;
}
nsecs_t totalTimeTakenNs = getTotalTime();
- nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / duarationUs;
+ nsecs_t timeTakenPerSec = (totalTimeTakenNs * 1000000) / durationUs;
nsecs_t timeToFirstFrameNs = *mOutputTimer.begin() - mStartTimeNs;
int32_t size = std::accumulate(mFrameSizes.begin(), mFrameSizes.end(), 0);
// get min and max output intervals.
@@ -52,15 +58,32 @@
else if (maxTimeTakenNs < intervalNs) maxTimeTakenNs = intervalNs;
}
- // Print the Stats
- std::cout << "Input Reference : " << inputReference << endl;
- std::cout << "Setup Time in nano sec : " << mInitTimeNs << endl;
- std::cout << "Average Time in nano sec : " << totalTimeTakenNs / mOutputTimer.size() << endl;
- std::cout << "Time to first frame in nano sec : " << timeToFirstFrameNs << endl;
- std::cout << "Time taken (in nano sec) to " << operation
- << " 1 sec of content : " << timeTakenPerSec << endl;
- std::cout << "Total bytes " << operation << "ed : " << size << endl;
- std::cout << "Minimum Time in nano sec : " << minTimeTakenNs << endl;
- std::cout << "Maximum Time in nano sec : " << maxTimeTakenNs << endl;
- std::cout << "Destroy Time in nano sec : " << mDeInitTimeNs << endl;
+ // Write the stats data to file.
+ int64_t dataSize = size;
+ int64_t bytesPerSec = ((int64_t)dataSize * 1000000000) / totalTimeTakenNs;
+ string rowData = "";
+ rowData.append(to_string(systemTime(CLOCK_MONOTONIC)) + ", ");
+ rowData.append(inputReference + ", ");
+ rowData.append(operation + ", ");
+ rowData.append(componentName + ", ");
+ rowData.append("NDK, ");
+ rowData.append(mode + ", ");
+ rowData.append(to_string(mInitTimeNs) + ", ");
+ rowData.append(to_string(mDeInitTimeNs) + ", ");
+ rowData.append(to_string(minTimeTakenNs) + ", ");
+ rowData.append(to_string(maxTimeTakenNs) + ", ");
+ rowData.append(to_string(totalTimeTakenNs / mOutputTimer.size()) + ", ");
+ rowData.append(to_string(timeTakenPerSec) + ", ");
+ rowData.append(to_string(bytesPerSec) + ", ");
+ rowData.append(to_string(timeToFirstFrameNs) + ", ");
+ rowData.append(to_string(size) + ",");
+ rowData.append(to_string(totalTimeTakenNs) + ",\n");
+
+ ofstream out(statsFile, ios::out | ios::app);
+ if(out.bad()) {
+ ALOGE("Failed to open stats file for writing!");
+ return;
+ }
+ out << rowData;
+ out.close();
}
diff --git a/media/tests/benchmark/src/native/common/Stats.h b/media/tests/benchmark/src/native/common/Stats.h
index 2f556ee..18e4b06 100644
--- a/media/tests/benchmark/src/native/common/Stats.h
+++ b/media/tests/benchmark/src/native/common/Stats.h
@@ -18,6 +18,7 @@
#define __STATS_H__
#include <android/log.h>
+#include <inttypes.h>
#ifndef ALOG
#define ALOG(priority, tag, ...) ((void)__android_log_print(ANDROID_##priority, tag, __VA_ARGS__))
@@ -25,6 +26,12 @@
#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)
+
+#ifndef LOG_NDEBUG
+#define LOG_NDEBUG 1
+#endif
+
#if LOG_NDEBUG
#define ALOGV(cond, ...) ((void)0)
#else
@@ -95,7 +102,8 @@
return (*(mOutputTimer.end() - 1) - mStartTimeNs);
}
- void dumpStatistics(std::string operation, std::string inputReference, int64_t duarationUs);
+ void dumpStatistics(string operation, string inputReference, int64_t duarationUs,
+ string codecName = "", string mode = "", string statsFile = "");
};
#endif // __STATS_H__
diff --git a/media/tests/benchmark/src/native/decoder/Android.bp b/media/tests/benchmark/src/native/decoder/Android.bp
index b5072ab..9791c11 100644
--- a/media/tests/benchmark/src/native/decoder/Android.bp
+++ b/media/tests/benchmark/src/native/decoder/Android.bp
@@ -27,24 +27,26 @@
export_include_dirs: ["."],
- ldflags: ["-Wl,-Bsymbolic"]
+ ldflags: ["-Wl,-Bsymbolic"],
}
cc_library_static {
name: "libmediabenchmark_codec2_decoder",
defaults: [
- "libmediabenchmark_common-defaults",
"libmediabenchmark_codec2_common-defaults",
],
- srcs: ["C2Decoder.cpp"],
+ srcs: [
+ "C2Decoder.cpp",
+ "Decoder.cpp",
+ ],
static_libs: [
"libmediabenchmark_codec2_common",
- "libmediabenchmark_extractor",
+ "libmediabenchmark_codec2_extractor",
],
export_include_dirs: ["."],
- ldflags: ["-Wl,-Bsymbolic"]
+ ldflags: ["-Wl,-Bsymbolic"],
}
diff --git a/media/tests/benchmark/src/native/decoder/Decoder.cpp b/media/tests/benchmark/src/native/decoder/Decoder.cpp
index ac0d525..b797cc9 100644
--- a/media/tests/benchmark/src/native/decoder/Decoder.cpp
+++ b/media/tests/benchmark/src/native/decoder/Decoder.cpp
@@ -238,10 +238,11 @@
mStats->setDeInitTime(timeTaken);
}
-void Decoder::dumpStatistics(string inputReference) {
+void Decoder::dumpStatistics(string inputReference, string componentName, string mode,
+ string statsFile) {
int64_t durationUs = mExtractor->getClipDuration();
string operation = "decode";
- mStats->dumpStatistics(operation, inputReference, durationUs);
+ mStats->dumpStatistics(operation, inputReference, durationUs, componentName, mode, statsFile);
}
void Decoder::resetDecoder() {
diff --git a/media/tests/benchmark/src/native/decoder/Decoder.h b/media/tests/benchmark/src/native/decoder/Decoder.h
index aeda080..f3fa6a1 100644
--- a/media/tests/benchmark/src/native/decoder/Decoder.h
+++ b/media/tests/benchmark/src/native/decoder/Decoder.h
@@ -71,7 +71,8 @@
int32_t decode(uint8_t *inputBuffer, vector<AMediaCodecBufferInfo> &frameInfo,
string &codecName, bool asyncMode, FILE *outFp = nullptr);
- void dumpStatistics(string inputReference);
+ void dumpStatistics(string inputReference, string componentName = "", string mode = "",
+ string statsFile = "");
private:
AMediaCodec *mCodec;
diff --git a/media/tests/benchmark/src/native/encoder/Android.bp b/media/tests/benchmark/src/native/encoder/Android.bp
index 239f378..8de7823 100644
--- a/media/tests/benchmark/src/native/encoder/Android.bp
+++ b/media/tests/benchmark/src/native/encoder/Android.bp
@@ -23,11 +23,31 @@
srcs: ["Encoder.cpp"],
- static_libs: ["libmediabenchmark_extractor",
- "libmediabenchmark_decoder",
+ static_libs: [
+ "libmediabenchmark_extractor",
+ "libmediabenchmark_decoder",
],
export_include_dirs: ["."],
- ldflags: ["-Wl,-Bsymbolic"]
+ ldflags: ["-Wl,-Bsymbolic"],
+}
+
+cc_library_static {
+ name: "libmediabenchmark_codec2_encoder",
+ defaults: [
+ "libmediabenchmark_codec2_common-defaults",
+ ],
+
+ srcs: ["C2Encoder.cpp"],
+
+ static_libs: [
+ "libmediabenchmark_codec2_common",
+ "libmediabenchmark_codec2_extractor",
+ "libmediabenchmark_codec2_decoder",
+ ],
+
+ export_include_dirs: ["."],
+
+ ldflags: ["-Wl,-Bsymbolic"],
}
diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.cpp b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp
new file mode 100644
index 0000000..33429ef
--- /dev/null
+++ b/media/tests/benchmark/src/native/encoder/C2Encoder.cpp
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "C2Encoder"
+
+#include "C2Encoder.h"
+
+int32_t C2Encoder::createCodec2Component(string compName, AMediaFormat *format) {
+ ALOGV("In %s", __func__);
+ mListener.reset(new CodecListener(
+ [this](std::list<std::unique_ptr<C2Work>> &workItems) { handleWorkDone(workItems); }));
+ if (!mListener) return -1;
+
+ const char *mime = nullptr;
+ AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
+ if (!mime) {
+ ALOGE("Error in AMediaFormat_getString");
+ return -1;
+ }
+ // Configure the plugin with Input properties
+ std::vector<C2Param *> configParam;
+ if (!strncmp(mime, "audio/", 6)) {
+ mIsAudioEncoder = true;
+ int32_t numChannels;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate)) {
+ ALOGE("AMEDIAFORMAT_KEY_SAMPLE_RATE not set");
+ return -1;
+ }
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &numChannels)) {
+ ALOGE("AMEDIAFORMAT_KEY_CHANNEL_COUNT not set");
+ return -1;
+ }
+ C2StreamSampleRateInfo::input sampleRateInfo(0u, mSampleRate);
+ C2StreamChannelCountInfo::input channelCountInfo(0u, numChannels);
+ configParam.push_back(&sampleRateInfo);
+ configParam.push_back(&channelCountInfo);
+ } else {
+ mIsAudioEncoder = false;
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth)) {
+ ALOGE("AMEDIAFORMAT_KEY_WIDTH not set");
+ return -1;
+ }
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight)) {
+ ALOGE("AMEDIAFORMAT_KEY_HEIGHT not set");
+ return -1;
+ }
+ C2StreamPictureSizeInfo::input inputSize(0u, mWidth, mHeight);
+ configParam.push_back(&inputSize);
+
+ if (!AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &mFrameRate) ||
+ (mFrameRate <= 0)) {
+ mFrameRate = KDefaultFrameRate;
+ }
+ }
+
+ int64_t sTime = mStats->getCurTime();
+ mComponent = mClient->CreateComponentByName(compName.c_str(), mListener, &mClient);
+ if (mComponent == nullptr) {
+ ALOGE("Create component failed for %s", compName.c_str());
+ return -1;
+ }
+ std::vector<std::unique_ptr<C2SettingResult>> failures;
+ int32_t status = mComponent->config(configParam, C2_DONT_BLOCK, &failures);
+ if (failures.size() != 0) {
+ ALOGE("Invalid Configuration");
+ return -1;
+ }
+
+ status |= mComponent->start();
+ int64_t eTime = mStats->getCurTime();
+ int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
+ mStats->setInitTime(timeTaken);
+ return status;
+}
+
+// In encoder components, fetch the size of input buffer allocated
+int32_t C2Encoder::getInputMaxBufSize() {
+ int32_t bitStreamInfo[1] = {0};
+ std::vector<std::unique_ptr<C2Param>> inParams;
+ c2_status_t status = mComponent->query({}, {C2StreamMaxBufferSizeInfo::input::PARAM_TYPE},
+ C2_DONT_BLOCK, &inParams);
+ if (status != C2_OK && inParams.size() == 0) {
+ ALOGE("Query MaxBufferSizeInfo failed => %d", status);
+ return status;
+ } else {
+ size_t offset = sizeof(C2Param);
+ for (size_t i = 0; i < inParams.size(); ++i) {
+ C2Param *param = inParams[i].get();
+ bitStreamInfo[i] = *(int32_t *)((uint8_t *)param + offset);
+ }
+ }
+ mInputMaxBufSize = bitStreamInfo[0];
+ if (mInputMaxBufSize < 0) {
+ ALOGE("Invalid mInputMaxBufSize %d\n", mInputMaxBufSize);
+ return -1;
+ }
+ return status;
+}
+
+int32_t C2Encoder::encodeFrames(ifstream &eleStream, size_t inputBufferSize) {
+ ALOGV("In %s", __func__);
+ int32_t frameSize = 0;
+ if (!mIsAudioEncoder) {
+ frameSize = mWidth * mHeight * 3 / 2;
+ } else {
+ frameSize = DEFAULT_AUDIO_FRAME_SIZE;
+ if (getInputMaxBufSize() != 0) return -1;
+ if (frameSize > mInputMaxBufSize) {
+ frameSize = mInputMaxBufSize;
+ }
+ }
+ int32_t numFrames = (inputBufferSize + frameSize - 1) / frameSize;
+ // Temporary buffer to read data from the input file
+ char *data = (char *)malloc(frameSize);
+ if (!data) {
+ ALOGE("Insufficient memory to read from input file");
+ return -1;
+ }
+
+ typedef std::unique_lock<std::mutex> ULock;
+ uint64_t presentationTimeUs = 0;
+ size_t offset = 0;
+ c2_status_t status = C2_OK;
+
+ mStats->setStartTime();
+ while (numFrames > 0) {
+ std::unique_ptr<C2Work> work;
+ // Prepare C2Work
+ {
+ ULock l(mQueueLock);
+ if (mWorkQueue.empty()) mQueueCondition.wait_for(l, MAX_RETRY * TIME_OUT);
+ if (!mWorkQueue.empty()) {
+ mStats->addInputTime();
+ work.swap(mWorkQueue.front());
+ mWorkQueue.pop_front();
+ } else {
+ cout << "Wait for generating C2Work exceeded timeout" << endl;
+ return -1;
+ }
+ }
+
+ if (mIsAudioEncoder) {
+ presentationTimeUs = mNumInputFrame * frameSize * (1000000 / mSampleRate);
+ } else {
+ presentationTimeUs = mNumInputFrame * (1000000 / mFrameRate);
+ }
+ uint32_t flags = 0;
+ if (numFrames == 1) flags |= C2FrameData::FLAG_END_OF_STREAM;
+
+ work->input.flags = (C2FrameData::flags_t)flags;
+ work->input.ordinal.timestamp = presentationTimeUs;
+ work->input.ordinal.frameIndex = mNumInputFrame;
+ work->input.buffers.clear();
+
+ if (inputBufferSize - offset < frameSize) {
+ frameSize = inputBufferSize - offset;
+ }
+ eleStream.read(data, frameSize);
+ if (eleStream.gcount() != frameSize) {
+ ALOGE("read() from file failed. Incorrect bytes read");
+ return -1;
+ }
+ offset += frameSize;
+
+ if (frameSize) {
+ if (mIsAudioEncoder) {
+ std::shared_ptr<C2LinearBlock> block;
+ status = mLinearPool->fetchLinearBlock(
+ frameSize, {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block);
+ if (status != C2_OK || !block) {
+ cout << "fetchLinearBlock failed : " << status << endl;
+ return status;
+ }
+ C2WriteView view = block->map().get();
+ if (view.error() != C2_OK) {
+ cout << "C2LinearBlock::map() failed : " << view.error() << endl;
+ return view.error();
+ }
+
+ memcpy(view.base(), data, frameSize);
+ work->input.buffers.emplace_back(new LinearBuffer(block));
+ } else {
+ std::shared_ptr<C2GraphicBlock> block;
+ status = mGraphicPool->fetchGraphicBlock(
+ mWidth, mHeight, HAL_PIXEL_FORMAT_YV12,
+ {C2MemoryUsage::CPU_READ, C2MemoryUsage::CPU_WRITE}, &block);
+ if (status != C2_OK || !block) {
+ cout << "fetchGraphicBlock failed : " << status << endl;
+ return status;
+ }
+ C2GraphicView view = block->map().get();
+ if (view.error() != C2_OK) {
+ cout << "C2GraphicBlock::map() failed : " << view.error() << endl;
+ return view.error();
+ }
+
+ uint8_t *pY = view.data()[C2PlanarLayout::PLANE_Y];
+ uint8_t *pU = view.data()[C2PlanarLayout::PLANE_U];
+ uint8_t *pV = view.data()[C2PlanarLayout::PLANE_V];
+ memcpy(pY, data, mWidth * mHeight);
+ memcpy(pU, data + mWidth * mHeight, (mWidth * mHeight >> 2));
+ memcpy(pV, data + (mWidth * mHeight * 5 >> 2), mWidth * mHeight >> 2);
+ work->input.buffers.emplace_back(new GraphicBuffer(block));
+ }
+ mStats->addFrameSize(frameSize);
+ }
+
+ work->worklets.clear();
+ work->worklets.emplace_back(new C2Worklet);
+
+ std::list<std::unique_ptr<C2Work>> items;
+ items.push_back(std::move(work));
+ // queue() invokes process() function of C2 Plugin.
+ status = mComponent->queue(&items);
+ if (status != C2_OK) {
+ ALOGE("queue failed");
+ return status;
+ }
+ ALOGV("Frame #%d size = %d queued", mNumInputFrame, frameSize);
+ numFrames--;
+ mNumInputFrame++;
+ }
+ free(data);
+ return status;
+}
+
+void C2Encoder::deInitCodec() {
+ ALOGV("In %s", __func__);
+ if (!mComponent) return;
+
+ int64_t sTime = mStats->getCurTime();
+ mComponent->stop();
+ mComponent->release();
+ mComponent = nullptr;
+ int64_t eTime = mStats->getCurTime();
+ int64_t timeTaken = mStats->getTimeDiff(sTime, eTime);
+ mStats->setDeInitTime(timeTaken);
+}
+
+void C2Encoder::dumpStatistics(string inputReference, int64_t durationUs) {
+ string operation = "c2encode";
+ mStats->dumpStatistics(operation, inputReference, durationUs);
+}
+
+void C2Encoder::resetEncoder() {
+ mIsAudioEncoder = false;
+ mNumInputFrame = 0;
+ mEos = false;
+ if (mStats) mStats->reset();
+}
diff --git a/media/tests/benchmark/src/native/encoder/C2Encoder.h b/media/tests/benchmark/src/native/encoder/C2Encoder.h
new file mode 100644
index 0000000..a4ca097
--- /dev/null
+++ b/media/tests/benchmark/src/native/encoder/C2Encoder.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __C2_ENCODER_H__
+#define __C2_ENCODER_H__
+
+#include <stdio.h>
+#include <algorithm>
+#include <fstream>
+
+#include "BenchmarkC2Common.h"
+
+#define DEFAULT_AUDIO_FRAME_SIZE 4096
+
+constexpr int32_t KDefaultFrameRate = 25;
+
+class C2Encoder : public BenchmarkC2Common {
+ public:
+ C2Encoder()
+ : mIsAudioEncoder(false),
+ mWidth(0),
+ mHeight(0),
+ mNumInputFrame(0),
+ mComponent(nullptr) {}
+
+ int32_t createCodec2Component(string codecName, AMediaFormat *format);
+
+ int32_t encodeFrames(ifstream &eleStream, size_t inputBufferSize);
+
+ int32_t getInputMaxBufSize();
+
+ void deInitCodec();
+
+ void dumpStatistics(string inputReference, int64_t durationUs);
+
+ void resetEncoder();
+
+ private:
+ bool mIsAudioEncoder;
+
+ int32_t mWidth;
+ int32_t mHeight;
+ int32_t mFrameRate;
+ int32_t mSampleRate;
+
+ int32_t mNumInputFrame;
+ int32_t mInputMaxBufSize;
+
+ std::shared_ptr<android::Codec2Client::Listener> mListener;
+ std::shared_ptr<android::Codec2Client::Component> mComponent;
+};
+
+#endif // __C2_ENCODER_H__
diff --git a/media/tests/benchmark/src/native/encoder/Encoder.cpp b/media/tests/benchmark/src/native/encoder/Encoder.cpp
index a5605de..f119cf3 100644
--- a/media/tests/benchmark/src/native/encoder/Encoder.cpp
+++ b/media/tests/benchmark/src/native/encoder/Encoder.cpp
@@ -174,9 +174,10 @@
memset(&mParams, 0, sizeof mParams);
}
-void Encoder::dumpStatistics(string inputReference, int64_t durationUs) {
+void Encoder::dumpStatistics(string inputReference, int64_t durationUs, string componentName,
+ string mode, string statsFile) {
string operation = "encode";
- mStats->dumpStatistics(operation, inputReference, durationUs);
+ mStats->dumpStatistics(operation, inputReference, durationUs, componentName, mode, statsFile);
}
int32_t Encoder::encode(string &codecName, ifstream &eleStream, size_t eleSize,
@@ -206,7 +207,7 @@
AMediaFormat_setInt32(mFormat, AMEDIAFORMAT_KEY_BIT_RATE, mParams.bitrate);
}
const char *s = AMediaFormat_toString(mFormat);
- ALOGV("Input format: %s\n", s);
+ ALOGI("Input format: %s\n", s);
int64_t sTime = mStats->getCurTime();
mCodec = createMediaCodec(mFormat, mMime, codecName, true /*isEncoder*/);
diff --git a/media/tests/benchmark/src/native/encoder/Encoder.h b/media/tests/benchmark/src/native/encoder/Encoder.h
index 6059c4a..3d12600 100644
--- a/media/tests/benchmark/src/native/encoder/Encoder.h
+++ b/media/tests/benchmark/src/native/encoder/Encoder.h
@@ -75,7 +75,8 @@
int32_t encode(std::string &codecName, std::ifstream &eleStream, size_t eleSize, bool asyncMode,
encParameter encParams, char *mime);
- void dumpStatistics(string inputReference, int64_t durationUs);
+ void dumpStatistics(string inputReference, int64_t durationUs, string codecName = "",
+ string mode = "", string statsFile = "");
private:
AMediaCodec *mCodec;
diff --git a/media/tests/benchmark/src/native/extractor/Android.bp b/media/tests/benchmark/src/native/extractor/Android.bp
index dfd0d49..7ed9476 100644
--- a/media/tests/benchmark/src/native/extractor/Android.bp
+++ b/media/tests/benchmark/src/native/extractor/Android.bp
@@ -27,3 +27,20 @@
ldflags: ["-Wl,-Bsymbolic"]
}
+
+cc_library_static {
+ name: "libmediabenchmark_codec2_extractor",
+ defaults: [
+ "libmediabenchmark_codec2_common-defaults",
+ ],
+
+ srcs: ["Extractor.cpp"],
+
+ static_libs: [
+ "libmediabenchmark_codec2_common",
+ ],
+
+ export_include_dirs: ["."],
+
+ ldflags: ["-Wl,-Bsymbolic"]
+}
diff --git a/media/tests/benchmark/src/native/extractor/Extractor.cpp b/media/tests/benchmark/src/native/extractor/Extractor.cpp
index b4cad0b..f0bb3b9 100644
--- a/media/tests/benchmark/src/native/extractor/Extractor.cpp
+++ b/media/tests/benchmark/src/native/extractor/Extractor.cpp
@@ -111,9 +111,9 @@
return AMEDIA_OK;
}
-void Extractor::dumpStatistics(string inputReference) {
+void Extractor::dumpStatistics(string inputReference, string componentName, string statsFile) {
string operation = "extract";
- mStats->dumpStatistics(operation, inputReference, mDurationUs);
+ mStats->dumpStatistics(operation, inputReference, mDurationUs, componentName, "", statsFile);
}
void Extractor::deInitExtractor() {
diff --git a/media/tests/benchmark/src/native/extractor/Extractor.h b/media/tests/benchmark/src/native/extractor/Extractor.h
index 4c39a72..1694fc7 100644
--- a/media/tests/benchmark/src/native/extractor/Extractor.h
+++ b/media/tests/benchmark/src/native/extractor/Extractor.h
@@ -45,7 +45,7 @@
int32_t extract(int32_t trackId);
- void dumpStatistics(std::string inputReference);
+ void dumpStatistics(string inputReference, string componentName = "", string statsFile = "");
void deInitExtractor();
diff --git a/media/tests/benchmark/src/native/muxer/Muxer.cpp b/media/tests/benchmark/src/native/muxer/Muxer.cpp
index b297a66..f8627cb 100644
--- a/media/tests/benchmark/src/native/muxer/Muxer.cpp
+++ b/media/tests/benchmark/src/native/muxer/Muxer.cpp
@@ -29,7 +29,7 @@
int64_t sTime = mStats->getCurTime();
mMuxer = AMediaMuxer_new(fd, (OutputFormat)outputFormat);
if (!mMuxer) {
- cout << "[ WARN ] Test Skipped. Unable to create muxer \n";
+ ALOGV("Unable to create muxer");
return AMEDIA_ERROR_INVALID_OBJECT;
}
/*
@@ -38,7 +38,7 @@
*/
ssize_t index = AMediaMuxer_addTrack(mMuxer, mFormat);
if (index < 0) {
- cout << "[ WARN ] Test Skipped. Format not supported \n";
+ ALOGV("Format not supported");
return index;
}
AMediaMuxer_start(mMuxer);
@@ -66,9 +66,10 @@
if (mStats) mStats->reset();
}
-void Muxer::dumpStatistics(string inputReference) {
+void Muxer::dumpStatistics(string inputReference, string componentName, string statsFile) {
string operation = "mux";
- mStats->dumpStatistics(operation, inputReference, mExtractor->getClipDuration());
+ mStats->dumpStatistics(operation, inputReference, mExtractor->getClipDuration(), componentName,
+ "", statsFile);
}
int32_t Muxer::mux(uint8_t *inputBuffer, vector<AMediaCodecBufferInfo> &frameInfos) {
diff --git a/media/tests/benchmark/src/native/muxer/Muxer.h b/media/tests/benchmark/src/native/muxer/Muxer.h
index eee3146..860fdaf 100644
--- a/media/tests/benchmark/src/native/muxer/Muxer.h
+++ b/media/tests/benchmark/src/native/muxer/Muxer.h
@@ -51,7 +51,7 @@
/* Process the frames and give Muxed output */
int32_t mux(uint8_t *inputBuffer, vector<AMediaCodecBufferInfo> &frameSizes);
- void dumpStatistics(string inputReference);
+ void dumpStatistics(string inputReference, string codecName = "", string statsFile = "");
private:
AMediaFormat *mFormat;
diff --git a/media/tests/benchmark/tests/Android.bp b/media/tests/benchmark/tests/Android.bp
index 128d055..f46fa4a 100644
--- a/media/tests/benchmark/tests/Android.bp
+++ b/media/tests/benchmark/tests/Android.bp
@@ -87,8 +87,25 @@
srcs: ["C2DecoderTest.cpp"],
static_libs: [
- "libmediabenchmark_extractor",
+ "libmediabenchmark_codec2_extractor",
"libmediabenchmark_codec2_common",
"libmediabenchmark_codec2_decoder",
],
}
+
+cc_test {
+ name: "C2EncoderTest",
+ gtest: true,
+ defaults: [
+ "libmediabenchmark_codec2_common-defaults",
+ ],
+
+ srcs: ["C2EncoderTest.cpp"],
+
+ static_libs: [
+ "libmediabenchmark_codec2_extractor",
+ "libmediabenchmark_codec2_decoder",
+ "libmediabenchmark_codec2_common",
+ "libmediabenchmark_codec2_encoder",
+ ],
+}
diff --git a/media/tests/benchmark/tests/C2DecoderTest.cpp b/media/tests/benchmark/tests/C2DecoderTest.cpp
index 3531d8a..ecd9759 100644
--- a/media/tests/benchmark/tests/C2DecoderTest.cpp
+++ b/media/tests/benchmark/tests/C2DecoderTest.cpp
@@ -29,82 +29,64 @@
class C2DecoderTest : public ::testing::TestWithParam<pair<string, string>> {
public:
- C2DecoderTest() : mDecoder(nullptr), disableTest(false) { setupC2DecoderTest(); }
+ C2DecoderTest() : mDecoder(nullptr) {}
+
+ ~C2DecoderTest() {
+ if (!mCodecList.empty()) {
+ mCodecList.clear();
+ }
+ if (mDecoder) {
+ delete mDecoder;
+ mDecoder = nullptr;
+ }
+ }
+
+ virtual void SetUp() override { setupC2DecoderTest(); }
void setupC2DecoderTest();
vector<string> mCodecList;
C2Decoder *mDecoder;
- bool disableTest;
};
void C2DecoderTest::setupC2DecoderTest() {
mDecoder = new C2Decoder();
- if (!mDecoder) {
- cout << "[ WARN ] Test Skipped. C2Decoder creation failed\n";
- disableTest = true;
- return;
- }
+ ASSERT_NE(mDecoder, nullptr) << "C2Decoder creation failed";
+
int32_t status = mDecoder->setupCodec2();
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Codec2 setup failed \n";
- disableTest = true;
- return;
- }
+ ASSERT_EQ(status, 0) << "Codec2 setup failed";
+
mCodecList = mDecoder->getSupportedComponentList(false /* isEncoder*/);
- if (!mCodecList.size()) {
- cout << "[ WARN ] Test Skipped. Codec2 client didn't recognise any component \n";
- disableTest = true;
- return;
- }
+ ASSERT_GT(mCodecList.size(), 0) << "Codec2 client didn't recognise any component";
}
TEST_P(C2DecoderTest, Codec2Decode) {
- if (disableTest) return;
-
ALOGV("Decode the samples given by extractor using codec2");
string inputFile = gEnv->getRes() + GetParam().first;
FILE *inputFp = fopen(inputFile.c_str(), "rb");
- if (!inputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open input file" << inputFile
- << " for reading \n";
- return;
- }
+ ASSERT_NE(inputFp, nullptr) << "Unable to open " << inputFile << " file for reading";
Extractor *extractor = new Extractor();
- if (!extractor) {
- cout << "[ WARN ] Test Skipped. Extractor creation failed \n";
- return;
- }
+ ASSERT_NE(extractor, nullptr) << "Extractor creation failed";
// Read file properties
- fseek(inputFp, 0, SEEK_END);
- size_t fileSize = ftell(inputFp);
- fseek(inputFp, 0, SEEK_SET);
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
int32_t fd = fileno(inputFp);
- if (fileSize > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Input file size is greater than the threshold memory "
- "dedicated to the test \n";
- }
+ ASSERT_LE(fileSize, kMaxBufferSize)
+ << "Input file size is greater than the threshold memory dedicated to the test";
int32_t trackCount = extractor->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- cout << "[ WARN ] Test Skipped. initExtractor failed\n";
- return;
- }
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
+
for (int32_t curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = extractor->setupTrackFormat(curTrack);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Track Format invalid \n";
- return;
- }
+ ASSERT_EQ(status, 0) << "Track Format invalid";
uint8_t *inputBuffer = (uint8_t *)malloc(fileSize);
- if (!inputBuffer) {
- cout << "[ WARN ] Test Skipped. Insufficient memory \n";
- return;
- }
+ ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory";
vector<AMediaCodecBufferInfo> frameInfo;
AMediaCodecBufferInfo info;
@@ -116,11 +98,8 @@
void *csdBuffer = extractor->getCSDSample(info, idx);
if (!csdBuffer || !info.size) break;
// copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > fileSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
+ ASSERT_LE(inputBufferOffset + info.size, fileSize) << "Memory allocated not sufficient";
+
memcpy(inputBuffer + inputBufferOffset, csdBuffer, info.size);
frameInfo.push_back(info);
inputBufferOffset += info.size;
@@ -132,11 +111,8 @@
status = extractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > fileSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
+ ASSERT_LE(inputBufferOffset + info.size, fileSize) << "Memory allocated not sufficient";
+
memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
frameInfo.push_back(info);
inputBufferOffset += info.size;
@@ -148,21 +124,18 @@
if (codecName.find(GetParam().second) != string::npos &&
codecName.find("secure") == string::npos) {
status = mDecoder->createCodec2Component(codecName, format);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Create component failed for " << codecName
- << "\n";
- continue;
- }
+ ASSERT_EQ(status, 0) << "Create component failed for " << codecName;
// Send the inputs to C2 Decoder and wait till all buffers are returned.
- mDecoder->decodeFrames(inputBuffer, frameInfo);
+ status = mDecoder->decodeFrames(inputBuffer, frameInfo);
+ ASSERT_EQ(status, 0) << "Decoder failed for " << codecName;
+
mDecoder->waitOnInputConsumption();
- if (!mDecoder->mEos) {
- cout << "[ WARN ] Test Failed. Didn't receive EOS \n";
- }
+ ASSERT_TRUE(mDecoder->mEos) << "Test Failed. Didn't receive EOS \n";
+
mDecoder->deInitCodec();
int64_t durationUs = extractor->getClipDuration();
- cout << "codec: " << codecName << endl;
+ ALOGV("codec : %s", codecName.c_str());
mDecoder->dumpStatistics(GetParam().first, durationUs);
mDecoder->resetDecoder();
}
@@ -172,6 +145,7 @@
extractor->deInitExtractor();
delete extractor;
delete mDecoder;
+ mDecoder = nullptr;
}
}
@@ -179,26 +153,24 @@
// Add wav files
INSTANTIATE_TEST_SUITE_P(
AudioDecoderTest, C2DecoderTest,
- ::testing::Values(
- make_pair("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "aac"),
- make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", "mp3"),
- make_pair("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "amrnb"),
- make_pair("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "amrnb"),
- make_pair("bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", "vorbis"),
- make_pair("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "flac"),
- make_pair("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "opus")));
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "aac"),
+ make_pair("bbb_44100hz_2ch_128kbps_mp3_30sec.mp3", "mp3"),
+ make_pair("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "amrnb"),
+ make_pair("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "amrnb"),
+ make_pair("bbb_44100hz_2ch_80kbps_vorbis_30sec.mp4", "vorbis"),
+ make_pair("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "flac"),
+ make_pair("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "opus")));
INSTANTIATE_TEST_SUITE_P(
VideoDecoderTest, C2DecoderTest,
- ::testing::Values(
- make_pair("crowd_1920x1080_25fps_4000kbps_vp9.webm", "vp9"),
- make_pair("crowd_1920x1080_25fps_4000kbps_vp8.webm", "vp8"),
- make_pair("crowd_1920x1080_25fps_4000kbps_av1.webm", "av1"),
- make_pair("crowd_1920x1080_25fps_7300kbps_mpeg2.mp4", "mpeg2"),
- make_pair("crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", "mpeg4"),
- make_pair("crowd_352x288_25fps_6000kbps_h263.3gp", "h263"),
- make_pair("crowd_1920x1080_25fps_6700kbps_h264.ts", "avc"),
- make_pair("crowd_1920x1080_25fps_4000kbps_h265.mkv", "hevc")));
+ ::testing::Values(make_pair("crowd_1920x1080_25fps_4000kbps_vp9.webm", "vp9"),
+ make_pair("crowd_1920x1080_25fps_4000kbps_vp8.webm", "vp8"),
+ make_pair("crowd_1920x1080_25fps_4000kbps_av1.webm", "av1"),
+ make_pair("crowd_1920x1080_25fps_7300kbps_mpeg2.mp4", "mpeg2"),
+ make_pair("crowd_1920x1080_25fps_6000kbps_mpeg4.mp4", "mpeg4"),
+ make_pair("crowd_352x288_25fps_6000kbps_h263.3gp", "h263"),
+ make_pair("crowd_1920x1080_25fps_6700kbps_h264.ts", "avc"),
+ make_pair("crowd_1920x1080_25fps_4000kbps_h265.mkv", "hevc")));
int main(int argc, char **argv) {
gEnv = new BenchmarkTestEnvironment();
diff --git a/media/tests/benchmark/tests/C2EncoderTest.cpp b/media/tests/benchmark/tests/C2EncoderTest.cpp
new file mode 100644
index 0000000..98eb17a
--- /dev/null
+++ b/media/tests/benchmark/tests/C2EncoderTest.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "C2EncoderTest"
+
+#include <fstream>
+#include <iostream>
+#include <limits>
+
+#include "BenchmarkTestEnvironment.h"
+#include "C2Encoder.h"
+#include "Decoder.h"
+
+static BenchmarkTestEnvironment *gEnv = nullptr;
+
+class C2EncoderTest : public ::testing::TestWithParam<pair<string, string>> {
+ public:
+ C2EncoderTest() : mEncoder(nullptr) {}
+
+ ~C2EncoderTest() {
+ if (!mCodecList.empty()) {
+ mCodecList.clear();
+ }
+ if (mEncoder) {
+ delete mEncoder;
+ mEncoder = nullptr;
+ }
+ }
+
+ virtual void SetUp() override { setupC2EncoderTest(); }
+
+ void setupC2EncoderTest();
+
+ vector<string> mCodecList;
+ C2Encoder *mEncoder;
+};
+
+void C2EncoderTest::setupC2EncoderTest() {
+ mEncoder = new C2Encoder();
+ ASSERT_NE(mEncoder, nullptr) << "C2Encoder creation failed";
+
+ int32_t status = mEncoder->setupCodec2();
+ ASSERT_EQ(status, 0) << "Codec2 setup failed";
+
+ mCodecList = mEncoder->getSupportedComponentList(true /* isEncoder*/);
+ ASSERT_GT(mCodecList.size(), 0) << "Codec2 client didn't recognise any component";
+}
+
+TEST_P(C2EncoderTest, Codec2Encode) {
+ ALOGV("Encodes the input using codec2 framework");
+ string inputFile = gEnv->getRes() + GetParam().first;
+ FILE *inputFp = fopen(inputFile.c_str(), "rb");
+ ASSERT_NE(inputFp, nullptr) << "Unable to open input file for reading";
+
+ Decoder *decoder = new Decoder();
+ ASSERT_NE(decoder, nullptr) << "Decoder creation failed";
+
+ Extractor *extractor = decoder->getExtractor();
+ ASSERT_NE(extractor, nullptr) << "Extractor creation failed";
+
+ // Read file properties
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
+ int32_t fd = fileno(inputFp);
+
+ ASSERT_LE(fileSize, kMaxBufferSize)
+ << "Input file size is greater than the threshold memory dedicated to the test";
+
+ int32_t trackCount = extractor->initExtractor(fd, fileSize);
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
+
+ for (int curTrack = 0; curTrack < trackCount; curTrack++) {
+ int32_t status = extractor->setupTrackFormat(curTrack);
+ ASSERT_EQ(status, 0) << "Track Format invalid";
+
+ uint8_t *inputBuffer = (uint8_t *)malloc(fileSize);
+ ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory";
+
+ vector<AMediaCodecBufferInfo> frameInfo;
+ AMediaCodecBufferInfo info;
+ uint32_t inputBufferOffset = 0;
+
+ // Get frame data
+ while (1) {
+ status = extractor->getFrameSample(info);
+ if (status || !info.size) break;
+ // copy the meta data and buffer to be passed to decoder
+ ASSERT_LE(inputBufferOffset + info.size, fileSize) << "Memory allocated not sufficient";
+
+ memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
+ frameInfo.push_back(info);
+ inputBufferOffset += info.size;
+ }
+
+ string decName = "";
+ string outputFileName = "decode.out";
+ FILE *outFp = fopen(outputFileName.c_str(), "wb");
+ ASSERT_NE(outFp, nullptr) << "Unable to open output file" << outputFileName
+ << " for dumping decoder's output";
+
+ decoder->setupDecoder();
+ status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp);
+ ASSERT_EQ(status, AMEDIA_OK) << "Decode returned error : " << status;
+
+ // Encode the given input stream for all C2 codecs supported by device
+ AMediaFormat *format = extractor->getFormat();
+ ifstream eleStream;
+ eleStream.open(outputFileName.c_str(), ifstream::binary | ifstream::ate);
+ ASSERT_EQ(eleStream.is_open(), true) << outputFileName.c_str() << " - file not found";
+ size_t eleSize = eleStream.tellg();
+
+ for (string codecName : mCodecList) {
+ if (codecName.find(GetParam().second) != string::npos) {
+ status = mEncoder->createCodec2Component(codecName, format);
+ ASSERT_EQ(status, 0) << "Create component failed for " << codecName;
+
+ // Send the inputs to C2 Encoder and wait till all buffers are returned.
+ eleStream.seekg(0, ifstream::beg);
+ status = mEncoder->encodeFrames(eleStream, eleSize);
+ ASSERT_EQ(status, 0) << "Encoder failed for " << codecName;
+
+ mEncoder->waitOnInputConsumption();
+ ASSERT_TRUE(mEncoder->mEos) << "Test Failed. Didn't receive EOS \n";
+
+ mEncoder->deInitCodec();
+ int64_t durationUs = extractor->getClipDuration();
+ ALOGV("codec : %s", codecName.c_str());
+ mEncoder->dumpStatistics(GetParam().first, durationUs);
+ mEncoder->resetEncoder();
+ }
+ }
+
+ // Destroy the decoder for the given input
+ decoder->deInitCodec();
+ decoder->resetDecoder();
+ free(inputBuffer);
+ }
+ fclose(inputFp);
+ extractor->deInitExtractor();
+ delete decoder;
+ delete mEncoder;
+ mEncoder = nullptr;
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ AudioEncoderTest, C2EncoderTest,
+ ::testing::Values(make_pair("bbb_44100hz_2ch_128kbps_aac_30sec.mp4", "aac"),
+ make_pair("bbb_8000hz_1ch_8kbps_amrnb_30sec.3gp", "amrnb"),
+ make_pair("bbb_16000hz_1ch_9kbps_amrwb_30sec.3gp", "amrwb"),
+ make_pair("bbb_44100hz_2ch_600kbps_flac_30sec.mp4", "flac"),
+ make_pair("bbb_48000hz_2ch_100kbps_opus_30sec.webm", "opus")));
+
+INSTANTIATE_TEST_SUITE_P(
+ VideoEncoderTest, C2EncoderTest,
+ ::testing::Values(make_pair("crowd_1920x1080_25fps_4000kbps_vp9.webm", "vp9"),
+ make_pair("crowd_1920x1080_25fps_4000kbps_vp8.webm", "vp8"),
+ make_pair("crowd_176x144_25fps_6000kbps_mpeg4.mp4", "mpeg4"),
+ make_pair("crowd_176x144_25fps_6000kbps_h263.3gp", "h263"),
+ make_pair("crowd_1920x1080_25fps_6700kbps_h264.ts", "avc"),
+ make_pair("crowd_1920x1080_25fps_4000kbps_h265.mkv", "hevc")));
+
+int main(int argc, char **argv) {
+ gEnv = new BenchmarkTestEnvironment();
+ ::testing::AddGlobalTestEnvironment(gEnv);
+ ::testing::InitGoogleTest(&argc, argv);
+ int status = gEnv->initFromOptions(argc, argv);
+ if (status == 0) {
+ status = RUN_ALL_TESTS();
+ ALOGV("C2 Encoder Test result = %d\n", status);
+ }
+ return status;
+}
diff --git a/media/tests/benchmark/tests/DecoderTest.cpp b/media/tests/benchmark/tests/DecoderTest.cpp
index fa37435..5c6aa5b 100644
--- a/media/tests/benchmark/tests/DecoderTest.cpp
+++ b/media/tests/benchmark/tests/DecoderTest.cpp
@@ -21,8 +21,8 @@
#include <iostream>
#include <limits>
-#include "Decoder.h"
#include "BenchmarkTestEnvironment.h"
+#include "Decoder.h"
static BenchmarkTestEnvironment *gEnv = nullptr;
@@ -34,41 +34,30 @@
string inputFile = gEnv->getRes() + get<0>(params);
FILE *inputFp = fopen(inputFile.c_str(), "rb");
- if (!inputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open input file for reading \n";
- return;
- }
+ ASSERT_NE(inputFp, nullptr) << "Unable to open " << inputFile << " file for reading";
Decoder *decoder = new Decoder();
+ ASSERT_NE(decoder, nullptr) << "Decoder creation failed";
+
Extractor *extractor = decoder->getExtractor();
- if (!extractor) {
- cout << "[ WARN ] Test Skipped. Extractor creation failed \n";
- return;
- }
+ ASSERT_NE(extractor, nullptr) << "Extractor creation failed";
// Read file properties
- fseek(inputFp, 0, SEEK_END);
- size_t fileSize = ftell(inputFp);
- fseek(inputFp, 0, SEEK_SET);
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
int32_t fd = fileno(inputFp);
int32_t trackCount = extractor->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- cout << "[ WARN ] Test Skipped. initExtractor failed\n";
- return;
- }
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
+
for (int curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = extractor->setupTrackFormat(curTrack);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Track Format invalid \n";
- return;
- }
+ ASSERT_EQ(status, 0) << "Track Format invalid";
uint8_t *inputBuffer = (uint8_t *)malloc(kMaxBufferSize);
- if (!inputBuffer) {
- cout << "[ WARN ] Test Skipped. Insufficient memory \n";
- return;
- }
+ ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory";
+
vector<AMediaCodecBufferInfo> frameInfo;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
@@ -78,11 +67,9 @@
status = extractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
+ ASSERT_LE(inputBufferOffset + info.size, kMaxBufferSize)
+ << "Memory allocated not sufficient";
+
memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
frameInfo.push_back(info);
inputBufferOffset += info.size;
@@ -92,13 +79,10 @@
bool asyncMode = get<2>(params);
decoder->setupDecoder();
status = decoder->decode(inputBuffer, frameInfo, codecName, asyncMode);
- if (status != AMEDIA_OK) {
- cout << "[ WARN ] Test Failed. Decode returned error " << status << endl;
- free(inputBuffer);
- return;
- }
+ ASSERT_EQ(status, AMEDIA_OK) << "Decoder failed for " << codecName;
+
decoder->deInitCodec();
- cout << "codec : " << codecName << endl;
+ ALOGV("codec : %s", codecName.c_str());
string inputReference = get<0>(params);
decoder->dumpStatistics(inputReference);
free(inputBuffer);
diff --git a/media/tests/benchmark/tests/EncoderTest.cpp b/media/tests/benchmark/tests/EncoderTest.cpp
index c3963f8..dc2a2dd 100644
--- a/media/tests/benchmark/tests/EncoderTest.cpp
+++ b/media/tests/benchmark/tests/EncoderTest.cpp
@@ -20,8 +20,8 @@
#include <fstream>
#include "BenchmarkTestEnvironment.h"
-#include "Encoder.h"
#include "Decoder.h"
+#include "Encoder.h"
static BenchmarkTestEnvironment *gEnv = nullptr;
@@ -33,42 +33,33 @@
string inputFile = gEnv->getRes() + get<0>(params);
FILE *inputFp = fopen(inputFile.c_str(), "rb");
- if (!inputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open input file for reading \n";
- return;
- }
+ ASSERT_NE(inputFp, nullptr) << "Unable to open " << inputFile << " file for reading";
Decoder *decoder = new Decoder();
+ ASSERT_NE(decoder, nullptr) << "Decoder creation failed";
+
Extractor *extractor = decoder->getExtractor();
- if (!extractor) {
- cout << "[ WARN ] Test Skipped. Extractor creation failed \n";
- return;
- }
+ ASSERT_NE(extractor, nullptr) << "Extractor creation failed";
+
// Read file properties
- fseek(inputFp, 0, SEEK_END);
- size_t fileSize = ftell(inputFp);
- fseek(inputFp, 0, SEEK_SET);
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
int32_t fd = fileno(inputFp);
int32_t trackCount = extractor->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- cout << "[ WARN ] Test Skipped. initExtractor failed\n";
- return;
- }
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
Encoder *encoder = new Encoder();
+ ASSERT_NE(encoder, nullptr) << "Decoder creation failed";
+
for (int curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = extractor->setupTrackFormat(curTrack);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Track Format invalid \n";
- return;
- }
+ ASSERT_EQ(status, 0) << "Track Format invalid";
uint8_t *inputBuffer = (uint8_t *)malloc(kMaxBufferSize);
- if (!inputBuffer) {
- cout << "[ WARN ] Test Skipped. Insufficient memory \n";
- return;
- }
+ ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory";
+
vector<AMediaCodecBufferInfo> frameInfo;
AMediaCodecBufferInfo info;
uint32_t inputBufferOffset = 0;
@@ -78,11 +69,9 @@
status = extractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to decoder
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
+ ASSERT_LE(inputBufferOffset + info.size, kMaxBufferSize)
+ << "Memory allocated not sufficient";
+
memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
frameInfo.push_back(info);
inputBufferOffset += info.size;
@@ -91,16 +80,12 @@
string decName = "";
string outputFileName = "decode.out";
FILE *outFp = fopen(outputFileName.c_str(), "wb");
- if (outFp == nullptr) {
- ALOGE("Unable to open output file for writing");
- return;
- }
+ ASSERT_NE(outFp, nullptr) << "Unable to open output file" << outputFileName
+ << " for dumping decoder's output";
+
decoder->setupDecoder();
status = decoder->decode(inputBuffer, frameInfo, decName, false /*asyncMode */, outFp);
- if (status != AMEDIA_OK) {
- cout << "[ WARN ] Test Skipped. Decode returned error \n";
- return;
- }
+ ASSERT_EQ(status, AMEDIA_OK) << "Decode returned error : " << status;
ifstream eleStream;
eleStream.open(outputFileName.c_str(), ifstream::binary | ifstream::ate);
@@ -111,15 +96,13 @@
AMediaFormat *format = extractor->getFormat();
const char *mime = nullptr;
AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
- if (!mime) {
- ALOGE("Error in AMediaFormat_getString");
- return;
- }
+ ASSERT_NE(mime, nullptr) << "Invalid mime type";
+
// Get encoder params
encParameter encParams;
if (!strncmp(mime, "video/", 6)) {
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &encParams.width);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &encParams.height);
+ ASSERT_TRUE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &encParams.width));
+ ASSERT_TRUE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &encParams.height));
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, &encParams.frameRate);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &encParams.bitrate);
if (encParams.bitrate <= 0 || encParams.frameRate <= 0) {
@@ -133,8 +116,10 @@
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_PROFILE, &encParams.profile);
AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_LEVEL, &encParams.level);
} else {
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &encParams.sampleRate);
- AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &encParams.numChannels);
+ ASSERT_TRUE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE,
+ &encParams.sampleRate));
+ ASSERT_TRUE(AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT,
+ &encParams.numChannels));
encParams.bitrate =
encParams.sampleRate * encParams.numChannels * 16 /* bitsPerSample */;
}
@@ -143,13 +128,10 @@
string codecName = get<1>(params);
bool asyncMode = get<2>(params);
status = encoder->encode(codecName, eleStream, eleSize, asyncMode, encParams, (char *)mime);
- if (status != AMEDIA_OK) {
- cout << "[ WARN ] Test Failed. Encode returned error " << status << endl;
- free(inputBuffer);
- return;
- }
+ ASSERT_EQ(status, 0) << "Encoder failed for " << codecName;
+
encoder->deInitCodec();
- cout << "codec : " << codecName << endl;
+ ALOGV("codec : %s", codecName.c_str());
string inputReference = get<0>(params);
encoder->dumpStatistics(inputReference, extractor->getClipDuration());
eleStream.close();
diff --git a/media/tests/benchmark/tests/ExtractorTest.cpp b/media/tests/benchmark/tests/ExtractorTest.cpp
index dd0d711..c2d72ff 100644
--- a/media/tests/benchmark/tests/ExtractorTest.cpp
+++ b/media/tests/benchmark/tests/ExtractorTest.cpp
@@ -19,8 +19,8 @@
#include <gtest/gtest.h>
-#include "Extractor.h"
#include "BenchmarkTestEnvironment.h"
+#include "Extractor.h"
static BenchmarkTestEnvironment *gEnv = nullptr;
@@ -28,33 +28,24 @@
TEST_P(ExtractorTest, Extract) {
Extractor *extractObj = new Extractor();
+ ASSERT_NE(extractObj, nullptr) << "Extractor creation failed";
string inputFile = gEnv->getRes() + GetParam().first;
FILE *inputFp = fopen(inputFile.c_str(), "rb");
- if (!inputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open input file for reading \n";
- return;
- }
+ ASSERT_NE(inputFp, nullptr) << "Unable to open " << inputFile << " file for reading";
// Read file properties
- size_t fileSize = 0;
- fseek(inputFp, 0, SEEK_END);
- fileSize = ftell(inputFp);
- fseek(inputFp, 0, SEEK_SET);
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
int32_t fd = fileno(inputFp);
int32_t trackCount = extractObj->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- cout << "[ WARN ] Test Skipped. initExtractor failed\n";
- return;
- }
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
int32_t trackID = GetParam().second;
int32_t status = extractObj->extract(trackID);
- if (status != AMEDIA_OK) {
- cout << "[ WARN ] Test Skipped. Extraction failed \n";
- return;
- }
+ ASSERT_EQ(status, AMEDIA_OK) << "Extraction failed \n";
extractObj->deInitExtractor();
@@ -79,7 +70,8 @@
make_pair("bbb_8000hz_1ch_8kbps_amrnb_5mins.3gp", 0),
make_pair("bbb_16000hz_1ch_9kbps_amrwb_5mins.3gp", 0),
make_pair("bbb_44100hz_2ch_80kbps_vorbis_5mins.mp4", 0),
- make_pair("bbb_48000hz_2ch_100kbps_opus_5mins.webm", 0)));
+ make_pair("bbb_48000hz_2ch_100kbps_opus_5mins.webm",
+ 0)));
int main(int argc, char **argv) {
gEnv = new BenchmarkTestEnvironment();
diff --git a/media/tests/benchmark/tests/MuxerTest.cpp b/media/tests/benchmark/tests/MuxerTest.cpp
index e814f90..7b01732 100644
--- a/media/tests/benchmark/tests/MuxerTest.cpp
+++ b/media/tests/benchmark/tests/MuxerTest.cpp
@@ -21,8 +21,8 @@
#include <fstream>
#include <iostream>
-#include "Muxer.h"
#include "BenchmarkTestEnvironment.h"
+#include "Muxer.h"
#define OUTPUT_FILE_NAME "/data/local/tmp/mux.out"
@@ -53,49 +53,34 @@
ALOGV("Mux the samples given by extractor");
string inputFile = gEnv->getRes() + GetParam().first;
FILE *inputFp = fopen(inputFile.c_str(), "rb");
- if (!inputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open input file for reading \n";
- return;
- }
+ ASSERT_NE(inputFp, nullptr) << "Unable to open " << inputFile << " file for reading";
+
string fmt = GetParam().second;
MUXER_OUTPUT_T outputFormat = getMuxerOutFormat(fmt);
- if (outputFormat == MUXER_OUTPUT_FORMAT_INVALID) {
- ALOGE("output format is MUXER_OUTPUT_FORMAT_INVALID");
- return;
- }
+ ASSERT_NE(outputFormat, MUXER_OUTPUT_FORMAT_INVALID) << "Invalid muxer output format";
Muxer *muxerObj = new Muxer();
+ ASSERT_NE(muxerObj, nullptr) << "Muxer creation failed";
+
Extractor *extractor = muxerObj->getExtractor();
- if (!extractor) {
- cout << "[ WARN ] Test Skipped. Extractor creation failed \n";
- return;
- }
+ ASSERT_NE(extractor, nullptr) << "Extractor creation failed";
// Read file properties
- size_t fileSize = 0;
- fseek(inputFp, 0, SEEK_END);
- fileSize = ftell(inputFp);
- fseek(inputFp, 0, SEEK_SET);
+ struct stat buf;
+ stat(inputFile.c_str(), &buf);
+ size_t fileSize = buf.st_size;
int32_t fd = fileno(inputFp);
int32_t trackCount = extractor->initExtractor(fd, fileSize);
- if (trackCount <= 0) {
- cout << "[ WARN ] Test Skipped. initExtractor failed\n";
- return;
- }
+ ASSERT_GT(trackCount, 0) << "initExtractor failed";
for (int curTrack = 0; curTrack < trackCount; curTrack++) {
int32_t status = extractor->setupTrackFormat(curTrack);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Track Format invalid \n";
- return;
- }
+ ASSERT_EQ(status, 0) << "Track Format invalid";
uint8_t *inputBuffer = (uint8_t *)malloc(kMaxBufferSize);
- if (!inputBuffer) {
- std::cout << "[ WARN ] Test Skipped. Insufficient memory \n";
- return;
- }
+ ASSERT_NE(inputBuffer, nullptr) << "Insufficient memory";
+
// AMediaCodecBufferInfo : <size of frame> <flags> <presentationTimeUs> <offset>
vector<AMediaCodecBufferInfo> frameInfos;
AMediaCodecBufferInfo info;
@@ -106,11 +91,9 @@
status = extractor->getFrameSample(info);
if (status || !info.size) break;
// copy the meta data and buffer to be passed to muxer
- if (inputBufferOffset + info.size > kMaxBufferSize) {
- cout << "[ WARN ] Test Skipped. Memory allocated not sufficient\n";
- free(inputBuffer);
- return;
- }
+ ASSERT_LE(inputBufferOffset + info.size, kMaxBufferSize)
+ << "Memory allocated not sufficient";
+
memcpy(inputBuffer + inputBufferOffset, extractor->getFrameBuf(), info.size);
info.offset = inputBufferOffset;
frameInfos.push_back(info);
@@ -119,22 +102,16 @@
string outputFileName = OUTPUT_FILE_NAME;
FILE *outputFp = fopen(outputFileName.c_str(), "w+b");
- if (!outputFp) {
- cout << "[ WARN ] Test Skipped. Unable to open output file for writing \n";
- return;
- }
+ ASSERT_NE(outputFp, nullptr)
+ << "Unable to open output file" << outputFileName << " for writing";
+
int32_t fd = fileno(outputFp);
status = muxerObj->initMuxer(fd, outputFormat);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. initMuxer failed\n";
- return;
- }
+ ASSERT_EQ(status, 0) << "initMuxer failed";
status = muxerObj->mux(inputBuffer, frameInfos);
- if (status != 0) {
- cout << "[ WARN ] Test Skipped. Mux failed \n";
- return;
- }
+ ASSERT_EQ(status, 0) << "Mux failed";
+
muxerObj->deInitMuxer();
muxerObj->dumpStatistics(GetParam().first + "." + fmt.c_str());
free(inputBuffer);
diff --git a/media/utils/Android.bp b/media/utils/Android.bp
index 5047b19..b7037e9 100644
--- a/media/utils/Android.bp
+++ b/media/utils/Android.bp
@@ -29,6 +29,7 @@
"libc_malloc_debug_backtrace",
],
shared_libs: [
+ "libaudioutils", // for clock.h
"libbinder",
"libcutils",
"liblog",
diff --git a/media/utils/ServiceUtilities.cpp b/media/utils/ServiceUtilities.cpp
index a77311e..343ec05 100644
--- a/media/utils/ServiceUtilities.cpp
+++ b/media/utils/ServiceUtilities.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "ServiceUtilities"
+#include <audio_utils/clock.h>
#include <binder/AppOpsManager.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
@@ -24,6 +25,7 @@
#include <iterator>
#include <algorithm>
+#include <pwd.h>
/* When performing permission checks we do not use permission cache for
* runtime permissions (protection level dangerous) as they may change at
@@ -329,4 +331,130 @@
}
}
+// How long we hold info before we re-fetch it (24 hours) if we found it previously.
+static constexpr nsecs_t INFO_EXPIRATION_NS = 24 * 60 * 60 * NANOS_PER_SECOND;
+// Maximum info records we retain before clearing everything.
+static constexpr size_t INFO_CACHE_MAX = 1000;
+
+// The original code is from MediaMetricsService.cpp.
+mediautils::UidInfo::Info mediautils::UidInfo::getInfo(uid_t uid)
+{
+ const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
+ struct mediautils::UidInfo::Info info;
+ {
+ std::lock_guard _l(mLock);
+ auto it = mInfoMap.find(uid);
+ if (it != mInfoMap.end()) {
+ info = it->second;
+ ALOGV("%s: uid %d expiration %lld now %lld",
+ __func__, uid, (long long)info.expirationNs, (long long)now);
+ if (info.expirationNs <= now) {
+ // purge the stale entry and fall into re-fetching
+ ALOGV("%s: entry for uid %d expired, now %lld",
+ __func__, uid, (long long)now);
+ mInfoMap.erase(it);
+ info.uid = (uid_t)-1; // this is always fully overwritten
+ }
+ }
+ }
+
+ // if we did not find it in our map, look it up
+ if (info.uid == (uid_t)(-1)) {
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<content::pm::IPackageManagerNative> package_mgr;
+ if (sm.get() == nullptr) {
+ ALOGE("%s: Cannot find service manager", __func__);
+ } else {
+ sp<IBinder> binder = sm->getService(String16("package_native"));
+ if (binder.get() == nullptr) {
+ ALOGE("%s: Cannot find package_native", __func__);
+ } else {
+ package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
+ }
+ }
+
+ // find package name
+ std::string pkg;
+ if (package_mgr != nullptr) {
+ std::vector<std::string> names;
+ binder::Status status = package_mgr->getNamesForUids({(int)uid}, &names);
+ if (!status.isOk()) {
+ ALOGE("%s: getNamesForUids failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ } else {
+ if (!names[0].empty()) {
+ pkg = names[0].c_str();
+ }
+ }
+ }
+
+ if (pkg.empty()) {
+ struct passwd pw{}, *result;
+ char buf[8192]; // extra buffer space - should exceed what is
+ // required in struct passwd_pw (tested),
+ // and even then this is only used in backup
+ // when the package manager is unavailable.
+ if (getpwuid_r(uid, &pw, buf, sizeof(buf), &result) == 0
+ && result != nullptr
+ && result->pw_name != nullptr) {
+ pkg = result->pw_name;
+ }
+ }
+
+ // strip any leading "shared:" strings that came back
+ if (pkg.compare(0, 7, "shared:") == 0) {
+ pkg.erase(0, 7);
+ }
+
+ // determine how pkg was installed and the versionCode
+ std::string installer;
+ int64_t versionCode = 0;
+ bool notFound = false;
+ if (pkg.empty()) {
+ pkg = std::to_string(uid); // not found
+ notFound = true;
+ } else if (strchr(pkg.c_str(), '.') == nullptr) {
+ // not of form 'com.whatever...'; assume internal
+ // so we don't need to look it up in package manager.
+ } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
+ // android.* packages are assumed fine
+ } else if (package_mgr.get() != nullptr) {
+ String16 pkgName16(pkg.c_str());
+ binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
+ if (!status.isOk()) {
+ ALOGE("%s: getInstallerForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+
+ // skip if we didn't get an installer
+ if (status.isOk()) {
+ status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
+ if (!status.isOk()) {
+ ALOGE("%s: getVersionCodeForPackage failed: %s",
+ __func__, status.exceptionMessage().c_str());
+ }
+ }
+
+ ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
+ __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
+ }
+
+ // add it to the map, to save a subsequent lookup
+ std::lock_guard _l(mLock);
+ // first clear if we have too many cached elements. This would be rare.
+ if (mInfoMap.size() >= INFO_CACHE_MAX) mInfoMap.clear();
+
+ // always overwrite
+ info.uid = uid;
+ info.package = std::move(pkg);
+ info.installer = std::move(installer);
+ info.versionCode = versionCode;
+ info.expirationNs = now + (notFound ? 0 : INFO_EXPIRATION_NS);
+ ALOGV("%s: adding uid %d package '%s' expirationNs: %lld",
+ __func__, uid, info.package.c_str(), (long long)info.expirationNs);
+ mInfoMap[uid] = info;
+ }
+ return info;
+}
+
} // namespace android
diff --git a/media/utils/include/mediautils/ServiceUtilities.h b/media/utils/include/mediautils/ServiceUtilities.h
index e467058..4925cdb 100644
--- a/media/utils/include/mediautils/ServiceUtilities.h
+++ b/media/utils/include/mediautils/ServiceUtilities.h
@@ -28,6 +28,7 @@
#include <map>
#include <optional>
#include <string>
+#include <unordered_map>
#include <vector>
namespace android {
@@ -118,6 +119,40 @@
using Packages = std::vector<Package>;
std::map<uid_t, Packages> mDebugLog;
};
-}
+
+namespace mediautils {
+
+/**
+ * This class is used to retrieve (and cache) package information
+ * for a given uid.
+ */
+class UidInfo {
+public:
+ struct Info {
+ uid_t uid = -1; // uid used for lookup.
+ std::string package; // package name.
+ std::string installer; // installer for the package (e.g. preload, play store).
+ int64_t versionCode = 0; // reported version code.
+ int64_t expirationNs = 0; // after this time in SYSTEM_TIME_REALTIME we refetch.
+ };
+
+ /**
+ * Returns the package information for a UID.
+ *
+ * The package name will be the uid if we cannot find the associated name.
+ *
+ * \param uid is the uid of the app or service.
+ */
+ Info getInfo(uid_t uid);
+
+private:
+ std::mutex mLock;
+ // TODO: use concurrent hashmap with striped lock.
+ std::unordered_map<uid_t, Info> mInfoMap; // GUARDED_BY(mLock)
+};
+
+} // namespace mediautils
+
+} // namespace android
#endif // ANDROID_MEDIAUTILS_SERVICEUTILITIES_H
diff --git a/services/audioflinger/Android.bp b/services/audioflinger/Android.bp
index de8c7e7..3873600 100644
--- a/services/audioflinger/Android.bp
+++ b/services/audioflinger/Android.bp
@@ -9,6 +9,7 @@
"AudioStreamOut.cpp",
"AudioWatchdog.cpp",
"BufLog.cpp",
+ "DeviceEffectManager.cpp",
"Effects.cpp",
"FastCapture.cpp",
"FastCaptureDumpState.cpp",
@@ -62,6 +63,7 @@
],
header_libs: [
+ "libaudiohal_headers",
"libmedia_headers",
],
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 0911a1f..ee4c5ba 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -170,6 +170,7 @@
mClientSharedHeapSize(kMinimumClientSharedHeapSizeBytes),
mGlobalEffectEnableTime(0),
mPatchPanel(this),
+ mDeviceEffectManager(this),
mSystemReady(false)
{
// unsigned instead of audio_unique_id_use_t, because ++ operator is unavailable for enum
@@ -347,6 +348,9 @@
thread->configure(&localAttr, streamType, actualSessionId, callback, *deviceId, portId);
*handle = portId;
*sessionId = actualSessionId;
+ config->sample_rate = thread->sampleRate();
+ config->channel_mask = thread->channelMask();
+ config->format = thread->format();
} else {
if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
AudioSystem::releaseOutput(portId);
@@ -382,6 +386,24 @@
}
}
+status_t AudioFlinger::addEffectToHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect) {
+ AudioHwDevice *audioHwDevice = mAudioHwDevs.valueFor(hwModuleId);
+ if (audioHwDevice == nullptr) {
+ return NO_INIT;
+ }
+ return audioHwDevice->hwDevice()->addDeviceEffect(deviceId, effect);
+}
+
+status_t AudioFlinger::removeEffectFromHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect) {
+ AudioHwDevice *audioHwDevice = mAudioHwDevs.valueFor(hwModuleId);
+ if (audioHwDevice == nullptr) {
+ return NO_INIT;
+ }
+ return audioHwDevice->hwDevice()->removeDeviceEffect(deviceId, effect);
+}
+
static const char * const audio_interfaces[] = {
AUDIO_HARDWARE_MODULE_ID_PRIMARY,
AUDIO_HARDWARE_MODULE_ID_A2DP,
@@ -422,31 +444,30 @@
void AudioFlinger::dumpClients(int fd, const Vector<String16>& args __unused)
{
- const size_t SIZE = 256;
- char buffer[SIZE];
String8 result;
result.append("Clients:\n");
for (size_t i = 0; i < mClients.size(); ++i) {
sp<Client> client = mClients.valueAt(i).promote();
if (client != 0) {
- snprintf(buffer, SIZE, " pid: %d\n", client->pid());
- result.append(buffer);
+ result.appendFormat(" pid: %d\n", client->pid());
}
}
result.append("Notification Clients:\n");
+ result.append(" pid uid name\n");
for (size_t i = 0; i < mNotificationClients.size(); ++i) {
- snprintf(buffer, SIZE, " pid: %d\n", mNotificationClients.keyAt(i));
- result.append(buffer);
+ const pid_t pid = mNotificationClients[i]->getPid();
+ const uid_t uid = mNotificationClients[i]->getUid();
+ const mediautils::UidInfo::Info info = mUidInfo.getInfo(uid);
+ result.appendFormat("%6d %6u %s\n", pid, uid, info.package.c_str());
}
result.append("Global session refs:\n");
- result.append(" session pid count\n");
+ result.append(" session cnt pid\n");
for (size_t i = 0; i < mAudioSessionRefs.size(); i++) {
AudioSessionRef *r = mAudioSessionRefs[i];
- snprintf(buffer, SIZE, " %7d %5d %5d\n", r->mSessionid, r->mPid, r->mCnt);
- result.append(buffer);
+ result.appendFormat(" %7d %4d %7d\n", r->mSessionid, r->mCnt, r->mPid);
}
write(fd, result.string(), result.size());
}
@@ -558,6 +579,8 @@
mPatchPanel.dump(fd);
+ mDeviceEffectManager.dump(fd);
+
// dump external setParameters
auto dumpLogger = [fd](SimpleLog& logger, const char* name) {
dprintf(fd, "\n%s setParameters:\n", name);
@@ -1582,11 +1605,6 @@
AutoMutex lock(mHardwareLock);
mHardwareStatus = AUDIO_HW_GET_INPUT_BUFFER_SIZE;
- audio_config_t config, proposed;
- memset(&proposed, 0, sizeof(proposed));
- proposed.sample_rate = sampleRate;
- proposed.channel_mask = channelMask;
- proposed.format = format;
sp<DeviceHalInterface> dev = mPrimaryHardwareDev->hwDevice();
std::vector<audio_channel_mask_t> channelMasks = {channelMask};
@@ -1610,12 +1628,16 @@
mHardwareStatus = AUDIO_HW_IDLE;
+ // Change parameters of the configuration each iteration until we find a
+ // configuration that the device will support.
+ audio_config_t config = AUDIO_CONFIG_INITIALIZER;
for (auto testChannelMask : channelMasks) {
config.channel_mask = testChannelMask;
for (auto testFormat : formats) {
config.format = testFormat;
for (auto testSampleRate : sampleRates) {
config.sample_rate = testSampleRate;
+
size_t bytes = 0;
status_t result = dev->getInputBufferSize(&config, &bytes);
if (result != OK || bytes == 0) {
@@ -1695,13 +1717,16 @@
return;
}
pid_t pid = IPCThreadState::self()->getCallingPid();
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
{
Mutex::Autolock _cl(mClientLock);
if (mNotificationClients.indexOfKey(pid) < 0) {
sp<NotificationClient> notificationClient = new NotificationClient(this,
client,
- pid);
- ALOGV("registerClient() client %p, pid %d", notificationClient.get(), pid);
+ pid,
+ uid);
+ ALOGV("registerClient() client %p, pid %d, uid %u",
+ notificationClient.get(), pid, uid);
mNotificationClients.add(pid, notificationClient);
@@ -1841,8 +1866,9 @@
AudioFlinger::NotificationClient::NotificationClient(const sp<AudioFlinger>& audioFlinger,
const sp<IAudioFlingerClient>& client,
- pid_t pid)
- : mAudioFlinger(audioFlinger), mPid(pid), mAudioFlingerClient(client)
+ pid_t pid,
+ uid_t uid)
+ : mAudioFlinger(audioFlinger), mPid(pid), mUid(uid), mAudioFlingerClient(client)
{
}
@@ -2993,7 +3019,7 @@
for (size_t i = 0; i < chains.size(); i++) {
sp<EffectChain> ec = chains[i];
int sessionid = ec->sessionId();
- sp<ThreadBase> t = ec->mThread.promote();
+ sp<ThreadBase> t = ec->thread().promote();
if (t == 0) {
continue;
}
@@ -3016,7 +3042,7 @@
effect->unPin();
t->removeEffect_l(effect, /*release*/ true);
if (effect->purgeHandles()) {
- t->checkSuspendOnEffectEnabled_l(effect, false, effect->sessionId());
+ effect->checkSuspendOnEffectEnabled(false, true /*threadLocked*/);
}
removedEffects.push_back(effect);
}
@@ -3316,7 +3342,7 @@
int32_t priority,
audio_io_handle_t io,
audio_session_t sessionId,
- const AudioDeviceTypeAddr& device __unused,
+ const AudioDeviceTypeAddr& device,
const String16& opPackageName,
pid_t pid,
status_t *status,
@@ -3380,7 +3406,6 @@
lStatus = BAD_VALUE;
goto Exit;
}
- //TODO: add check on device ID when added to arguments
} else {
// general sessionId.
@@ -3433,6 +3458,23 @@
Mutex::Autolock _l(mLock);
+ if (sessionId == AUDIO_SESSION_DEVICE) {
+ sp<Client> client = registerPid(pid);
+ ALOGV("%s device type %d address %s", __func__, device.mType, device.getAddress());
+ handle = mDeviceEffectManager.createEffect_l(
+ &desc, device, client, effectClient, mPatchPanel.patches_l(),
+ enabled, &lStatus);
+ if (lStatus != NO_ERROR && lStatus != ALREADY_EXISTS) {
+ // remove local strong reference to Client with mClientLock held
+ Mutex::Autolock _cl(mClientLock);
+ client.clear();
+ } else {
+ // handle must be valid here, but check again to be safe.
+ if (handle.get() != nullptr && id != nullptr) *id = handle->id();
+ }
+ goto Register;
+ }
+
// If output is not specified try to find a matching audio session ID in one of the
// output threads.
// If output is 0 here, sessionId is neither SESSION_OUTPUT_STAGE nor SESSION_OUTPUT_MIX
@@ -3527,9 +3569,10 @@
}
}
+Register:
if (lStatus == NO_ERROR || lStatus == ALREADY_EXISTS) {
// Check CPU and memory usage
- sp<EffectModule> effect = handle->effect().promote();
+ sp<EffectBase> effect = handle->effect().promote();
if (effect != nullptr) {
status_t rStatus = effect->updatePolicyState();
if (rStatus != NO_ERROR) {
@@ -3639,7 +3682,7 @@
// if the move request is not received from audio policy manager, the effect must be
// re-registered with the new strategy and output
if (dstChain == 0) {
- dstChain = effect->chain().promote();
+ dstChain = effect->callback()->chain().promote();
if (dstChain == 0) {
ALOGW("moveEffectChain_l() cannot get chain from effect %p", effect.get());
status = NO_INIT;
@@ -3689,7 +3732,7 @@
goto Exit;
}
- dstChain = effect->chain().promote();
+ dstChain = effect->callback()->chain().promote();
if (dstChain == 0) {
thread->addEffect_l(effect);
status = INVALID_OPERATION;
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index eb851ec..5c975e1 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -71,6 +71,7 @@
#include <media/DeviceDescriptorBase.h>
#include <media/ExtendedAudioBufferProvider.h>
#include <media/VolumeShaper.h>
+#include <mediautils/ServiceUtilities.h>
#include <audio_utils/clock.h>
#include <audio_utils/FdToString.h>
@@ -308,6 +309,12 @@
static int onExternalVibrationStart(const sp<os::ExternalVibration>& externalVibration);
static void onExternalVibrationStop(const sp<os::ExternalVibration>& externalVibration);
+
+ status_t addEffectToHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect);
+ status_t removeEffectFromHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect);
+
private:
// FIXME The 400 is temporarily too high until a leak of writers in media.log is fixed.
static const size_t kLogMemorySize = 400 * 1024;
@@ -474,10 +481,13 @@
public:
NotificationClient(const sp<AudioFlinger>& audioFlinger,
const sp<IAudioFlingerClient>& client,
- pid_t pid);
+ pid_t pid,
+ uid_t uid);
virtual ~NotificationClient();
sp<IAudioFlingerClient> audioFlingerClient() const { return mAudioFlingerClient; }
+ pid_t getPid() const { return mPid; }
+ uid_t getUid() const { return mUid; }
// IBinder::DeathRecipient
virtual void binderDied(const wp<IBinder>& who);
@@ -487,6 +497,7 @@
const sp<AudioFlinger> mAudioFlinger;
const pid_t mPid;
+ const uid_t mUid;
const sp<IAudioFlingerClient> mAudioFlingerClient;
};
@@ -533,9 +544,14 @@
class AsyncCallbackThread;
class Track;
class RecordTrack;
+ class EffectBase;
class EffectModule;
class EffectHandle;
class EffectChain;
+ class DeviceEffectProxy;
+ class DeviceEffectManager;
+ class PatchPanel;
+ class DeviceEffectManagerCallback;
struct AudioStreamIn;
struct TeePatch;
@@ -573,9 +589,11 @@
#include "Threads.h"
+#include "PatchPanel.h"
+
#include "Effects.h"
-#include "PatchPanel.h"
+#include "DeviceEffectManager.h"
// Find io handle by session id.
// Preference is given to an io handle with a matching effect chain to session id.
@@ -921,8 +939,12 @@
PatchPanel mPatchPanel;
sp<EffectsFactoryHalInterface> mEffectsFactoryHal;
+ DeviceEffectManager mDeviceEffectManager;
+
bool mSystemReady;
+ mediautils::UidInfo mUidInfo;
+
SimpleLog mRejectedSetParameterLog;
SimpleLog mAppSetParameterLog;
SimpleLog mSystemSetParameterLog;
diff --git a/services/audioflinger/DeviceEffectManager.cpp b/services/audioflinger/DeviceEffectManager.cpp
new file mode 100644
index 0000000..87a4c6e
--- /dev/null
+++ b/services/audioflinger/DeviceEffectManager.cpp
@@ -0,0 +1,277 @@
+/*
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+
+#define LOG_TAG "AudioFlinger::DeviceEffectManager"
+//#define LOG_NDEBUG 0
+
+#include <utils/Log.h>
+#include <audio_utils/primitives.h>
+
+#include "AudioFlinger.h"
+#include <media/audiohal/EffectsFactoryHalInterface.h>
+
+// ----------------------------------------------------------------------------
+
+
+namespace android {
+
+void AudioFlinger::DeviceEffectManager::createAudioPatch(audio_patch_handle_t handle,
+ const PatchPanel::Patch& patch) {
+ ALOGV("%s handle %d mHalHandle %d num sinks %d device sink %08x",
+ __func__, handle, patch.mHalHandle,
+ patch.mAudioPatch.num_sinks,
+ patch.mAudioPatch.num_sinks > 0 ? patch.mAudioPatch.sinks[0].ext.device.type : 0);
+
+ mCommandThread->createAudioPatchCommand(handle, patch);
+}
+
+void AudioFlinger::DeviceEffectManager::onCreateAudioPatch(audio_patch_handle_t handle,
+ const PatchPanel::Patch& patch) {
+ ALOGV("%s handle %d mHalHandle %d device sink %08x",
+ __func__, handle, patch.mHalHandle,
+ patch.mAudioPatch.num_sinks > 0 ? patch.mAudioPatch.sinks[0].ext.device.type : 0);
+ Mutex::Autolock _l(mLock);
+ for (auto& effect : mDeviceEffects) {
+ status_t status = effect.second->onCreatePatch(handle, patch);
+ ALOGV("%s Effect onCreatePatch status %d", __func__, status);
+ ALOGW_IF(status == BAD_VALUE, "%s onCreatePatch error %d", __func__, status);
+ }
+}
+
+void AudioFlinger::DeviceEffectManager::releaseAudioPatch(audio_patch_handle_t handle) {
+ ALOGV("%s", __func__);
+ mCommandThread->releaseAudioPatchCommand(handle);
+}
+
+void AudioFlinger::DeviceEffectManager::onReleaseAudioPatch(audio_patch_handle_t handle) {
+ ALOGV("%s", __func__);
+ Mutex::Autolock _l(mLock);
+ for (auto& effect : mDeviceEffects) {
+ effect.second->onReleasePatch(handle);
+ }
+}
+
+// DeviceEffectManager::createEffect_l() must be called with AudioFlinger::mLock held
+sp<AudioFlinger::EffectHandle> AudioFlinger::DeviceEffectManager::createEffect_l(
+ effect_descriptor_t *descriptor,
+ const AudioDeviceTypeAddr& device,
+ const sp<AudioFlinger::Client>& client,
+ const sp<IEffectClient>& effectClient,
+ const std::map<audio_patch_handle_t, PatchPanel::Patch>& patches,
+ int *enabled,
+ status_t *status) {
+ sp<DeviceEffectProxy> effect;
+ sp<EffectHandle> handle;
+ status_t lStatus;
+
+ lStatus = checkEffectCompatibility(descriptor);
+ if (lStatus != NO_ERROR) {
+ *status = lStatus;
+ return handle;
+ }
+
+ {
+ Mutex::Autolock _l(mLock);
+ auto iter = mDeviceEffects.find(device);
+ if (iter != mDeviceEffects.end()) {
+ effect = iter->second;
+ } else {
+ effect = new DeviceEffectProxy(device, mMyCallback,
+ descriptor, mAudioFlinger.nextUniqueId(AUDIO_UNIQUE_ID_USE_EFFECT));
+ }
+ // create effect handle and connect it to effect module
+ handle = new EffectHandle(effect, client, effectClient, 0 /*priority*/);
+ lStatus = handle->initCheck();
+ if (lStatus == NO_ERROR) {
+ lStatus = effect->addHandle(handle.get());
+ if (lStatus == NO_ERROR) {
+ effect->init(patches);
+ mDeviceEffects.emplace(device, effect);
+ }
+ }
+ }
+ if (enabled != NULL) {
+ *enabled = (int)effect->isEnabled();
+ }
+ *status = lStatus;
+ return handle;
+}
+
+status_t AudioFlinger::DeviceEffectManager::checkEffectCompatibility(
+ const effect_descriptor_t *desc) {
+
+ if ((desc->flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_PRE_PROC
+ && (desc->flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_POST_PROC) {
+ ALOGW("%s() non pre/post processing device effect %s", __func__, desc->name);
+ return BAD_VALUE;
+ }
+
+ return NO_ERROR;
+}
+
+status_t AudioFlinger::DeviceEffectManager::createEffectHal(
+ const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t deviceId,
+ sp<EffectHalInterface> *effect) {
+ status_t status = NO_INIT;
+ sp<EffectsFactoryHalInterface> effectsFactory = mAudioFlinger.getEffectsFactory();
+ if (effectsFactory != 0) {
+ status = effectsFactory->createEffect(
+ pEffectUuid, sessionId, AUDIO_IO_HANDLE_NONE, deviceId, effect);
+ }
+ return status;
+}
+
+void AudioFlinger::DeviceEffectManager::dump(int fd) {
+ const bool locked = dumpTryLock(mLock);
+ if (!locked) {
+ String8 result("DeviceEffectManager may be deadlocked\n");
+ write(fd, result.string(), result.size());
+ }
+
+ write(fd, "\nDevice Effects:\n", sizeof("\nDevice Effects:\n"));
+ for (const auto& iter : mDeviceEffects) {
+ String8 outStr;
+ outStr.appendFormat("%*sEffect for device %s address %s:\n", 2, "",
+ ::android::toString(iter.first.mType).c_str(), iter.first.getAddress());
+ write(fd, outStr.string(), outStr.size());
+ iter.second->dump(fd, 4);
+ }
+
+ if (locked) {
+ mLock.unlock();
+ }
+}
+
+
+size_t AudioFlinger::DeviceEffectManager::removeEffect(const sp<DeviceEffectProxy>& effect)
+{
+ Mutex::Autolock _l(mLock);
+ mDeviceEffects.erase(effect->device());
+ return mDeviceEffects.size();
+}
+
+bool AudioFlinger::DeviceEffectManagerCallback::disconnectEffectHandle(
+ EffectHandle *handle, bool unpinIfLast) {
+ sp<EffectBase> effectBase = handle->effect().promote();
+ if (effectBase == nullptr) {
+ return false;
+ }
+
+ sp<DeviceEffectProxy> effect = effectBase->asDeviceEffectProxy();
+ if (effect == nullptr) {
+ return false;
+ }
+ // restore suspended effects if the disconnected handle was enabled and the last one.
+ bool remove = (effect->removeHandle(handle) == 0) && (!effect->isPinned() || unpinIfLast);
+ if (remove) {
+ mManager.removeEffect(effect);
+ if (handle->enabled()) {
+ effectBase->checkSuspendOnEffectEnabled(false, false /*threadLocked*/);
+ }
+ }
+ return true;
+}
+
+// ----------- DeviceEffectManager::CommandThread implementation ----------
+
+
+AudioFlinger::DeviceEffectManager::CommandThread::~CommandThread()
+{
+ Mutex::Autolock _l(mLock);
+ mCommands.clear();
+}
+
+void AudioFlinger::DeviceEffectManager::CommandThread::onFirstRef()
+{
+ run("DeviceEffectManage_CommandThread", ANDROID_PRIORITY_AUDIO);
+}
+
+bool AudioFlinger::DeviceEffectManager::CommandThread::threadLoop()
+{
+ mLock.lock();
+ while (!exitPending())
+ {
+ while (!mCommands.empty() && !exitPending()) {
+ sp<Command> command = mCommands.front();
+ mCommands.pop_front();
+ mLock.unlock();
+
+ switch (command->mCommand) {
+ case CREATE_AUDIO_PATCH: {
+ CreateAudioPatchData *data = (CreateAudioPatchData *)command->mData.get();
+ ALOGV("CommandThread() processing create audio patch handle %d", data->mHandle);
+ mManager.onCreateAudioPatch(data->mHandle, data->mPatch);
+ } break;
+ case RELEASE_AUDIO_PATCH: {
+ ReleaseAudioPatchData *data = (ReleaseAudioPatchData *)command->mData.get();
+ ALOGV("CommandThread() processing release audio patch handle %d", data->mHandle);
+ mManager.onReleaseAudioPatch(data->mHandle);
+ } break;
+ default:
+ ALOGW("CommandThread() unknown command %d", command->mCommand);
+ }
+ mLock.lock();
+ }
+
+ // At this stage we have either an empty command queue or the first command in the queue
+ // has a finite delay. So unless we are exiting it is safe to wait.
+ if (!exitPending()) {
+ ALOGV("CommandThread() going to sleep");
+ mWaitWorkCV.wait(mLock);
+ }
+ }
+ mLock.unlock();
+ return false;
+}
+
+void AudioFlinger::DeviceEffectManager::CommandThread::sendCommand(sp<Command> command) {
+ Mutex::Autolock _l(mLock);
+ mCommands.push_back(command);
+ mWaitWorkCV.signal();
+}
+
+void AudioFlinger::DeviceEffectManager::CommandThread::createAudioPatchCommand(
+ audio_patch_handle_t handle, const PatchPanel::Patch& patch)
+{
+ sp<Command> command = new Command(CREATE_AUDIO_PATCH, new CreateAudioPatchData(handle, patch));
+ ALOGV("CommandThread() adding create patch handle %d mHalHandle %d.", handle, patch.mHalHandle);
+ sendCommand(command);
+}
+
+void AudioFlinger::DeviceEffectManager::CommandThread::releaseAudioPatchCommand(
+ audio_patch_handle_t handle)
+{
+ sp<Command> command = new Command(RELEASE_AUDIO_PATCH, new ReleaseAudioPatchData(handle));
+ ALOGV("CommandThread() adding release patch");
+ sendCommand(command);
+}
+
+void AudioFlinger::DeviceEffectManager::CommandThread::exit()
+{
+ ALOGV("CommandThread::exit");
+ {
+ AutoMutex _l(mLock);
+ requestExit();
+ mWaitWorkCV.signal();
+ }
+ // Note that we can call it from the thread loop if all other references have been released
+ // but it will safely return WOULD_BLOCK in this case
+ requestExitAndWait();
+}
+
+} // namespace android
diff --git a/services/audioflinger/DeviceEffectManager.h b/services/audioflinger/DeviceEffectManager.h
new file mode 100644
index 0000000..14ff14d
--- /dev/null
+++ b/services/audioflinger/DeviceEffectManager.h
@@ -0,0 +1,203 @@
+/*
+**
+** Copyright 2019, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef INCLUDING_FROM_AUDIOFLINGER_H
+ #error This header file should only be included from AudioFlinger.h
+#endif
+
+// DeviceEffectManager is concealed within AudioFlinger, their lifetimes are the same.
+class DeviceEffectManager {
+public:
+ explicit DeviceEffectManager(AudioFlinger* audioFlinger)
+ : mCommandThread(new CommandThread(*this)), mAudioFlinger(*audioFlinger),
+ mMyCallback(new DeviceEffectManagerCallback(this)) {}
+
+ ~DeviceEffectManager() {
+ mCommandThread->exit();
+ }
+
+ sp<EffectHandle> createEffect_l(effect_descriptor_t *descriptor,
+ const AudioDeviceTypeAddr& device,
+ const sp<AudioFlinger::Client>& client,
+ const sp<IEffectClient>& effectClient,
+ const std::map<audio_patch_handle_t, PatchPanel::Patch>& patches,
+ int *enabled,
+ status_t *status);
+ void createAudioPatch(audio_patch_handle_t handle, const PatchPanel::Patch& patch);
+ void releaseAudioPatch(audio_patch_handle_t handle);
+
+ size_t removeEffect(const sp<DeviceEffectProxy>& effect);
+ status_t createEffectHal(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t deviceId,
+ sp<EffectHalInterface> *effect);
+ status_t addEffectToHal(audio_port_handle_t deviceId, audio_module_handle_t hwModuleId,
+ sp<EffectHalInterface> effect) {
+ return mAudioFlinger.addEffectToHal(deviceId, hwModuleId, effect);
+ };
+ status_t removeEffectFromHal(audio_port_handle_t deviceId, audio_module_handle_t hwModuleId,
+ sp<EffectHalInterface> effect) {
+ return mAudioFlinger.removeEffectFromHal(deviceId, hwModuleId, effect);
+ };
+
+ AudioFlinger& audioFlinger() const { return mAudioFlinger; }
+
+ void dump(int fd);
+
+private:
+
+ // Thread to execute create and release patch commands asynchronously. This is needed because
+ // PatchPanel::createAudioPatch and releaseAudioPatch are executed from audio policy service
+ // with mutex locked and effect management requires to call back into audio policy service
+ class Command;
+ class CommandThread : public Thread {
+ public:
+
+ enum {
+ CREATE_AUDIO_PATCH,
+ RELEASE_AUDIO_PATCH,
+ };
+
+ CommandThread(DeviceEffectManager& manager)
+ : Thread(false), mManager(manager) {}
+ ~CommandThread() override;
+
+ // Thread virtuals
+ void onFirstRef() override;
+ bool threadLoop() override;
+
+ void exit();
+
+ void createAudioPatchCommand(audio_patch_handle_t handle,
+ const PatchPanel::Patch& patch);
+ void releaseAudioPatchCommand(audio_patch_handle_t handle);
+
+ private:
+ class CommandData;
+
+ // descriptor for requested tone playback event
+ class Command: public RefBase {
+ public:
+ Command() = default;
+ Command(int command, sp<CommandData> data)
+ : mCommand(command), mData(data) {}
+
+ int mCommand = -1;
+ sp<CommandData> mData;
+ };
+
+ class CommandData: public RefBase {
+ public:
+ virtual ~CommandData() = default;
+ };
+
+ class CreateAudioPatchData : public CommandData {
+ public:
+ CreateAudioPatchData(audio_patch_handle_t handle, const PatchPanel::Patch& patch)
+ : mHandle(handle), mPatch(patch) {}
+
+ audio_patch_handle_t mHandle;
+ const PatchPanel::Patch mPatch;
+ };
+
+ class ReleaseAudioPatchData : public CommandData {
+ public:
+ ReleaseAudioPatchData(audio_patch_handle_t handle)
+ : mHandle(handle) {}
+
+ audio_patch_handle_t mHandle;
+ };
+
+ void sendCommand(sp<Command> command);
+
+ Mutex mLock;
+ Condition mWaitWorkCV;
+ std::deque <sp<Command>> mCommands; // list of pending commands
+ DeviceEffectManager& mManager;
+ };
+
+ void onCreateAudioPatch(audio_patch_handle_t handle, const PatchPanel::Patch& patch);
+ void onReleaseAudioPatch(audio_patch_handle_t handle);
+
+ status_t checkEffectCompatibility(const effect_descriptor_t *desc);
+
+ Mutex mLock;
+ sp<CommandThread> mCommandThread;
+ AudioFlinger &mAudioFlinger;
+ const sp<DeviceEffectManagerCallback> mMyCallback;
+ std::map<AudioDeviceTypeAddr, sp<DeviceEffectProxy>> mDeviceEffects;
+};
+
+class DeviceEffectManagerCallback : public EffectCallbackInterface {
+public:
+ DeviceEffectManagerCallback(DeviceEffectManager *manager)
+ : mManager(*manager) {}
+
+ status_t createEffectHal(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t deviceId,
+ sp<EffectHalInterface> *effect) override {
+ return mManager.createEffectHal(pEffectUuid, sessionId, deviceId, effect);
+ }
+ status_t allocateHalBuffer(size_t size __unused,
+ sp<EffectBufferHalInterface>* buffer __unused) override { return NO_ERROR; }
+ bool updateOrphanEffectChains(const sp<EffectBase>& effect __unused) override { return false; }
+
+ audio_io_handle_t io() const override { return AUDIO_IO_HANDLE_NONE; }
+ bool isOutput() const override { return false; }
+ bool isOffload() const override { return false; }
+ bool isOffloadOrDirect() const override { return false; }
+ bool isOffloadOrMmap() const override { return false; }
+
+ uint32_t sampleRate() const override { return 0; }
+ audio_channel_mask_t channelMask() const override { return AUDIO_CHANNEL_NONE; }
+ uint32_t channelCount() const override { return 0; }
+ size_t frameCount() const override { return 0; }
+ uint32_t latency() const override { return 0; }
+
+ status_t addEffectToHal(sp<EffectHalInterface> effect __unused) override {
+ return NO_ERROR;
+ }
+ status_t removeEffectFromHal(sp<EffectHalInterface> effect __unused) override {
+ return NO_ERROR;
+ }
+
+ bool disconnectEffectHandle(EffectHandle *handle, bool unpinIfLast) override;
+ void setVolumeForOutput(float left __unused, float right __unused) const override {}
+
+ // check if effects should be suspended or restored when a given effect is enable or disabled
+ void checkSuspendOnEffectEnabled(const sp<EffectBase>& effect __unused,
+ bool enabled __unused, bool threadLocked __unused) override {}
+ void resetVolume() override {}
+ uint32_t strategy() const override { return 0; }
+ int32_t activeTrackCnt() const override { return 0; }
+ void onEffectEnable(const sp<EffectBase>& effect __unused) override {}
+ void onEffectDisable(const sp<EffectBase>& effect __unused) override {}
+
+ wp<EffectChain> chain() const override { return nullptr; }
+
+ int newEffectId() { return mManager.audioFlinger().nextUniqueId(AUDIO_UNIQUE_ID_USE_EFFECT); }
+
+ status_t addEffectToHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect) {
+ return mManager.addEffectToHal(deviceId, hwModuleId, effect);
+ }
+ status_t removeEffectFromHal(audio_port_handle_t deviceId,
+ audio_module_handle_t hwModuleId, sp<EffectHalInterface> effect) {
+ return mManager.removeEffectFromHal(deviceId, hwModuleId, effect);
+ }
+private:
+ DeviceEffectManager& mManager;
+};
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index f27c6ad..70c56e3 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -58,81 +58,115 @@
namespace android {
// ----------------------------------------------------------------------------
-// EffectModule implementation
+// EffectBase implementation
// ----------------------------------------------------------------------------
#undef LOG_TAG
-#define LOG_TAG "AudioFlinger::EffectModule"
+#define LOG_TAG "AudioFlinger::EffectBase"
-AudioFlinger::EffectModule::EffectModule(ThreadBase *thread,
- const wp<AudioFlinger::EffectChain>& chain,
+AudioFlinger::EffectBase::EffectBase(const sp<AudioFlinger::EffectCallbackInterface>& callback,
effect_descriptor_t *desc,
int id,
audio_session_t sessionId,
bool pinned)
: mPinned(pinned),
- mThread(thread), mChain(chain), mId(id), mSessionId(sessionId),
- mDescriptor(*desc),
- // clear mConfig to ensure consistent initial value of buffer framecount
- // in case buffers are associated by setInBuffer() or setOutBuffer()
- // prior to configure().
- mConfig{{}, {}},
- mStatus(NO_INIT), mState(IDLE),
- mMaxDisableWaitCnt(1), // set by configure(), should be >= 1
- mDisableWaitCnt(0), // set by process() and updateState()
- mSuspended(false),
- mOffloaded(false),
- mAudioFlinger(thread->mAudioFlinger)
-#ifdef FLOAT_EFFECT_CHAIN
- , mSupportsFloat(false)
-#endif
+ mCallback(callback), mId(id), mSessionId(sessionId),
+ mDescriptor(*desc)
{
- ALOGV("Constructor %p pinned %d", this, pinned);
- int lStatus;
+}
- // create effect engine from effect factory
- mStatus = -ENODEV;
- sp<AudioFlinger> audioFlinger = mAudioFlinger.promote();
- if (audioFlinger != 0) {
- sp<EffectsFactoryHalInterface> effectsFactory = audioFlinger->getEffectsFactory();
- if (effectsFactory != 0) {
- mStatus = effectsFactory->createEffect(
- &desc->uuid, sessionId, thread->id(), AUDIO_PORT_HANDLE_NONE, &mEffectInterface);
+// must be called with EffectModule::mLock held
+status_t AudioFlinger::EffectBase::setEnabled_l(bool enabled)
+{
+
+ ALOGV("setEnabled %p enabled %d", this, enabled);
+
+ if (enabled != isEnabled()) {
+ switch (mState) {
+ // going from disabled to enabled
+ case IDLE:
+ mState = STARTING;
+ break;
+ case STOPPED:
+ mState = RESTART;
+ break;
+ case STOPPING:
+ mState = ACTIVE;
+ break;
+
+ // going from enabled to disabled
+ case RESTART:
+ mState = STOPPED;
+ break;
+ case STARTING:
+ mState = IDLE;
+ break;
+ case ACTIVE:
+ mState = STOPPING;
+ break;
+ case DESTROYED:
+ return NO_ERROR; // simply ignore as we are being destroyed
+ }
+ for (size_t i = 1; i < mHandles.size(); i++) {
+ EffectHandle *h = mHandles[i];
+ if (h != NULL && !h->disconnected()) {
+ h->setEnabled(enabled);
+ }
}
}
-
- if (mStatus != NO_ERROR) {
- return;
- }
- lStatus = init();
- if (lStatus < 0) {
- mStatus = lStatus;
- goto Error;
- }
-
- setOffloaded(thread->type() == ThreadBase::OFFLOAD, thread->id());
- ALOGV("Constructor success name %s, Interface %p", mDescriptor.name, mEffectInterface.get());
-
- return;
-Error:
- mEffectInterface.clear();
- ALOGV("Constructor Error %d", mStatus);
+ return NO_ERROR;
}
-AudioFlinger::EffectModule::~EffectModule()
+status_t AudioFlinger::EffectBase::setEnabled(bool enabled, bool fromHandle)
{
- ALOGV("Destructor %p", this);
- if (mEffectInterface != 0) {
- char uuidStr[64];
- AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
- ALOGW("EffectModule %p destructor called with unreleased interface, effect %s",
- this, uuidStr);
- release_l();
+ status_t status;
+ {
+ Mutex::Autolock _l(mLock);
+ status = setEnabled_l(enabled);
}
-
+ if (fromHandle) {
+ if (enabled) {
+ if (status != NO_ERROR) {
+ mCallback->checkSuspendOnEffectEnabled(this, false, false /*threadLocked*/);
+ } else {
+ mCallback->onEffectEnable(this);
+ }
+ } else {
+ mCallback->onEffectDisable(this);
+ }
+ }
+ return status;
}
-status_t AudioFlinger::EffectModule::addHandle(EffectHandle *handle)
+bool AudioFlinger::EffectBase::isEnabled() const
+{
+ switch (mState) {
+ case RESTART:
+ case STARTING:
+ case ACTIVE:
+ return true;
+ case IDLE:
+ case STOPPING:
+ case STOPPED:
+ case DESTROYED:
+ default:
+ return false;
+ }
+}
+
+void AudioFlinger::EffectBase::setSuspended(bool suspended)
+{
+ Mutex::Autolock _l(mLock);
+ mSuspended = suspended;
+}
+
+bool AudioFlinger::EffectBase::suspended() const
+{
+ Mutex::Autolock _l(mLock);
+ return mSuspended;
+}
+
+status_t AudioFlinger::EffectBase::addHandle(EffectHandle *handle)
{
status_t status;
@@ -171,7 +205,7 @@
return status;
}
-status_t AudioFlinger::EffectModule::updatePolicyState()
+status_t AudioFlinger::EffectBase::updatePolicyState()
{
status_t status = NO_ERROR;
bool doRegister = false;
@@ -188,14 +222,8 @@
doRegister = true;
mPolicyRegistered = mHandles.size() > 0;
if (mPolicyRegistered) {
- sp <EffectChain> chain = mChain.promote();
- sp <ThreadBase> thread = mThread.promote();
-
- if (thread == nullptr || chain == nullptr) {
- return INVALID_OPERATION;
- }
- io = thread->id();
- strategy = chain->strategy();
+ io = mCallback->io();
+ strategy = mCallback->strategy();
}
}
// enable effect when registered according to enable state requested by controlling handle
@@ -233,13 +261,13 @@
}
-ssize_t AudioFlinger::EffectModule::removeHandle(EffectHandle *handle)
+ssize_t AudioFlinger::EffectBase::removeHandle(EffectHandle *handle)
{
Mutex::Autolock _l(mLock);
return removeHandle_l(handle);
}
-ssize_t AudioFlinger::EffectModule::removeHandle_l(EffectHandle *handle)
+ssize_t AudioFlinger::EffectBase::removeHandle_l(EffectHandle *handle)
{
size_t size = mHandles.size();
size_t i;
@@ -263,19 +291,15 @@
}
}
- // Prevent calls to process() and other functions on effect interface from now on.
- // The effect engine will be released by the destructor when the last strong reference on
- // this object is released which can happen after next process is called.
if (mHandles.size() == 0 && !mPinned) {
mState = DESTROYED;
- mEffectInterface->close();
}
return mHandles.size();
}
// must be called with EffectModule::mLock held
-AudioFlinger::EffectHandle *AudioFlinger::EffectModule::controlHandle_l()
+AudioFlinger::EffectHandle *AudioFlinger::EffectBase::controlHandle_l()
{
// the first valid handle in the list has control over the module
for (size_t i = 0; i < mHandles.size(); i++) {
@@ -289,22 +313,271 @@
}
// unsafe method called when the effect parent thread has been destroyed
-ssize_t AudioFlinger::EffectModule::disconnectHandle(EffectHandle *handle, bool unpinIfLast)
+ssize_t AudioFlinger::EffectBase::disconnectHandle(EffectHandle *handle, bool unpinIfLast)
{
ALOGV("disconnect() %p handle %p", this, handle);
+ if (mCallback->disconnectEffectHandle(handle, unpinIfLast)) {
+ return mHandles.size();
+ }
+
Mutex::Autolock _l(mLock);
ssize_t numHandles = removeHandle_l(handle);
if ((numHandles == 0) && (!mPinned || unpinIfLast)) {
- sp<AudioFlinger> af = mAudioFlinger.promote();
- if (af != 0) {
- mLock.unlock();
- af->updateOrphanEffectChains(this);
- mLock.lock();
- }
+ mLock.unlock();
+ mCallback->updateOrphanEffectChains(this);
+ mLock.lock();
}
return numHandles;
}
+bool AudioFlinger::EffectBase::purgeHandles()
+{
+ bool enabled = false;
+ Mutex::Autolock _l(mLock);
+ EffectHandle *handle = controlHandle_l();
+ if (handle != NULL) {
+ enabled = handle->enabled();
+ }
+ mHandles.clear();
+ return enabled;
+}
+
+void AudioFlinger::EffectBase::checkSuspendOnEffectEnabled(bool enabled, bool threadLocked) {
+ mCallback->checkSuspendOnEffectEnabled(this, enabled, threadLocked);
+}
+
+static String8 effectFlagsToString(uint32_t flags) {
+ String8 s;
+
+ s.append("conn. mode: ");
+ switch (flags & EFFECT_FLAG_TYPE_MASK) {
+ case EFFECT_FLAG_TYPE_INSERT: s.append("insert"); break;
+ case EFFECT_FLAG_TYPE_AUXILIARY: s.append("auxiliary"); break;
+ case EFFECT_FLAG_TYPE_REPLACE: s.append("replace"); break;
+ case EFFECT_FLAG_TYPE_PRE_PROC: s.append("preproc"); break;
+ case EFFECT_FLAG_TYPE_POST_PROC: s.append("postproc"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+
+ s.append("insert pref: ");
+ switch (flags & EFFECT_FLAG_INSERT_MASK) {
+ case EFFECT_FLAG_INSERT_ANY: s.append("any"); break;
+ case EFFECT_FLAG_INSERT_FIRST: s.append("first"); break;
+ case EFFECT_FLAG_INSERT_LAST: s.append("last"); break;
+ case EFFECT_FLAG_INSERT_EXCLUSIVE: s.append("exclusive"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+
+ s.append("volume mgmt: ");
+ switch (flags & EFFECT_FLAG_VOLUME_MASK) {
+ case EFFECT_FLAG_VOLUME_NONE: s.append("none"); break;
+ case EFFECT_FLAG_VOLUME_CTRL: s.append("implements control"); break;
+ case EFFECT_FLAG_VOLUME_IND: s.append("requires indication"); break;
+ case EFFECT_FLAG_VOLUME_MONITOR: s.append("monitors volume"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+
+ uint32_t devind = flags & EFFECT_FLAG_DEVICE_MASK;
+ if (devind) {
+ s.append("device indication: ");
+ switch (devind) {
+ case EFFECT_FLAG_DEVICE_IND: s.append("requires updates"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+ }
+
+ s.append("input mode: ");
+ switch (flags & EFFECT_FLAG_INPUT_MASK) {
+ case EFFECT_FLAG_INPUT_DIRECT: s.append("direct"); break;
+ case EFFECT_FLAG_INPUT_PROVIDER: s.append("provider"); break;
+ case EFFECT_FLAG_INPUT_BOTH: s.append("direct+provider"); break;
+ default: s.append("not set"); break;
+ }
+ s.append(", ");
+
+ s.append("output mode: ");
+ switch (flags & EFFECT_FLAG_OUTPUT_MASK) {
+ case EFFECT_FLAG_OUTPUT_DIRECT: s.append("direct"); break;
+ case EFFECT_FLAG_OUTPUT_PROVIDER: s.append("provider"); break;
+ case EFFECT_FLAG_OUTPUT_BOTH: s.append("direct+provider"); break;
+ default: s.append("not set"); break;
+ }
+ s.append(", ");
+
+ uint32_t accel = flags & EFFECT_FLAG_HW_ACC_MASK;
+ if (accel) {
+ s.append("hardware acceleration: ");
+ switch (accel) {
+ case EFFECT_FLAG_HW_ACC_SIMPLE: s.append("non-tunneled"); break;
+ case EFFECT_FLAG_HW_ACC_TUNNEL: s.append("tunneled"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+ }
+
+ uint32_t modeind = flags & EFFECT_FLAG_AUDIO_MODE_MASK;
+ if (modeind) {
+ s.append("mode indication: ");
+ switch (modeind) {
+ case EFFECT_FLAG_AUDIO_MODE_IND: s.append("required"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+ }
+
+ uint32_t srcind = flags & EFFECT_FLAG_AUDIO_SOURCE_MASK;
+ if (srcind) {
+ s.append("source indication: ");
+ switch (srcind) {
+ case EFFECT_FLAG_AUDIO_SOURCE_IND: s.append("required"); break;
+ default: s.append("unknown/reserved"); break;
+ }
+ s.append(", ");
+ }
+
+ if (flags & EFFECT_FLAG_OFFLOAD_MASK) {
+ s.append("offloadable, ");
+ }
+
+ int len = s.length();
+ if (s.length() > 2) {
+ (void) s.lockBuffer(len);
+ s.unlockBuffer(len - 2);
+ }
+ return s;
+}
+
+void AudioFlinger::EffectBase::dump(int fd, const Vector<String16>& args __unused)
+{
+ String8 result;
+
+ result.appendFormat("\tEffect ID %d:\n", mId);
+
+ bool locked = AudioFlinger::dumpTryLock(mLock);
+ // failed to lock - AudioFlinger is probably deadlocked
+ if (!locked) {
+ result.append("\t\tCould not lock Fx mutex:\n");
+ }
+
+ result.append("\t\tSession State Registered Enabled Suspended:\n");
+ result.appendFormat("\t\t%05d %03d %s %s %s\n",
+ mSessionId, mState, mPolicyRegistered ? "y" : "n",
+ mPolicyEnabled ? "y" : "n", mSuspended ? "y" : "n");
+
+ result.append("\t\tDescriptor:\n");
+ char uuidStr[64];
+ AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+ result.appendFormat("\t\t- UUID: %s\n", uuidStr);
+ AudioEffect::guidToString(&mDescriptor.type, uuidStr, sizeof(uuidStr));
+ result.appendFormat("\t\t- TYPE: %s\n", uuidStr);
+ result.appendFormat("\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
+ mDescriptor.apiVersion,
+ mDescriptor.flags,
+ effectFlagsToString(mDescriptor.flags).string());
+ result.appendFormat("\t\t- name: %s\n",
+ mDescriptor.name);
+
+ result.appendFormat("\t\t- implementor: %s\n",
+ mDescriptor.implementor);
+
+ result.appendFormat("\t\t%zu Clients:\n", mHandles.size());
+ result.append("\t\t\t Pid Priority Ctrl Locked client server\n");
+ char buffer[256];
+ for (size_t i = 0; i < mHandles.size(); ++i) {
+ EffectHandle *handle = mHandles[i];
+ if (handle != NULL && !handle->disconnected()) {
+ handle->dumpToBuffer(buffer, sizeof(buffer));
+ result.append(buffer);
+ }
+ }
+ if (locked) {
+ mLock.unlock();
+ }
+
+ write(fd, result.string(), result.length());
+}
+
+// ----------------------------------------------------------------------------
+// EffectModule implementation
+// ----------------------------------------------------------------------------
+
+#undef LOG_TAG
+#define LOG_TAG "AudioFlinger::EffectModule"
+
+AudioFlinger::EffectModule::EffectModule(const sp<AudioFlinger::EffectCallbackInterface>& callback,
+ effect_descriptor_t *desc,
+ int id,
+ audio_session_t sessionId,
+ bool pinned,
+ audio_port_handle_t deviceId)
+ : EffectBase(callback, desc, id, sessionId, pinned),
+ // clear mConfig to ensure consistent initial value of buffer framecount
+ // in case buffers are associated by setInBuffer() or setOutBuffer()
+ // prior to configure().
+ mConfig{{}, {}},
+ mStatus(NO_INIT),
+ mMaxDisableWaitCnt(1), // set by configure(), should be >= 1
+ mDisableWaitCnt(0), // set by process() and updateState()
+ mOffloaded(false)
+#ifdef FLOAT_EFFECT_CHAIN
+ , mSupportsFloat(false)
+#endif
+{
+ ALOGV("Constructor %p pinned %d", this, pinned);
+ int lStatus;
+
+ // create effect engine from effect factory
+ mStatus = callback->createEffectHal(
+ &desc->uuid, sessionId, deviceId, &mEffectInterface);
+ if (mStatus != NO_ERROR) {
+ return;
+ }
+ lStatus = init();
+ if (lStatus < 0) {
+ mStatus = lStatus;
+ goto Error;
+ }
+
+ setOffloaded(callback->isOffload(), callback->io());
+ ALOGV("Constructor success name %s, Interface %p", mDescriptor.name, mEffectInterface.get());
+
+ return;
+Error:
+ mEffectInterface.clear();
+ ALOGV("Constructor Error %d", mStatus);
+}
+
+AudioFlinger::EffectModule::~EffectModule()
+{
+ ALOGV("Destructor %p", this);
+ if (mEffectInterface != 0) {
+ char uuidStr[64];
+ AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
+ ALOGW("EffectModule %p destructor called with unreleased interface, effect %s",
+ this, uuidStr);
+ release_l();
+ }
+
+}
+
+ssize_t AudioFlinger::EffectModule::removeHandle_l(EffectHandle *handle)
+{
+ ssize_t status = EffectBase::removeHandle_l(handle);
+
+ // Prevent calls to process() and other functions on effect interface from now on.
+ // The effect engine will be released by the destructor when the last strong reference on
+ // this object is released which can happen after next process is called.
+ if (status == 0 && !mPinned) {
+ mEffectInterface->close();
+ }
+
+ return status;
+}
+
bool AudioFlinger::EffectModule::updateState() {
Mutex::Autolock _l(mLock);
@@ -542,8 +815,7 @@
mConfig.inputCfg.buffer.raw != mConfig.outputCfg.buffer.raw) {
// If an insert effect is idle and input buffer is different from output buffer,
// accumulate input onto output
- sp<EffectChain> chain = mChain.promote();
- if (chain.get() != nullptr && chain->activeTrackCnt() != 0) {
+ if (mCallback->activeTrackCnt() != 0) {
// similar handling with data_bypass above.
if (mConfig.outputCfg.accessMode == EFFECT_BUFFER_ACCESS_ACCUMULATE) {
accumulateInputToOutput();
@@ -566,7 +838,6 @@
{
ALOGVV("configure() started");
status_t status;
- sp<ThreadBase> thread;
uint32_t size;
audio_channel_mask_t channelMask;
@@ -575,17 +846,11 @@
goto exit;
}
- thread = mThread.promote();
- if (thread == 0) {
- status = DEAD_OBJECT;
- goto exit;
- }
-
// TODO: handle configuration of effects replacing track process
// TODO: handle configuration of input (record) SW effects above the HAL,
// similar to output EFFECT_FLAG_TYPE_INSERT/REPLACE,
// in which case input channel masks should be used here.
- channelMask = thread->channelMask();
+ channelMask = mCallback->channelMask();
mConfig.inputCfg.channels = channelMask;
mConfig.outputCfg.channels = channelMask;
@@ -622,11 +887,11 @@
mConfig.outputCfg.format = EFFECT_BUFFER_FORMAT;
// Don't use sample rate for thread if effect isn't offloadable.
- if ((thread->type() == ThreadBase::OFFLOAD) && !isOffloaded()) {
+ if (mCallback->isOffloadOrDirect() && !isOffloaded()) {
mConfig.inputCfg.samplingRate = DEFAULT_OUTPUT_SAMPLE_RATE;
ALOGV("Overriding effect input as 48kHz");
} else {
- mConfig.inputCfg.samplingRate = thread->sampleRate();
+ mConfig.inputCfg.samplingRate = mCallback->sampleRate();
}
mConfig.outputCfg.samplingRate = mConfig.inputCfg.samplingRate;
mConfig.inputCfg.bufferProvider.cookie = NULL;
@@ -652,11 +917,13 @@
}
mConfig.inputCfg.mask = EFFECT_CONFIG_ALL;
mConfig.outputCfg.mask = EFFECT_CONFIG_ALL;
- mConfig.inputCfg.buffer.frameCount = thread->frameCount();
+ mConfig.inputCfg.buffer.frameCount = mCallback->frameCount();
mConfig.outputCfg.buffer.frameCount = mConfig.inputCfg.buffer.frameCount;
- ALOGV("configure() %p thread %p buffer %p framecount %zu",
- this, thread.get(), mConfig.inputCfg.buffer.raw, mConfig.inputCfg.buffer.frameCount);
+ ALOGV("configure() %p chain %p buffer %p framecount %zu",
+ this, mCallback->chain().promote() != nullptr ? mCallback->chain().promote().get() :
+ nullptr,
+ mConfig.inputCfg.buffer.raw, mConfig.inputCfg.buffer.frameCount);
status_t cmdStatus;
size = sizeof(int);
@@ -671,7 +938,7 @@
#ifdef MULTICHANNEL_EFFECT_CHAIN
if (status != NO_ERROR &&
- thread->isOutput() &&
+ mCallback->isOutput() &&
(mConfig.inputCfg.channels != AUDIO_CHANNEL_OUT_STEREO
|| mConfig.outputCfg.channels != AUDIO_CHANNEL_OUT_STEREO)) {
// Older effects may require exact STEREO position mask.
@@ -738,11 +1005,7 @@
size = sizeof(int);
*(int32_t *)p->data = VISUALIZER_PARAM_LATENCY;
- uint32_t latency = 0;
- PlaybackThread *pbt = thread->mAudioFlinger->checkPlaybackThread_l(thread->mId);
- if (pbt != NULL) {
- latency = pbt->latency_l();
- }
+ uint32_t latency = mCallback->latency();
*((int32_t *)p->data + 1)= latency;
mEffectInterface->command(EFFECT_CMD_SET_PARAM,
@@ -789,31 +1052,20 @@
{
if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC ||
(mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_POST_PROC) {
- sp<ThreadBase> thread = mThread.promote();
- if (thread != 0) {
- sp<StreamHalInterface> stream = thread->stream();
- if (stream != 0) {
- status_t result = stream->addEffect(mEffectInterface);
- ALOGE_IF(result != OK, "Error when adding effect: %d", result);
- }
- }
+ (void)mCallback->addEffectToHal(mEffectInterface);
}
}
// start() must be called with PlaybackThread::mLock or EffectChain::mLock held
status_t AudioFlinger::EffectModule::start()
{
- sp<EffectChain> chain;
status_t status;
{
Mutex::Autolock _l(mLock);
status = start_l();
- if (status == NO_ERROR) {
- chain = mChain.promote();
- }
}
- if (chain != 0) {
- chain->resetVolume_l();
+ if (status == NO_ERROR) {
+ mCallback->resetVolume();
}
return status;
}
@@ -860,11 +1112,10 @@
uint32_t size = sizeof(status_t);
if (isVolumeControl() && isOffloadedOrDirect()) {
- sp<EffectChain>chain = mChain.promote();
// We have the EffectChain and EffectModule lock, permit a reentrant call to setVolume:
// resetVolume_l --> setVolume_l --> EffectModule::setVolume
mSetVolumeReentrantTid = gettid();
- chain->resetVolume_l();
+ mCallback->resetVolume();
mSetVolumeReentrantTid = INVALID_PID;
}
@@ -877,7 +1128,7 @@
status = cmdStatus;
}
if (status == NO_ERROR) {
- status = remove_effect_from_hal_l();
+ status = removeEffectFromHal_l();
}
return status;
}
@@ -886,25 +1137,18 @@
void AudioFlinger::EffectModule::release_l()
{
if (mEffectInterface != 0) {
- remove_effect_from_hal_l();
+ removeEffectFromHal_l();
// release effect engine
mEffectInterface->close();
mEffectInterface.clear();
}
}
-status_t AudioFlinger::EffectModule::remove_effect_from_hal_l()
+status_t AudioFlinger::EffectModule::removeEffectFromHal_l()
{
if ((mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC ||
(mDescriptor.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_POST_PROC) {
- sp<ThreadBase> thread = mThread.promote();
- if (thread != 0) {
- sp<StreamHalInterface> stream = thread->stream();
- if (stream != 0) {
- status_t result = stream->removeEffect(mEffectInterface);
- ALOGE_IF(result != OK, "Error when removing effect: %d", result);
- }
- }
+ mCallback->removeEffectFromHal(mEffectInterface);
}
return NO_ERROR;
}
@@ -994,70 +1238,6 @@
return status;
}
-status_t AudioFlinger::EffectModule::setEnabled(bool enabled)
-{
- Mutex::Autolock _l(mLock);
- return setEnabled_l(enabled);
-}
-
-// must be called with EffectModule::mLock held
-status_t AudioFlinger::EffectModule::setEnabled_l(bool enabled)
-{
-
- ALOGV("setEnabled %p enabled %d", this, enabled);
-
- if (enabled != isEnabled()) {
- switch (mState) {
- // going from disabled to enabled
- case IDLE:
- mState = STARTING;
- break;
- case STOPPED:
- mState = RESTART;
- break;
- case STOPPING:
- mState = ACTIVE;
- break;
-
- // going from enabled to disabled
- case RESTART:
- mState = STOPPED;
- break;
- case STARTING:
- mState = IDLE;
- break;
- case ACTIVE:
- mState = STOPPING;
- break;
- case DESTROYED:
- return NO_ERROR; // simply ignore as we are being destroyed
- }
- for (size_t i = 1; i < mHandles.size(); i++) {
- EffectHandle *h = mHandles[i];
- if (h != NULL && !h->disconnected()) {
- h->setEnabled(enabled);
- }
- }
- }
- return NO_ERROR;
-}
-
-bool AudioFlinger::EffectModule::isEnabled() const
-{
- switch (mState) {
- case RESTART:
- case STARTING:
- case ACTIVE:
- return true;
- case IDLE:
- case STOPPING:
- case STOPPED:
- case DESTROYED:
- default:
- return false;
- }
-}
-
bool AudioFlinger::EffectModule::isProcessEnabled() const
{
if (mStatus != NO_ERROR) {
@@ -1080,7 +1260,7 @@
bool AudioFlinger::EffectModule::isOffloadedOrDirect() const
{
- return (mThreadType == ThreadBase::OFFLOAD || mThreadType == ThreadBase::DIRECT);
+ return mCallback->isOffloadOrDirect();
}
bool AudioFlinger::EffectModule::isVolumeControlEnabled() const
@@ -1124,9 +1304,7 @@
|| size > mInConversionBuffer->getSize())) {
mInConversionBuffer.clear();
ALOGV("%s: allocating mInConversionBuffer %zu", __func__, size);
- sp<AudioFlinger> audioFlinger = mAudioFlinger.promote();
- LOG_ALWAYS_FATAL_IF(audioFlinger == nullptr, "EM could not retrieved audioFlinger");
- (void)audioFlinger->mEffectsFactoryHal->allocateBuffer(size, &mInConversionBuffer);
+ (void)mCallback->allocateHalBuffer(size, &mInConversionBuffer);
}
if (mInConversionBuffer.get() != nullptr) {
mInConversionBuffer->setFrameCount(inFrameCount);
@@ -1170,9 +1348,7 @@
|| size > mOutConversionBuffer->getSize())) {
mOutConversionBuffer.clear();
ALOGV("%s: allocating mOutConversionBuffer %zu", __func__, size);
- sp<AudioFlinger> audioFlinger = mAudioFlinger.promote();
- LOG_ALWAYS_FATAL_IF(audioFlinger == nullptr, "EM could not retrieved audioFlinger");
- (void)audioFlinger->mEffectsFactoryHal->allocateBuffer(size, &mOutConversionBuffer);
+ (void)mCallback->allocateHalBuffer(size, &mOutConversionBuffer);
}
if (mOutConversionBuffer.get() != nullptr) {
mOutConversionBuffer->setFrameCount(outFrameCount);
@@ -1220,14 +1396,10 @@
void AudioFlinger::EffectChain::setVolumeForOutput_l(uint32_t left, uint32_t right)
{
- sp<ThreadBase> thread = mThread.promote();
- if (thread != 0 &&
- (thread->type() == ThreadBase::OFFLOAD || thread->type() == ThreadBase::DIRECT) &&
- !isNonOffloadableEnabled_l()) {
- PlaybackThread *t = (PlaybackThread *)thread.get();
+ if (mEffectCallback->isOffloadOrDirect() && !isNonOffloadableEnabled_l()) {
float vol_l = (float)left / (1 << 24);
float vol_r = (float)right / (1 << 24);
- t->setVolumeForOutput_l(vol_l, vol_r);
+ mEffectCallback->setVolumeForOutput(vol_l, vol_r);
}
}
@@ -1307,30 +1479,6 @@
return status;
}
-void AudioFlinger::EffectModule::setSuspended(bool suspended)
-{
- Mutex::Autolock _l(mLock);
- mSuspended = suspended;
-}
-
-bool AudioFlinger::EffectModule::suspended() const
-{
- Mutex::Autolock _l(mLock);
- return mSuspended;
-}
-
-bool AudioFlinger::EffectModule::purgeHandles()
-{
- bool enabled = false;
- Mutex::Autolock _l(mLock);
- EffectHandle *handle = controlHandle_l();
- if (handle != NULL) {
- enabled = handle->enabled();
- }
- mHandles.clear();
- return enabled;
-}
-
status_t AudioFlinger::EffectModule::setOffloaded(bool offloaded, audio_io_handle_t io)
{
Mutex::Autolock _l(mLock);
@@ -1370,111 +1518,6 @@
return mOffloaded;
}
-String8 effectFlagsToString(uint32_t flags) {
- String8 s;
-
- s.append("conn. mode: ");
- switch (flags & EFFECT_FLAG_TYPE_MASK) {
- case EFFECT_FLAG_TYPE_INSERT: s.append("insert"); break;
- case EFFECT_FLAG_TYPE_AUXILIARY: s.append("auxiliary"); break;
- case EFFECT_FLAG_TYPE_REPLACE: s.append("replace"); break;
- case EFFECT_FLAG_TYPE_PRE_PROC: s.append("preproc"); break;
- case EFFECT_FLAG_TYPE_POST_PROC: s.append("postproc"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
-
- s.append("insert pref: ");
- switch (flags & EFFECT_FLAG_INSERT_MASK) {
- case EFFECT_FLAG_INSERT_ANY: s.append("any"); break;
- case EFFECT_FLAG_INSERT_FIRST: s.append("first"); break;
- case EFFECT_FLAG_INSERT_LAST: s.append("last"); break;
- case EFFECT_FLAG_INSERT_EXCLUSIVE: s.append("exclusive"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
-
- s.append("volume mgmt: ");
- switch (flags & EFFECT_FLAG_VOLUME_MASK) {
- case EFFECT_FLAG_VOLUME_NONE: s.append("none"); break;
- case EFFECT_FLAG_VOLUME_CTRL: s.append("implements control"); break;
- case EFFECT_FLAG_VOLUME_IND: s.append("requires indication"); break;
- case EFFECT_FLAG_VOLUME_MONITOR: s.append("monitors volume"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
-
- uint32_t devind = flags & EFFECT_FLAG_DEVICE_MASK;
- if (devind) {
- s.append("device indication: ");
- switch (devind) {
- case EFFECT_FLAG_DEVICE_IND: s.append("requires updates"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
- }
-
- s.append("input mode: ");
- switch (flags & EFFECT_FLAG_INPUT_MASK) {
- case EFFECT_FLAG_INPUT_DIRECT: s.append("direct"); break;
- case EFFECT_FLAG_INPUT_PROVIDER: s.append("provider"); break;
- case EFFECT_FLAG_INPUT_BOTH: s.append("direct+provider"); break;
- default: s.append("not set"); break;
- }
- s.append(", ");
-
- s.append("output mode: ");
- switch (flags & EFFECT_FLAG_OUTPUT_MASK) {
- case EFFECT_FLAG_OUTPUT_DIRECT: s.append("direct"); break;
- case EFFECT_FLAG_OUTPUT_PROVIDER: s.append("provider"); break;
- case EFFECT_FLAG_OUTPUT_BOTH: s.append("direct+provider"); break;
- default: s.append("not set"); break;
- }
- s.append(", ");
-
- uint32_t accel = flags & EFFECT_FLAG_HW_ACC_MASK;
- if (accel) {
- s.append("hardware acceleration: ");
- switch (accel) {
- case EFFECT_FLAG_HW_ACC_SIMPLE: s.append("non-tunneled"); break;
- case EFFECT_FLAG_HW_ACC_TUNNEL: s.append("tunneled"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
- }
-
- uint32_t modeind = flags & EFFECT_FLAG_AUDIO_MODE_MASK;
- if (modeind) {
- s.append("mode indication: ");
- switch (modeind) {
- case EFFECT_FLAG_AUDIO_MODE_IND: s.append("required"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
- }
-
- uint32_t srcind = flags & EFFECT_FLAG_AUDIO_SOURCE_MASK;
- if (srcind) {
- s.append("source indication: ");
- switch (srcind) {
- case EFFECT_FLAG_AUDIO_SOURCE_IND: s.append("required"); break;
- default: s.append("unknown/reserved"); break;
- }
- s.append(", ");
- }
-
- if (flags & EFFECT_FLAG_OFFLOAD_MASK) {
- s.append("offloadable, ");
- }
-
- int len = s.length();
- if (s.length() > 2) {
- (void) s.lockBuffer(len);
- s.unlockBuffer(len - 2);
- }
- return s;
-}
-
static std::string dumpInOutBuffer(bool isInput, const sp<EffectBufferHalInterface> &buffer) {
std::stringstream ss;
@@ -1490,38 +1533,16 @@
return ss.str();
}
-void AudioFlinger::EffectModule::dump(int fd, const Vector<String16>& args __unused)
+void AudioFlinger::EffectModule::dump(int fd, const Vector<String16>& args)
{
+ EffectBase::dump(fd, args);
+
String8 result;
-
- result.appendFormat("\tEffect ID %d:\n", mId);
-
bool locked = AudioFlinger::dumpTryLock(mLock);
- // failed to lock - AudioFlinger is probably deadlocked
- if (!locked) {
- result.append("\t\tCould not lock Fx mutex:\n");
- }
- result.append("\t\tSession Status State Registered Enabled Suspended Engine:\n");
- result.appendFormat("\t\t%05d %03d %03d %s %s %s %p\n",
- mSessionId, mStatus, mState, mPolicyRegistered ? "y" : "n", mPolicyEnabled ? "y" : "n",
- mSuspended ? "y" : "n", mEffectInterface.get());
-
- result.append("\t\tDescriptor:\n");
- char uuidStr[64];
- AudioEffect::guidToString(&mDescriptor.uuid, uuidStr, sizeof(uuidStr));
- result.appendFormat("\t\t- UUID: %s\n", uuidStr);
- AudioEffect::guidToString(&mDescriptor.type, uuidStr, sizeof(uuidStr));
- result.appendFormat("\t\t- TYPE: %s\n", uuidStr);
- result.appendFormat("\t\t- apiVersion: %08X\n\t\t- flags: %08X (%s)\n",
- mDescriptor.apiVersion,
- mDescriptor.flags,
- effectFlagsToString(mDescriptor.flags).string());
- result.appendFormat("\t\t- name: %s\n",
- mDescriptor.name);
-
- result.appendFormat("\t\t- implementor: %s\n",
- mDescriptor.implementor);
+ result.append("\t\tStatus Engine:\n");
+ result.appendFormat("\t\t%03d %p\n",
+ mStatus, mEffectInterface.get());
result.appendFormat("\t\t- data: %s\n", mSupportsFloat ? "float" : "int16");
@@ -1555,17 +1576,6 @@
dumpInOutBuffer(false /* isInput */, mOutConversionBuffer).c_str());
#endif
- result.appendFormat("\t\t%zu Clients:\n", mHandles.size());
- result.append("\t\t\t Pid Priority Ctrl Locked client server\n");
- char buffer[256];
- for (size_t i = 0; i < mHandles.size(); ++i) {
- EffectHandle *handle = mHandles[i];
- if (handle != NULL && !handle->disconnected()) {
- handle->dumpToBuffer(buffer, sizeof(buffer));
- result.append(buffer);
- }
- }
-
write(fd, result.string(), result.length());
if (mEffectInterface != 0) {
@@ -1585,7 +1595,7 @@
#undef LOG_TAG
#define LOG_TAG "AudioFlinger::EffectHandle"
-AudioFlinger::EffectHandle::EffectHandle(const sp<EffectModule>& effect,
+AudioFlinger::EffectHandle::EffectHandle(const sp<EffectBase>& effect,
const sp<AudioFlinger::Client>& client,
const sp<IEffectClient>& effectClient,
int32_t priority)
@@ -1593,7 +1603,7 @@
mEffect(effect), mEffectClient(effectClient), mClient(client), mCblk(NULL),
mPriority(priority), mHasControl(false), mEnabled(false), mDisconnected(false)
{
- ALOGV("constructor %p", this);
+ ALOGV("constructor %p client %p", this, client.get());
if (client == 0) {
return;
@@ -1626,7 +1636,7 @@
{
AutoMutex _l(mLock);
ALOGV("enable %p", this);
- sp<EffectModule> effect = mEffect.promote();
+ sp<EffectBase> effect = mEffect.promote();
if (effect == 0 || mDisconnected) {
return DEAD_OBJECT;
}
@@ -1646,38 +1656,16 @@
return status;
}
- sp<ThreadBase> thread = effect->thread().promote();
- if (thread != 0) {
- thread->checkSuspendOnEffectEnabled(effect, true, effect->sessionId());
- }
+ effect->checkSuspendOnEffectEnabled(true, false /*threadLocked*/);
// checkSuspendOnEffectEnabled() can suspend this same effect when enabled
if (effect->suspended()) {
return NO_ERROR;
}
- status = effect->setEnabled(true);
+ status = effect->setEnabled(true, true /*fromHandle*/);
if (status != NO_ERROR) {
- if (thread != 0) {
- thread->checkSuspendOnEffectEnabled(effect, false, effect->sessionId());
- }
mEnabled = false;
- } else {
- if (thread != 0) {
- if (thread->type() == ThreadBase::OFFLOAD || thread->type() == ThreadBase::MMAP) {
- Mutex::Autolock _l(thread->mLock);
- thread->broadcast_l();
- }
- if (!effect->isOffloadable()) {
- if (thread->type() == ThreadBase::OFFLOAD) {
- PlaybackThread *t = (PlaybackThread *)thread.get();
- t->invalidateTracks(AUDIO_STREAM_MUSIC);
- }
- if (effect->sessionId() == AUDIO_SESSION_OUTPUT_MIX) {
- thread->mAudioFlinger->onNonOffloadableGlobalEffectEnable();
- }
- }
- }
}
return status;
}
@@ -1686,7 +1674,7 @@
{
ALOGV("disable %p", this);
AutoMutex _l(mLock);
- sp<EffectModule> effect = mEffect.promote();
+ sp<EffectBase> effect = mEffect.promote();
if (effect == 0 || mDisconnected) {
return DEAD_OBJECT;
}
@@ -1705,17 +1693,7 @@
return NO_ERROR;
}
- status_t status = effect->setEnabled(false);
-
- sp<ThreadBase> thread = effect->thread().promote();
- if (thread != 0) {
- thread->checkSuspendOnEffectEnabled(effect, false, effect->sessionId());
- if (thread->type() == ThreadBase::OFFLOAD || thread->type() == ThreadBase::MMAP) {
- Mutex::Autolock _l(thread->mLock);
- thread->broadcast_l();
- }
- }
-
+ status_t status = effect->setEnabled(false, true /*fromHandle*/);
return status;
}
@@ -1737,12 +1715,9 @@
}
mDisconnected = true;
{
- sp<EffectModule> effect = mEffect.promote();
+ sp<EffectBase> effect = mEffect.promote();
if (effect != 0) {
- sp<ThreadBase> thread = effect->thread().promote();
- if (thread != 0) {
- thread->disconnectEffectHandle(this, unpinIfLast);
- } else if (effect->disconnectHandle(this, unpinIfLast) > 0) {
+ if (effect->disconnectHandle(this, unpinIfLast) > 0) {
ALOGW("%s Effect handle %p disconnected after thread destruction",
__func__, this);
}
@@ -1808,7 +1783,7 @@
}
AutoMutex _l(mLock);
- sp<EffectModule> effect = mEffect.promote();
+ sp<EffectBase> effect = mEffect.promote();
if (effect == 0 || mDisconnected) {
return DEAD_OBJECT;
}
@@ -1816,12 +1791,13 @@
if (!mHasControl && cmdCode != EFFECT_CMD_GET_PARAM) {
return INVALID_OPERATION;
}
- if (mClient == 0) {
- return INVALID_OPERATION;
- }
// handle commands that are not forwarded transparently to effect engine
if (cmdCode == EFFECT_CMD_SET_PARAM_COMMIT) {
+ if (mClient == 0) {
+ return INVALID_OPERATION;
+ }
+
if (*replySize < sizeof(int)) {
android_errorWriteLog(0x534e4554, "32095713");
return BAD_VALUE;
@@ -1856,12 +1832,13 @@
}
// copy to local memory in case of client corruption b/32220769
- param = (effect_param_t *)realloc(param, size);
- if (param == NULL) {
+ auto *newParam = (effect_param_t *)realloc(param, size);
+ if (newParam == NULL) {
ALOGW("command(): out of memory");
status = NO_MEMORY;
break;
}
+ param = newParam;
memcpy(param, p, size);
int reply = 0;
@@ -1962,12 +1939,13 @@
AudioFlinger::EffectChain::EffectChain(ThreadBase *thread,
audio_session_t sessionId)
- : mThread(thread), mSessionId(sessionId), mActiveTrackCnt(0), mTrackCnt(0), mTailBufferCount(0),
+ : mSessionId(sessionId), mActiveTrackCnt(0), mTrackCnt(0), mTailBufferCount(0),
mVolumeCtrlIdx(-1), mLeftVolume(UINT_MAX), mRightVolume(UINT_MAX),
- mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX)
+ mNewLeftVolume(UINT_MAX), mNewRightVolume(UINT_MAX),
+ mEffectCallback(new EffectCallback(this, thread, thread->mAudioFlinger.get()))
{
mStrategy = AudioSystem::getStrategyForStream(AUDIO_STREAM_MUSIC);
- if (thread == NULL) {
+ if (thread == nullptr) {
return;
}
mMaxTailBuffers = ((kProcessTailDurationMs * thread->sampleRate()) / 1000) /
@@ -2033,40 +2011,29 @@
void AudioFlinger::EffectChain::clearInputBuffer()
{
Mutex::Autolock _l(mLock);
- sp<ThreadBase> thread = mThread.promote();
- if (thread == 0) {
- ALOGW("clearInputBuffer(): cannot promote mixer thread");
- return;
- }
- clearInputBuffer_l(thread);
+ clearInputBuffer_l();
}
// Must be called with EffectChain::mLock locked
-void AudioFlinger::EffectChain::clearInputBuffer_l(const sp<ThreadBase>& thread)
+void AudioFlinger::EffectChain::clearInputBuffer_l()
{
if (mInBuffer == NULL) {
return;
}
const size_t frameSize =
- audio_bytes_per_sample(EFFECT_BUFFER_FORMAT) * thread->channelCount();
+ audio_bytes_per_sample(EFFECT_BUFFER_FORMAT) * mEffectCallback->channelCount();
- memset(mInBuffer->audioBuffer()->raw, 0, thread->frameCount() * frameSize);
+ memset(mInBuffer->audioBuffer()->raw, 0, mEffectCallback->frameCount() * frameSize);
mInBuffer->commit();
}
// Must be called with EffectChain::mLock locked
void AudioFlinger::EffectChain::process_l()
{
- sp<ThreadBase> thread = mThread.promote();
- if (thread == 0) {
- ALOGW("process_l(): cannot promote mixer thread");
- return;
- }
// never process effects when:
// - on an OFFLOAD thread
// - no more tracks are on the session and the effect tail has been rendered
- bool doProcess = (thread->type() != ThreadBase::OFFLOAD)
- && (thread->type() != ThreadBase::MMAP);
+ bool doProcess = !mEffectCallback->isOffloadOrMmap();
if (!audio_is_global_session(mSessionId)) {
bool tracksOnSession = (trackCnt() != 0);
@@ -2078,7 +2045,7 @@
// if no track is active and the effect tail has not been rendered,
// the input buffer must be cleared here as the mixer process will not do it
if (tracksOnSession || mTailBufferCount > 0) {
- clearInputBuffer_l(thread);
+ clearInputBuffer_l();
if (mTailBufferCount > 0) {
mTailBufferCount--;
}
@@ -2114,14 +2081,13 @@
// createEffect_l() must be called with ThreadBase::mLock held
status_t AudioFlinger::EffectChain::createEffect_l(sp<EffectModule>& effect,
- ThreadBase *thread,
effect_descriptor_t *desc,
int id,
audio_session_t sessionId,
bool pinned)
{
Mutex::Autolock _l(mLock);
- effect = new EffectModule(thread, this, desc, id, sessionId, pinned);
+ effect = new EffectModule(mEffectCallback, desc, id, sessionId, pinned, AUDIO_PORT_HANDLE_NONE);
status_t lStatus = effect->status();
if (lStatus == NO_ERROR) {
lStatus = addEffect_ll(effect);
@@ -2144,12 +2110,7 @@
effect_descriptor_t desc = effect->desc();
uint32_t insertPref = desc.flags & EFFECT_FLAG_INSERT_MASK;
- effect->setChain(this);
- sp<ThreadBase> thread = mThread.promote();
- if (thread == 0) {
- return NO_INIT;
- }
- effect->setThread(thread);
+ effect->setCallback(mEffectCallback);
if ((desc.flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_AUXILIARY) {
// Auxiliary effects are inserted at the beginning of mEffects vector as
@@ -2160,13 +2121,13 @@
// 32 bit format. This is to avoid saturation in AudoMixer
// accumulation stage. Saturation is done in EffectModule::process() before
// calling the process in effect engine
- size_t numSamples = thread->frameCount();
+ size_t numSamples = mEffectCallback->frameCount();
sp<EffectBufferHalInterface> halBuffer;
#ifdef FLOAT_EFFECT_CHAIN
- status_t result = thread->mAudioFlinger->mEffectsFactoryHal->allocateBuffer(
+ status_t result = mEffectCallback->allocateHalBuffer(
numSamples * sizeof(float), &halBuffer);
#else
- status_t result = thread->mAudioFlinger->mEffectsFactoryHal->allocateBuffer(
+ status_t result = mEffectCallback->allocateHalBuffer(
numSamples * sizeof(int32_t), &halBuffer);
#endif
if (result != OK) return result;
@@ -2484,7 +2445,7 @@
if (effect != 0) {
desc->mEffect = effect;
effect->setSuspended(true);
- effect->setEnabled(false);
+ effect->setEnabled(false, false /*fromHandle*/);
}
}
} else {
@@ -2642,7 +2603,7 @@
// if effect is requested to suspended but was not yet enabled, suspend it now.
if (desc->mEffect == 0) {
desc->mEffect = effect;
- effect->setEnabled(false);
+ effect->setEnabled(false, false /*fromHandle*/);
effect->setSuspended(true);
}
} else {
@@ -2677,10 +2638,7 @@
void AudioFlinger::EffectChain::setThread(const sp<ThreadBase>& thread)
{
Mutex::Autolock _l(mLock);
- mThread = thread;
- for (size_t i = 0; i < mEffects.size(); i++) {
- mEffects[i]->setThread(thread);
- }
+ mEffectCallback->setThread(thread.get());
}
void AudioFlinger::EffectChain::checkOutputFlagCompatibility(audio_output_flags_t *flags) const
@@ -2740,4 +2698,549 @@
return true;
}
+// EffectCallbackInterface implementation
+status_t AudioFlinger::EffectChain::EffectCallback::createEffectHal(
+ const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t deviceId,
+ sp<EffectHalInterface> *effect) {
+ status_t status = NO_INIT;
+ sp<AudioFlinger> af = mAudioFlinger.promote();
+ if (af == nullptr) {
+ return status;
+ }
+ sp<EffectsFactoryHalInterface> effectsFactory = af->getEffectsFactory();
+ if (effectsFactory != 0) {
+ status = effectsFactory->createEffect(pEffectUuid, sessionId, io(), deviceId, effect);
+ }
+ return status;
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::updateOrphanEffectChains(
+ const sp<AudioFlinger::EffectBase>& effect) {
+ sp<AudioFlinger> af = mAudioFlinger.promote();
+ if (af == nullptr) {
+ return false;
+ }
+ // in EffectChain context, an EffectBase is always from an EffectModule so static cast is safe
+ return af->updateOrphanEffectChains(effect->asEffectModule());
+}
+
+status_t AudioFlinger::EffectChain::EffectCallback::allocateHalBuffer(
+ size_t size, sp<EffectBufferHalInterface>* buffer) {
+ sp<AudioFlinger> af = mAudioFlinger.promote();
+ LOG_ALWAYS_FATAL_IF(af == nullptr, "allocateHalBuffer() could not retrieved audio flinger");
+ return af->mEffectsFactoryHal->allocateBuffer(size, buffer);
+}
+
+status_t AudioFlinger::EffectChain::EffectCallback::addEffectToHal(
+ sp<EffectHalInterface> effect) {
+ status_t result = NO_INIT;
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return result;
+ }
+ sp <StreamHalInterface> st = t->stream();
+ if (st == nullptr) {
+ return result;
+ }
+ result = st->addEffect(effect);
+ ALOGE_IF(result != OK, "Error when adding effect: %d", result);
+ return result;
+}
+
+status_t AudioFlinger::EffectChain::EffectCallback::removeEffectFromHal(
+ sp<EffectHalInterface> effect) {
+ status_t result = NO_INIT;
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return result;
+ }
+ sp <StreamHalInterface> st = t->stream();
+ if (st == nullptr) {
+ return result;
+ }
+ result = st->removeEffect(effect);
+ ALOGE_IF(result != OK, "Error when removing effect: %d", result);
+ return result;
+}
+
+audio_io_handle_t AudioFlinger::EffectChain::EffectCallback::io() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return AUDIO_IO_HANDLE_NONE;
+ }
+ return t->id();
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::isOutput() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return true;
+ }
+ return t->isOutput();
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::isOffload() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return false;
+ }
+ return t->type() == ThreadBase::OFFLOAD;
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::isOffloadOrDirect() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return false;
+ }
+ return t->type() == ThreadBase::OFFLOAD || t->type() == ThreadBase::DIRECT;
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::isOffloadOrMmap() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return false;
+ }
+ return t->type() == ThreadBase::OFFLOAD || t->type() == ThreadBase::MMAP;
+}
+
+uint32_t AudioFlinger::EffectChain::EffectCallback::sampleRate() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return 0;
+ }
+ return t->sampleRate();
+}
+
+audio_channel_mask_t AudioFlinger::EffectChain::EffectCallback::channelMask() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return AUDIO_CHANNEL_NONE;
+ }
+ return t->channelMask();
+}
+
+uint32_t AudioFlinger::EffectChain::EffectCallback::channelCount() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return 0;
+ }
+ return t->channelCount();
+}
+
+size_t AudioFlinger::EffectChain::EffectCallback::frameCount() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return 0;
+ }
+ return t->frameCount();
+}
+
+uint32_t AudioFlinger::EffectChain::EffectCallback::latency() const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return 0;
+ }
+ return t->latency_l();
+}
+
+void AudioFlinger::EffectChain::EffectCallback::setVolumeForOutput(float left, float right) const {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return;
+ }
+ t->setVolumeForOutput_l(left, right);
+}
+
+void AudioFlinger::EffectChain::EffectCallback::checkSuspendOnEffectEnabled(
+ const sp<EffectBase>& effect, bool enabled, bool threadLocked) {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return;
+ }
+ t->checkSuspendOnEffectEnabled(enabled, effect->sessionId(), threadLocked);
+
+ sp<EffectChain> c = mChain.promote();
+ if (c == nullptr) {
+ return;
+ }
+ // in EffectChain context, an EffectBase is always from an EffectModule so static cast is safe
+ c->checkSuspendOnEffectEnabled(effect->asEffectModule(), enabled);
+}
+
+void AudioFlinger::EffectChain::EffectCallback::onEffectEnable(const sp<EffectBase>& effect) {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return;
+ }
+ // in EffectChain context, an EffectBase is always from an EffectModule so static cast is safe
+ t->onEffectEnable(effect->asEffectModule());
+}
+
+void AudioFlinger::EffectChain::EffectCallback::onEffectDisable(const sp<EffectBase>& effect) {
+ checkSuspendOnEffectEnabled(effect, false, false /*threadLocked*/);
+
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return;
+ }
+ t->onEffectDisable();
+}
+
+bool AudioFlinger::EffectChain::EffectCallback::disconnectEffectHandle(EffectHandle *handle,
+ bool unpinIfLast) {
+ sp<ThreadBase> t = mThread.promote();
+ if (t == nullptr) {
+ return false;
+ }
+ t->disconnectEffectHandle(handle, unpinIfLast);
+ return true;
+}
+
+void AudioFlinger::EffectChain::EffectCallback::resetVolume() {
+ sp<EffectChain> c = mChain.promote();
+ if (c == nullptr) {
+ return;
+ }
+ c->resetVolume_l();
+
+}
+
+uint32_t AudioFlinger::EffectChain::EffectCallback::strategy() const {
+ sp<EffectChain> c = mChain.promote();
+ if (c == nullptr) {
+ return PRODUCT_STRATEGY_NONE;
+ }
+ return c->strategy();
+}
+
+int32_t AudioFlinger::EffectChain::EffectCallback::activeTrackCnt() const {
+ sp<EffectChain> c = mChain.promote();
+ if (c == nullptr) {
+ return 0;
+ }
+ return c->activeTrackCnt();
+}
+
+
+#undef LOG_TAG
+#define LOG_TAG "AudioFlinger::DeviceEffectProxy"
+
+status_t AudioFlinger::DeviceEffectProxy::setEnabled(bool enabled, bool fromHandle)
+{
+ status_t status = EffectBase::setEnabled(enabled, fromHandle);
+ Mutex::Autolock _l(mProxyLock);
+ if (status == NO_ERROR) {
+ for (auto& handle : mEffectHandles) {
+ if (enabled) {
+ status = handle.second->enable();
+ } else {
+ status = handle.second->disable();
+ }
+ }
+ }
+ ALOGV("%s enable %d status %d", __func__, enabled, status);
+ return status;
+}
+
+status_t AudioFlinger::DeviceEffectProxy::init(
+ const std::map <audio_patch_handle_t, PatchPanel::Patch>& patches) {
+//For all audio patches
+//If src or sink device match
+//If the effect is HW accelerated
+// if no corresponding effect module
+// Create EffectModule: mHalEffect
+//Create and attach EffectHandle
+//If the effect is not HW accelerated and the patch sink or src is a mixer port
+// Create Effect on patch input or output thread on session -1
+//Add EffectHandle to EffectHandle map of Effect Proxy:
+ ALOGV("%s device type %d address %s", __func__, mDevice.mType, mDevice.getAddress());
+ status_t status = NO_ERROR;
+ for (auto &patch : patches) {
+ status = onCreatePatch(patch.first, patch.second);
+ ALOGV("%s onCreatePatch status %d", __func__, status);
+ if (status == BAD_VALUE) {
+ return status;
+ }
+ }
+ return status;
+}
+
+status_t AudioFlinger::DeviceEffectProxy::onCreatePatch(
+ audio_patch_handle_t patchHandle, const AudioFlinger::PatchPanel::Patch& patch) {
+ status_t status = NAME_NOT_FOUND;
+ sp<EffectHandle> handle;
+ // only consider source[0] as this is the only "true" source of a patch
+ status = checkPort(patch, &patch.mAudioPatch.sources[0], &handle);
+ ALOGV("%s source checkPort status %d", __func__, status);
+ for (uint32_t i = 0; i < patch.mAudioPatch.num_sinks && status == NAME_NOT_FOUND; i++) {
+ status = checkPort(patch, &patch.mAudioPatch.sinks[i], &handle);
+ ALOGV("%s sink %d checkPort status %d", __func__, i, status);
+ }
+ if (status == NO_ERROR || status == ALREADY_EXISTS) {
+ Mutex::Autolock _l(mProxyLock);
+ mEffectHandles.emplace(patchHandle, handle);
+ }
+ ALOGW_IF(status == BAD_VALUE,
+ "%s cannot attach effect %s on patch %d", __func__, mDescriptor.name, patchHandle);
+
+ return status;
+}
+
+status_t AudioFlinger::DeviceEffectProxy::checkPort(const PatchPanel::Patch& patch,
+ const struct audio_port_config *port, sp <EffectHandle> *handle) {
+
+ ALOGV("%s type %d device type %d address %s device ID %d patch.isSoftware() %d",
+ __func__, port->type, port->ext.device.type,
+ port->ext.device.address, port->id, patch.isSoftware());
+ if (port->type != AUDIO_PORT_TYPE_DEVICE || port->ext.device.type != mDevice.mType
+ || port->ext.device.address != mDevice.mAddress) {
+ return NAME_NOT_FOUND;
+ }
+ status_t status = NAME_NOT_FOUND;
+
+ if (mDescriptor.flags & EFFECT_FLAG_HW_ACC_TUNNEL) {
+ Mutex::Autolock _l(mProxyLock);
+ mDevicePort = *port;
+ mHalEffect = new EffectModule(mMyCallback,
+ const_cast<effect_descriptor_t *>(&mDescriptor),
+ mMyCallback->newEffectId(), AUDIO_SESSION_DEVICE,
+ false /* pinned */, port->id);
+ if (audio_is_input_device(mDevice.mType)) {
+ mHalEffect->setInputDevice(mDevice);
+ } else {
+ mHalEffect->setDevices({mDevice});
+ }
+ *handle = new EffectHandle(mHalEffect, nullptr, nullptr, 0 /*priority*/);
+ status = (*handle)->initCheck();
+ if (status == OK) {
+ status = mHalEffect->addHandle((*handle).get());
+ } else {
+ mHalEffect.clear();
+ mDevicePort.id = AUDIO_PORT_HANDLE_NONE;
+ }
+ } else if (patch.isSoftware() || patch.thread().promote() != nullptr) {
+ sp <ThreadBase> thread;
+ if (audio_port_config_has_input_direction(port)) {
+ if (patch.isSoftware()) {
+ thread = patch.mRecord.thread();
+ } else {
+ thread = patch.thread().promote();
+ }
+ } else {
+ if (patch.isSoftware()) {
+ thread = patch.mPlayback.thread();
+ } else {
+ thread = patch.thread().promote();
+ }
+ }
+ int enabled;
+ *handle = thread->createEffect_l(nullptr, nullptr, 0, AUDIO_SESSION_DEVICE,
+ const_cast<effect_descriptor_t *>(&mDescriptor),
+ &enabled, &status, false);
+ ALOGV("%s thread->createEffect_l status %d", __func__, status);
+ } else {
+ status = BAD_VALUE;
+ }
+ if (status == NO_ERROR || status == ALREADY_EXISTS) {
+ if (isEnabled()) {
+ (*handle)->enable();
+ } else {
+ (*handle)->disable();
+ }
+ }
+ return status;
+}
+
+void AudioFlinger::DeviceEffectProxy::onReleasePatch(audio_patch_handle_t patchHandle) {
+ Mutex::Autolock _l(mProxyLock);
+ mEffectHandles.erase(patchHandle);
+}
+
+
+size_t AudioFlinger::DeviceEffectProxy::removeEffect(const sp<EffectModule>& effect)
+{
+ Mutex::Autolock _l(mProxyLock);
+ if (effect == mHalEffect) {
+ mHalEffect.clear();
+ mDevicePort.id = AUDIO_PORT_HANDLE_NONE;
+ }
+ return mHalEffect == nullptr ? 0 : 1;
+}
+
+status_t AudioFlinger::DeviceEffectProxy::addEffectToHal(
+ sp<EffectHalInterface> effect) {
+ if (mHalEffect == nullptr) {
+ return NO_INIT;
+ }
+ return mManagerCallback->addEffectToHal(
+ mDevicePort.id, mDevicePort.ext.device.hw_module, effect);
+}
+
+status_t AudioFlinger::DeviceEffectProxy::removeEffectFromHal(
+ sp<EffectHalInterface> effect) {
+ if (mHalEffect == nullptr) {
+ return NO_INIT;
+ }
+ return mManagerCallback->removeEffectFromHal(
+ mDevicePort.id, mDevicePort.ext.device.hw_module, effect);
+}
+
+bool AudioFlinger::DeviceEffectProxy::isOutput() const {
+ if (mDevicePort.id != AUDIO_PORT_HANDLE_NONE) {
+ return mDevicePort.role == AUDIO_PORT_ROLE_SINK;
+ }
+ return true;
+}
+
+uint32_t AudioFlinger::DeviceEffectProxy::sampleRate() const {
+ if (mDevicePort.id != AUDIO_PORT_HANDLE_NONE &&
+ (mDevicePort.config_mask & AUDIO_PORT_CONFIG_SAMPLE_RATE) != 0) {
+ return mDevicePort.sample_rate;
+ }
+ return DEFAULT_OUTPUT_SAMPLE_RATE;
+}
+
+audio_channel_mask_t AudioFlinger::DeviceEffectProxy::channelMask() const {
+ if (mDevicePort.id != AUDIO_PORT_HANDLE_NONE &&
+ (mDevicePort.config_mask & AUDIO_PORT_CONFIG_CHANNEL_MASK) != 0) {
+ return mDevicePort.channel_mask;
+ }
+ return AUDIO_CHANNEL_OUT_STEREO;
+}
+
+uint32_t AudioFlinger::DeviceEffectProxy::channelCount() const {
+ if (isOutput()) {
+ return audio_channel_count_from_out_mask(channelMask());
+ }
+ return audio_channel_count_from_in_mask(channelMask());
+}
+
+void AudioFlinger::DeviceEffectProxy::dump(int fd, int spaces) {
+ const Vector<String16> args;
+ EffectBase::dump(fd, args);
+
+ const bool locked = dumpTryLock(mProxyLock);
+ if (!locked) {
+ String8 result("DeviceEffectProxy may be deadlocked\n");
+ write(fd, result.string(), result.size());
+ }
+
+ String8 outStr;
+ if (mHalEffect != nullptr) {
+ outStr.appendFormat("%*sHAL Effect Id: %d\n", spaces, "", mHalEffect->id());
+ } else {
+ outStr.appendFormat("%*sNO HAL Effect\n", spaces, "");
+ }
+ write(fd, outStr.string(), outStr.size());
+ outStr.clear();
+
+ outStr.appendFormat("%*sSub Effects:\n", spaces, "");
+ write(fd, outStr.string(), outStr.size());
+ outStr.clear();
+
+ for (const auto& iter : mEffectHandles) {
+ outStr.appendFormat("%*sEffect for patch handle %d:\n", spaces + 2, "", iter.first);
+ write(fd, outStr.string(), outStr.size());
+ outStr.clear();
+ sp<EffectBase> effect = iter.second->effect().promote();
+ if (effect != nullptr) {
+ effect->dump(fd, args);
+ }
+ }
+
+ if (locked) {
+ mLock.unlock();
+ }
+}
+
+#undef LOG_TAG
+#define LOG_TAG "AudioFlinger::DeviceEffectProxy::ProxyCallback"
+
+int AudioFlinger::DeviceEffectProxy::ProxyCallback::newEffectId() {
+ return mManagerCallback->newEffectId();
+}
+
+
+bool AudioFlinger::DeviceEffectProxy::ProxyCallback::disconnectEffectHandle(
+ EffectHandle *handle, bool unpinIfLast) {
+ sp<EffectBase> effectBase = handle->effect().promote();
+ if (effectBase == nullptr) {
+ return false;
+ }
+
+ sp<EffectModule> effect = effectBase->asEffectModule();
+ if (effect == nullptr) {
+ return false;
+ }
+
+ // restore suspended effects if the disconnected handle was enabled and the last one.
+ bool remove = (effect->removeHandle(handle) == 0) && (!effect->isPinned() || unpinIfLast);
+ if (remove) {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy != nullptr) {
+ proxy->removeEffect(effect);
+ }
+ if (handle->enabled()) {
+ effectBase->checkSuspendOnEffectEnabled(false, false /*threadLocked*/);
+ }
+ }
+ return true;
+}
+
+status_t AudioFlinger::DeviceEffectProxy::ProxyCallback::createEffectHal(
+ const effect_uuid_t *pEffectUuid, int32_t sessionId, int32_t deviceId,
+ sp<EffectHalInterface> *effect) {
+ return mManagerCallback->createEffectHal(pEffectUuid, sessionId, deviceId, effect);
+}
+
+status_t AudioFlinger::DeviceEffectProxy::ProxyCallback::addEffectToHal(
+ sp<EffectHalInterface> effect) {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return NO_INIT;
+ }
+ return proxy->addEffectToHal(effect);
+}
+
+status_t AudioFlinger::DeviceEffectProxy::ProxyCallback::removeEffectFromHal(
+ sp<EffectHalInterface> effect) {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return NO_INIT;
+ }
+ return proxy->addEffectToHal(effect);
+}
+
+bool AudioFlinger::DeviceEffectProxy::ProxyCallback::isOutput() const {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return true;
+ }
+ return proxy->isOutput();
+}
+
+uint32_t AudioFlinger::DeviceEffectProxy::ProxyCallback::sampleRate() const {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return DEFAULT_OUTPUT_SAMPLE_RATE;
+ }
+ return proxy->sampleRate();
+}
+
+audio_channel_mask_t AudioFlinger::DeviceEffectProxy::ProxyCallback::channelMask() const {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return AUDIO_CHANNEL_OUT_STEREO;
+ }
+ return proxy->channelMask();
+}
+
+uint32_t AudioFlinger::DeviceEffectProxy::ProxyCallback::channelCount() const {
+ sp<DeviceEffectProxy> proxy = mProxy.promote();
+ if (proxy == nullptr) {
+ return 2;
+ }
+ return proxy->channelCount();
+}
+
} // namespace android
diff --git a/services/audioflinger/Effects.h b/services/audioflinger/Effects.h
index dbf63c8..40bb226 100644
--- a/services/audioflinger/Effects.h
+++ b/services/audioflinger/Effects.h
@@ -21,34 +21,78 @@
//--- Audio Effect Management
-// EffectModule and EffectChain classes both have their own mutex to protect
+// Interface implemented by the EffectModule parent or owner (e.g an EffectChain) to abstract
+// interactions between the EffectModule and the reset of the audio framework.
+class EffectCallbackInterface : public RefBase {
+public:
+ ~EffectCallbackInterface() override = default;
+
+ // Trivial methods usually implemented with help from ThreadBase
+ virtual audio_io_handle_t io() const = 0;
+ virtual bool isOutput() const = 0;
+ virtual bool isOffload() const = 0;
+ virtual bool isOffloadOrDirect() const = 0;
+ virtual bool isOffloadOrMmap() const = 0;
+ virtual uint32_t sampleRate() const = 0;
+ virtual audio_channel_mask_t channelMask() const = 0;
+ virtual uint32_t channelCount() const = 0;
+ virtual size_t frameCount() const = 0;
+
+ // Non trivial methods usually implemented with help from ThreadBase:
+ // pay attention to mutex locking order
+ virtual uint32_t latency() const { return 0; }
+ virtual status_t addEffectToHal(sp<EffectHalInterface> effect) = 0;
+ virtual status_t removeEffectFromHal(sp<EffectHalInterface> effect) = 0;
+ virtual void setVolumeForOutput(float left, float right) const = 0;
+ virtual bool disconnectEffectHandle(EffectHandle *handle, bool unpinIfLast) = 0;
+ virtual void checkSuspendOnEffectEnabled(const sp<EffectBase>& effect,
+ bool enabled,
+ bool threadLocked) = 0;
+ virtual void onEffectEnable(const sp<EffectBase>& effect) = 0;
+ virtual void onEffectDisable(const sp<EffectBase>& effect) = 0;
+
+ // Methods usually implemented with help from AudioFlinger: pay attention to mutex locking order
+ virtual status_t createEffectHal(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t deviceId, sp<EffectHalInterface> *effect) = 0;
+ virtual status_t allocateHalBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) = 0;
+ virtual bool updateOrphanEffectChains(const sp<EffectBase>& effect) = 0;
+
+ // Methods usually implemented with help from EffectChain: pay attention to mutex locking order
+ virtual uint32_t strategy() const = 0;
+ virtual int32_t activeTrackCnt() const = 0;
+ virtual void resetVolume() = 0;
+
+ virtual wp<EffectChain> chain() const = 0;
+};
+
+// EffectBase(EffectModule) and EffectChain classes both have their own mutex to protect
// state changes or resource modifications. Always respect the following order
// if multiple mutexes must be acquired to avoid cross deadlock:
-// AudioFlinger -> ThreadBase -> EffectChain -> EffectModule
-// AudioHandle -> ThreadBase -> EffectChain -> EffectModule
+// AudioFlinger -> ThreadBase -> EffectChain -> EffectBase(EffectModule)
+// AudioHandle -> ThreadBase -> EffectChain -> EffectBase(EffectModule)
+
+// NOTE: When implementing the EffectCallbackInterface, in an EffectChain or other, it is important
+// to pay attention to this locking order as some callback methods can be called from a state where
+// EffectModule and/or EffectChain mutexes are held.
+
// In addition, methods that lock the AudioPolicyService mutex (getOutputForEffect(),
// startOutput(), getInputForAttr(), releaseInput()...) should never be called with AudioFlinger or
// Threadbase mutex locked to avoid cross deadlock with other clients calling AudioPolicyService
// methods that in turn call AudioFlinger thus locking the same mutexes in the reverse order.
-// The EffectModule class is a wrapper object controlling the effect engine implementation
-// in the effect library. It prevents concurrent calls to process() and command() functions
-// from different client threads. It keeps a list of EffectHandle objects corresponding
-// to all client applications using this effect and notifies applications of effect state,
-// control or parameter changes. It manages the activation state machine to send appropriate
-// reset, enable, disable commands to effect engine and provide volume
-// ramping when effects are activated/deactivated.
-// When controlling an auxiliary effect, the EffectModule also provides an input buffer used by
-// the attached track(s) to accumulate their auxiliary channel.
-class EffectModule : public RefBase {
+
+// The EffectBase class contains common properties, state and behavior for and EffectModule or
+// other derived classes managing an audio effect instance within the effect framework.
+// It also contains the class mutex (see comment on locking order above).
+class EffectBase : public RefBase {
public:
- EffectModule(ThreadBase *thread,
- const wp<AudioFlinger::EffectChain>& chain,
- effect_descriptor_t *desc,
- int id,
- audio_session_t sessionId,
- bool pinned);
- virtual ~EffectModule();
+ EffectBase(const sp<EffectCallbackInterface>& callback,
+ effect_descriptor_t *desc,
+ int id,
+ audio_session_t sessionId,
+ bool pinned);
+
+ ~EffectBase() override = default;
enum effect_state {
IDLE,
@@ -60,72 +104,14 @@
DESTROYED
};
- int id() const { return mId; }
- void process();
- bool updateState();
- status_t command(uint32_t cmdCode,
- uint32_t cmdSize,
- void *pCmdData,
- uint32_t *replySize,
- void *pReplyData);
-
- void reset_l();
- status_t configure();
- status_t init();
+ int id() const { return mId; }
effect_state state() const {
return mState;
}
- uint32_t status() {
- return mStatus;
- }
audio_session_t sessionId() const {
return mSessionId;
}
- status_t setEnabled(bool enabled);
- status_t setEnabled_l(bool enabled);
- bool isEnabled() const;
- bool isProcessEnabled() const;
- bool isOffloadedOrDirect() const;
- bool isVolumeControlEnabled() const;
-
- void setInBuffer(const sp<EffectBufferHalInterface>& buffer);
- int16_t *inBuffer() const {
- return mInBuffer != 0 ? reinterpret_cast<int16_t*>(mInBuffer->ptr()) : NULL;
- }
- void setOutBuffer(const sp<EffectBufferHalInterface>& buffer);
- int16_t *outBuffer() const {
- return mOutBuffer != 0 ? reinterpret_cast<int16_t*>(mOutBuffer->ptr()) : NULL;
- }
- void setChain(const wp<EffectChain>& chain) { mChain = chain; }
- void setThread(const wp<ThreadBase>& thread)
- { mThread = thread; mThreadType = thread.promote()->type(); }
- const wp<ThreadBase>& thread() { return mThread; }
-
- status_t addHandle(EffectHandle *handle);
- ssize_t disconnectHandle(EffectHandle *handle, bool unpinIfLast);
- ssize_t removeHandle(EffectHandle *handle);
- ssize_t removeHandle_l(EffectHandle *handle);
-
const effect_descriptor_t& desc() const { return mDescriptor; }
- wp<EffectChain>& chain() { return mChain; }
-
- status_t setDevices(const AudioDeviceTypeAddrVector &devices);
- status_t setInputDevice(const AudioDeviceTypeAddr &device);
- status_t setVolume(uint32_t *left, uint32_t *right, bool controller);
- status_t setMode(audio_mode_t mode);
- status_t setAudioSource(audio_source_t source);
- status_t start();
- status_t stop();
- void setSuspended(bool suspended);
- bool suspended() const;
-
- EffectHandle* controlHandle_l();
-
- bool isPinned() const { return mPinned; }
- void unPin() { mPinned = false; }
- bool purgeHandles();
- void lock() { mLock.lock(); }
- void unlock() { mLock.unlock(); }
bool isOffloadable() const
{ return (mDescriptor.flags & EFFECT_FLAG_OFFLOAD_SUPPORTED) != 0; }
bool isImplementationSoftware() const
@@ -138,18 +124,143 @@
bool isVolumeMonitor() const
{ return (mDescriptor.flags & EFFECT_FLAG_VOLUME_MASK)
== EFFECT_FLAG_VOLUME_MONITOR; }
- status_t setOffloaded(bool offloaded, audio_io_handle_t io);
- bool isOffloaded() const;
- void addEffectToHal_l();
- void release_l();
+
+ virtual status_t setEnabled(bool enabled, bool fromHandle);
+ status_t setEnabled_l(bool enabled);
+ bool isEnabled() const;
+
+ void setSuspended(bool suspended);
+ bool suspended() const;
+
+ virtual status_t command(uint32_t cmdCode __unused,
+ uint32_t cmdSize __unused,
+ void *pCmdData __unused,
+ uint32_t *replySize __unused,
+ void *pReplyData __unused) { return NO_ERROR; };
+
+ void setCallback(const sp<EffectCallbackInterface>& callback) { mCallback = callback; }
+ sp<EffectCallbackInterface>& callback() { return mCallback; }
+
+ status_t addHandle(EffectHandle *handle);
+ ssize_t disconnectHandle(EffectHandle *handle, bool unpinIfLast);
+ ssize_t removeHandle(EffectHandle *handle);
+ virtual ssize_t removeHandle_l(EffectHandle *handle);
+ EffectHandle* controlHandle_l();
+ bool purgeHandles();
+
+ void checkSuspendOnEffectEnabled(bool enabled, bool threadLocked);
+
+ bool isPinned() const { return mPinned; }
+ void unPin() { mPinned = false; }
+
+ void lock() { mLock.lock(); }
+ void unlock() { mLock.unlock(); }
status_t updatePolicyState();
+ virtual sp<EffectModule> asEffectModule() { return nullptr; }
+ virtual sp<DeviceEffectProxy> asDeviceEffectProxy() { return nullptr; }
+
void dump(int fd, const Vector<String16>& args);
private:
friend class AudioFlinger; // for mHandles
- bool mPinned;
+ bool mPinned = false;
+
+ DISALLOW_COPY_AND_ASSIGN(EffectBase);
+
+mutable Mutex mLock; // mutex for process, commands and handles list protection
+ sp<EffectCallbackInterface> mCallback; // parent effect chain
+ const int mId; // this instance unique ID
+ const audio_session_t mSessionId; // audio session ID
+ const effect_descriptor_t mDescriptor;// effect descriptor received from effect engine
+ effect_state mState = IDLE; // current activation state
+ // effect is suspended: temporarily disabled by framework
+ bool mSuspended = false;
+
+ Vector<EffectHandle *> mHandles; // list of client handles
+ // First handle in mHandles has highest priority and controls the effect module
+
+ // Audio policy effect state management
+ // Mutex protecting transactions with audio policy manager as mLock cannot
+ // be held to avoid cross deadlocks with audio policy mutex
+ Mutex mPolicyLock;
+ // Effect is registered in APM or not
+ bool mPolicyRegistered = false;
+ // Effect enabled state communicated to APM. Enabled state corresponds to
+ // state requested by the EffectHandle with control
+ bool mPolicyEnabled = false;
+};
+
+// The EffectModule class is a wrapper object controlling the effect engine implementation
+// in the effect library. It prevents concurrent calls to process() and command() functions
+// from different client threads. It keeps a list of EffectHandle objects corresponding
+// to all client applications using this effect and notifies applications of effect state,
+// control or parameter changes. It manages the activation state machine to send appropriate
+// reset, enable, disable commands to effect engine and provide volume
+// ramping when effects are activated/deactivated.
+// When controlling an auxiliary effect, the EffectModule also provides an input buffer used by
+// the attached track(s) to accumulate their auxiliary channel.
+class EffectModule : public EffectBase {
+public:
+ EffectModule(const sp<EffectCallbackInterface>& callabck,
+ effect_descriptor_t *desc,
+ int id,
+ audio_session_t sessionId,
+ bool pinned,
+ audio_port_handle_t deviceId);
+ virtual ~EffectModule();
+
+ void process();
+ bool updateState();
+ status_t command(uint32_t cmdCode,
+ uint32_t cmdSize,
+ void *pCmdData,
+ uint32_t *replySize,
+ void *pReplyData) override;
+
+ void reset_l();
+ status_t configure();
+ status_t init();
+
+ uint32_t status() {
+ return mStatus;
+ }
+
+ bool isProcessEnabled() const;
+ bool isOffloadedOrDirect() const;
+ bool isVolumeControlEnabled() const;
+
+ void setInBuffer(const sp<EffectBufferHalInterface>& buffer);
+ int16_t *inBuffer() const {
+ return mInBuffer != 0 ? reinterpret_cast<int16_t*>(mInBuffer->ptr()) : NULL;
+ }
+ void setOutBuffer(const sp<EffectBufferHalInterface>& buffer);
+ int16_t *outBuffer() const {
+ return mOutBuffer != 0 ? reinterpret_cast<int16_t*>(mOutBuffer->ptr()) : NULL;
+ }
+
+ ssize_t removeHandle_l(EffectHandle *handle) override;
+
+ status_t setDevices(const AudioDeviceTypeAddrVector &devices);
+ status_t setInputDevice(const AudioDeviceTypeAddr &device);
+ status_t setVolume(uint32_t *left, uint32_t *right, bool controller);
+ status_t setMode(audio_mode_t mode);
+ status_t setAudioSource(audio_source_t source);
+ status_t start();
+ status_t stop();
+
+ status_t setOffloaded(bool offloaded, audio_io_handle_t io);
+ bool isOffloaded() const;
+ void addEffectToHal_l();
+ void release_l();
+
+ sp<EffectModule> asEffectModule() override { return this; }
+
+ void dump(int fd, const Vector<String16>& args);
+
+private:
+ friend class AudioFlinger; // for mHandles
// Maximum time allocated to effect engines to complete the turn off sequence
static const uint32_t MAX_DISABLE_TIME_MS = 10000;
@@ -158,30 +269,19 @@
status_t start_l();
status_t stop_l();
- status_t remove_effect_from_hal_l();
+ status_t removeEffectFromHal_l();
status_t sendSetAudioDevicesCommand(const AudioDeviceTypeAddrVector &devices, uint32_t cmdCode);
-mutable Mutex mLock; // mutex for process, commands and handles list protection
- wp<ThreadBase> mThread; // parent thread
- ThreadBase::type_t mThreadType; // parent thread type
- wp<EffectChain> mChain; // parent effect chain
- const int mId; // this instance unique ID
- const audio_session_t mSessionId; // audio session ID
- const effect_descriptor_t mDescriptor;// effect descriptor received from effect engine
effect_config_t mConfig; // input and output audio configuration
sp<EffectHalInterface> mEffectInterface; // Effect module HAL
sp<EffectBufferHalInterface> mInBuffer; // Buffers for interacting with HAL
sp<EffectBufferHalInterface> mOutBuffer;
status_t mStatus; // initialization status
- effect_state mState; // current activation state
- Vector<EffectHandle *> mHandles; // list of client handles
// First handle in mHandles has highest priority and controls the effect module
uint32_t mMaxDisableWaitCnt; // maximum grace period before forcing an effect off after
// sending disable command.
uint32_t mDisableWaitCnt; // current process() calls count during disable period.
- bool mSuspended; // effect is suspended: temporarily disabled by framework
bool mOffloaded; // effect is currently offloaded to the audio DSP
- wp<AudioFlinger> mAudioFlinger;
#ifdef FLOAT_EFFECT_CHAIN
bool mSupportsFloat; // effect supports float processing
@@ -208,16 +308,6 @@
static constexpr pid_t INVALID_PID = (pid_t)-1;
// this tid is allowed to call setVolume() without acquiring the mutex.
pid_t mSetVolumeReentrantTid = INVALID_PID;
-
- // Audio policy effect state management
- // Mutex protecting transactions with audio policy manager as mLock cannot
- // be held to avoid cross deadlocks with audio policy mutex
- Mutex mPolicyLock;
- // Effect is registered in APM or not
- bool mPolicyRegistered = false;
- // Effect enabled state communicated to APM. Enabled state corresponds to
- // state requested by the EffectHandle with control
- bool mPolicyEnabled = false;
};
// The EffectHandle class implements the IEffect interface. It provides resources
@@ -229,7 +319,7 @@
class EffectHandle: public android::BnEffect {
public:
- EffectHandle(const sp<EffectModule>& effect,
+ EffectHandle(const sp<EffectBase>& effect,
const sp<AudioFlinger::Client>& client,
const sp<IEffectClient>& effectClient,
int32_t priority);
@@ -267,9 +357,9 @@
bool enabled() const { return mEnabled; }
// Getters
- wp<EffectModule> effect() const { return mEffect; }
+ wp<EffectBase> effect() const { return mEffect; }
int id() const {
- sp<EffectModule> effect = mEffect.promote();
+ sp<EffectBase> effect = mEffect.promote();
if (effect == 0) {
return 0;
}
@@ -286,7 +376,7 @@
DISALLOW_COPY_AND_ASSIGN(EffectHandle);
Mutex mLock; // protects IEffect method calls
- wp<EffectModule> mEffect; // pointer to controlled EffectModule
+ wp<EffectBase> mEffect; // pointer to controlled EffectModule
sp<IEffectClient> mEffectClient; // callback interface for client notifications
/*const*/ sp<Client> mClient; // client for shared memory allocation, see disconnect()
sp<IMemory> mCblkMemory; // shared memory for control block
@@ -333,7 +423,6 @@
}
status_t createEffect_l(sp<EffectModule>& effect,
- ThreadBase *thread,
effect_descriptor_t *desc,
int id,
audio_session_t sessionId,
@@ -389,9 +478,8 @@
bool suspend);
// suspend all eligible effects
void setEffectSuspendedAll_l(bool suspend);
- // check if effects should be suspend or restored when a given effect is enable or disabled
- void checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
- bool enabled);
+ // check if effects should be suspended or restored when a given effect is enable or disabled
+ void checkSuspendOnEffectEnabled(const sp<EffectModule>& effect, bool enabled);
void clearInputBuffer();
@@ -416,9 +504,60 @@
// isCompatibleWithThread_l() must be called with thread->mLock held
bool isCompatibleWithThread_l(const sp<ThreadBase>& thread) const;
+ sp<EffectCallbackInterface> effectCallback() const { return mEffectCallback; }
+ wp<ThreadBase> thread() const { return mEffectCallback->thread(); }
+
void dump(int fd, const Vector<String16>& args);
private:
+
+ class EffectCallback : public EffectCallbackInterface {
+ public:
+ EffectCallback(EffectChain *chain, ThreadBase *thread, AudioFlinger *audioFlinger)
+ : mChain(chain), mThread(thread), mAudioFlinger(audioFlinger) {}
+
+ status_t createEffectHal(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t deviceId, sp<EffectHalInterface> *effect) override;
+ status_t allocateHalBuffer(size_t size, sp<EffectBufferHalInterface>* buffer) override;
+ bool updateOrphanEffectChains(const sp<EffectBase>& effect) override;
+
+ audio_io_handle_t io() const override;
+ bool isOutput() const override;
+ bool isOffload() const override;
+ bool isOffloadOrDirect() const override;
+ bool isOffloadOrMmap() const override;
+
+ uint32_t sampleRate() const override;
+ audio_channel_mask_t channelMask() const override;
+ uint32_t channelCount() const override;
+ size_t frameCount() const override;
+ uint32_t latency() const override;
+
+ status_t addEffectToHal(sp<EffectHalInterface> effect) override;
+ status_t removeEffectFromHal(sp<EffectHalInterface> effect) override;
+ bool disconnectEffectHandle(EffectHandle *handle, bool unpinIfLast) override;
+ void setVolumeForOutput(float left, float right) const override;
+
+ // check if effects should be suspended/restored when a given effect is enable/disabled
+ void checkSuspendOnEffectEnabled(const sp<EffectBase>& effect,
+ bool enabled, bool threadLocked) override;
+ void resetVolume() override;
+ uint32_t strategy() const override;
+ int32_t activeTrackCnt() const override;
+ void onEffectEnable(const sp<EffectBase>& effect) override;
+ void onEffectDisable(const sp<EffectBase>& effect) override;
+
+ wp<EffectChain> chain() const override { return mChain; }
+
+ wp<ThreadBase> thread() { return mThread; }
+ void setThread(ThreadBase *thread) { mThread = thread; };
+
+ private:
+ wp<EffectChain> mChain;
+ wp<ThreadBase> mThread;
+ wp<AudioFlinger> mAudioFlinger;
+ };
+
friend class AudioFlinger; // for mThread, mEffects
DISALLOW_COPY_AND_ASSIGN(EffectChain);
@@ -444,13 +583,12 @@
static bool isEffectEligibleForBtNrecSuspend(const effect_uuid_t *type);
- void clearInputBuffer_l(const sp<ThreadBase>& thread);
+ void clearInputBuffer_l();
void setThread(const sp<ThreadBase>& thread);
void setVolumeForOutput_l(uint32_t left, uint32_t right);
- wp<ThreadBase> mThread; // parent mixer thread
mutable Mutex mLock; // mutex protecting effect list
Vector< sp<EffectModule> > mEffects; // list of effect modules
audio_session_t mSessionId; // audio session ID
@@ -474,4 +612,99 @@
// timeLow fields among effect type UUIDs.
// Updated by setEffectSuspended_l() and setEffectSuspendedAll_l() only.
KeyedVector< int, sp<SuspendedEffectDesc> > mSuspendedEffects;
+
+ const sp<EffectCallback> mEffectCallback;
+};
+
+class DeviceEffectProxy : public EffectBase {
+public:
+ DeviceEffectProxy (const AudioDeviceTypeAddr& device,
+ const sp<DeviceEffectManagerCallback>& callback,
+ effect_descriptor_t *desc, int id)
+ : EffectBase(callback, desc, id, AUDIO_SESSION_DEVICE, false),
+ mDevice(device), mManagerCallback(callback),
+ mMyCallback(new ProxyCallback(this, callback)) {}
+
+ status_t setEnabled(bool enabled, bool fromHandle) override;
+ sp<DeviceEffectProxy> asDeviceEffectProxy() override { return this; }
+
+ status_t init(const std::map<audio_patch_handle_t, PatchPanel::Patch>& patches);
+ status_t onCreatePatch(audio_patch_handle_t patchHandle, const PatchPanel::Patch& patch);
+ void onReleasePatch(audio_patch_handle_t patchHandle);
+
+ size_t removeEffect(const sp<EffectModule>& effect);
+
+ status_t addEffectToHal(sp<EffectHalInterface> effect);
+ status_t removeEffectFromHal(sp<EffectHalInterface> effect);
+
+ const AudioDeviceTypeAddr& device() { return mDevice; };
+ bool isOutput() const;
+ uint32_t sampleRate() const;
+ audio_channel_mask_t channelMask() const;
+ uint32_t channelCount() const;
+
+ void dump(int fd, int spaces);
+
+private:
+
+ class ProxyCallback : public EffectCallbackInterface {
+ public:
+ ProxyCallback(DeviceEffectProxy *proxy,
+ const sp<DeviceEffectManagerCallback>& callback)
+ : mProxy(proxy), mManagerCallback(callback) {}
+
+ status_t createEffectHal(const effect_uuid_t *pEffectUuid,
+ int32_t sessionId, int32_t deviceId, sp<EffectHalInterface> *effect) override;
+ status_t allocateHalBuffer(size_t size __unused,
+ sp<EffectBufferHalInterface>* buffer __unused) override { return NO_ERROR; }
+ bool updateOrphanEffectChains(const sp<EffectBase>& effect __unused) override {
+ return false;
+ }
+
+ audio_io_handle_t io() const override { return AUDIO_IO_HANDLE_NONE; }
+ bool isOutput() const override;
+ bool isOffload() const override { return false; }
+ bool isOffloadOrDirect() const override { return false; }
+ bool isOffloadOrMmap() const override { return false; }
+
+ uint32_t sampleRate() const override;
+ audio_channel_mask_t channelMask() const override;
+ uint32_t channelCount() const override;
+ size_t frameCount() const override { return 0; }
+ uint32_t latency() const override { return 0; }
+
+ status_t addEffectToHal(sp<EffectHalInterface> effect) override;
+ status_t removeEffectFromHal(sp<EffectHalInterface> effect) override;
+
+ bool disconnectEffectHandle(EffectHandle *handle, bool unpinIfLast) override;
+ void setVolumeForOutput(float left __unused, float right __unused) const override {}
+
+ void checkSuspendOnEffectEnabled(const sp<EffectBase>& effect __unused,
+ bool enabled __unused, bool threadLocked __unused) override {}
+ void resetVolume() override {}
+ uint32_t strategy() const override { return 0; }
+ int32_t activeTrackCnt() const override { return 0; }
+ void onEffectEnable(const sp<EffectBase>& effect __unused) override {}
+ void onEffectDisable(const sp<EffectBase>& effect __unused) override {}
+
+ wp<EffectChain> chain() const override { return nullptr; }
+
+ int newEffectId();
+
+ private:
+ const wp<DeviceEffectProxy> mProxy;
+ const sp<DeviceEffectManagerCallback> mManagerCallback;
+ };
+
+ status_t checkPort(const PatchPanel::Patch& patch, const struct audio_port_config *port,
+ sp<EffectHandle> *handle);
+
+ const AudioDeviceTypeAddr mDevice;
+ const sp<DeviceEffectManagerCallback> mManagerCallback;
+ const sp<ProxyCallback> mMyCallback;
+
+ Mutex mProxyLock;
+ std::map<audio_patch_handle_t, sp<EffectHandle>> mEffectHandles; // protected by mProxyLock
+ sp<EffectModule> mHalEffect; // protected by mProxyLock
+ struct audio_port_config mDevicePort = { .id = AUDIO_PORT_HANDLE_NONE };
};
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 53e2dd5..5501856 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -170,8 +170,7 @@
}
halHandle = removedPatch.mHalHandle;
}
- mPatches.erase(iter);
- removeSoftwarePatchFromInsertedModules(*handle);
+ erasePatch(*handle);
}
}
@@ -326,10 +325,14 @@
}
}
status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
+ if (status == NO_ERROR) {
+ newPatch.setThread(thread);
+ }
+
// remove stale audio patch with same input as sink if any
for (auto& iter : mPatches) {
if (iter.second.mAudioPatch.sinks[0].ext.mix.handle == thread->id()) {
- mPatches.erase(iter.first);
+ erasePatch(iter.first);
break;
}
}
@@ -388,11 +391,14 @@
}
status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
+ if (status == NO_ERROR) {
+ newPatch.setThread(thread);
+ }
// remove stale audio patch with same output as source if any
for (auto& iter : mPatches) {
if (iter.second.mAudioPatch.sources[0].ext.mix.handle == thread->id()) {
- mPatches.erase(iter.first);
+ erasePatch(iter.first);
break;
}
}
@@ -406,11 +412,11 @@
if (status == NO_ERROR) {
*handle = (audio_patch_handle_t) mAudioFlinger.nextUniqueId(AUDIO_UNIQUE_ID_USE_PATCH);
newPatch.mHalHandle = halHandle;
+ mAudioFlinger.mDeviceEffectManager.createAudioPatch(*handle, newPatch);
mPatches.insert(std::make_pair(*handle, std::move(newPatch)));
if (insertedModule != AUDIO_MODULE_HANDLE_NONE) {
addSoftwarePatchToInsertedModules(insertedModule, *handle);
}
- ALOGV("%s() added new patch handle %d halHandle %d", __func__, *handle, halHandle);
} else {
newPatch.clearConnections(this);
}
@@ -448,18 +454,6 @@
*mPlayback.handlePtr() = AUDIO_PATCH_HANDLE_NONE;
}
- // use a pseudo LCM between input and output framecount
- size_t playbackFrameCount = mPlayback.thread()->frameCount();
- int playbackShift = __builtin_ctz(playbackFrameCount);
- size_t recordFrameCount = mRecord.thread()->frameCount();
- int shift = __builtin_ctz(recordFrameCount);
- if (playbackShift < shift) {
- shift = playbackShift;
- }
- size_t frameCount = (playbackFrameCount * recordFrameCount) >> shift;
- ALOGV("%s() playframeCount %zu recordFrameCount %zu frameCount %zu",
- __func__, playbackFrameCount, recordFrameCount, frameCount);
-
// create a special record track to capture from record thread
uint32_t channelCount = mPlayback.thread()->channelCount();
audio_channel_mask_t inChannelMask = audio_channel_in_mask_from_count(channelCount);
@@ -503,7 +497,15 @@
}
sp<RecordThread::PatchRecord> tempRecordTrack;
+ const size_t playbackFrameCount = mPlayback.thread()->frameCount();
+ const size_t recordFrameCount = mRecord.thread()->frameCount();
+ size_t frameCount = 0;
if ((inputFlags & AUDIO_INPUT_FLAG_DIRECT) && (outputFlags & AUDIO_OUTPUT_FLAG_DIRECT)) {
+ // PassthruPatchRecord producesBufferOnDemand, so use
+ // maximum of playback and record thread framecounts
+ frameCount = std::max(playbackFrameCount, recordFrameCount);
+ ALOGV("%s() playframeCount %zu recordFrameCount %zu frameCount %zu",
+ __func__, playbackFrameCount, recordFrameCount, frameCount);
tempRecordTrack = new RecordThread::PassthruPatchRecord(
mRecord.thread().get(),
sampleRate,
@@ -512,6 +514,16 @@
frameCount,
inputFlags);
} else {
+ // use a pseudo LCM between input and output framecount
+ int playbackShift = __builtin_ctz(playbackFrameCount);
+ int shift = __builtin_ctz(recordFrameCount);
+ if (playbackShift < shift) {
+ shift = playbackShift;
+ }
+ frameCount = (playbackFrameCount * recordFrameCount) >> shift;
+ ALOGV("%s() playframeCount %zu recordFrameCount %zu frameCount %zu",
+ __func__, playbackFrameCount, recordFrameCount, frameCount);
+
tempRecordTrack = new RecordThread::PatchRecord(
mRecord.thread().get(),
sampleRate,
@@ -628,8 +640,21 @@
String8 AudioFlinger::PatchPanel::Patch::dump(audio_patch_handle_t myHandle) const
{
// TODO: Consider table dump form for patches, just like tracks.
- String8 result = String8::format("Patch %d: thread %p => thread %p",
- myHandle, mRecord.const_thread().get(), mPlayback.const_thread().get());
+ String8 result = String8::format("Patch %d: %s (thread %p => thread %p)",
+ myHandle, isSoftware() ? "Software bridge between" : "No software bridge",
+ mRecord.const_thread().get(), mPlayback.const_thread().get());
+
+ bool hasSinkDevice =
+ mAudioPatch.num_sinks > 0 && mAudioPatch.sinks[0].type == AUDIO_PORT_TYPE_DEVICE;
+ bool hasSourceDevice =
+ mAudioPatch.num_sources > 0 && mAudioPatch.sources[0].type == AUDIO_PORT_TYPE_DEVICE;
+ result.appendFormat(" thread %p %s (%d) first device type %08x", mThread.unsafe_get(),
+ hasSinkDevice ? "num sinks" :
+ (hasSourceDevice ? "num sources" : "no devices"),
+ hasSinkDevice ? mAudioPatch.num_sinks :
+ (hasSourceDevice ? mAudioPatch.num_sources : 0),
+ hasSinkDevice ? mAudioPatch.sinks[0].ext.device.type :
+ (hasSourceDevice ? mAudioPatch.sources[0].ext.device.type : 0));
// add latency if it exists
double latencyMs;
@@ -705,11 +730,16 @@
status = BAD_VALUE;
}
- mPatches.erase(iter);
- removeSoftwarePatchFromInsertedModules(handle);
+ erasePatch(handle);
return status;
}
+void AudioFlinger::PatchPanel::erasePatch(audio_patch_handle_t handle) {
+ mPatches.erase(handle);
+ removeSoftwarePatchFromInsertedModules(handle);
+ mAudioFlinger.mDeviceEffectManager.releaseAudioPatch(handle);
+}
+
/* List connected audio ports and they attributes */
status_t AudioFlinger::PatchPanel::listAudioPatches(unsigned int *num_patches __unused,
struct audio_patch *patches __unused)
@@ -793,16 +823,13 @@
String8 patchPanelDump;
const char *indent = " ";
- // Only dump software patches.
bool headerPrinted = false;
for (const auto& iter : mPatches) {
- if (iter.second.isSoftware()) {
- if (!headerPrinted) {
- patchPanelDump += "\nSoftware patches:\n";
- headerPrinted = true;
- }
- patchPanelDump.appendFormat("%s%s\n", indent, iter.second.dump(iter.first).string());
+ if (!headerPrinted) {
+ patchPanelDump += "\nPatches:\n";
+ headerPrinted = true;
}
+ patchPanelDump.appendFormat("%s%s\n", indent, iter.second.dump(iter.first).string());
}
headerPrinted = false;
diff --git a/services/audioflinger/PatchPanel.h b/services/audioflinger/PatchPanel.h
index 181e27c..4e0d243 100644
--- a/services/audioflinger/PatchPanel.h
+++ b/services/audioflinger/PatchPanel.h
@@ -76,13 +76,18 @@
void dump(int fd) const;
-private:
template<typename ThreadType, typename TrackType>
- class Endpoint {
+ class Endpoint final {
public:
Endpoint() = default;
Endpoint(const Endpoint&) = delete;
- Endpoint& operator=(const Endpoint&) = delete;
+ Endpoint& operator=(const Endpoint& other) noexcept {
+ mThread = other.mThread;
+ mCloseThread = other.mCloseThread;
+ mHandle = other.mHandle;
+ mTrack = other.mTrack;
+ return *this;
+ }
Endpoint(Endpoint&& other) noexcept { swap(other); }
Endpoint& operator=(Endpoint&& other) noexcept {
swap(other);
@@ -98,8 +103,8 @@
return trackOrNull->initCheck();
}
audio_patch_handle_t handle() const { return mHandle; }
- sp<ThreadType> thread() { return mThread; }
- sp<TrackType> track() { return mTrack; }
+ sp<ThreadType> thread() const { return mThread; }
+ sp<TrackType> track() const { return mTrack; }
sp<const ThreadType> const_thread() const { return mThread; }
sp<const TrackType> const_track() const { return mTrack; }
@@ -150,14 +155,36 @@
sp<TrackType> mTrack;
};
- class Patch {
+ class Patch final {
public:
explicit Patch(const struct audio_patch &patch) : mAudioPatch(patch) {}
+ Patch() = default;
~Patch();
- Patch(const Patch&) = delete;
- Patch(Patch&&) = default;
- Patch& operator=(const Patch&) = delete;
- Patch& operator=(Patch&&) = default;
+ Patch(const Patch& other) noexcept {
+ mAudioPatch = other.mAudioPatch;
+ mHalHandle = other.mHalHandle;
+ mPlayback = other.mPlayback;
+ mRecord = other.mRecord;
+ mThread = other.mThread;
+ }
+ Patch(Patch&& other) noexcept { swap(other); }
+ Patch& operator=(Patch&& other) noexcept {
+ swap(other);
+ return *this;
+ }
+
+ void swap(Patch &other) noexcept {
+ using std::swap;
+ swap(mAudioPatch, other.mAudioPatch);
+ swap(mHalHandle, other.mHalHandle);
+ swap(mPlayback, other.mPlayback);
+ swap(mRecord, other.mRecord);
+ swap(mThread, other.mThread);
+ }
+
+ friend void swap(Patch &a, Patch &b) noexcept {
+ a.swap(b);
+ }
status_t createConnections(PatchPanel *panel);
void clearConnections(PatchPanel *panel);
@@ -165,6 +192,9 @@
return mRecord.handle() != AUDIO_PATCH_HANDLE_NONE ||
mPlayback.handle() != AUDIO_PATCH_HANDLE_NONE; }
+ void setThread(sp<ThreadBase> thread) { mThread = thread; }
+ wp<ThreadBase> thread() const { return mThread; }
+
// returns the latency of the patch (from record to playback).
status_t getLatencyMs(double *latencyMs) const;
@@ -182,13 +212,20 @@
Endpoint<PlaybackThread, PlaybackThread::PatchTrack> mPlayback;
// connects source device to record thread input
Endpoint<RecordThread, RecordThread::PatchRecord> mRecord;
+
+ wp<ThreadBase> mThread;
};
+ // Call with AudioFlinger mLock held
+ std::map<audio_patch_handle_t, Patch>& patches_l() { return mPatches; }
+
+private:
AudioHwDevice* findAudioHwDeviceByModule(audio_module_handle_t module);
sp<DeviceHalInterface> findHwDeviceByModule(audio_module_handle_t module);
void addSoftwarePatchToInsertedModules(
audio_module_handle_t module, audio_patch_handle_t handle);
void removeSoftwarePatchFromInsertedModules(audio_patch_handle_t handle);
+ void erasePatch(audio_patch_handle_t handle);
AudioFlinger &mAudioFlinger;
std::map<audio_patch_handle_t, Patch> mPatches;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index e70c776..87b72fb 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -1146,18 +1146,13 @@
}
}
-void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
- bool enabled,
- audio_session_t sessionId)
-{
- Mutex::Autolock _l(mLock);
- checkSuspendOnEffectEnabled_l(effect, enabled, sessionId);
-}
+void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled(bool enabled,
+ audio_session_t sessionId,
+ bool threadLocked) {
+ if (!threadLocked) {
+ mLock.lock();
+ }
-void AudioFlinger::ThreadBase::checkSuspendOnEffectEnabled_l(const sp<EffectModule>& effect,
- bool enabled,
- audio_session_t sessionId)
-{
if (mType != RECORD) {
// suspend all effects in AUDIO_SESSION_OUTPUT_MIX when enabling any effect on
// another session. This gives the priority to well behaved effect control panels
@@ -1169,9 +1164,8 @@
}
}
- sp<EffectChain> chain = getEffectChain_l(sessionId);
- if (chain != 0) {
- chain->checkSuspendOnEffectEnabled(effect, enabled);
+ if (!threadLocked) {
+ mLock.unlock();
}
}
@@ -1379,7 +1373,7 @@
if (effect == 0) {
effectId = mAudioFlinger->nextUniqueId(AUDIO_UNIQUE_ID_USE_EFFECT);
// create a new effect module if none present in the chain
- lStatus = chain->createEffect_l(effect, this, desc, effectId, sessionId, pinned);
+ lStatus = chain->createEffect_l(effect, desc, effectId, sessionId, pinned);
if (lStatus != NO_ERROR) {
goto Exit;
}
@@ -1425,9 +1419,12 @@
sp<EffectModule> effect;
{
Mutex::Autolock _l(mLock);
-
- effect = handle->effect().promote();
- if (effect == 0) {
+ sp<EffectBase> effectBase = handle->effect().promote();
+ if (effectBase == nullptr) {
+ return;
+ }
+ effect = effectBase->asEffectModule();
+ if (effect == nullptr) {
return;
}
// restore suspended effects if the disconnected handle was enabled and the last one.
@@ -1439,11 +1436,34 @@
if (remove) {
mAudioFlinger->updateOrphanEffectChains(effect);
if (handle->enabled()) {
- checkSuspendOnEffectEnabled(effect, false, effect->sessionId());
+ effect->checkSuspendOnEffectEnabled(false, false /*threadLocked*/);
}
}
}
+void AudioFlinger::ThreadBase::onEffectEnable(const sp<EffectModule>& effect) {
+ if (mType == OFFLOAD || mType == MMAP) {
+ Mutex::Autolock _l(mLock);
+ broadcast_l();
+ }
+ if (!effect->isOffloadable()) {
+ if (mType == ThreadBase::OFFLOAD) {
+ PlaybackThread *t = (PlaybackThread *)this;
+ t->invalidateTracks(AUDIO_STREAM_MUSIC);
+ }
+ if (effect->sessionId() == AUDIO_SESSION_OUTPUT_MIX) {
+ mAudioFlinger->onNonOffloadableGlobalEffectEnable();
+ }
+ }
+}
+
+void AudioFlinger::ThreadBase::onEffectDisable() {
+ if (mType == OFFLOAD || mType == MMAP) {
+ Mutex::Autolock _l(mLock);
+ broadcast_l();
+ }
+}
+
sp<AudioFlinger::EffectModule> AudioFlinger::ThreadBase::getEffect(audio_session_t sessionId,
int effectId)
{
@@ -1519,7 +1539,7 @@
detachAuxEffect_l(effect->id());
}
- sp<EffectChain> chain = effect->chain().promote();
+ sp<EffectChain> chain = effect->callback()->chain().promote();
if (chain != 0) {
// remove effect chain if removing last effect
if (chain->removeEffect_l(effect, release) == 0) {
@@ -1725,7 +1745,7 @@
mLastRecordedTimestampVerifierN = mTimestampVerifier.getN();
mLastRecordedTimeNs = timeNs;
- std::unique_ptr<MediaAnalyticsItem> item(MediaAnalyticsItem::create("audiothread"));
+ std::unique_ptr<mediametrics::Item> item(mediametrics::Item::create("audiothread"));
#define MM_PREFIX "android.media.audiothread." // avoid cut-n-paste errors.
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index ef7eb6e..f1adc23 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -271,6 +271,8 @@
// Called by AudioFlinger::frameCount(audio_io_handle_t output) and effects,
// and returns the [normal mix] buffer's frame count.
virtual size_t frameCount() const = 0;
+ virtual uint32_t latency_l() const { return 0; }
+ virtual void setVolumeForOutput_l(float left __unused, float right __unused) const {}
// Return's the HAL's frame count i.e. fast mixer buffer size.
size_t frameCountHAL() const { return mFrameCount; }
@@ -424,14 +426,9 @@
// check if some effects must be suspended/restored when an effect is enabled
// or disabled
- void checkSuspendOnEffectEnabled(const sp<EffectModule>& effect,
- bool enabled,
- audio_session_t sessionId =
- AUDIO_SESSION_OUTPUT_MIX);
- void checkSuspendOnEffectEnabled_l(const sp<EffectModule>& effect,
- bool enabled,
- audio_session_t sessionId =
- AUDIO_SESSION_OUTPUT_MIX);
+ void checkSuspendOnEffectEnabled(bool enabled,
+ audio_session_t sessionId,
+ bool threadLocked);
virtual status_t setSyncEvent(const sp<SyncEvent>& event) = 0;
virtual bool isValidSyncEvent(const sp<SyncEvent>& event) const = 0;
@@ -465,6 +462,9 @@
mutable Mutex mLock;
+ void onEffectEnable(const sp<EffectModule>& effect);
+ void onEffectDisable();
+
protected:
// entry describing an effect being suspended in mSuspendedSessions keyed vector
@@ -814,7 +814,7 @@
// return estimated latency in milliseconds, as reported by HAL
uint32_t latency() const;
// same, but lock must already be held
- uint32_t latency_l() const;
+ uint32_t latency_l() const override;
// VolumeInterface
virtual void setMasterVolume(float value);
@@ -824,7 +824,7 @@
virtual void setStreamMute(audio_stream_type_t stream, bool muted);
virtual float streamVolume(audio_stream_type_t stream) const;
- void setVolumeForOutput_l(float left, float right) const;
+ void setVolumeForOutput_l(float left, float right) const override;
sp<Track> createTrack_l(
const sp<AudioFlinger::Client>& client,
diff --git a/services/audiopolicy/AudioPolicyInterface.h b/services/audiopolicy/AudioPolicyInterface.h
index 00f14e1..dd0cd9b 100644
--- a/services/audiopolicy/AudioPolicyInterface.h
+++ b/services/audiopolicy/AudioPolicyInterface.h
@@ -192,6 +192,10 @@
// return the enabled output devices for the given stream type
virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream) = 0;
+ // retrieves the list of enabled output devices for the given audio attributes
+ virtual status_t getDevicesForAttributes(const audio_attributes_t &attr,
+ AudioDeviceTypeAddrVector *devices) = 0;
+
// Audio effect management
virtual audio_io_handle_t getOutputForEffect(const effect_descriptor_t *desc) = 0;
virtual status_t registerEffect(const effect_descriptor_t *desc,
@@ -403,6 +407,12 @@
std::vector<effect_descriptor_t> effects,
audio_patch_handle_t patchHandle,
audio_source_t source) = 0;
+
+ // Used to notify the sound trigger module that an audio capture is about to
+ // take place. This should typically result in any active recognition
+ // sessions to be preempted on modules that do not support sound trigger
+ // recognition concurrently with audio capture.
+ virtual void setSoundTriggerCaptureState(bool active) = 0;
};
extern "C" AudioPolicyInterface* createAudioPolicyManager(AudioPolicyClientInterface *clientInterface);
diff --git a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
index 20c0a24..b1103ab 100644
--- a/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
+++ b/services/audiopolicy/common/managerdefinitions/src/AudioPolicyMix.cpp
@@ -197,7 +197,9 @@
ALOGV("%s: Mix %zu ignored as secondaryOutput because not opened yet", __func__, i);
} else {
ALOGV("%s: Add a secondary desc %zu", __func__, i);
- secondaryDescs->push_back(policyDesc);
+ if (secondaryDescs != nullptr) {
+ secondaryDescs->push_back(policyDesc);
+ }
}
}
}
diff --git a/services/audiopolicy/config/audio_policy_volumes.xml b/services/audiopolicy/config/audio_policy_volumes.xml
index 27bd3ff..1dec6f4 100644
--- a/services/audiopolicy/config/audio_policy_volumes.xml
+++ b/services/audiopolicy/config/audio_policy_volumes.xml
@@ -181,6 +181,16 @@
ref="DEFAULT_NON_MUTABLE_VOLUME_CURVE"/>
<volume stream="AUDIO_STREAM_ACCESSIBILITY" deviceCategory="DEVICE_CATEGORY_HEARING_AID"
ref="DEFAULT_NON_MUTABLE_HEARING_AID_VOLUME_CURVE"/>
+ <volume stream="AUDIO_STREAM_ASSISTANT" deviceCategory="DEVICE_CATEGORY_HEADSET"
+ ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume stream="AUDIO_STREAM_ASSISTANT" deviceCategory="DEVICE_CATEGORY_SPEAKER"
+ ref="DEFAULT_DEVICE_CATEGORY_SPEAKER_VOLUME_CURVE"/>
+ <volume stream="AUDIO_STREAM_ASSISTANT" deviceCategory="DEVICE_CATEGORY_EARPIECE"
+ ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume stream="AUDIO_STREAM_ASSISTANT" deviceCategory="DEVICE_CATEGORY_EXT_MEDIA"
+ ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume stream="AUDIO_STREAM_ASSISTANT" deviceCategory="DEVICE_CATEGORY_HEARING_AID"
+ ref="DEFAULT_HEARING_AID_VOLUME_CURVE"/>
<volume stream="AUDIO_STREAM_REROUTING" deviceCategory="DEVICE_CATEGORY_HEADSET"
ref="FULL_SCALE_VOLUME_CURVE"/>
<volume stream="AUDIO_STREAM_REROUTING" deviceCategory="DEVICE_CATEGORY_SPEAKER"
diff --git a/services/audiopolicy/engine/common/src/EngineBase.cpp b/services/audiopolicy/engine/common/src/EngineBase.cpp
index 01efc7a..46b950c 100644
--- a/services/audiopolicy/engine/common/src/EngineBase.cpp
+++ b/services/audiopolicy/engine/common/src/EngineBase.cpp
@@ -106,48 +106,33 @@
engineConfig::ParsingResult EngineBase::loadAudioPolicyEngineConfig()
{
- auto loadProductStrategies =
- [](auto& strategyConfigs, auto& productStrategies, auto& volumeGroups) {
- for (auto& strategyConfig : strategyConfigs) {
- sp<ProductStrategy> strategy = new ProductStrategy(strategyConfig.name);
- for (const auto &group : strategyConfig.attributesGroups) {
- const auto &iter = std::find_if(begin(volumeGroups), end(volumeGroups),
- [&group](const auto &volumeGroup) {
- return group.volumeGroup == volumeGroup.second->getName(); });
- ALOG_ASSERT(iter != end(volumeGroups), "Invalid Volume Group Name %s",
- group.volumeGroup.c_str());
- if (group.stream != AUDIO_STREAM_DEFAULT) {
- iter->second->addSupportedStream(group.stream);
- }
- for (const auto &attr : group.attributesVect) {
- strategy->addAttributes({group.stream, iter->second->getId(), attr});
- iter->second->addSupportedAttributes(attr);
- }
- }
- product_strategy_t strategyId = strategy->getId();
- productStrategies[strategyId] = strategy;
- }
- };
- auto loadVolumeGroups = [](auto &volumeConfigs, auto &volumeGroups) {
- for (auto &volumeConfig : volumeConfigs) {
- sp<VolumeGroup> volumeGroup = new VolumeGroup(volumeConfig.name, volumeConfig.indexMin,
- volumeConfig.indexMax);
- volumeGroups[volumeGroup->getId()] = volumeGroup;
+ auto loadVolumeConfig = [](auto &volumeGroups, auto &volumeConfig) {
+ sp<VolumeGroup> volumeGroup = new VolumeGroup(volumeConfig.name, volumeConfig.indexMin,
+ volumeConfig.indexMax);
+ volumeGroups[volumeGroup->getId()] = volumeGroup;
- for (auto &configCurve : volumeConfig.volumeCurves) {
- device_category deviceCat = DEVICE_CATEGORY_SPEAKER;
- if (!DeviceCategoryConverter::fromString(configCurve.deviceCategory, deviceCat)) {
- ALOGE("%s: Invalid %s", __FUNCTION__, configCurve.deviceCategory.c_str());
- continue;
- }
- sp<VolumeCurve> curve = new VolumeCurve(deviceCat);
- for (auto &point : configCurve.curvePoints) {
- curve->add({point.index, point.attenuationInMb});
- }
- volumeGroup->add(curve);
+ for (auto &configCurve : volumeConfig.volumeCurves) {
+ device_category deviceCat = DEVICE_CATEGORY_SPEAKER;
+ if (!DeviceCategoryConverter::fromString(configCurve.deviceCategory, deviceCat)) {
+ ALOGE("%s: Invalid %s", __FUNCTION__, configCurve.deviceCategory.c_str());
+ continue;
}
+ sp<VolumeCurve> curve = new VolumeCurve(deviceCat);
+ for (auto &point : configCurve.curvePoints) {
+ curve->add({point.index, point.attenuationInMb});
+ }
+ volumeGroup->add(curve);
+ }
+ return volumeGroup;
+ };
+ auto addSupportedStreamAttributes = [](auto &group, auto &volumeGroup, auto &strategy) {
+ volumeGroup->addSupportedStream(group.stream);
+ for (const auto &attr : group.attributesVect) {
+ strategy->addAttributes({group.stream, volumeGroup->getId(), attr});
+ volumeGroup->addSupportedAttributes(attr);
}
};
+
auto result = engineConfig::parse();
if (result.parsedConfig == nullptr) {
ALOGW("%s: No configuration found, using default matching phone experience.", __FUNCTION__);
@@ -157,9 +142,37 @@
static_cast<size_t>(ret == NO_ERROR ? 0 : 1)};
}
ALOGE_IF(result.nbSkippedElement != 0, "skipped %zu elements", result.nbSkippedElement);
- loadVolumeGroups(result.parsedConfig->volumeGroups, mVolumeGroups);
- loadProductStrategies(result.parsedConfig->productStrategies, mProductStrategies,
- mVolumeGroups);
+
+ engineConfig::VolumeGroup defaultVolumeConfig;
+ for (auto &volumeConfig : result.parsedConfig->volumeGroups) {
+ // save default volume config for streams not defined in configuration
+ if (volumeConfig.name.compare("AUDIO_STREAM_MUSIC") == 0) {
+ defaultVolumeConfig = volumeConfig;
+ }
+ loadVolumeConfig(mVolumeGroups, volumeConfig);
+ }
+ for (auto& strategyConfig : result.parsedConfig->productStrategies) {
+ sp<ProductStrategy> strategy = new ProductStrategy(strategyConfig.name);
+ for (const auto &group : strategyConfig.attributesGroups) {
+ const auto &iter = std::find_if(begin(mVolumeGroups), end(mVolumeGroups),
+ [&group](const auto &volumeGroup) {
+ return group.volumeGroup == volumeGroup.second->getName(); });
+ if (group.stream != AUDIO_STREAM_DEFAULT) {
+ if (iter == end(mVolumeGroups)) {
+ ALOGW("%s: No configuration of %s found, using default volume configuration"
+ , __FUNCTION__, group.volumeGroup.c_str());
+ defaultVolumeConfig.name = group.volumeGroup;
+ sp<VolumeGroup> volumeGroup =
+ loadVolumeConfig(mVolumeGroups, defaultVolumeConfig);
+ addSupportedStreamAttributes(group, volumeGroup, strategy);
+ } else {
+ addSupportedStreamAttributes(group, iter->second, strategy);
+ }
+ }
+ }
+ product_strategy_t strategyId = strategy->getId();
+ mProductStrategies[strategyId] = strategy;
+ }
mProductStrategies.initialize();
return result;
}
diff --git a/services/audiopolicy/engine/common/src/EngineDefaultConfig.h b/services/audiopolicy/engine/common/src/EngineDefaultConfig.h
index 20c57ee..fbce801 100644
--- a/services/audiopolicy/engine/common/src/EngineDefaultConfig.h
+++ b/services/audiopolicy/engine/common/src/EngineDefaultConfig.h
@@ -81,6 +81,10 @@
},
{"STRATEGY_MEDIA",
{
+ {"assistant", AUDIO_STREAM_ASSISTANT, "AUDIO_STREAM_ASSISTANT",
+ {{AUDIO_CONTENT_TYPE_SPEECH, AUDIO_USAGE_ASSISTANT,
+ AUDIO_SOURCE_DEFAULT, 0, ""}}
+ },
{"music", AUDIO_STREAM_MUSIC, "AUDIO_STREAM_MUSIC",
{
{AUDIO_CONTENT_TYPE_UNKNOWN, AUDIO_USAGE_MEDIA, AUDIO_SOURCE_DEFAULT, 0, ""},
diff --git a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
index 9398743..b1c0dcf 100644
--- a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
+++ b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_product_strategies.xml
@@ -72,6 +72,12 @@
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/> </Attributes>
<Attributes></Attributes>
</AttributesGroup>
+ <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
+ <Attributes>
+ <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
+ <Usage value="AUDIO_USAGE_ASSISTANT"/>
+ </Attributes>
+ </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_SYSTEM" volumeGroup="system">
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/> </Attributes>
</AttributesGroup>
diff --git a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_stream_volumes.xml b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_stream_volumes.xml
index 707a184..0f9614e 100644
--- a/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_stream_volumes.xml
+++ b/services/audiopolicy/engineconfigurable/config/example/phone/audio_policy_engine_stream_volumes.xml
@@ -205,7 +205,16 @@
<volume deviceCategory="DEVICE_CATEGORY_EXT_MEDIA" ref="DEFAULT_NON_MUTABLE_VOLUME_CURVE"/>
<volume deviceCategory="DEVICE_CATEGORY_HEARING_AID" ref="DEFAULT_NON_MUTABLE_HEARING_AID_VOLUME_CURVE"/>
</volumeGroup>
-
+ <volumeGroup>
+ <name>assistant</name>
+ <indexMin>0</indexMin>
+ <indexMax>15</indexMax>
+ <volume deviceCategory="DEVICE_CATEGORY_HEADSET" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_SPEAKER" ref="DEFAULT_DEVICE_CATEGORY_SPEAKER_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_EARPIECE" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_EXT_MEDIA" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_HEARING_AID" ref="DEFAULT_HEARING_AID_VOLUME_CURVE"/>
+ </volumeGroup>
<volumeGroup>
<name>rerouting</name>
<indexMin>0</indexMin>
diff --git a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
index 9398743..b1c0dcf 100644
--- a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
+++ b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_product_strategies.xml
@@ -72,6 +72,12 @@
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_NAVIGATION_GUIDANCE"/> </Attributes>
<Attributes></Attributes>
</AttributesGroup>
+ <AttributesGroup streamType="AUDIO_STREAM_ASSISTANT" volumeGroup="assistant">
+ <Attributes>
+ <ContentType value="AUDIO_CONTENT_TYPE_SPEECH"/>
+ <Usage value="AUDIO_USAGE_ASSISTANT"/>
+ </Attributes>
+ </AttributesGroup>
<AttributesGroup streamType="AUDIO_STREAM_SYSTEM" volumeGroup="system">
<Attributes> <Usage value="AUDIO_USAGE_ASSISTANCE_SONIFICATION"/> </Attributes>
</AttributesGroup>
diff --git a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_stream_volumes.xml b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_stream_volumes.xml
index 707a184..a259950 100644
--- a/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_stream_volumes.xml
+++ b/services/audiopolicy/enginedefault/config/example/phone/audio_policy_engine_stream_volumes.xml
@@ -207,6 +207,17 @@
</volumeGroup>
<volumeGroup>
+ <name>assistant</name>
+ <indexMin>0</indexMin>
+ <indexMax>15</indexMax>
+ <volume deviceCategory="DEVICE_CATEGORY_HEADSET" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_SPEAKER" ref="DEFAULT_DEVICE_CATEGORY_SPEAKER_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_EARPIECE" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_EXT_MEDIA" ref="DEFAULT_MEDIA_VOLUME_CURVE"/>
+ <volume deviceCategory="DEVICE_CATEGORY_HEARING_AID" ref="DEFAULT_HEARING_AID_VOLUME_CURVE"/>
+ </volumeGroup>
+
+ <volumeGroup>
<name>rerouting</name>
<indexMin>0</indexMin>
<indexMax>1</indexMax>
diff --git a/services/audiopolicy/managerdefault/Android.bp b/services/audiopolicy/managerdefault/Android.bp
index 1fa0d19..577b42f 100644
--- a/services/audiopolicy/managerdefault/Android.bp
+++ b/services/audiopolicy/managerdefault/Android.bp
@@ -15,7 +15,6 @@
"libutils",
"liblog",
"libaudiopolicy",
- "libsoundtrigger",
"libmedia_helper",
"libmediametrics",
"libbinder",
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
index 9d80a16..75c89aa 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.cpp
@@ -46,7 +46,6 @@
#include <utils/Log.h>
#include <media/AudioParameter.h>
#include <private/android_filesystem_config.h>
-#include <soundtrigger/SoundTrigger.h>
#include <system/audio.h>
#include "AudioPolicyManager.h"
#include <Serializer.h>
@@ -972,19 +971,21 @@
if (usePrimaryOutputFromPolicyMixes) {
*output = policyDesc->mIoHandle;
sp<AudioPolicyMix> mix = policyDesc->mPolicyMix.promote();
- sp<DeviceDescriptor> deviceDesc =
- mAvailableOutputDevices.getDevice(mix->mDeviceType,
- mix->mDeviceAddress,
- AUDIO_FORMAT_DEFAULT);
- *selectedDeviceId = deviceDesc != 0 ? deviceDesc->getId() : AUDIO_PORT_HANDLE_NONE;
+ if (mix != nullptr) {
+ sp<DeviceDescriptor> deviceDesc =
+ mAvailableOutputDevices.getDevice(mix->mDeviceType,
+ mix->mDeviceAddress,
+ AUDIO_FORMAT_DEFAULT);
+ *selectedDeviceId = deviceDesc != 0 ? deviceDesc->getId() : AUDIO_PORT_HANDLE_NONE;
- ALOGV("getOutputForAttr() returns output %d", *output);
- if (resultAttr->usage == AUDIO_USAGE_VIRTUAL_SOURCE) {
- *outputType = API_OUT_MIX_PLAYBACK;
- } else {
- *outputType = API_OUTPUT_LEGACY;
+ ALOGV("getOutputForAttr() returns output %d", *output);
+ if (resultAttr->usage == AUDIO_USAGE_VIRTUAL_SOURCE) {
+ *outputType = API_OUT_MIX_PLAYBACK;
+ } else {
+ *outputType = API_OUTPUT_LEGACY;
+ }
+ return NO_ERROR;
}
- return NO_ERROR;
}
// Virtual sources must always be dynamicaly or explicitly routed
if (resultAttr->usage == AUDIO_USAGE_VIRTUAL_SOURCE) {
@@ -1370,6 +1371,14 @@
// For encoded streams force direct flag to prevent downstream mixing.
sinkConfig->flags.output = static_cast<audio_output_flags_t>(
sinkConfig->flags.output | AUDIO_OUTPUT_FLAG_DIRECT);
+ if (audio_is_iec61937_compatible(sinkConfig->format)) {
+ // For formats compatible with IEC61937 encapsulation, assume that
+ // the record thread input from MSD is IEC61937 framed (for proportional buffer sizing).
+ // Add the AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO flag so downstream HAL can distinguish between
+ // raw and IEC61937 framed streams.
+ sinkConfig->flags.output = static_cast<audio_output_flags_t>(
+ sinkConfig->flags.output | AUDIO_OUTPUT_FLAG_IEC958_NONAUDIO);
+ }
sourceConfig->sample_rate = bestSinkConfig.sample_rate;
// Specify exact channel mask to prevent guessing by bit count in PatchPanel.
sourceConfig->channel_mask = audio_channel_mask_out_to_in(bestSinkConfig.channel_mask);
@@ -1639,7 +1648,7 @@
DeviceVector devices;
sp<AudioPolicyMix> policyMix = outputDesc->mPolicyMix.promote();
const char *address = NULL;
- if (policyMix != NULL) {
+ if (policyMix != nullptr) {
audio_devices_t newDeviceType;
address = policyMix->mDeviceAddress.string();
if ((policyMix->mRouteFlags & MIX_ROUTE_FLAG_LOOP_BACK) == MIX_ROUTE_FLAG_LOOP_BACK) {
@@ -1821,7 +1830,7 @@
sp<AudioPolicyMix> policyMix = outputDesc->mPolicyMix.promote();
if (isSingleDeviceType(
outputDesc->devices().types(), &audio_is_remote_submix_device) &&
- policyMix != NULL &&
+ policyMix != nullptr &&
policyMix->mMixType == MIX_TYPE_RECORDERS) {
setDeviceConnectionStateInt(AUDIO_DEVICE_IN_REMOTE_SUBMIX,
AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE,
@@ -2268,7 +2277,7 @@
if (status == NO_ERROR && inputDesc->activeCount() == 1) {
sp<AudioPolicyMix> policyMix = inputDesc->mPolicyMix.promote();
// if input maps to a dynamic policy with an activity listener, notify of state change
- if ((policyMix != NULL)
+ if ((policyMix != nullptr)
&& ((policyMix->mCbFlags & AudioMix::kCbFlagNotifyActivity) != 0)) {
mpClientInterface->onDynamicPolicyMixStateUpdate(policyMix->mDeviceAddress,
MIX_STATE_MIXING);
@@ -2277,7 +2286,7 @@
DeviceVector primaryInputDevices = availablePrimaryModuleInputDevices();
if (primaryInputDevices.contains(device) &&
mInputs.activeInputsCountOnDevices(primaryInputDevices) == 1) {
- SoundTrigger::setCaptureState(true);
+ mpClientInterface->setSoundTriggerCaptureState(true);
}
// automatically enable the remote submix output when input is started if not
@@ -2285,7 +2294,7 @@
// For remote submix (a virtual device), we open only one input per capture request.
if (audio_is_remote_submix_device(inputDesc->getDeviceType())) {
String8 address = String8("");
- if (policyMix == NULL) {
+ if (policyMix == nullptr) {
address = String8("0");
} else if (policyMix->mMixType == MIX_TYPE_PLAYERS) {
address = policyMix->mDeviceAddress;
@@ -2332,7 +2341,7 @@
} else {
sp<AudioPolicyMix> policyMix = inputDesc->mPolicyMix.promote();
// if input maps to a dynamic policy with an activity listener, notify of state change
- if ((policyMix != NULL)
+ if ((policyMix != nullptr)
&& ((policyMix->mCbFlags & AudioMix::kCbFlagNotifyActivity) != 0)) {
mpClientInterface->onDynamicPolicyMixStateUpdate(policyMix->mDeviceAddress,
MIX_STATE_IDLE);
@@ -2342,7 +2351,7 @@
// used by a policy mix of type MIX_TYPE_RECORDERS
if (audio_is_remote_submix_device(inputDesc->getDeviceType())) {
String8 address = String8("");
- if (policyMix == NULL) {
+ if (policyMix == nullptr) {
address = String8("0");
} else if (policyMix->mMixType == MIX_TYPE_PLAYERS) {
address = policyMix->mDeviceAddress;
@@ -2360,7 +2369,7 @@
DeviceVector primaryInputDevices = availablePrimaryModuleInputDevices();
if (primaryInputDevices.contains(inputDesc->getDevice()) &&
mInputs.activeInputsCountOnDevices(primaryInputDevices) == 0) {
- SoundTrigger::setCaptureState(false);
+ mpClientInterface->setSoundTriggerCaptureState(false);
}
inputDesc->clearPreemptedSessions();
}
@@ -2763,12 +2772,14 @@
int session,
int id)
{
- ssize_t index = mOutputs.indexOfKey(io);
- if (index < 0) {
- index = mInputs.indexOfKey(io);
+ if (session != AUDIO_SESSION_DEVICE) {
+ ssize_t index = mOutputs.indexOfKey(io);
if (index < 0) {
- ALOGW("registerEffect() unknown io %d", io);
- return INVALID_OPERATION;
+ index = mInputs.indexOfKey(io);
+ if (index < 0) {
+ ALOGW("registerEffect() unknown io %d", io);
+ return INVALID_OPERATION;
+ }
}
}
return mEffects.registerEffect(desc, io, session, id,
@@ -2788,16 +2799,6 @@
return mEffects.unregisterEffect(id);
}
-void AudioPolicyManager::cleanUpEffectsForIo(audio_io_handle_t io)
-{
- EffectDescriptorCollection effects = mEffects.getEffectsForIo(io);
- for (size_t i = 0; i < effects.size(); i++) {
- ALOGW("%s removing stale effect %s, id %d on closed IO %d",
- __func__, effects.valueAt(i)->mDesc.name, effects.keyAt(i), io);
- unregisterEffect(effects.keyAt(i));
- }
-}
-
status_t AudioPolicyManager::setEffectEnabled(int id, bool enabled)
{
sp<EffectDescriptor> effect = mEffects.getEffect(id);
@@ -5044,8 +5045,6 @@
setMsdPatch();
}
}
-
- cleanUpEffectsForIo(output);
}
void AudioPolicyManager::closeInput(audio_io_handle_t input)
@@ -5075,10 +5074,8 @@
DeviceVector primaryInputDevices = availablePrimaryModuleInputDevices();
if (primaryInputDevices.contains(device) &&
mInputs.activeInputsCountOnDevices(primaryInputDevices) == 0) {
- SoundTrigger::setCaptureState(false);
+ mpClientInterface->setSoundTriggerCaptureState(false);
}
-
- cleanUpEffectsForIo(input);
}
SortedVector<audio_io_handle_t> AudioPolicyManager::getOutputsForDevices(
@@ -5345,7 +5342,8 @@
auto attr = mEngine->getAllAttributesForProductStrategy(productStrategy).front();
if ((hasVoiceStream(streams) &&
- (isInCall() || mOutputs.isStrategyActiveOnSameModule(productStrategy, outputDesc))) ||
+ (isInCall() || mOutputs.isStrategyActiveOnSameModule(productStrategy, outputDesc)) &&
+ !isStreamActive(AUDIO_STREAM_ENFORCED_AUDIBLE, 0)) ||
((hasStream(streams, AUDIO_STREAM_ALARM) || hasStream(streams, AUDIO_STREAM_ENFORCED_AUDIBLE)) &&
mOutputs.isStrategyActiveOnSameModule(productStrategy, outputDesc)) ||
outputDesc->isStrategyActive(productStrategy)) {
@@ -5441,6 +5439,35 @@
return deviceTypesToBitMask(devices.types());
}
+status_t AudioPolicyManager::getDevicesForAttributes(
+ const audio_attributes_t &attr, AudioDeviceTypeAddrVector *devices) {
+ if (devices == nullptr) {
+ return BAD_VALUE;
+ }
+ // check dynamic policies but only for primary descriptors (secondary not used for audible
+ // audio routing, only used for duplication for playback capture)
+ sp<SwAudioOutputDescriptor> policyDesc;
+ status_t status = mPolicyMixes.getOutputForAttr(attr, 0 /*uid unknown here*/,
+ AUDIO_OUTPUT_FLAG_NONE, policyDesc, nullptr);
+ if (status != OK) {
+ return status;
+ }
+ if (policyDesc != nullptr) {
+ sp<AudioPolicyMix> mix = policyDesc->mPolicyMix.promote();
+ if (mix != nullptr) {
+ AudioDeviceTypeAddr device(mix->mDeviceType, mix->mDeviceAddress.c_str());
+ devices->push_back(device);
+ return NO_ERROR;
+ }
+ }
+
+ DeviceVector curDevices = mEngine->getOutputDevicesForAttributes(attr, nullptr, false);
+ for (const auto& device : curDevices) {
+ devices->push_back(device->getDeviceTypeAddr());
+ }
+ return NO_ERROR;
+}
+
void AudioPolicyManager::handleNotificationRoutingForStream(audio_stream_type_t stream) {
switch(stream) {
case AUDIO_STREAM_MUSIC:
diff --git a/services/audiopolicy/managerdefault/AudioPolicyManager.h b/services/audiopolicy/managerdefault/AudioPolicyManager.h
index f6e4fc1..7e0e16f 100644
--- a/services/audiopolicy/managerdefault/AudioPolicyManager.h
+++ b/services/audiopolicy/managerdefault/AudioPolicyManager.h
@@ -193,6 +193,10 @@
// return the enabled output devices for the given stream type
virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream);
+ virtual status_t getDevicesForAttributes(
+ const audio_attributes_t &attributes,
+ AudioDeviceTypeAddrVector *devices);
+
virtual audio_io_handle_t getOutputForEffect(const effect_descriptor_t *desc = NULL);
virtual status_t registerEffect(const effect_descriptor_t *desc,
audio_io_handle_t io,
@@ -886,8 +890,6 @@
int delayMs,
uid_t uid,
sp<AudioPatch> *patchDescPtr);
-
- void cleanUpEffectsForIo(audio_io_handle_t io);
};
};
diff --git a/services/audiopolicy/service/Android.mk b/services/audiopolicy/service/Android.mk
index fdf3eae..80f4eab 100644
--- a/services/audiopolicy/service/Android.mk
+++ b/services/audiopolicy/service/Android.mk
@@ -31,7 +31,8 @@
libmediametrics \
libmediautils \
libeffectsconfig \
- libsensorprivacy
+ libsensorprivacy \
+ soundtrigger_middleware-aidl-cpp
LOCAL_EXPORT_SHARED_LIBRARY_HEADERS := \
libsensorprivacy
diff --git a/services/audiopolicy/service/AudioPolicyClientImpl.cpp b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
index 6de0c80..5b81b9d 100644
--- a/services/audiopolicy/service/AudioPolicyClientImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyClientImpl.cpp
@@ -17,10 +17,13 @@
#define LOG_TAG "AudioPolicyClientImpl"
//#define LOG_NDEBUG 0
-#include <soundtrigger/SoundTrigger.h>
-#include <utils/Log.h>
#include "AudioPolicyService.h"
+#include <android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.h>
+#include <utils/Log.h>
+
+#include "BinderProxy.h"
+
namespace android {
/* implementation of the client interface from the policy manager */
@@ -239,4 +242,12 @@
return AudioSystem::newAudioUniqueId(use);
}
+void AudioPolicyService::AudioPolicyClient::setSoundTriggerCaptureState(bool active) {
+ using media::soundtrigger_middleware::ISoundTriggerMiddlewareService;
+
+ static BinderProxy<ISoundTriggerMiddlewareService>
+ proxy("soundtrigger_middleware");
+ proxy.waitServiceOrDie()->setExternalCaptureState(active);
+}
+
} // namespace android
diff --git a/services/audiopolicy/service/AudioPolicyEffects.cpp b/services/audiopolicy/service/AudioPolicyEffects.cpp
index 4947714..60caa31 100644
--- a/services/audiopolicy/service/AudioPolicyEffects.cpp
+++ b/services/audiopolicy/service/AudioPolicyEffects.cpp
@@ -562,7 +562,8 @@
AUDIO_STREAM_BLUETOOTH_SCO_TAG,
AUDIO_STREAM_ENFORCED_AUDIBLE_TAG,
AUDIO_STREAM_DTMF_TAG,
- AUDIO_STREAM_TTS_TAG
+ AUDIO_STREAM_TTS_TAG,
+ AUDIO_STREAM_ASSISTANT_TAG
};
// returns the audio_stream_t enum corresponding to the output stream name or
diff --git a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
index 6379a8a..68a2a8c 100644
--- a/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
+++ b/services/audiopolicy/service/AudioPolicyInterfaceImpl.cpp
@@ -19,7 +19,7 @@
#include "AudioPolicyService.h"
#include "TypeConverter.h"
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <media/AudioPolicy.h>
#include <utils/Log.h>
@@ -363,12 +363,17 @@
return NO_INIT;
}
+ audio_source_t inputSource = attr->source;
+ if (inputSource == AUDIO_SOURCE_DEFAULT) {
+ inputSource = AUDIO_SOURCE_MIC;
+ }
+
// already checked by client, but double-check in case the client wrapper is bypassed
- if ((attr->source < AUDIO_SOURCE_DEFAULT)
- || (attr->source >= AUDIO_SOURCE_CNT
- && attr->source != AUDIO_SOURCE_HOTWORD
- && attr->source != AUDIO_SOURCE_FM_TUNER
- && attr->source != AUDIO_SOURCE_ECHO_REFERENCE)) {
+ if ((inputSource < AUDIO_SOURCE_DEFAULT)
+ || (inputSource >= AUDIO_SOURCE_CNT
+ && inputSource != AUDIO_SOURCE_HOTWORD
+ && inputSource != AUDIO_SOURCE_FM_TUNER
+ && inputSource != AUDIO_SOURCE_ECHO_REFERENCE)) {
return BAD_VALUE;
}
@@ -399,17 +404,17 @@
}
bool canCaptureOutput = captureAudioOutputAllowed(pid, uid);
- if ((attr->source == AUDIO_SOURCE_VOICE_UPLINK ||
- attr->source == AUDIO_SOURCE_VOICE_DOWNLINK ||
- attr->source == AUDIO_SOURCE_VOICE_CALL ||
- attr->source == AUDIO_SOURCE_ECHO_REFERENCE||
- attr->source == AUDIO_SOURCE_FM_TUNER) &&
+ if ((inputSource == AUDIO_SOURCE_VOICE_UPLINK ||
+ inputSource == AUDIO_SOURCE_VOICE_DOWNLINK ||
+ inputSource == AUDIO_SOURCE_VOICE_CALL ||
+ inputSource == AUDIO_SOURCE_ECHO_REFERENCE||
+ inputSource == AUDIO_SOURCE_FM_TUNER) &&
!canCaptureOutput) {
return PERMISSION_DENIED;
}
bool canCaptureHotword = captureHotwordAllowed(opPackageName, pid, uid);
- if ((attr->source == AUDIO_SOURCE_HOTWORD) && !canCaptureHotword) {
+ if ((inputSource == AUDIO_SOURCE_HOTWORD) && !canCaptureHotword) {
return BAD_VALUE;
}
@@ -474,7 +479,7 @@
if (audioPolicyEffects != 0) {
// create audio pre processors according to input source
- status_t status = audioPolicyEffects->addInputEffects(*input, attr->source, session);
+ status_t status = audioPolicyEffects->addInputEffects(*input, inputSource, session);
if (status != NO_ERROR && status != ALREADY_EXISTS) {
ALOGW("Failed to add effects on input %d", *input);
}
@@ -548,7 +553,7 @@
static constexpr char kAudioPolicyActiveDevice[] =
"android.media.audiopolicy.active.device";
- MediaAnalyticsItem *item = MediaAnalyticsItem::create(kAudioPolicy);
+ mediametrics::Item *item = mediametrics::Item::create(kAudioPolicy);
if (item != NULL) {
item->setInt32(kAudioPolicyStatus, status);
@@ -801,6 +806,17 @@
return mAudioPolicyManager->getDevicesForStream(stream);
}
+status_t AudioPolicyService::getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices) const
+{
+ if (mAudioPolicyManager == NULL) {
+ return NO_INIT;
+ }
+ Mutex::Autolock _l(mLock);
+ AutoCallerClear acc;
+ return mAudioPolicyManager->getDevicesForAttributes(aa.getAttributes(), devices);
+}
+
audio_io_handle_t AudioPolicyService::getOutputForEffect(const effect_descriptor_t *desc)
{
// FIXME change return type to status_t, and return NO_INIT here
diff --git a/services/audiopolicy/service/AudioPolicyService.h b/services/audiopolicy/service/AudioPolicyService.h
index c6fff63..84adcc2 100644
--- a/services/audiopolicy/service/AudioPolicyService.h
+++ b/services/audiopolicy/service/AudioPolicyService.h
@@ -127,6 +127,8 @@
virtual uint32_t getStrategyForStream(audio_stream_type_t stream);
virtual audio_devices_t getDevicesForStream(audio_stream_type_t stream);
+ virtual status_t getDevicesForAttributes(const AudioAttributes &aa,
+ AudioDeviceTypeAddrVector *devices) const;
virtual audio_io_handle_t getOutputForEffect(const effect_descriptor_t *desc);
virtual status_t registerEffect(const effect_descriptor_t *desc,
@@ -717,6 +719,8 @@
virtual audio_unique_id_t newAudioUniqueId(audio_unique_id_use_t use);
+ void setSoundTriggerCaptureState(bool active) override;
+
private:
AudioPolicyService *mAudioPolicyService;
};
diff --git a/services/audiopolicy/service/BinderProxy.h b/services/audiopolicy/service/BinderProxy.h
new file mode 100644
index 0000000..516e84d
--- /dev/null
+++ b/services/audiopolicy/service/BinderProxy.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <mutex>
+#include <type_traits>
+#include <binder/IInterface.h>
+#include <binder/IServiceManager.h>
+#include <utils/Log.h>
+
+namespace android {
+
+// A simple utility that caches a proxy for a service and handles death notification.
+// Typically, intended to be used as a static-lifetime object.
+//
+// Example usage:
+// static BinderProxy<IMyInterface> myInterface("my_interface_svc");
+// ...
+// myInterface.waitServiceOrDie()->doSomething();
+//
+// If the service is unavailable, will wait until it becomes available.
+// Will die if the service doesn't implement the requested interface, or cannot be used for
+// permission reasons.
+template<typename ServiceType>
+class BinderProxy {
+public:
+ static_assert(std::is_base_of_v<IInterface, ServiceType>,
+ "Service type must be a sub-type of IInterface.");
+
+ explicit BinderProxy(std::string_view serviceName)
+ : mServiceName(serviceName), mDeathRecipient(new DeathRecipient(this)) {}
+
+ ~BinderProxy() {
+ if (mDelegate != nullptr) {
+ sp<IBinder> binder = IInterface::asBinder(mDelegate);
+ if (binder != nullptr) {
+ binder->unlinkToDeath(mDeathRecipient);
+ }
+ }
+ }
+
+ sp<ServiceType> waitServiceOrDie() {
+ std::lock_guard<std::mutex> _l(mDelegateMutex);
+ if (mDelegate == nullptr) {
+ mDelegate = waitForService<ServiceType>(String16(mServiceName.c_str()));
+ LOG_ALWAYS_FATAL_IF(mDelegate == nullptr,
+ "Service %s doesn't implement the required interface.",
+ mServiceName.c_str());
+ sp<IBinder> binder = IInterface::asBinder(mDelegate);
+ if (binder != nullptr) {
+ binder->linkToDeath(mDeathRecipient);
+ }
+ }
+ return mDelegate;
+ }
+
+private:
+ sp<ServiceType> mDelegate;
+ std::mutex mDelegateMutex;
+ const std::string mServiceName;
+ sp<IBinder::DeathRecipient> mDeathRecipient;
+
+ class DeathRecipient : public IBinder::DeathRecipient {
+ public:
+ DeathRecipient(BinderProxy* proxy) : mProxy(proxy) {}
+
+ void binderDied(const wp<IBinder>&) override {
+ mProxy->binderDied();
+ }
+
+ private:
+ BinderProxy* const mProxy;
+ };
+
+ void binderDied() {
+ std::lock_guard<std::mutex> _l(mDelegateMutex);
+ mDelegate.clear();
+ ALOGW("Binder died: %s", mServiceName.c_str());
+ }
+};
+
+} // namespace android
diff --git a/services/audiopolicy/tests/AudioPolicyTestClient.h b/services/audiopolicy/tests/AudioPolicyTestClient.h
index b92a2e6..c628e70 100644
--- a/services/audiopolicy/tests/AudioPolicyTestClient.h
+++ b/services/audiopolicy/tests/AudioPolicyTestClient.h
@@ -86,6 +86,7 @@
void setEffectSuspended(int effectId __unused,
audio_session_t sessionId __unused,
bool suspended __unused) {}
+ void setSoundTriggerCaptureState(bool active __unused) override {};
};
} // namespace android
diff --git a/services/audiopolicy/tests/audiopolicymanager_tests.cpp b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
index e4a19ea..2a8349c 100644
--- a/services/audiopolicy/tests/audiopolicymanager_tests.cpp
+++ b/services/audiopolicy/tests/audiopolicymanager_tests.cpp
@@ -800,6 +800,8 @@
(audio_attributes_t){AUDIO_CONTENT_TYPE_MUSIC, AUDIO_USAGE_VIRTUAL_SOURCE,
AUDIO_SOURCE_DEFAULT, 0, "addr=remote_submix_media"},
(audio_attributes_t){AUDIO_CONTENT_TYPE_MUSIC, AUDIO_USAGE_ASSISTANT,
+ AUDIO_SOURCE_DEFAULT, 0, "addr=remote_submix_media"},
+ (audio_attributes_t){AUDIO_CONTENT_TYPE_SPEECH, AUDIO_USAGE_ASSISTANT,
AUDIO_SOURCE_DEFAULT, 0, "addr=remote_submix_media"}
)
);
@@ -841,6 +843,8 @@
(audio_attributes_t){AUDIO_CONTENT_TYPE_MUSIC, AUDIO_USAGE_GAME,
AUDIO_SOURCE_DEFAULT, 0, ""},
(audio_attributes_t){AUDIO_CONTENT_TYPE_MUSIC, AUDIO_USAGE_ASSISTANT,
+ AUDIO_SOURCE_DEFAULT, 0, ""},
+ (audio_attributes_t){AUDIO_CONTENT_TYPE_SPEECH, AUDIO_USAGE_ASSISTANT,
AUDIO_SOURCE_DEFAULT, 0, ""}
)
);
diff --git a/services/camera/libcameraservice/Android.bp b/services/camera/libcameraservice/Android.bp
index c50a3c6..c63feb2 100644
--- a/services/camera/libcameraservice/Android.bp
+++ b/services/camera/libcameraservice/Android.bp
@@ -26,7 +26,9 @@
"CameraFlashlight.cpp",
"common/Camera2ClientBase.cpp",
"common/CameraDeviceBase.cpp",
+ "common/CameraOfflineSessionBase.cpp",
"common/CameraProviderManager.cpp",
+ "common/DepthPhotoProcessor.cpp",
"common/FrameProcessorBase.cpp",
"api1/CameraClient.cpp",
"api1/Camera2Client.cpp",
@@ -45,6 +47,7 @@
"api2/HeicCompositeStream.cpp",
"device1/CameraHardwareInterface.cpp",
"device3/Camera3Device.cpp",
+ "device3/Camera3OfflineSession.cpp",
"device3/Camera3Stream.cpp",
"device3/Camera3IOStreamBase.cpp",
"device3/Camera3InputStream.cpp",
@@ -54,7 +57,9 @@
"device3/StatusTracker.cpp",
"device3/Camera3BufferManager.cpp",
"device3/Camera3StreamSplitter.cpp",
+ "device3/CoordinateMapper.cpp",
"device3/DistortionMapper.cpp",
+ "device3/ZoomRatioMapper.cpp",
"gui/RingBufferConsumer.cpp",
"utils/CameraThreadState.cpp",
"hidl/AidlCameraDeviceCallbacks.cpp",
@@ -87,10 +92,12 @@
"libmediautils",
"libcamera_client",
"libcamera_metadata",
+ "libdynamic_depth",
"libfmq",
"libgui",
"libhardware",
"libhidlbase",
+ "libimage_io",
"libjpeg",
"libmedia_codeclist",
"libmedia_omx",
@@ -98,6 +105,7 @@
"libsensorprivacy",
"libstagefright",
"libstagefright_foundation",
+ "libxml2",
"libyuv",
"android.frameworks.cameraservice.common@2.0",
"android.frameworks.cameraservice.service@2.0",
@@ -110,6 +118,7 @@
"android.hardware.camera.device@3.3",
"android.hardware.camera.device@3.4",
"android.hardware.camera.device@3.5",
+ "android.hardware.camera.device@3.6"
],
export_shared_lib_headers: [
@@ -138,40 +147,3 @@
}
-cc_library_shared {
- name: "libdepthphoto",
-
- srcs: [
- "utils/ExifUtils.cpp",
- "common/DepthPhotoProcessor.cpp",
- ],
-
- shared_libs: [
- "libimage_io",
- "libdynamic_depth",
- "libxml2",
- "liblog",
- "libutilscallstack",
- "libutils",
- "libcutils",
- "libjpeg",
- "libmemunreachable",
- "libexif",
- "libcamera_client",
- ],
-
- include_dirs: [
- "external/dynamic_depth/includes",
- "external/dynamic_depth/internal",
- ],
-
- export_include_dirs: ["."],
-
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wno-ignored-qualifiers",
- ],
-
-}
diff --git a/services/camera/libcameraservice/CameraService.cpp b/services/camera/libcameraservice/CameraService.cpp
index b704573..18ed3a5 100644
--- a/services/camera/libcameraservice/CameraService.cpp
+++ b/services/camera/libcameraservice/CameraService.cpp
@@ -2776,6 +2776,20 @@
// ----------------------------------------------------------------------------
+sp<CameraService> CameraService::OfflineClient::sCameraService;
+
+status_t CameraService::OfflineClient::startCameraOps() {
+ // TODO
+ return OK;
+}
+
+status_t CameraService::OfflineClient::finishCameraOps() {
+ // TODO
+ return OK;
+}
+
+// ----------------------------------------------------------------------------
+
void CameraService::Client::notifyError(int32_t errorCode,
const CaptureResultExtras& resultExtras) {
(void) resultExtras;
diff --git a/services/camera/libcameraservice/CameraService.h b/services/camera/libcameraservice/CameraService.h
index 1f40fc3..dae9c09 100644
--- a/services/camera/libcameraservice/CameraService.h
+++ b/services/camera/libcameraservice/CameraService.h
@@ -71,6 +71,7 @@
public:
class Client;
class BasicClient;
+ class OfflineClient;
// The effective API level. The Camera2 API running in LEGACY mode counts as API_1.
enum apiLevel {
@@ -401,6 +402,87 @@
int mCameraId; // All API1 clients use integer camera IDs
}; // class Client
+
+ // Client for offline session. Note that offline session client does not affect camera service's
+ // client arbitration logic. It is camera HAL's decision to decide whether a normal camera
+ // client is conflicting with existing offline client(s).
+ // The other distinctive difference between offline clients and normal clients is that normal
+ // clients are created through ICameraService binder calls, while the offline session client
+ // is created through ICameraDeviceUser::switchToOffline call.
+ class OfflineClient : public virtual RefBase {
+
+ virtual status_t dump(int fd, const Vector<String16>& args) = 0;
+
+ // Block the client form using the camera
+ virtual void block() = 0;
+
+ // Return the package name for this client
+ virtual String16 getPackageName() const = 0;
+
+ // Notify client about a fatal error
+ // TODO: maybe let impl notify within block?
+ virtual void notifyError(int32_t errorCode,
+ const CaptureResultExtras& resultExtras) = 0;
+
+ // Get the UID of the application client using this
+ virtual uid_t getClientUid() const = 0;
+
+ // Get the PID of the application client using this
+ virtual int getClientPid() const = 0;
+
+ protected:
+ OfflineClient(const sp<CameraService>& cameraService,
+ const String16& clientPackageName,
+ const String8& cameraIdStr,
+ int clientPid,
+ uid_t clientUid,
+ int servicePid): mCameraIdStr(cameraIdStr),
+ mClientPackageName(clientPackageName), mClientPid(clientPid),
+ mClientUid(clientUid), mServicePid(servicePid) {
+ if (sCameraService == nullptr) {
+ sCameraService = cameraService;
+ }
+ }
+
+ virtual ~OfflineClient() { /*TODO*/ }
+
+ // these are initialized in the constructor.
+ static sp<CameraService> sCameraService;
+ const String8 mCameraIdStr;
+ String16 mClientPackageName;
+ pid_t mClientPid;
+ const uid_t mClientUid;
+ const pid_t mServicePid;
+ bool mDisconnected;
+
+ // - The app-side Binder interface to receive callbacks from us
+ sp<IBinder> mRemoteBinder; // immutable after constructor
+
+ // permissions management
+ status_t startCameraOps();
+ status_t finishCameraOps();
+
+ private:
+ std::unique_ptr<AppOpsManager> mAppOpsManager = nullptr;
+
+ class OpsCallback : public BnAppOpsCallback {
+ public:
+ explicit OpsCallback(wp<OfflineClient> client) : mClient(client) {}
+ virtual void opChanged(int32_t /*op*/, const String16& /*packageName*/) {
+ //TODO
+ }
+
+ private:
+ wp<OfflineClient> mClient;
+
+ }; // class OpsCallback
+
+ sp<OpsCallback> mOpsCallback;
+
+ // IAppOpsCallback interface, indirected through opListener
+ // virtual void opChanged(int32_t op, const String16& packageName);
+ }; // class OfflineClient
+
/**
* A listener class that implements the LISTENER interface for use with a ClientManager, and
* implements the following methods:
diff --git a/services/camera/libcameraservice/api1/client2/Parameters.cpp b/services/camera/libcameraservice/api1/client2/Parameters.cpp
index 18addb5..20333d1 100644
--- a/services/camera/libcameraservice/api1/client2/Parameters.cpp
+++ b/services/camera/libcameraservice/api1/client2/Parameters.cpp
@@ -2454,12 +2454,9 @@
camera_metadata_ro_entry_t availableFocalLengths =
staticInfo(ANDROID_LENS_INFO_AVAILABLE_FOCAL_LENGTHS, 0, 0, /*required*/false);
- if (!availableFocalLengths.count && !fastInfo.isExternalCamera) return NO_INIT;
// Find focal length in PREVIEW template to use as default focal length.
- if (fastInfo.isExternalCamera) {
- fastInfo.defaultFocalLength = -1.0;
- } else {
+ if (availableFocalLengths.count) {
// Find smallest (widest-angle) focal length to use as basis of still
// picture FOV reporting.
fastInfo.defaultFocalLength = availableFocalLengths.data.f[0];
@@ -2481,6 +2478,10 @@
if (entry.count != 0) {
fastInfo.defaultFocalLength = entry.data.f[0];
}
+ } else if (fastInfo.isExternalCamera) {
+ fastInfo.defaultFocalLength = -1.0;
+ } else {
+ return NO_INIT;
}
return OK;
}
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
index 27bebde..28421ba 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.cpp
@@ -1901,6 +1901,80 @@
return binder::Status::ok();
}
+binder::Status CameraDeviceClient::switchToOffline(
+ const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
+ const std::vector<view::Surface>& offlineOutputs,
+ /*out*/
+ sp<hardware::camera2::ICameraOfflineSession>* session) {
+ ATRACE_CALL();
+
+ binder::Status res;
+ if (!(res = checkPidStatus(__FUNCTION__)).isOk()) return res;
+
+ Mutex::Autolock icl(mBinderSerializationLock);
+
+ if (!mDevice.get()) {
+ return STATUS_ERROR(CameraService::ERROR_DISCONNECTED, "Camera device no longer alive");
+ }
+
+ if (offlineOutputs.empty()) {
+ String8 msg = String8::format("Offline outputs must not be empty");
+ ALOGE("%s: %s", __FUNCTION__, msg.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.string());
+ }
+
+ if (session == nullptr) {
+ String8 msg = String8::format("Invalid offline session");
+ ALOGE("%s: %s", __FUNCTION__, msg.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.string());
+ }
+
+ std::vector<int32_t> offlineStreamIds(offlineOutputs.size());
+ for (auto& surface : offlineOutputs) {
+ sp<IBinder> binder = IInterface::asBinder(surface.graphicBufferProducer);
+ ssize_t index = mStreamMap.indexOfKey(binder);
+ if (index == NAME_NOT_FOUND) {
+ String8 msg = String8::format("Offline output is invalid");
+ ALOGE("%s: %s", __FUNCTION__, msg.string());
+ return STATUS_ERROR(CameraService::ERROR_ILLEGAL_ARGUMENT, msg.string());
+ }
+ // TODO: Also check whether the offline output is supported by Hal for offline mode.
+
+ sp<Surface> s = new Surface(surface.graphicBufferProducer);
+ bool isCompositeStream = camera3::DepthCompositeStream::isDepthCompositeStream(s);
+ isCompositeStream |= camera3::HeicCompositeStream::isHeicCompositeStream(s);
+ if (isCompositeStream) {
+ // TODO: Add composite specific handling
+ } else {
+ offlineStreamIds.push_back(mStreamMap.valueAt(index).streamId());
+ }
+ }
+
+ sp<CameraOfflineSessionBase> offlineSession;
+ auto ret = mDevice->switchToOffline(offlineStreamIds, &offlineSession);
+ if (ret != OK) {
+ return STATUS_ERROR_FMT(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Camera %s: Error switching to offline mode: %s (%d)",
+ mCameraIdStr.string(), strerror(ret), ret);
+ }
+
+ sp<CameraOfflineSessionClient> offlineClient = new CameraOfflineSessionClient(sCameraService,
+ offlineSession, cameraCb, mClientPackageName, mCameraIdStr, mClientPid, mClientUid,
+ mServicePid);
+ ret = offlineClient->initialize();
+ if (ret == OK) {
+ // TODO: We need to update mStreamMap, mConfiguredOutputs
+ } else {
+ return STATUS_ERROR_FMT(CameraService::ERROR_ILLEGAL_ARGUMENT,
+ "Camera %s: Failed to initilize offline session: %s (%d)",
+ mCameraIdStr.string(), strerror(ret), ret);
+ }
+
+ *session = offlineClient;
+
+ return binder::Status::ok();
+}
+
status_t CameraDeviceClient::dump(int fd, const Vector<String16>& args) {
return BasicClient::dump(fd, args);
}
diff --git a/services/camera/libcameraservice/api2/CameraDeviceClient.h b/services/camera/libcameraservice/api2/CameraDeviceClient.h
index 7efc5ab..0a8f377 100644
--- a/services/camera/libcameraservice/api2/CameraDeviceClient.h
+++ b/services/camera/libcameraservice/api2/CameraDeviceClient.h
@@ -23,6 +23,7 @@
#include <camera/camera2/SessionConfiguration.h>
#include <camera/camera2/SubmitInfo.h>
+#include "CameraOfflineSessionClient.h"
#include "CameraService.h"
#include "common/FrameProcessorBase.h"
#include "common/Camera2ClientBase.h"
@@ -157,6 +158,12 @@
virtual binder::Status getGlobalAudioRestriction(/*out*/int32_t* outMode) override;
+ virtual binder::Status switchToOffline(
+ const sp<hardware::camera2::ICameraDeviceCallbacks>& cameraCb,
+ const std::vector<view::Surface>& offlineOutputs,
+ /*out*/
+ sp<hardware::camera2::ICameraOfflineSession>* session) override;
+
/**
* Interface used by CameraService
*/
diff --git a/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
new file mode 100644
index 0000000..cb83e29
--- /dev/null
+++ b/services/camera/libcameraservice/api2/CameraOfflineSessionClient.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_CAMERA_PHOTOGRAPHY_CAMERAOFFLINESESSIONCLIENT_H
+#define ANDROID_SERVERS_CAMERA_PHOTOGRAPHY_CAMERAOFFLINESESSIONCLIENT_H
+
+#include <android/hardware/camera2/BnCameraOfflineSession.h>
+#include <android/hardware/camera2/ICameraDeviceCallbacks.h>
+#include "CameraService.h"
+
+namespace android {
+
+using android::hardware::camera2::ICameraDeviceCallbacks;
+
+class CameraOfflineSessionClient :
+ public CameraService::OfflineClient,
+ public hardware::camera2::BnCameraOfflineSession
+ // public camera2::FrameProcessorBase::FilteredListener?
+{
+public:
+ CameraOfflineSessionClient(
+ const sp<CameraService>& cameraService,
+ sp<CameraOfflineSessionBase> session,
+ const sp<ICameraDeviceCallbacks>& remoteCallback,
+ const String16& clientPackageName,
+ const String8& cameraIdStr,
+ int clientPid, uid_t clientUid, int servicePid) :
+ CameraService::OfflineClient(cameraService, clientPackageName,
+ cameraIdStr, clientPid, clientUid, servicePid),
+ mRemoteCallback(remoteCallback), mOfflineSession(session) {}
+
+ ~CameraOfflineSessionClient() {}
+
+ virtual binder::Status disconnect() override { return binder::Status::ok(); }
+
+ virtual status_t dump(int /*fd*/, const Vector<String16>& /*args*/) override {
+ return OK;
+ }
+
+ // Block the client form using the camera
+ virtual void block() override {};
+
+ // Return the package name for this client
+ virtual String16 getPackageName() const override { String16 ret; return ret; };
+
+ // Notify client about a fatal error
+ // TODO: maybe let impl notify within block?
+ virtual void notifyError(int32_t /*errorCode*/,
+ const CaptureResultExtras& /*resultExtras*/) override {}
+
+ // Get the UID of the application client using this
+ virtual uid_t getClientUid() const override { return 0; }
+
+ // Get the PID of the application client using this
+ virtual int getClientPid() const override { return 0; }
+
+ status_t initialize() {
+ // TODO: Talk to camera service to add the offline session client book keeping
+ return OK;
+ }
+private:
+ sp<CameraOfflineSessionBase> mSession;
+
+ sp<hardware::camera2::ICameraDeviceCallbacks> mRemoteCallback;
+ // This class is responsible to convert HAL callbacks to AIDL callbacks
+
+ sp<CameraOfflineSessionBase> mOfflineSession;
+};
+
+} // namespace android
+
+#endif // ANDROID_SERVERS_CAMERA_PHOTOGRAPHY_CAMERAOFFLINESESSIONCLIENT_H
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
index 0b91016..ac6f8d6 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.cpp
@@ -20,7 +20,6 @@
#include "api1/client2/JpegProcessor.h"
#include "common/CameraProviderManager.h"
-#include "dlfcn.h"
#include <gui/Surface.h>
#include <utils/Log.h>
#include <utils/Trace.h>
@@ -43,9 +42,7 @@
mBlobBufferAcquired(false),
mProducerListener(new ProducerListener()),
mMaxJpegSize(-1),
- mIsLogicalCamera(false),
- mDepthPhotoLibHandle(nullptr),
- mDepthPhotoProcess(nullptr) {
+ mIsLogicalCamera(false) {
sp<CameraDeviceBase> cameraDevice = device.promote();
if (cameraDevice.get() != nullptr) {
CameraMetadata staticInfo = cameraDevice->info();
@@ -83,19 +80,6 @@
}
getSupportedDepthSizes(staticInfo, &mSupportedDepthSizes);
-
- mDepthPhotoLibHandle = dlopen(camera3::kDepthPhotoLibrary, RTLD_NOW | RTLD_LOCAL);
- if (mDepthPhotoLibHandle != nullptr) {
- mDepthPhotoProcess = reinterpret_cast<camera3::process_depth_photo_frame> (
- dlsym(mDepthPhotoLibHandle, camera3::kDepthPhotoProcessFunction));
- if (mDepthPhotoProcess == nullptr) {
- ALOGE("%s: Failed to link to depth photo process function: %s", __FUNCTION__,
- dlerror());
- }
- } else {
- ALOGE("%s: Failed to link to depth photo library: %s", __FUNCTION__, dlerror());
- }
-
}
}
@@ -108,11 +92,6 @@
mDepthSurface.clear();
mDepthConsumer = nullptr;
mDepthSurface = nullptr;
- if (mDepthPhotoLibHandle != nullptr) {
- dlclose(mDepthPhotoLibHandle);
- mDepthPhotoLibHandle = nullptr;
- }
- mDepthPhotoProcess = nullptr;
}
void DepthCompositeStream::compilePendingInputLocked() {
@@ -356,7 +335,7 @@
}
size_t actualJpegSize = 0;
- res = mDepthPhotoProcess(depthPhoto, finalJpegBufferSize, dstBuffer, &actualJpegSize);
+ res = processDepthPhotoFrame(depthPhoto, finalJpegBufferSize, dstBuffer, &actualJpegSize);
if (res != 0) {
ALOGE("%s: Depth photo processing failed: %s (%d)", __FUNCTION__, strerror(-res), res);
outputANW->cancelBuffer(mOutputSurface.get(), anb, /*fence*/ -1);
@@ -583,11 +562,6 @@
return NO_ERROR;
}
- if ((mDepthPhotoLibHandle == nullptr) || (mDepthPhotoProcess == nullptr)) {
- ALOGE("%s: Depth photo library is not present!", __FUNCTION__);
- return NO_INIT;
- }
-
if (mOutputSurface.get() == nullptr) {
ALOGE("%s: No valid output surface set!", __FUNCTION__);
return NO_INIT;
diff --git a/services/camera/libcameraservice/api2/DepthCompositeStream.h b/services/camera/libcameraservice/api2/DepthCompositeStream.h
index 28a7826..60c6b1e 100644
--- a/services/camera/libcameraservice/api2/DepthCompositeStream.h
+++ b/services/camera/libcameraservice/api2/DepthCompositeStream.h
@@ -126,8 +126,6 @@
std::vector<std::tuple<size_t, size_t>> mSupportedDepthSizes;
std::vector<float> mIntrinsicCalibration, mLensDistortion;
bool mIsLogicalCamera;
- void* mDepthPhotoLibHandle;
- process_depth_photo_frame mDepthPhotoProcess;
// Keep all incoming Depth buffer timestamps pending further processing.
std::vector<int64_t> mInputDepthBuffers;
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
index bf4d1c8..9cdad70 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.cpp
@@ -67,6 +67,7 @@
mDequeuedOutputBufferCnt(0),
mLockedAppSegmentBufferCnt(0),
mCodecOutputCounter(0),
+ mQuality(-1),
mGridTimestampUs(0) {
}
@@ -525,6 +526,12 @@
mPendingInputFrames[it->first].orientation = it->second.first;
mPendingInputFrames[it->first].quality = it->second.second;
mSettingsByTimestamp.erase(it);
+
+ // Set encoder quality if no inflight encoding
+ if (mPendingInputFrames.size() == 1) {
+ int32_t newQuality = mPendingInputFrames.begin()->second.quality;
+ updateCodecQualityLocked(newQuality);
+ }
}
while (!mInputAppSegmentBuffers.empty()) {
@@ -851,17 +858,6 @@
strerror(-res), res);
return res;
}
- // Set encoder quality
- {
- sp<AMessage> qualityParams = new AMessage;
- qualityParams->setInt32(PARAMETER_KEY_VIDEO_BITRATE, inputFrame.quality);
- res = mCodec->setParameters(qualityParams);
- if (res != OK) {
- ALOGE("%s: Failed to set codec quality: %s (%d)",
- __FUNCTION__, strerror(-res), res);
- return res;
- }
- }
ssize_t trackId = inputFrame.muxer->addTrack(inputFrame.format);
if (trackId < 0) {
@@ -1148,16 +1144,30 @@
void HeicCompositeStream::releaseInputFramesLocked() {
auto it = mPendingInputFrames.begin();
+ bool inputFrameDone = false;
while (it != mPendingInputFrames.end()) {
auto& inputFrame = it->second;
if (inputFrame.error ||
(inputFrame.appSegmentWritten && inputFrame.pendingOutputTiles == 0)) {
releaseInputFrameLocked(&inputFrame);
it = mPendingInputFrames.erase(it);
+ inputFrameDone = true;
} else {
it++;
}
}
+
+ // Update codec quality based on first upcoming input frame.
+ // Note that when encoding is in surface mode, currently there is no
+ // way for camera service to synchronize quality setting on a per-frame
+ // basis: we don't get notification when codec is ready to consume a new
+ // input frame. So we update codec quality on a best-effort basis.
+ if (inputFrameDone) {
+ auto firstPendingFrame = mPendingInputFrames.begin();
+ if (firstPendingFrame != mPendingInputFrames.end()) {
+ updateCodecQualityLocked(firstPendingFrame->second.quality);
+ }
+ }
}
status_t HeicCompositeStream::initializeCodec(uint32_t width, uint32_t height,
@@ -1546,6 +1556,20 @@
return maxAppsSegment * (2 + 0xFFFF) + sizeof(struct CameraBlob);
}
+void HeicCompositeStream::updateCodecQualityLocked(int32_t quality) {
+ if (quality != mQuality) {
+ sp<AMessage> qualityParams = new AMessage;
+ qualityParams->setInt32(PARAMETER_KEY_VIDEO_BITRATE, quality);
+ status_t res = mCodec->setParameters(qualityParams);
+ if (res != OK) {
+ ALOGE("%s: Failed to set codec quality: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ } else {
+ mQuality = quality;
+ }
+ }
+}
+
bool HeicCompositeStream::threadLoop() {
int64_t currentTs = INT64_MAX;
bool newInputAvailable = false;
diff --git a/services/camera/libcameraservice/api2/HeicCompositeStream.h b/services/camera/libcameraservice/api2/HeicCompositeStream.h
index 04e7b83..e88471d 100644
--- a/services/camera/libcameraservice/api2/HeicCompositeStream.h
+++ b/services/camera/libcameraservice/api2/HeicCompositeStream.h
@@ -199,6 +199,7 @@
size_t top, size_t left, size_t width, size_t height);
void initCopyRowFunction(int32_t width);
static size_t calcAppSegmentMaxSize(const CameraMetadata& info);
+ void updateCodecQualityLocked(int32_t quality);
static const nsecs_t kWaitDuration = 10000000; // 10 ms
static const int32_t kDefaultJpegQuality = 99;
@@ -240,6 +241,7 @@
std::vector<CodecOutputBufferInfo> mCodecOutputBuffers;
std::queue<int64_t> mCodecOutputBufferTimestamps;
size_t mCodecOutputCounter;
+ int32_t mQuality;
// Keep all incoming Yuv buffer pending tiling and encoding (for HEVC YUV tiling only)
std::vector<int64_t> mInputYuvBuffers;
diff --git a/services/camera/libcameraservice/common/CameraDeviceBase.h b/services/camera/libcameraservice/common/CameraDeviceBase.h
index 935bc37..1026fdf 100644
--- a/services/camera/libcameraservice/common/CameraDeviceBase.h
+++ b/services/camera/libcameraservice/common/CameraDeviceBase.h
@@ -35,6 +35,8 @@
#include "device3/Camera3StreamInterface.h"
#include "binder/Status.h"
+#include "CameraOfflineSessionBase.h"
+
namespace android {
class CameraProviderManager;
@@ -389,6 +391,13 @@
* requests to complete, based on their settings
*/
virtual nsecs_t getExpectedInFlightDuration() = 0;
+
+ /**
+ * switch to offline session
+ */
+ virtual status_t switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/ sp<CameraOfflineSessionBase>* session) = 0;
};
}; // namespace android
diff --git a/services/camera/libcameraservice/common/CameraOfflineSessionBase.cpp b/services/camera/libcameraservice/common/CameraOfflineSessionBase.cpp
new file mode 100644
index 0000000..ff673a9
--- /dev/null
+++ b/services/camera/libcameraservice/common/CameraOfflineSessionBase.cpp
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CameraOfflineSessionBase.h"
+
+namespace android {
+
+/**
+ * Base class destructors
+ */
+CameraOfflineSessionBase::~CameraOfflineSessionBase() {
+}
+
+} // namespace android
diff --git a/services/camera/libcameraservice/common/CameraOfflineSessionBase.h b/services/camera/libcameraservice/common/CameraOfflineSessionBase.h
new file mode 100644
index 0000000..3862521
--- /dev/null
+++ b/services/camera/libcameraservice/common/CameraOfflineSessionBase.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_CAMERA_CAMERAOFFLINESESSIONBASE_H
+#define ANDROID_SERVERS_CAMERA_CAMERAOFFLINESESSIONBASE_H
+
+#include <utils/RefBase.h>
+#include <utils/String8.h>
+#include <utils/Timers.h>
+
+#include "camera/CaptureResult.h"
+
+namespace android {
+
+class CameraOfflineSessionBase : public virtual RefBase {
+ public:
+ virtual ~CameraOfflineSessionBase();
+
+ // The session's original camera ID
+ virtual const String8& getId() const = 0;
+
+ virtual status_t disconnect() = 0;
+
+ virtual status_t dump(int fd) = 0;
+
+ virtual status_t abort() = 0;
+
+ /**
+ * Capture result passing
+ */
+ virtual status_t waitForNextFrame(nsecs_t timeout) = 0;
+
+ virtual status_t getNextResult(CaptureResult *frame) = 0;
+
+ // TODO: notification passing path
+}; // class CameraOfflineSessionBase
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.cpp b/services/camera/libcameraservice/common/CameraProviderManager.cpp
index 7ed4c3d..0f74a48 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.cpp
+++ b/services/camera/libcameraservice/common/CameraProviderManager.cpp
@@ -40,6 +40,7 @@
#include <utils/Trace.h>
#include "api2/HeicCompositeStream.h"
+#include "device3/ZoomRatioMapper.h"
namespace android {
@@ -232,6 +233,15 @@
return deviceInfo->hasFlashUnit();
}
+bool CameraProviderManager::supportNativeZoomRatio(const std::string &id) const {
+ std::lock_guard<std::mutex> lock(mInterfaceMutex);
+
+ auto deviceInfo = findDeviceInfoLocked(id);
+ if (deviceInfo == nullptr) return false;
+
+ return deviceInfo->supportNativeZoomRatio();
+}
+
status_t CameraProviderManager::getResourceCost(const std::string &id,
CameraResourceCost* cost) const {
std::lock_guard<std::mutex> lock(mInterfaceMutex);
@@ -706,31 +716,6 @@
}
}
-bool CameraProviderManager::ProviderInfo::DeviceInfo3::isDepthPhotoLibraryPresent() {
- static bool libraryPresent = false;
- static bool initialized = false;
- if (initialized) {
- return libraryPresent;
- } else {
- initialized = true;
- }
-
- void* depthLibHandle = dlopen(camera3::kDepthPhotoLibrary, RTLD_NOW | RTLD_LOCAL);
- if (depthLibHandle == nullptr) {
- return false;
- }
-
- auto processFunc = dlsym(depthLibHandle, camera3::kDepthPhotoProcessFunction);
- if (processFunc != nullptr) {
- libraryPresent = true;
- } else {
- libraryPresent = false;
- }
- dlclose(depthLibHandle);
-
- return libraryPresent;
-}
-
status_t CameraProviderManager::ProviderInfo::DeviceInfo3::addDynamicDepthTags() {
uint32_t depthExclTag = ANDROID_DEPTH_DEPTH_IS_EXCLUSIVE;
uint32_t depthSizesTag = ANDROID_DEPTH_AVAILABLE_DEPTH_STREAM_CONFIGURATIONS;
@@ -778,11 +763,6 @@
return OK;
}
- if(!isDepthPhotoLibraryPresent()) {
- // Depth photo processing library is not present, nothing more to do.
- return OK;
- }
-
std::vector<int32_t> dynamicDepthEntries;
for (const auto& it : supportedDynamicDepthSizes) {
int32_t entry[4] = {HAL_PIXEL_FORMAT_BLOB, static_cast<int32_t> (std::get<0>(it)),
@@ -2035,6 +2015,13 @@
__FUNCTION__, strerror(-res), res);
}
+ res = camera3::ZoomRatioMapper::overrideZoomRatioTags(
+ &mCameraCharacteristics, &mSupportNativeZoomRatio);
+ if (OK != res) {
+ ALOGE("%s: Unable to override zoomRatio related tags: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ }
+
camera_metadata_entry flashAvailable =
mCameraCharacteristics.find(ANDROID_FLASH_INFO_AVAILABLE);
if (flashAvailable.count == 1 &&
@@ -2095,6 +2082,13 @@
CameraProviderManager::statusToString(status), status);
return;
}
+
+ res = camera3::ZoomRatioMapper::overrideZoomRatioTags(
+ &mPhysicalCameraCharacteristics[id], &mSupportNativeZoomRatio);
+ if (OK != res) {
+ ALOGE("%s: Unable to override zoomRatio related tags: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ }
}
}
diff --git a/services/camera/libcameraservice/common/CameraProviderManager.h b/services/camera/libcameraservice/common/CameraProviderManager.h
index 2ef1f6f..58df0e8 100644
--- a/services/camera/libcameraservice/common/CameraProviderManager.h
+++ b/services/camera/libcameraservice/common/CameraProviderManager.h
@@ -187,6 +187,11 @@
bool hasFlashUnit(const std::string &id) const;
/**
+ * Return true if the camera device has native zoom ratio support.
+ */
+ bool supportNativeZoomRatio(const std::string &id) const;
+
+ /**
* Return the resource cost of this camera device
*/
status_t getResourceCost(const std::string &id,
@@ -416,6 +421,7 @@
sp<ProviderInfo> mParentProvider;
bool hasFlashUnit() const { return mHasFlashUnit; }
+ bool supportNativeZoomRatio() const { return mSupportNativeZoomRatio; }
virtual status_t setTorchMode(bool enabled) = 0;
virtual status_t getCameraInfo(hardware::CameraInfo *info) const = 0;
virtual bool isAPI1Compatible() const = 0;
@@ -449,10 +455,11 @@
mIsLogicalCamera(false), mResourceCost(resourceCost),
mStatus(hardware::camera::common::V1_0::CameraDeviceStatus::PRESENT),
mParentProvider(parentProvider), mHasFlashUnit(false),
- mPublicCameraIds(publicCameraIds) {}
+ mSupportNativeZoomRatio(false), mPublicCameraIds(publicCameraIds) {}
virtual ~DeviceInfo();
protected:
- bool mHasFlashUnit;
+ bool mHasFlashUnit; // const after constructor
+ bool mSupportNativeZoomRatio; // const after constructor
const std::vector<std::string>& mPublicCameraIds;
template<class InterfaceT>
@@ -538,7 +545,6 @@
void getSupportedDynamicDepthDurations(const std::vector<int64_t>& depthDurations,
const std::vector<int64_t>& blobDurations,
std::vector<int64_t> *dynamicDepthDurations /*out*/);
- static bool isDepthPhotoLibraryPresent();
static void getSupportedDynamicDepthSizes(
const std::vector<std::tuple<size_t, size_t>>& blobSizes,
const std::vector<std::tuple<size_t, size_t>>& depthSizes,
diff --git a/services/camera/libcameraservice/common/DepthPhotoProcessor.cpp b/services/camera/libcameraservice/common/DepthPhotoProcessor.cpp
index 94541d8..c995670 100644
--- a/services/camera/libcameraservice/common/DepthPhotoProcessor.cpp
+++ b/services/camera/libcameraservice/common/DepthPhotoProcessor.cpp
@@ -410,7 +410,7 @@
return DepthMap::FromData(depthParams, items);
}
-extern "C" int processDepthPhotoFrame(DepthPhotoInputFrame inputFrame, size_t depthPhotoBufferSize,
+int processDepthPhotoFrame(DepthPhotoInputFrame inputFrame, size_t depthPhotoBufferSize,
void* depthPhotoBuffer /*out*/, size_t* depthPhotoActualSize /*out*/) {
if ((inputFrame.mMainJpegBuffer == nullptr) || (inputFrame.mDepthMapBuffer == nullptr) ||
(depthPhotoBuffer == nullptr) || (depthPhotoActualSize == nullptr)) {
diff --git a/services/camera/libcameraservice/common/DepthPhotoProcessor.h b/services/camera/libcameraservice/common/DepthPhotoProcessor.h
index ba5ca9e..09b6935 100644
--- a/services/camera/libcameraservice/common/DepthPhotoProcessor.h
+++ b/services/camera/libcameraservice/common/DepthPhotoProcessor.h
@@ -64,9 +64,7 @@
mOrientation(DepthPhotoOrientation::DEPTH_ORIENTATION_0_DEGREES) {}
};
-static const char *kDepthPhotoLibrary = "libdepthphoto.so";
-static const char *kDepthPhotoProcessFunction = "processDepthPhotoFrame";
-typedef int (*process_depth_photo_frame) (DepthPhotoInputFrame /*inputFrame*/,
+int processDepthPhotoFrame(DepthPhotoInputFrame /*inputFrame*/,
size_t /*depthPhotoBufferSize*/, void* /*depthPhotoBuffer out*/,
size_t* /*depthPhotoActualSize out*/);
diff --git a/services/camera/libcameraservice/device3/Camera3Device.cpp b/services/camera/libcameraservice/device3/Camera3Device.cpp
index 2541365..92ba5e4 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.cpp
+++ b/services/camera/libcameraservice/device3/Camera3Device.cpp
@@ -58,6 +58,7 @@
#include "device3/Camera3InputStream.h"
#include "device3/Camera3DummyStream.h"
#include "device3/Camera3SharedOutputStream.h"
+#include "device3/Camera3OfflineSession.h"
#include "CameraService.h"
#include "utils/CameraThreadState.h"
@@ -132,6 +133,7 @@
session->close();
return res;
}
+ mSupportNativeZoomRatio = manager->supportNativeZoomRatio(mId.string());
std::vector<std::string> physicalCameraIds;
bool isLogical = manager->isLogicalCamera(mId.string(), &physicalCameraIds);
@@ -146,8 +148,11 @@
return res;
}
- if (DistortionMapper::isDistortionSupported(mPhysicalDeviceInfoMap[physicalId])) {
- mDistortionMappers[physicalId].setupStaticInfo(mPhysicalDeviceInfoMap[physicalId]);
+ bool usePrecorrectArray =
+ DistortionMapper::isDistortionSupported(mPhysicalDeviceInfoMap[physicalId]);
+ if (usePrecorrectArray) {
+ res = mDistortionMappers[physicalId].setupStaticInfo(
+ mPhysicalDeviceInfoMap[physicalId]);
if (res != OK) {
SET_ERR_L("Unable to read camera %s's calibration fields for distortion "
"correction", physicalId.c_str());
@@ -155,6 +160,10 @@
return res;
}
}
+
+ mZoomRatioMappers[physicalId] = ZoomRatioMapper(
+ &mPhysicalDeviceInfoMap[physicalId],
+ mSupportNativeZoomRatio, usePrecorrectArray);
}
}
@@ -206,7 +215,15 @@
ANDROID_INFO_SUPPORTED_BUFFER_MANAGEMENT_VERSION_HIDL_DEVICE_3_5);
}
- mInterface = new HalInterface(session, queue, mUseHalBufManager);
+ camera_metadata_entry_t capabilities = mDeviceInfo.find(ANDROID_REQUEST_AVAILABLE_CAPABILITIES);
+ for (size_t i = 0; i < capabilities.count; i++) {
+ uint8_t capability = capabilities.data.u8[i];
+ if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_OFFLINE_PROCESSING) {
+ mSupportOfflineProcessing = true;
+ }
+ }
+
+ mInterface = new HalInterface(session, queue, mUseHalBufManager, mSupportOfflineProcessing);
std::string providerType;
mVendorTagId = manager->getProviderTagIdLocked(mId.string());
mTagMonitor.initialize(mVendorTagId);
@@ -227,9 +244,8 @@
maxVersion.get_major(), maxVersion.get_minor());
bool isMonochrome = false;
- camera_metadata_entry_t entry = mDeviceInfo.find(ANDROID_REQUEST_AVAILABLE_CAPABILITIES);
- for (size_t i = 0; i < entry.count; i++) {
- uint8_t capability = entry.data.u8[i];
+ for (size_t i = 0; i < capabilities.count; i++) {
+ uint8_t capability = capabilities.data.u8[i];
if (capability == ANDROID_REQUEST_AVAILABLE_CAPABILITIES_MONOCHROME) {
isMonochrome = true;
}
@@ -323,13 +339,18 @@
}
}
- if (DistortionMapper::isDistortionSupported(mDeviceInfo)) {
+ bool usePrecorrectArray = DistortionMapper::isDistortionSupported(mDeviceInfo);
+ if (usePrecorrectArray) {
res = mDistortionMappers[mId.c_str()].setupStaticInfo(mDeviceInfo);
if (res != OK) {
SET_ERR_L("Unable to read necessary calibration fields for distortion correction");
return res;
}
}
+
+ mZoomRatioMappers[mId.c_str()] = ZoomRatioMapper(&mDeviceInfo,
+ mSupportNativeZoomRatio, usePrecorrectArray);
+
return OK;
}
@@ -2116,6 +2137,15 @@
set_camera_metadata_vendor_id(rawRequest, mVendorTagId);
mRequestTemplateCache[templateId].acquire(rawRequest);
+ // Override the template request with zoomRatioMapper
+ res = mZoomRatioMappers[mId.c_str()].initZoomRatioInTemplate(
+ &mRequestTemplateCache[templateId]);
+ if (res != OK) {
+ CLOGE("Failed to update zoom ratio for template %d: %s (%d)",
+ templateId, strerror(-res), res);
+ return res;
+ }
+
*request = mRequestTemplateCache[templateId];
mLastTemplateId = templateId;
}
@@ -2165,7 +2195,9 @@
}
void Camera3Device::pauseStateNotify(bool enable) {
- Mutex::Autolock il(mInterfaceLock);
+ // We must not hold mInterfaceLock here since this function is called from
+ // RequestThread::threadLoop and holding mInterfaceLock could lead to
+ // deadlocks (http://b/143513518)
Mutex::Autolock l(mLock);
mPauseStateNotify = enable;
@@ -2742,7 +2774,9 @@
ATRACE_CALL();
bool ret = false;
- Mutex::Autolock il(mInterfaceLock);
+ // We must not hold mInterfaceLock here since this function is called from
+ // RequestThread::threadLoop and holding mInterfaceLock could lead to
+ // deadlocks (http://b/143513518)
nsecs_t maxExpectedDuration = getExpectedInFlightDuration();
Mutex::Autolock l(mLock);
@@ -3138,14 +3172,15 @@
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
bool hasAppCallback, nsecs_t maxExpectedDuration,
std::set<String8>& physicalCameraIds, bool isStillCapture,
- bool isZslCapture, const SurfaceMap& outputSurfaces) {
+ bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
+ const SurfaceMap& outputSurfaces) {
ATRACE_CALL();
Mutex::Autolock l(mInFlightLock);
ssize_t res;
res = mInFlightMap.add(frameNumber, InFlightRequest(numBuffers, resultExtras, hasInput,
hasAppCallback, maxExpectedDuration, physicalCameraIds, isStillCapture, isZslCapture,
- outputSurfaces));
+ cameraIdsWithZoom, outputSurfaces));
if (res < 0) return res;
if (mInFlightMap.size() == 1) {
@@ -3496,7 +3531,7 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult,
uint32_t frameNumber,
- bool reprocess, bool zslStillCapture,
+ bool reprocess, bool zslStillCapture, const std::set<std::string>& cameraIdsWithZoom,
const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas) {
ATRACE_CALL();
if (pendingMetadata.isEmpty())
@@ -3565,19 +3600,39 @@
mDistortionMappers[mId.c_str()].correctCaptureResult(&captureResult.mMetadata);
if (res != OK) {
SET_ERR("Unable to correct capture result metadata for frame %d: %s (%d)",
- frameNumber, strerror(res), res);
+ frameNumber, strerror(-res), res);
return;
}
+
+ // Fix up result metadata to account for zoom ratio availabilities between
+ // HAL and app.
+ bool zoomRatioIs1 = cameraIdsWithZoom.find(mId.c_str()) == cameraIdsWithZoom.end();
+ res = mZoomRatioMappers[mId.c_str()].updateCaptureResult(
+ &captureResult.mMetadata, zoomRatioIs1);
+ if (res != OK) {
+ SET_ERR("Failed to update capture result zoom ratio metadata for frame %d: %s (%d)",
+ frameNumber, strerror(-res), res);
+ return;
+ }
+
for (auto& physicalMetadata : captureResult.mPhysicalMetadatas) {
String8 cameraId8(physicalMetadata.mPhysicalCameraId);
- if (mDistortionMappers.find(cameraId8.c_str()) == mDistortionMappers.end()) {
- continue;
+ if (mDistortionMappers.find(cameraId8.c_str()) != mDistortionMappers.end()) {
+ res = mDistortionMappers[cameraId8.c_str()].correctCaptureResult(
+ &physicalMetadata.mPhysicalCameraMetadata);
+ if (res != OK) {
+ SET_ERR("Unable to correct physical capture result metadata for frame %d: %s (%d)",
+ frameNumber, strerror(-res), res);
+ return;
+ }
}
- res = mDistortionMappers[cameraId8.c_str()].correctCaptureResult(
- &physicalMetadata.mPhysicalCameraMetadata);
+
+ zoomRatioIs1 = cameraIdsWithZoom.find(cameraId8.c_str()) == cameraIdsWithZoom.end();
+ res = mZoomRatioMappers[cameraId8.c_str()].updateCaptureResult(
+ &physicalMetadata.mPhysicalCameraMetadata, zoomRatioIs1);
if (res != OK) {
- SET_ERR("Unable to correct physical capture result metadata for frame %d: %s (%d)",
- frameNumber, strerror(res), res);
+ SET_ERR("Failed to update camera %s's physical zoom ratio metadata for "
+ "frame %d: %s(%d)", cameraId8.c_str(), frameNumber, strerror(-res), res);
return;
}
}
@@ -3782,7 +3837,7 @@
sendCaptureResult(metadata, request.resultExtras,
collectedPartialResult, frameNumber,
hasInputBufferInRequest, request.zslCapture && request.stillCapture,
- request.physicalMetadatas);
+ request.cameraIdsWithZoom, request.physicalMetadatas);
}
}
@@ -4000,7 +4055,7 @@
sendCaptureResult(r.pendingMetadata, r.resultExtras,
r.collectedPartialResult, msg.frame_number,
r.hasInputBuffer, r.zslCapture && r.stillCapture,
- r.physicalMetadatas);
+ r.cameraIdsWithZoom, r.physicalMetadatas);
}
bool timestampIncreasing = !(r.zslCapture || r.hasInputBuffer);
returnOutputBuffers(r.pendingOutputBuffers.array(),
@@ -4045,13 +4100,18 @@
Camera3Device::HalInterface::HalInterface(
sp<ICameraDeviceSession> &session,
std::shared_ptr<RequestMetadataQueue> queue,
- bool useHalBufManager) :
+ bool useHalBufManager, bool supportOfflineProcessing) :
mHidlSession(session),
mRequestMetadataQueue(queue),
mUseHalBufManager(useHalBufManager),
- mIsReconfigurationQuerySupported(true) {
+ mIsReconfigurationQuerySupported(true),
+ mSupportOfflineProcessing(supportOfflineProcessing) {
// Check with hardware service manager if we can downcast these interfaces
// Somewhat expensive, so cache the results at startup
+ auto castResult_3_6 = device::V3_6::ICameraDeviceSession::castFrom(mHidlSession);
+ if (castResult_3_6.isOk()) {
+ mHidlSession_3_6 = castResult_3_6;
+ }
auto castResult_3_5 = device::V3_5::ICameraDeviceSession::castFrom(mHidlSession);
if (castResult_3_5.isOk()) {
mHidlSession_3_5 = castResult_3_5;
@@ -4066,18 +4126,22 @@
}
}
-Camera3Device::HalInterface::HalInterface() : mUseHalBufManager(false) {}
+Camera3Device::HalInterface::HalInterface() :
+ mUseHalBufManager(false),
+ mSupportOfflineProcessing(false) {}
Camera3Device::HalInterface::HalInterface(const HalInterface& other) :
mHidlSession(other.mHidlSession),
mRequestMetadataQueue(other.mRequestMetadataQueue),
- mUseHalBufManager(other.mUseHalBufManager) {}
+ mUseHalBufManager(other.mUseHalBufManager),
+ mSupportOfflineProcessing(other.mSupportOfflineProcessing) {}
bool Camera3Device::HalInterface::valid() {
return (mHidlSession != nullptr);
}
void Camera3Device::HalInterface::clear() {
+ mHidlSession_3_6.clear();
mHidlSession_3_5.clear();
mHidlSession_3_4.clear();
mHidlSession_3_3.clear();
@@ -4753,6 +4817,38 @@
}
}
+status_t Camera3Device::HalInterface::switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/hardware::camera::device::V3_6::CameraOfflineSessionInfo* offlineSessionInfo,
+ /*out*/sp<hardware::camera::device::V3_6::ICameraOfflineSession>* offlineSession) {
+ ATRACE_NAME("CameraHal::switchToOffline");
+ if (!valid() || mHidlSession_3_6 == nullptr) {
+ ALOGE("%s called on invalid camera!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ if (offlineSessionInfo == nullptr || offlineSession == nullptr) {
+ ALOGE("%s: offlineSessionInfo and offlineSession must not be null!", __FUNCTION__);
+ return INVALID_OPERATION;
+ }
+
+ common::V1_0::Status status = common::V1_0::Status::INTERNAL_ERROR;
+
+ auto resultCallback =
+ [&status, &offlineSessionInfo, &offlineSession] (auto s, auto info, auto session) {
+ status = s;
+ *offlineSessionInfo = info;
+ *offlineSession = session;
+ };
+ auto err = mHidlSession_3_6->switchToOffline(streamsToKeep, resultCallback);
+
+ if (!err.isOk()) {
+ ALOGE("%s: Transaction error: %s", __FUNCTION__, err.description().c_str());
+ return DEAD_OBJECT;
+ }
+ return CameraProviderManager::mapToStatusT(status);
+}
+
void Camera3Device::HalInterface::getInflightBufferKeys(
std::vector<std::pair<int32_t, int32_t>>* out) {
std::lock_guard<std::mutex> lock(mInflightLock);
@@ -5396,6 +5492,9 @@
bool Camera3Device::RequestThread::threadLoop() {
ATRACE_CALL();
status_t res;
+ // Any function called from threadLoop() must not hold mInterfaceLock since
+ // it could lead to deadlocks (disconnect() -> hold mInterfaceMutex -> wait for request thread
+ // to finish -> request thread waits on mInterfaceMutex) http://b/143513518
// Handle paused state.
if (waitIfPaused()) {
@@ -5524,6 +5623,7 @@
Mutex::Autolock l(mRequestLock);
mNextRequests.clear();
}
+ mRequestSubmittedSignal.signal();
return submitRequestSuccess;
}
@@ -5560,6 +5660,7 @@
// request in a batch as new
!(batchedRequest && i > 0);
if (newRequest) {
+ std::set<std::string> cameraIdsWithZoom;
/**
* HAL workaround:
* Insert a dummy trigger ID if a trigger is set but no trigger ID is
@@ -5592,6 +5693,28 @@
return INVALID_OPERATION;
}
}
+
+ for (it = captureRequest->mSettingsList.begin();
+ it != captureRequest->mSettingsList.end(); it++) {
+ if (parent->mZoomRatioMappers.find(it->cameraId) ==
+ parent->mZoomRatioMappers.end()) {
+ continue;
+ }
+
+ camera_metadata_entry_t e = it->metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (e.count > 0 && e.data.f[0] != 1.0f) {
+ cameraIdsWithZoom.insert(it->cameraId);
+ }
+
+ res = parent->mZoomRatioMappers[it->cameraId].updateCaptureRequest(
+ &(it->metadata));
+ if (res != OK) {
+ SET_ERR("RequestThread: Unable to correct capture requests "
+ "for zoom ratio for request %d: %s (%d)",
+ halRequest->frame_number, strerror(-res), res);
+ return INVALID_OPERATION;
+ }
+ }
}
}
@@ -5602,6 +5725,7 @@
captureRequest->mSettingsList.begin()->metadata.sort();
halRequest->settings = captureRequest->mSettingsList.begin()->metadata.getAndLock();
mPrevRequest = captureRequest;
+ mPrevCameraIdsWithZoom = cameraIdsWithZoom;
ALOGVV("%s: Request settings are NEW", __FUNCTION__);
IF_ALOGV() {
@@ -5789,7 +5913,7 @@
/*hasInput*/halRequest->input_buffer != NULL,
hasCallback,
calculateMaxExpectedDuration(halRequest->settings),
- requestedPhysicalCameras, isStillCapture, isZslCapture,
+ requestedPhysicalCameras, isStillCapture, isZslCapture, mPrevCameraIdsWithZoom,
(mUseHalBufManager) ? uniqueSurfaceIdMap :
SurfaceMap{});
ALOGVV("%s: registered in flight requestId = %" PRId32 ", frameNumber = %" PRId64
@@ -5903,6 +6027,32 @@
mStreamIdsToBeDrained = streamIds;
}
+status_t Camera3Device::RequestThread::switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/hardware::camera::device::V3_6::CameraOfflineSessionInfo* offlineSessionInfo,
+ /*out*/sp<hardware::camera::device::V3_6::ICameraOfflineSession>* offlineSession) {
+ Mutex::Autolock l(mRequestLock);
+ clearRepeatingRequestsLocked(/*lastFrameNumber*/nullptr);
+
+ // Theoretically we should also check for mRepeatingRequests.empty(), but the API interface
+ // is serialized by mInterfaceLock so skip that check.
+ bool queueEmpty = mNextRequests.empty() && mRequestQueue.empty();
+ while (!queueEmpty) {
+ status_t res = mRequestSubmittedSignal.waitRelative(mRequestLock, kRequestSubmitTimeout);
+ if (res == TIMED_OUT) {
+ ALOGE("%s: request thread failed to submit a request within timeout!", __FUNCTION__);
+ return res;
+ } else if (res != OK) {
+ ALOGE("%s: request thread failed to submit a request: %s (%d)!",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+ queueEmpty = mNextRequests.empty() && mRequestQueue.empty();
+ }
+ return mInterface->switchToOffline(
+ streamsToKeep, offlineSessionInfo, offlineSession);
+}
+
nsecs_t Camera3Device::getExpectedInFlightDuration() {
ATRACE_CALL();
Mutex::Autolock al(mInFlightLock);
@@ -6812,4 +6962,34 @@
return res;
}
+status_t Camera3Device::switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/ sp<CameraOfflineSessionBase>* session) {
+ ATRACE_CALL();
+ if (session == nullptr) {
+ ALOGE("%s: session must not be null", __FUNCTION__);
+ return BAD_VALUE;
+ }
+
+ Mutex::Autolock il(mInterfaceLock);
+ Mutex::Autolock l(mLock);
+
+ // 1. Stop repeating request, wait until request thread submitted all requests, then
+ // call HAL switchToOffline
+ hardware::camera::device::V3_6::CameraOfflineSessionInfo offlineSessionInfo;
+ sp<hardware::camera::device::V3_6::ICameraOfflineSession> offlineSession;
+
+ mRequestThread->switchToOffline(streamsToKeep, &offlineSessionInfo, &offlineSession);
+
+
+ // 2. Verify offlineSessionInfo
+ // 3. create Camera3OfflineSession and transfer object ownership
+ // (streams, inflight requests, buffer caches)
+ *session = new Camera3OfflineSession(mId);
+
+ // 4. Delete unneeded streams
+ // 5. Verify Camera3Device is in unconfigured state
+ return OK;
+}
+
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3Device.h b/services/camera/libcameraservice/device3/Camera3Device.h
index 2573b48..5faabd1 100644
--- a/services/camera/libcameraservice/device3/Camera3Device.h
+++ b/services/camera/libcameraservice/device3/Camera3Device.h
@@ -34,6 +34,7 @@
#include <android/hardware/camera/device/3.3/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.4/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.5/ICameraDeviceSession.h>
+#include <android/hardware/camera/device/3.6/ICameraDeviceSession.h>
#include <android/hardware/camera/device/3.2/ICameraDeviceCallback.h>
#include <android/hardware/camera/device/3.4/ICameraDeviceCallback.h>
#include <android/hardware/camera/device/3.5/ICameraDeviceCallback.h>
@@ -45,6 +46,7 @@
#include "device3/StatusTracker.h"
#include "device3/Camera3BufferManager.h"
#include "device3/DistortionMapper.h"
+#include "device3/ZoomRatioMapper.h"
#include "utils/TagMonitor.h"
#include "utils/LatencyHistogram.h"
#include <camera_metadata_hidden.h>
@@ -196,6 +198,9 @@
nsecs_t getExpectedInFlightDuration() override;
+ status_t switchToOffline(const std::vector<int32_t>& streamsToKeep,
+ /*out*/ sp<CameraOfflineSessionBase>* session) override;
+
/**
* Helper functions to map between framework and HIDL values
*/
@@ -282,7 +287,7 @@
public:
HalInterface(sp<hardware::camera::device::V3_2::ICameraDeviceSession> &session,
std::shared_ptr<RequestMetadataQueue> queue,
- bool useHalBufManager);
+ bool useHalBufManager, bool supportOfflineProcessing);
HalInterface(const HalInterface &other);
HalInterface();
@@ -316,6 +321,11 @@
bool isReconfigurationRequired(CameraMetadata& oldSessionParams,
CameraMetadata& newSessionParams);
+ status_t switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/hardware::camera::device::V3_6::CameraOfflineSessionInfo* offlineSessionInfo,
+ /*out*/sp<hardware::camera::device::V3_6::ICameraOfflineSession>* offlineSession);
+
// method to extract buffer's unique ID
// return pair of (newlySeenBuffer?, bufferId)
std::pair<bool, uint64_t> getBufferId(const buffer_handle_t& buf, int streamId);
@@ -352,6 +362,8 @@
sp<hardware::camera::device::V3_4::ICameraDeviceSession> mHidlSession_3_4;
// Valid if ICameraDeviceSession is @3.5 or newer
sp<hardware::camera::device::V3_5::ICameraDeviceSession> mHidlSession_3_5;
+ // Valid if ICameraDeviceSession is @3.6 or newer
+ sp<hardware::camera::device::V3_6::ICameraDeviceSession> mHidlSession_3_6;
std::shared_ptr<RequestMetadataQueue> mRequestMetadataQueue;
@@ -424,11 +436,14 @@
const bool mUseHalBufManager;
bool mIsReconfigurationQuerySupported;
+
+ const bool mSupportOfflineProcessing;
};
sp<HalInterface> mInterface;
CameraMetadata mDeviceInfo;
+ bool mSupportNativeZoomRatio;
std::unordered_map<std::string, CameraMetadata> mPhysicalDeviceInfoMap;
CameraMetadata mRequestTemplateCache[CAMERA3_TEMPLATE_COUNT];
@@ -842,6 +857,11 @@
void signalPipelineDrain(const std::vector<int>& streamIds);
+ status_t switchToOffline(
+ const std::vector<int32_t>& streamsToKeep,
+ /*out*/hardware::camera::device::V3_6::CameraOfflineSessionInfo* offlineSessionInfo,
+ /*out*/sp<hardware::camera::device::V3_6::ICameraOfflineSession>* offlineSession);
+
protected:
virtual bool threadLoop();
@@ -862,6 +882,9 @@
static const nsecs_t kRequestTimeout = 50e6; // 50 ms
+ // TODO: does this need to be adjusted for long exposure requests?
+ static const nsecs_t kRequestSubmitTimeout = 200e6; // 200 ms
+
// Used to prepare a batch of requests.
struct NextRequest {
sp<CaptureRequest> captureRequest;
@@ -936,6 +959,7 @@
Mutex mRequestLock;
Condition mRequestSignal;
+ Condition mRequestSubmittedSignal;
RequestList mRequestQueue;
RequestList mRepeatingRequests;
// The next batch of requests being prepped for submission to the HAL, no longer
@@ -958,6 +982,7 @@
sp<CaptureRequest> mPrevRequest;
int32_t mPrevTriggers;
+ std::set<std::string> mPrevCameraIdsWithZoom;
uint32_t mFrameNumber;
@@ -1056,6 +1081,9 @@
// Indicates a ZSL capture request
bool zslCapture;
+ // Requested camera ids (both logical and physical) with zoomRatio != 1.0f
+ std::set<std::string> cameraIdsWithZoom;
+
// What shared surfaces an output should go to
SurfaceMap outputSurfaces;
@@ -1077,7 +1105,7 @@
InFlightRequest(int numBuffers, CaptureResultExtras extras, bool hasInput,
bool hasAppCallback, nsecs_t maxDuration,
const std::set<String8>& physicalCameraIdSet, bool isStillCapture,
- bool isZslCapture,
+ bool isZslCapture, const std::set<std::string>& idsWithZoom,
const SurfaceMap& outSurfaces = SurfaceMap{}) :
shutterTimestamp(0),
sensorTimestamp(0),
@@ -1092,6 +1120,7 @@
physicalCameraIds(physicalCameraIdSet),
stillCapture(isStillCapture),
zslCapture(isZslCapture),
+ cameraIdsWithZoom(idsWithZoom),
outputSurfaces(outSurfaces) {
}
};
@@ -1109,7 +1138,7 @@
status_t registerInFlight(uint32_t frameNumber,
int32_t numBuffers, CaptureResultExtras resultExtras, bool hasInput,
bool callback, nsecs_t maxExpectedDuration, std::set<String8>& physicalCameraIds,
- bool isStillCapture, bool isZslCapture,
+ bool isStillCapture, bool isZslCapture, const std::set<std::string>& cameraIdsWithZoom,
const SurfaceMap& outputSurfaces);
/**
@@ -1234,6 +1263,7 @@
CaptureResultExtras &resultExtras,
CameraMetadata &collectedPartialResult, uint32_t frameNumber,
bool reprocess, bool zslStillCapture,
+ const std::set<std::string>& cameraIdsWithZoom,
const std::vector<PhysicalCaptureResultInfo>& physicalMetadatas);
bool isLastFullResult(const InFlightRequest& inFlightRequest);
@@ -1266,6 +1296,11 @@
// logical camera and its physical subcameras.
std::unordered_map<std::string, camera3::DistortionMapper> mDistortionMappers;
+ /**
+ * Zoom ratio mapper support
+ */
+ std::unordered_map<std::string, camera3::ZoomRatioMapper> mZoomRatioMappers;
+
// Debug tracker for metadata tag value changes
// - Enabled with the -m <taglist> option to dumpsys, such as
// dumpsys -m android.control.aeState,android.control.aeMode
@@ -1364,6 +1399,9 @@
// Fix up result metadata for monochrome camera.
bool mNeedFixupMonochromeTags;
status_t fixupMonochromeTags(const CameraMetadata& deviceInfo, CameraMetadata& resultMetadata);
+
+ // Whether HAL supports offline processing capability.
+ bool mSupportOfflineProcessing = false;
}; // class Camera3Device
}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
new file mode 100644
index 0000000..dc5cbb3
--- /dev/null
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.cpp
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Camera3-OffLnSsn"
+#define ATRACE_TAG ATRACE_TAG_CAMERA
+//#define LOG_NDEBUG 0
+//#define LOG_NNDEBUG 0 // Per-frame verbose logging
+
+#ifdef LOG_NNDEBUG
+#define ALOGVV(...) ALOGV(__VA_ARGS__)
+#else
+#define ALOGVV(...) ((void)0)
+#endif
+
+#include <inttypes.h>
+
+#include <utils/Trace.h>
+
+#include "device3/Camera3OfflineSession.h"
+#include "device3/Camera3OutputStream.h"
+#include "device3/Camera3InputStream.h"
+#include "device3/Camera3SharedOutputStream.h"
+
+using namespace android::camera3;
+using namespace android::hardware::camera;
+
+namespace android {
+
+Camera3OfflineSession::Camera3OfflineSession(const String8 &id):
+ mId(id)
+{
+ ATRACE_CALL();
+ ALOGV("%s: Created offline session for camera %s", __FUNCTION__, mId.string());
+}
+
+Camera3OfflineSession::~Camera3OfflineSession()
+{
+ ATRACE_CALL();
+ ALOGV("%s: Tearing down offline session for camera id %s", __FUNCTION__, mId.string());
+}
+
+const String8& Camera3OfflineSession::getId() const {
+ return mId;
+}
+
+status_t Camera3OfflineSession::initialize(
+ sp<hardware::camera::device::V3_6::ICameraOfflineSession> /*hidlSession*/) {
+ ATRACE_CALL();
+ return OK;
+}
+
+status_t Camera3OfflineSession::dump(int /*fd*/) {
+ ATRACE_CALL();
+ return OK;
+}
+
+status_t Camera3OfflineSession::abort() {
+ ATRACE_CALL();
+ return OK;
+}
+
+status_t Camera3OfflineSession::disconnect() {
+ ATRACE_CALL();
+ return OK;
+}
+
+status_t Camera3OfflineSession::waitForNextFrame(nsecs_t /*timeout*/) {
+ ATRACE_CALL();
+ return OK;
+}
+
+status_t Camera3OfflineSession::getNextResult(CaptureResult* /*frame*/) {
+ ATRACE_CALL();
+ return OK;
+}
+
+hardware::Return<void> Camera3OfflineSession::processCaptureResult_3_4(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::CaptureResult>& /*results*/) {
+ return hardware::Void();
+}
+
+hardware::Return<void> Camera3OfflineSession::processCaptureResult(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::CaptureResult>& /*results*/) {
+ return hardware::Void();
+}
+
+hardware::Return<void> Camera3OfflineSession::notify(
+ const hardware::hidl_vec<hardware::camera::device::V3_2::NotifyMsg>& /*msgs*/) {
+ return hardware::Void();
+}
+
+hardware::Return<void> Camera3OfflineSession::requestStreamBuffers(
+ const hardware::hidl_vec<hardware::camera::device::V3_5::BufferRequest>& /*bufReqs*/,
+ requestStreamBuffers_cb /*_hidl_cb*/) {
+ return hardware::Void();
+}
+
+hardware::Return<void> Camera3OfflineSession::returnStreamBuffers(
+ const hardware::hidl_vec<hardware::camera::device::V3_2::StreamBuffer>& /*buffers*/) {
+ return hardware::Void();
+}
+
+}; // namespace android
diff --git a/services/camera/libcameraservice/device3/Camera3OfflineSession.h b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
new file mode 100644
index 0000000..30e8c4f
--- /dev/null
+++ b/services/camera/libcameraservice/device3/Camera3OfflineSession.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_CAMERA3OFFLINESESSION_H
+#define ANDROID_SERVERS_CAMERA3OFFLINESESSION_H
+
+#include <utils/String8.h>
+#include <utils/String16.h>
+
+#include <android/hardware/camera/device/3.6/ICameraOfflineSession.h>
+#include <fmq/MessageQueue.h>
+
+#include "common/CameraOfflineSessionBase.h"
+
+#include "device3/Camera3BufferManager.h"
+#include "device3/DistortionMapper.h"
+#include "utils/TagMonitor.h"
+#include "utils/LatencyHistogram.h"
+#include <camera_metadata_hidden.h>
+
+namespace android {
+
+namespace camera3 {
+
+class Camera3Stream;
+class Camera3OutputStreamInterface;
+class Camera3StreamInterface;
+
+} // namespace camera3
+
+/**
+ * Camera3OfflineSession for offline session defined in HIDL ICameraOfflineSession@3.6 or higher
+ */
+class Camera3OfflineSession :
+ public CameraOfflineSessionBase,
+ virtual public hardware::camera::device::V3_5::ICameraDeviceCallback {
+
+ public:
+
+ // initialize by Camera3Device. Camera3Device must send all info in separate argument.
+ // monitored tags
+ // mUseHalBufManager
+ // mUsePartialResult
+ // mNumPartialResults
+ explicit Camera3OfflineSession(const String8& id);
+
+ virtual ~Camera3OfflineSession();
+
+ status_t initialize(
+ sp<hardware::camera::device::V3_6::ICameraOfflineSession> hidlSession);
+
+ /**
+ * CameraOfflineSessionBase interface
+ */
+ const String8& getId() const override;
+
+ status_t disconnect() override;
+
+ status_t dump(int fd) override;
+
+ status_t abort() override;
+
+ // methods for capture result passing
+ status_t waitForNextFrame(nsecs_t timeout) override;
+ status_t getNextResult(CaptureResult *frame) override;
+
+ // TODO: methods for notification (error/idle/finished etc) passing
+
+ /**
+ * End of CameraOfflineSessionBase interface
+ */
+
+ /**
+ * HIDL ICameraDeviceCallback interface
+ */
+
+ /**
+ * Implementation of android::hardware::camera::device::V3_5::ICameraDeviceCallback
+ */
+
+ hardware::Return<void> processCaptureResult_3_4(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_4::CaptureResult>& results) override;
+ hardware::Return<void> processCaptureResult(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::CaptureResult>& results) override;
+ hardware::Return<void> notify(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::NotifyMsg>& msgs) override;
+
+ hardware::Return<void> requestStreamBuffers(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_5::BufferRequest>& bufReqs,
+ requestStreamBuffers_cb _hidl_cb) override;
+
+ hardware::Return<void> returnStreamBuffers(
+ const hardware::hidl_vec<
+ hardware::camera::device::V3_2::StreamBuffer>& buffers) override;
+
+ /**
+ * End of CameraOfflineSessionBase interface
+ */
+
+ private:
+
+ // Camera device ID
+ const String8 mId;
+
+}; // class Camera3OfflineSession
+
+}; // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/device3/CoordinateMapper.cpp b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
new file mode 100644
index 0000000..d62f397
--- /dev/null
+++ b/services/camera/libcameraservice/device3/CoordinateMapper.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <system/camera_metadata_tags.h>
+
+#include "device3/CoordinateMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+/**
+ * Metadata keys to correct when adjusting coordinates for distortion correction
+ */
+
+// Both capture request and result
+constexpr std::array<uint32_t, 3> CoordinateMapper::kMeteringRegionsToCorrect = {
+ ANDROID_CONTROL_AF_REGIONS,
+ ANDROID_CONTROL_AE_REGIONS,
+ ANDROID_CONTROL_AWB_REGIONS
+};
+
+// Both capture request and result
+constexpr std::array<uint32_t, 1> CoordinateMapper::kRectsToCorrect = {
+ ANDROID_SCALER_CROP_REGION,
+};
+
+// Only for capture result
+constexpr std::array<uint32_t, 2> CoordinateMapper::kResultPointsToCorrectNoClamp = {
+ ANDROID_STATISTICS_FACE_RECTANGLES, // Says rectangles, is really points
+ ANDROID_STATISTICS_FACE_LANDMARKS,
+};
+
+} // namespace camera3
+
+} // namespace android
diff --git a/services/camera/libcameraservice/device3/CoordinateMapper.h b/services/camera/libcameraservice/device3/CoordinateMapper.h
new file mode 100644
index 0000000..5164856
--- /dev/null
+++ b/services/camera/libcameraservice/device3/CoordinateMapper.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_COORDINATEMAPPER_H
+#define ANDROID_SERVERS_COORDINATEMAPPER_H
+
+#include <array>
+
+namespace android {
+
+namespace camera3 {
+
+class CoordinateMapper {
+ // Right now only stores metadata tags containing 2D coordinates
+ // to be corrected.
+protected:
+ // Metadata key lists to correct
+
+ // Both capture request and result
+ static const std::array<uint32_t, 3> kMeteringRegionsToCorrect;
+
+ // Both capture request and result
+ static const std::array<uint32_t, 1> kRectsToCorrect;
+
+ // Only for capture results; don't clamp
+ static const std::array<uint32_t, 2> kResultPointsToCorrectNoClamp;
+}; // class CoordinateMapper
+
+} // namespace camera3
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/device3/DistortionMapper.cpp b/services/camera/libcameraservice/device3/DistortionMapper.cpp
index ae7af8e..8132225 100644
--- a/services/camera/libcameraservice/device3/DistortionMapper.cpp
+++ b/services/camera/libcameraservice/device3/DistortionMapper.cpp
@@ -27,41 +27,14 @@
namespace camera3 {
-/**
- * Metadata keys to correct when adjusting coordinates for distortion correction
- */
-
-// Both capture request and result
-constexpr std::array<uint32_t, 3> DistortionMapper::kMeteringRegionsToCorrect = {
- ANDROID_CONTROL_AF_REGIONS,
- ANDROID_CONTROL_AE_REGIONS,
- ANDROID_CONTROL_AWB_REGIONS
-};
-
-// Only capture request
-constexpr std::array<uint32_t, 1> DistortionMapper::kRequestRectsToCorrect = {
- ANDROID_SCALER_CROP_REGION,
-};
-
-// Only for capture result
-constexpr std::array<uint32_t, 1> DistortionMapper::kResultRectsToCorrect = {
- ANDROID_SCALER_CROP_REGION,
-};
-
-// Only for capture result
-constexpr std::array<uint32_t, 2> DistortionMapper::kResultPointsToCorrectNoClamp = {
- ANDROID_STATISTICS_FACE_RECTANGLES, // Says rectangles, is really points
- ANDROID_STATISTICS_FACE_LANDMARKS,
-};
-
DistortionMapper::DistortionMapper() : mValidMapping(false), mValidGrids(false) {
}
-bool DistortionMapper::isDistortionSupported(const CameraMetadata &result) {
+bool DistortionMapper::isDistortionSupported(const CameraMetadata &deviceInfo) {
bool isDistortionCorrectionSupported = false;
camera_metadata_ro_entry_t distortionCorrectionModes =
- result.find(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES);
+ deviceInfo.find(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES);
for (size_t i = 0; i < distortionCorrectionModes.count; i++) {
if (distortionCorrectionModes.data.u8[i] !=
ANDROID_DISTORTION_CORRECTION_MODE_OFF) {
@@ -124,7 +97,7 @@
if (res != OK) return res;
}
}
- for (auto rect : kRequestRectsToCorrect) {
+ for (auto rect : kRectsToCorrect) {
e = request->find(rect);
res = mapCorrectedRectToRaw(e.data.i32, e.count / 4, /*clamp*/true);
if (res != OK) return res;
@@ -160,7 +133,7 @@
if (res != OK) return res;
}
}
- for (auto rect : kResultRectsToCorrect) {
+ for (auto rect : kRectsToCorrect) {
e = result->find(rect);
res = mapRawRectToCorrected(e.data.i32, e.count / 4, /*clamp*/true);
if (res != OK) return res;
@@ -390,7 +363,6 @@
return OK;
}
-
status_t DistortionMapper::mapCorrectedRectToRaw(int32_t *rects, int rectCount, bool clamp,
bool simple) const {
if (!mValidMapping) return INVALID_OPERATION;
diff --git a/services/camera/libcameraservice/device3/DistortionMapper.h b/services/camera/libcameraservice/device3/DistortionMapper.h
index 4c0a1a6..a255003 100644
--- a/services/camera/libcameraservice/device3/DistortionMapper.h
+++ b/services/camera/libcameraservice/device3/DistortionMapper.h
@@ -22,6 +22,7 @@
#include <mutex>
#include "camera/CameraMetadata.h"
+#include "device3/CoordinateMapper.h"
namespace android {
@@ -31,7 +32,7 @@
* Utilities to transform between raw (distorted) and warped (corrected) coordinate systems
* for cameras that support geometric distortion
*/
-class DistortionMapper {
+class DistortionMapper : private CoordinateMapper {
public:
DistortionMapper();
@@ -150,20 +151,6 @@
// Fuzziness for float inequality tests
constexpr static float kFloatFuzz = 1e-4;
- // Metadata key lists to correct
-
- // Both capture request and result
- static const std::array<uint32_t, 3> kMeteringRegionsToCorrect;
-
- // Only capture request
- static const std::array<uint32_t, 1> kRequestRectsToCorrect;
-
- // Only capture result
- static const std::array<uint32_t, 1> kResultRectsToCorrect;
-
- // Only for capture results; don't clamp
- static const std::array<uint32_t, 2> kResultPointsToCorrectNoClamp;
-
// Single implementation for various mapCorrectedToRaw methods
template<typename T>
status_t mapCorrectedToRawImpl(T* coordPairs, int coordCount, bool clamp, bool simple) const;
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
new file mode 100644
index 0000000..84da45a
--- /dev/null
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "Camera3-ZoomRatioMapper"
+//#define LOG_NDEBUG 0
+
+#include <algorithm>
+
+#include "device3/ZoomRatioMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+
+status_t ZoomRatioMapper::initZoomRatioInTemplate(CameraMetadata *request) {
+ camera_metadata_entry_t entry;
+ entry = request->find(ANDROID_CONTROL_ZOOM_RATIO);
+ float defaultZoomRatio = 1.0f;
+ if (entry.count == 0) {
+ return request->update(ANDROID_CONTROL_ZOOM_RATIO, &defaultZoomRatio, 1);
+ }
+ return OK;
+}
+
+status_t ZoomRatioMapper::overrideZoomRatioTags(
+ CameraMetadata* deviceInfo, bool* supportNativeZoomRatio) {
+ if (deviceInfo == nullptr || supportNativeZoomRatio == nullptr) {
+ return BAD_VALUE;
+ }
+
+ camera_metadata_entry_t entry;
+ entry = deviceInfo->find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ if (entry.count != 2 && entry.count != 0) return BAD_VALUE;
+
+ // Hal has zoom ratio support
+ if (entry.count == 2) {
+ *supportNativeZoomRatio = true;
+ return OK;
+ }
+
+ // Hal has no zoom ratio support
+ *supportNativeZoomRatio = false;
+
+ entry = deviceInfo->find(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
+ if (entry.count != 1) {
+ ALOGI("%s: Camera device doesn't support SCALER_AVAILABLE_MAX_DIGITAL_ZOOM key!",
+ __FUNCTION__);
+ return OK;
+ }
+
+ float zoomRange[] = {1.0f, entry.data.f[0]};
+ status_t res = deviceInfo->update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, zoomRange, 2);
+ if (res != OK) {
+ ALOGE("%s: Failed to update CONTROL_ZOOM_RATIO_RANGE key: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> requestKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
+ if (entry.count > 0) {
+ requestKeys.insert(requestKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ requestKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS,
+ requestKeys.data(), requestKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_REQUEST_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> resultKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
+ if (entry.count > 0) {
+ resultKeys.insert(resultKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ resultKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS,
+ resultKeys.data(), resultKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_RESULT_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ std::vector<int32_t> charKeys;
+ entry = deviceInfo->find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+ if (entry.count > 0) {
+ charKeys.insert(charKeys.end(), entry.data.i32, entry.data.i32 + entry.count);
+ }
+ charKeys.push_back(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ res = deviceInfo->update(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS,
+ charKeys.data(), charKeys.size());
+ if (res != OK) {
+ ALOGE("%s: Failed to update REQUEST_AVAILABLE_CHARACTERISTICS_KEYS: %s (%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ return OK;
+}
+
+ZoomRatioMapper::ZoomRatioMapper(const CameraMetadata* deviceInfo,
+ bool supportNativeZoomRatio, bool usePrecorrectArray) {
+ camera_metadata_ro_entry_t entry;
+
+ entry = deviceInfo->find(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE);
+ if (entry.count != 4) return;
+ int32_t arrayW = entry.data.i32[2];
+ int32_t arrayH = entry.data.i32[3];
+
+ entry = deviceInfo->find(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+ if (entry.count != 4) return;
+ int32_t activeW = entry.data.i32[2];
+ int32_t activeH = entry.data.i32[3];
+
+ if (usePrecorrectArray) {
+ mArrayWidth = arrayW;
+ mArrayHeight = arrayH;
+ } else {
+ mArrayWidth = activeW;
+ mArrayHeight = activeH;
+ }
+ mHalSupportsZoomRatio = supportNativeZoomRatio;
+
+ ALOGV("%s: array size: %d x %d, mHalSupportsZoomRatio %d",
+ __FUNCTION__, mArrayWidth, mArrayHeight, mHalSupportsZoomRatio);
+ mIsValid = true;
+}
+
+status_t ZoomRatioMapper::updateCaptureRequest(CameraMetadata* request) {
+ if (!mIsValid) return INVALID_OPERATION;
+
+ status_t res = OK;
+ bool zoomRatioIs1 = true;
+ camera_metadata_entry_t entry;
+
+ entry = request->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1 && entry.data.f[0] != 1.0f) {
+ zoomRatioIs1 = false;
+ }
+
+ if (mHalSupportsZoomRatio && zoomRatioIs1) {
+ res = separateZoomFromCropLocked(request, false/*isResult*/);
+ } else if (!mHalSupportsZoomRatio && !zoomRatioIs1) {
+ res = combineZoomAndCropLocked(request, false/*isResult*/);
+ }
+
+ // If CONTROL_ZOOM_RATIO is in request, but HAL doesn't support
+ // CONTROL_ZOOM_RATIO, remove it from the request.
+ if (!mHalSupportsZoomRatio && entry.count == 1) {
+ request->erase(ANDROID_CONTROL_ZOOM_RATIO);
+ }
+
+ return res;
+}
+
+status_t ZoomRatioMapper::updateCaptureResult(CameraMetadata* result, bool requestedZoomRatioIs1) {
+ if (!mIsValid) return INVALID_OPERATION;
+
+ status_t res = OK;
+
+ if (mHalSupportsZoomRatio && requestedZoomRatioIs1) {
+ res = combineZoomAndCropLocked(result, true/*isResult*/);
+ } else if (!mHalSupportsZoomRatio && !requestedZoomRatioIs1) {
+ res = separateZoomFromCropLocked(result, true/*isResult*/);
+ } else {
+ camera_metadata_entry_t entry = result->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 0) {
+ float zoomRatio1x = 1.0f;
+ result->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio1x, 1);
+ }
+ }
+
+ return res;
+}
+
+float ZoomRatioMapper::deriveZoomRatio(const CameraMetadata* metadata) {
+ float zoomRatio = 1.0;
+
+ camera_metadata_ro_entry_t entry;
+ entry = metadata->find(ANDROID_SCALER_CROP_REGION);
+ if (entry.count != 4) return zoomRatio;
+
+ // Center of the preCorrection/active size
+ float arrayCenterX = mArrayWidth / 2.0;
+ float arrayCenterY = mArrayHeight / 2.0;
+
+ // Re-map crop region to coordinate system centered to (arrayCenterX,
+ // arrayCenterY).
+ float cropRegionLeft = arrayCenterX - entry.data.i32[0] ;
+ float cropRegionTop = arrayCenterY - entry.data.i32[1];
+ float cropRegionRight = entry.data.i32[0] + entry.data.i32[2] - arrayCenterX;
+ float cropRegionBottom = entry.data.i32[1] + entry.data.i32[3] - arrayCenterY;
+
+ // Calculate the scaling factor for left, top, bottom, right
+ float zoomRatioLeft = std::max(mArrayWidth / (2 * cropRegionLeft), 1.0f);
+ float zoomRatioTop = std::max(mArrayHeight / (2 * cropRegionTop), 1.0f);
+ float zoomRatioRight = std::max(mArrayWidth / (2 * cropRegionRight), 1.0f);
+ float zoomRatioBottom = std::max(mArrayHeight / (2 * cropRegionBottom), 1.0f);
+
+ // Use minimum scaling factor to handle letterboxing or pillarboxing
+ zoomRatio = std::min(std::min(zoomRatioLeft, zoomRatioRight),
+ std::min(zoomRatioTop, zoomRatioBottom));
+
+ ALOGV("%s: derived zoomRatio is %f", __FUNCTION__, zoomRatio);
+ return zoomRatio;
+}
+
+status_t ZoomRatioMapper::separateZoomFromCropLocked(CameraMetadata* metadata, bool isResult) {
+ status_t res;
+ float zoomRatio = deriveZoomRatio(metadata);
+
+ // Update zoomRatio metadata tag
+ res = metadata->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ if (res != OK) {
+ ALOGE("%s: Failed to update ANDROID_CONTROL_ZOOM_RATIO: %s(%d)",
+ __FUNCTION__, strerror(-res), res);
+ return res;
+ }
+
+ // Scale regions using zoomRatio
+ camera_metadata_entry_t entry;
+ for (auto region : kMeteringRegionsToCorrect) {
+ entry = metadata->find(region);
+ for (size_t j = 0; j < entry.count; j += 5) {
+ int32_t weight = entry.data.i32[j + 4];
+ if (weight == 0) {
+ continue;
+ }
+ // Top-left is inclusively clamped
+ scaleCoordinates(entry.data.i32 + j, 1, zoomRatio, ClampInclusive);
+ // Bottom-right is exclusively clamped
+ scaleCoordinates(entry.data.i32 + j + 2, 1, zoomRatio, ClampExclusive);
+ }
+ }
+
+ for (auto rect : kRectsToCorrect) {
+ entry = metadata->find(rect);
+ scaleRects(entry.data.i32, entry.count / 4, zoomRatio);
+ }
+
+ if (isResult) {
+ for (auto pts : kResultPointsToCorrectNoClamp) {
+ entry = metadata->find(pts);
+ scaleCoordinates(entry.data.i32, entry.count / 2, zoomRatio, ClampOff);
+ }
+ }
+
+ return OK;
+}
+
+status_t ZoomRatioMapper::combineZoomAndCropLocked(CameraMetadata* metadata, bool isResult) {
+ float zoomRatio = 1.0f;
+ camera_metadata_entry_t entry;
+ entry = metadata->find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1) {
+ zoomRatio = entry.data.f[0];
+ }
+
+ // Unscale regions with zoomRatio
+ status_t res;
+ for (auto region : kMeteringRegionsToCorrect) {
+ entry = metadata->find(region);
+ for (size_t j = 0; j < entry.count; j += 5) {
+ int32_t weight = entry.data.i32[j + 4];
+ if (weight == 0) {
+ continue;
+ }
+ // Top-left is inclusively clamped
+ scaleCoordinates(entry.data.i32 + j, 1, 1.0 / zoomRatio, ClampInclusive);
+ // Bottom-right is exclusively clamped
+ scaleCoordinates(entry.data.i32 + j + 2, 1, 1.0 / zoomRatio, ClampExclusive);
+ }
+ }
+ for (auto rect : kRectsToCorrect) {
+ entry = metadata->find(rect);
+ scaleRects(entry.data.i32, entry.count / 4, 1.0 / zoomRatio);
+ }
+ if (isResult) {
+ for (auto pts : kResultPointsToCorrectNoClamp) {
+ entry = metadata->find(pts);
+ scaleCoordinates(entry.data.i32, entry.count / 2, 1.0 / zoomRatio, ClampOff);
+ }
+ }
+
+ zoomRatio = 1.0;
+ res = metadata->update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ if (res != OK) {
+ return res;
+ }
+
+ return OK;
+}
+
+void ZoomRatioMapper::scaleCoordinates(int32_t* coordPairs, int coordCount,
+ float scaleRatio, ClampMode clamp) {
+ for (int i = 0; i < coordCount * 2; i += 2) {
+ float x = coordPairs[i];
+ float y = coordPairs[i + 1];
+ float xCentered = x - mArrayWidth / 2;
+ float yCentered = y - mArrayHeight / 2;
+ float scaledX = xCentered * scaleRatio;
+ float scaledY = yCentered * scaleRatio;
+ scaledX += mArrayWidth / 2;
+ scaledY += mArrayHeight / 2;
+ // Clamp to within activeArray/preCorrectionActiveArray
+ coordPairs[i] = static_cast<int32_t>(scaledX);
+ coordPairs[i+1] = static_cast<int32_t>(scaledY);
+ if (clamp != ClampOff) {
+ int32_t right, bottom;
+ if (clamp == ClampInclusive) {
+ right = mArrayWidth - 1;
+ bottom = mArrayHeight - 1;
+ } else {
+ right = mArrayWidth;
+ bottom = mArrayHeight;
+ }
+ coordPairs[i] =
+ std::min(right, std::max(0, coordPairs[i]));
+ coordPairs[i+1] =
+ std::min(bottom, std::max(0, coordPairs[i+1]));
+ }
+ ALOGV("%s: coordinates: %d, %d", __FUNCTION__, coordPairs[i], coordPairs[i+1]);
+ }
+}
+
+void ZoomRatioMapper::scaleRects(int32_t* rects, int rectCount,
+ float scaleRatio) {
+ for (int i = 0; i < rectCount * 4; i += 4) {
+ // Map from (l, t, width, height) to (l, t, r, b).
+ // [l, t] is inclusive, and [r, b] is exclusive.
+ int32_t coords[4] = {
+ rects[i],
+ rects[i + 1],
+ rects[i] + rects[i + 2],
+ rects[i + 1] + rects[i + 3]
+ };
+
+ // top-left
+ scaleCoordinates(coords, 1, scaleRatio, ClampInclusive);
+ // bottom-right
+ scaleCoordinates(coords+2, 1, scaleRatio, ClampExclusive);
+
+ // Map back to (l, t, width, height)
+ rects[i] = coords[0];
+ rects[i + 1] = coords[1];
+ rects[i + 2] = coords[2] - coords[0];
+ rects[i + 3] = coords[3] - coords[1];
+ }
+}
+
+} // namespace camera3
+
+} // namespace android
diff --git a/services/camera/libcameraservice/device3/ZoomRatioMapper.h b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
new file mode 100644
index 0000000..16b223b
--- /dev/null
+++ b/services/camera/libcameraservice/device3/ZoomRatioMapper.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_SERVERS_ZOOMRATIOMAPPER_H
+#define ANDROID_SERVERS_ZOOMRATIOMAPPER_H
+
+#include <utils/Errors.h>
+#include <array>
+
+#include "camera/CameraMetadata.h"
+#include "device3/CoordinateMapper.h"
+
+namespace android {
+
+namespace camera3 {
+
+/**
+ * Utilities to convert between the new zoomRatio and existing cropRegion
+ * metadata tags. Note that this class does conversions in 2 scenarios:
+ * - HAL supports zoomRatio and the application uses cropRegion, or
+ * - HAL doesn't support zoomRatio, but the application uses zoomRatio
+ */
+class ZoomRatioMapper : private CoordinateMapper {
+ public:
+ ZoomRatioMapper() = default;
+ ZoomRatioMapper(const CameraMetadata *deviceInfo,
+ bool supportNativeZoomRatio, bool usePrecorrectArray);
+ ZoomRatioMapper(const ZoomRatioMapper& other) :
+ mHalSupportsZoomRatio(other.mHalSupportsZoomRatio),
+ mArrayWidth(other.mArrayWidth), mArrayHeight(other.mArrayHeight) {}
+
+ /**
+ * Initialize request template with valid zoomRatio if necessary.
+ */
+ static status_t initZoomRatioInTemplate(CameraMetadata *request);
+
+ /**
+ * Override zoomRatio related tags in the static metadata.
+ */
+ static status_t overrideZoomRatioTags(
+ CameraMetadata* deviceInfo, bool* supportNativeZoomRatio);
+
+ /**
+ * Update capture request to handle both cropRegion and zoomRatio.
+ */
+ status_t updateCaptureRequest(CameraMetadata *request);
+
+ /**
+ * Update capture result to handle both cropRegion and zoomRatio.
+ */
+ status_t updateCaptureResult(CameraMetadata *request, bool requestedZoomRatioIs1);
+
+ public: // Visible for testing. Do not use concurently.
+ enum ClampMode {
+ ClampOff,
+ ClampInclusive,
+ ClampExclusive,
+ };
+
+ void scaleCoordinates(int32_t* coordPairs, int coordCount,
+ float scaleRatio, ClampMode clamp);
+
+ bool isValid() { return mIsValid; }
+ private:
+ // const after construction
+ bool mHalSupportsZoomRatio;
+ // active array / pre-correction array dimension
+ int32_t mArrayWidth, mArrayHeight;
+
+ bool mIsValid = false;
+
+ float deriveZoomRatio(const CameraMetadata* metadata);
+ void scaleRects(int32_t* rects, int rectCount, float scaleRatio);
+
+ status_t separateZoomFromCropLocked(CameraMetadata* metadata, bool isResult);
+ status_t combineZoomAndCropLocked(CameraMetadata* metadata, bool isResult);
+};
+
+} // namespace camera3
+
+} // namespace android
+
+#endif
diff --git a/services/camera/libcameraservice/tests/DepthProcessorTest.cpp b/services/camera/libcameraservice/tests/DepthProcessorTest.cpp
index 2162514..673c149 100644
--- a/services/camera/libcameraservice/tests/DepthProcessorTest.cpp
+++ b/services/camera/libcameraservice/tests/DepthProcessorTest.cpp
@@ -20,7 +20,6 @@
#include <array>
#include <random>
-#include <dlfcn.h>
#include <gtest/gtest.h>
#include "../common/DepthPhotoProcessor.h"
@@ -36,19 +35,6 @@
static const size_t kTestBufferDepthSize (kTestBufferWidth * kTestBufferHeight);
static const size_t kSeed = 1234;
-void linkToDepthPhotoLibrary(void **libHandle /*out*/,
- process_depth_photo_frame *processFrameFunc /*out*/) {
- ASSERT_NE(libHandle, nullptr);
- ASSERT_NE(processFrameFunc, nullptr);
-
- *libHandle = dlopen(kDepthPhotoLibrary, RTLD_NOW | RTLD_LOCAL);
- if (*libHandle != nullptr) {
- *processFrameFunc = reinterpret_cast<camera3::process_depth_photo_frame> (
- dlsym(*libHandle, kDepthPhotoProcessFunction));
- ASSERT_NE(*processFrameFunc, nullptr);
- }
-}
-
void generateColorJpegBuffer(int jpegQuality, ExifOrientation orientationValue, bool includeExif,
bool switchDimensions, std::vector<uint8_t> *colorJpegBuffer /*out*/) {
ASSERT_NE(colorJpegBuffer, nullptr);
@@ -91,26 +77,9 @@
}
}
-TEST(DepthProcessorTest, LinkToLibray) {
- void *libHandle;
- process_depth_photo_frame processFunc;
- linkToDepthPhotoLibrary(&libHandle, &processFunc);
- if (libHandle != nullptr) {
- dlclose(libHandle);
- }
-}
-
TEST(DepthProcessorTest, BadInput) {
- void *libHandle;
int jpegQuality = 95;
- process_depth_photo_frame processFunc;
- linkToDepthPhotoLibrary(&libHandle, &processFunc);
- if (libHandle == nullptr) {
- // Depth library no present, nothing more to test.
- return;
- }
-
DepthPhotoInputFrame inputFrame;
// Worst case both depth and confidence maps have the same size as the main color image.
inputFrame.mMaxJpegSize = inputFrame.mMainJpegSize * 3;
@@ -128,37 +97,27 @@
inputFrame.mMainJpegWidth = kTestBufferWidth;
inputFrame.mMainJpegHeight = kTestBufferHeight;
inputFrame.mJpegQuality = jpegQuality;
- ASSERT_NE(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
+ ASSERT_NE(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
&actualDepthPhotoSize), 0);
inputFrame.mMainJpegBuffer = reinterpret_cast<const char*> (colorJpegBuffer.data());
inputFrame.mMainJpegSize = colorJpegBuffer.size();
- ASSERT_NE(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
+ ASSERT_NE(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
&actualDepthPhotoSize), 0);
inputFrame.mDepthMapBuffer = depth16Buffer.data();
inputFrame.mDepthMapWidth = inputFrame.mDepthMapStride = kTestBufferWidth;
inputFrame.mDepthMapHeight = kTestBufferHeight;
- ASSERT_NE(processFunc(inputFrame, depthPhotoBuffer.size(), nullptr,
+ ASSERT_NE(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(), nullptr,
&actualDepthPhotoSize), 0);
- ASSERT_NE(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(), nullptr),
- 0);
-
- dlclose(libHandle);
+ ASSERT_NE(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
+ nullptr), 0);
}
TEST(DepthProcessorTest, BasicDepthPhotoValidation) {
- void *libHandle;
int jpegQuality = 95;
- process_depth_photo_frame processFunc;
- linkToDepthPhotoLibrary(&libHandle, &processFunc);
- if (libHandle == nullptr) {
- // Depth library no present, nothing more to test.
- return;
- }
-
std::vector<uint8_t> colorJpegBuffer;
generateColorJpegBuffer(jpegQuality, ExifOrientation::ORIENTATION_UNDEFINED,
/*includeExif*/ false, /*switchDimensions*/ false, &colorJpegBuffer);
@@ -180,7 +139,7 @@
std::vector<uint8_t> depthPhotoBuffer(inputFrame.mMaxJpegSize);
size_t actualDepthPhotoSize = 0;
- ASSERT_EQ(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
+ ASSERT_EQ(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
&actualDepthPhotoSize), 0);
ASSERT_TRUE((actualDepthPhotoSize > 0) && (depthPhotoBuffer.size() >= actualDepthPhotoSize));
@@ -196,21 +155,11 @@
ASSERT_EQ(NV12Compressor::findJpegSize(depthPhotoBuffer.data() + mainJpegSize,
actualDepthPhotoSize - mainJpegSize, &depthMapSize), OK);
ASSERT_TRUE((depthMapSize > 0) && (depthMapSize < (actualDepthPhotoSize - mainJpegSize)));
-
- dlclose(libHandle);
}
TEST(DepthProcessorTest, TestDepthPhotoExifOrientation) {
- void *libHandle;
int jpegQuality = 95;
- process_depth_photo_frame processFunc;
- linkToDepthPhotoLibrary(&libHandle, &processFunc);
- if (libHandle == nullptr) {
- // Depth library no present, nothing more to test.
- return;
- }
-
ExifOrientation exifOrientations[] = { ExifOrientation::ORIENTATION_UNDEFINED,
ExifOrientation::ORIENTATION_0_DEGREES, ExifOrientation::ORIENTATION_90_DEGREES,
ExifOrientation::ORIENTATION_180_DEGREES, ExifOrientation::ORIENTATION_270_DEGREES };
@@ -242,8 +191,8 @@
std::vector<uint8_t> depthPhotoBuffer(inputFrame.mMaxJpegSize);
size_t actualDepthPhotoSize = 0;
- ASSERT_EQ(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
- &actualDepthPhotoSize), 0);
+ ASSERT_EQ(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(),
+ depthPhotoBuffer.data(), &actualDepthPhotoSize), 0);
ASSERT_TRUE((actualDepthPhotoSize > 0) &&
(depthPhotoBuffer.size() >= actualDepthPhotoSize));
@@ -281,21 +230,11 @@
ASSERT_EQ(confidenceJpegExifOrientation, exifOrientation);
}
}
-
- dlclose(libHandle);
}
TEST(DepthProcessorTest, TestDephtPhotoPhysicalRotation) {
- void *libHandle;
int jpegQuality = 95;
- process_depth_photo_frame processFunc;
- linkToDepthPhotoLibrary(&libHandle, &processFunc);
- if (libHandle == nullptr) {
- // Depth library no present, nothing more to test.
- return;
- }
-
// In case of physical rotation, the EXIF orientation must always be 0.
auto exifOrientation = ExifOrientation::ORIENTATION_0_DEGREES;
DepthPhotoOrientation depthOrientations[] = {
@@ -339,8 +278,8 @@
std::vector<uint8_t> depthPhotoBuffer(inputFrame.mMaxJpegSize);
size_t actualDepthPhotoSize = 0;
- ASSERT_EQ(processFunc(inputFrame, depthPhotoBuffer.size(), depthPhotoBuffer.data(),
- &actualDepthPhotoSize), 0);
+ ASSERT_EQ(processDepthPhotoFrame(inputFrame, depthPhotoBuffer.size(),
+ depthPhotoBuffer.data(), &actualDepthPhotoSize), 0);
ASSERT_TRUE((actualDepthPhotoSize > 0) &&
(depthPhotoBuffer.size() >= actualDepthPhotoSize));
@@ -377,6 +316,4 @@
ASSERT_EQ(confidenceMapWidth, expectedWidth);
ASSERT_EQ(confidenceMapHeight, expectedHeight);
}
-
- dlclose(libHandle);
}
diff --git a/services/camera/libcameraservice/tests/ZoomRatioTest.cpp b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
new file mode 100644
index 0000000..300da09
--- /dev/null
+++ b/services/camera/libcameraservice/tests/ZoomRatioTest.cpp
@@ -0,0 +1,457 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_NDEBUG 0
+#define LOG_TAG "ZoomRatioMapperTest"
+
+#include <gtest/gtest.h>
+#include <utils/Errors.h>
+
+#include "../device3/ZoomRatioMapper.h"
+
+using namespace std;
+using namespace android;
+using namespace android::camera3;
+
+constexpr int kMaxAllowedPixelError = 1;
+constexpr float kMaxAllowedRatioError = 0.1;
+
+constexpr int32_t testActiveArraySize[] = {100, 100, 1024, 768};
+constexpr int32_t testPreCorrActiveArraySize[] = {90, 90, 1044, 788};
+
+constexpr int32_t testDefaultCropSize[][4] = {
+ {0, 0, 1024, 768}, // active array default crop
+ {0, 0, 1044, 788}, // preCorrection active array default crop
+};
+
+constexpr int32_t test2xCropRegion[][4] = {
+ {256, 192, 512, 384}, // active array 2x zoom crop
+ {261, 197, 522, 394}, // preCorrection active array default crop
+};
+
+constexpr int32_t testLetterBoxSize[][4] = {
+ {0, 96, 1024, 576}, // active array 2x zoom crop
+ {0, 106, 1024, 576}, // preCorrection active array default crop
+};
+
+status_t setupTestMapper(ZoomRatioMapper *m, float maxDigitalZoom,
+ const int32_t activeArray[4], const int32_t preCorrectArray[4],
+ bool hasZoomRatioRange, float zoomRatioRange[2],
+ bool usePreCorrectArray) {
+ CameraMetadata deviceInfo;
+
+ deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, activeArray, 4);
+ deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE, preCorrectArray, 4);
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, &maxDigitalZoom, 1);
+ if (hasZoomRatioRange) {
+ deviceInfo.update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, zoomRatioRange, 2);
+ }
+
+ bool supportNativeZoomRatio;
+ status_t res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ if (res != OK) {
+ return res;
+ }
+
+ *m = ZoomRatioMapper(&deviceInfo, hasZoomRatioRange, usePreCorrectArray);
+ return OK;
+}
+
+TEST(ZoomRatioTest, Initialization) {
+ CameraMetadata deviceInfo;
+ status_t res;
+ camera_metadata_entry_t entry;
+
+ deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
+ testPreCorrActiveArraySize, 4);
+ deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE, testActiveArraySize, 4);
+
+ // Test initialization from devices not supporting zoomRange
+ float maxDigitalZoom = 4.0f;
+ ZoomRatioMapper mapperNoZoomRange;
+ deviceInfo.update(ANDROID_SCALER_AVAILABLE_MAX_DIGITAL_ZOOM, &maxDigitalZoom, 1);
+ bool supportNativeZoomRatio;
+ res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ ASSERT_EQ(res, OK);
+ ASSERT_EQ(supportNativeZoomRatio, false);
+ mapperNoZoomRange = ZoomRatioMapper(&deviceInfo,
+ supportNativeZoomRatio, true/*usePreCorrectArray*/);
+ ASSERT_TRUE(mapperNoZoomRange.isValid());
+ mapperNoZoomRange = ZoomRatioMapper(&deviceInfo,
+ supportNativeZoomRatio, false/*usePreCorrectArray*/);
+ ASSERT_TRUE(mapperNoZoomRange.isValid());
+
+ entry = deviceInfo.find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ ASSERT_EQ(entry.count, 2U);
+ ASSERT_EQ(entry.data.f[0], 1.0);
+ ASSERT_EQ(entry.data.f[1], maxDigitalZoom);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_CHARACTERISTICS_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO_RANGE), entry.data.i32 + entry.count);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_REQUEST_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO), entry.data.i32 + entry.count);
+
+ entry = deviceInfo.find(ANDROID_REQUEST_AVAILABLE_RESULT_KEYS);
+ ASSERT_GT(entry.count, 0U);
+ ASSERT_NE(std::find(entry.data.i32, entry.data.i32 + entry.count,
+ ANDROID_CONTROL_ZOOM_RATIO), entry.data.i32 + entry.count);
+
+ // Test initialization from devices supporting zoomRange
+ float ratioRange[2] = {0.2f, maxDigitalZoom};
+ deviceInfo.update(ANDROID_CONTROL_ZOOM_RATIO_RANGE, ratioRange, 2);
+ res = ZoomRatioMapper::overrideZoomRatioTags(&deviceInfo, &supportNativeZoomRatio);
+ ASSERT_EQ(res, OK);
+ ASSERT_EQ(supportNativeZoomRatio, true);
+ ZoomRatioMapper mapperWithZoomRange;
+ mapperWithZoomRange = ZoomRatioMapper(&deviceInfo,
+ supportNativeZoomRatio, true/*usePreCorrectArray*/);
+ ASSERT_TRUE(mapperWithZoomRange.isValid());
+ mapperWithZoomRange = ZoomRatioMapper(&deviceInfo,
+ supportNativeZoomRatio, false/*usePreCorrectArray*/);
+ ASSERT_TRUE(mapperWithZoomRange.isValid());
+
+ entry = deviceInfo.find(ANDROID_CONTROL_ZOOM_RATIO_RANGE);
+ ASSERT_EQ(entry.count, 2U);
+ ASSERT_EQ(entry.data.f[0], ratioRange[0]);
+ ASSERT_EQ(entry.data.f[1], ratioRange[1]);
+
+ // Test default zoom ratio in template
+ CameraMetadata requestTemplate;
+ res = ZoomRatioMapper::initZoomRatioInTemplate(&requestTemplate);
+ ASSERT_EQ(res, OK);
+ entry = requestTemplate.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.count, 1U);
+ ASSERT_EQ(entry.data.f[0], 1.0f);
+
+ float customRatio = 0.5f;
+ res = requestTemplate.update(ANDROID_CONTROL_ZOOM_RATIO, &customRatio, 1);
+ ASSERT_EQ(res, OK);
+ res = ZoomRatioMapper::initZoomRatioInTemplate(&requestTemplate);
+ ASSERT_EQ(res, OK);
+ entry = requestTemplate.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.count, 1U);
+ ASSERT_EQ(entry.data.f[0], customRatio);
+}
+
+void subScaleCoordinatesTest(bool usePreCorrectArray) {
+ ZoomRatioMapper mapper;
+ float maxDigitalZoom = 4.0f;
+ float zoomRatioRange[2];
+ ASSERT_EQ(OK, setupTestMapper(&mapper, maxDigitalZoom,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray));
+
+ size_t index = 0;
+ int32_t width = testActiveArraySize[2];
+ int32_t height = testActiveArraySize[3];
+ if (usePreCorrectArray) {
+ index = 1;
+ width = testPreCorrActiveArraySize[2];
+ height = testPreCorrActiveArraySize[3];
+ }
+
+ std::array<int32_t, 16> originalCoords = {
+ 0, 0, // top-left
+ width, 0, // top-right
+ 0, height, // bottom-left
+ width, height, // bottom-right
+ width / 2, height / 2, // center
+ width / 4, height / 4, // top-left after 2x
+ width / 3, height * 2 / 3, // bottom-left after 3x zoom
+ width * 7 / 8, height / 2, // middle-right after 1.33x zoom
+ };
+
+ // Verify 1.0x zoom doesn't change the coordinates
+ auto coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_EQ(coords[i], originalCoords[i]);
+ }
+
+ // Verify 2.0x zoom work as expected (no clamping)
+ std::array<float, 16> expected2xCoords = {
+ - width / 2.0f, - height / 2.0f,// top-left
+ width * 3 / 2.0f, - height / 2.0f, // top-right
+ - width / 2.0f, height * 3 / 2.0f, // bottom-left
+ width * 3 / 2.0f, height * 3 / 2.0f, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f, // bottom-left after 3x zoom
+ width + width / 4.0f, height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoords[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 2.0x zoom work as expected (with inclusive clamping)
+ std::array<float, 16> expected2xCoordsClampedInc = {
+ 0, 0, // top-left
+ static_cast<float>(width) - 1, 0, // top-right
+ 0, static_cast<float>(height) - 1, // bottom-left
+ static_cast<float>(width) - 1, static_cast<float>(height) - 1, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
+ static_cast<float>(width) - 1, height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampInclusive);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedInc[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 2.0x zoom work as expected (with exclusive clamping)
+ std::array<float, 16> expected2xCoordsClampedExc = {
+ 0, 0, // top-left
+ static_cast<float>(width), 0, // top-right
+ 0, static_cast<float>(height), // bottom-left
+ static_cast<float>(width), static_cast<float>(height), // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ 0, 0, // top-left after 2x
+ width / 6.0f, height - height / 6.0f , // bottom-left after 3x zoom
+ static_cast<float>(width), height / 2.0f, // middle-right after 1.33x zoom
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 2.0f, ZoomRatioMapper::ClampExclusive);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expected2xCoordsClampedExc[i]), kMaxAllowedPixelError);
+ }
+
+ // Verify 0.33x zoom work as expected
+ std::array<float, 16> expectedZoomOutCoords = {
+ width / 3.0f, height / 3.0f, // top-left
+ width * 2 / 3.0f, height / 3.0f, // top-right
+ width / 3.0f, height * 2 / 3.0f, // bottom-left
+ width * 2 / 3.0f, height * 2 / 3.0f, // bottom-right
+ width / 2.0f, height / 2.0f, // center
+ width * 5 / 12.0f, height * 5 / 12.0f, // top-left after 2x
+ width * 4 / 9.0f, height * 5 / 9.0f, // bottom-left after 3x zoom-in
+ width * 5 / 8.0f, height / 2.0f, // middle-right after 1.33x zoom-in
+ };
+ coords = originalCoords;
+ mapper.scaleCoordinates(coords.data(), coords.size()/2, 1.0f/3, ZoomRatioMapper::ClampOff);
+ for (size_t i = 0; i < coords.size(); i++) {
+ EXPECT_LE(std::abs(coords[i] - expectedZoomOutCoords[i]), kMaxAllowedPixelError);
+ }
+}
+
+TEST(ZoomRatioTest, scaleCoordinatesTest) {
+ subScaleCoordinatesTest(false/*usePreCorrectArray*/);
+ subScaleCoordinatesTest(true/*usePreCorrectArray*/);
+}
+
+void subCropOverMaxDigitalZoomTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float noZoomRatioRange[2];
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, noZoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+
+ metadata.update(ANDROID_SCALER_CROP_REGION, test2xCropRegion[index], 4);
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_TRUE(entry.count == 0 || (entry.count == 1 && entry.data.f[0] == 1.0f));
+}
+
+TEST(ZoomRatioTest, CropOverMaxDigitalZoomTest) {
+ subCropOverMaxDigitalZoomTest(false/*usePreCorrectArray*/);
+ subCropOverMaxDigitalZoomTest(true/*usePreCorrectArray*/);
+}
+
+void subCropOverZoomRangeTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float zoomRatioRange[2] = {0.5f, 4.0f};
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ true/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // 2x zoom crop region, zoomRatio is 1.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, test2xCropRegion[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 2.0f, kMaxAllowedRatioError);
+
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], test2xCropRegion[index][i]);
+ }
+
+ // Letter boxing crop region, zoomRatio is 1.0
+ float zoomRatio = 1.0f;
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ metadata.update(ANDROID_SCALER_CROP_REGION, testLetterBoxSize[index], 4);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testLetterBoxSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+
+ res = mapper.updateCaptureResult(&metadata, true/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i++) {
+ EXPECT_EQ(entry.data.i32[i], testLetterBoxSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+}
+
+TEST(ZoomRatioTest, CropOverZoomRangeTest) {
+ subCropOverZoomRangeTest(false/*usePreCorrectArray*/);
+ subCropOverZoomRangeTest(true/*usePreCorrectArray*/);
+}
+
+void subZoomOverMaxDigitalZoomTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float noZoomRatioRange[2];
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ false/*hasZoomRatioRange*/, noZoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ float zoomRatio = 3.0f;
+ camera_metadata_entry_t entry;
+
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // Full active array crop, zoomRatio is 3.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ std::array<float, 4> expectedCrop = {
+ testDefaultCropSize[index][2] / 3.0f, /*x*/
+ testDefaultCropSize[index][3] / 3.0f, /*y*/
+ testDefaultCropSize[index][2] / 3.0f, /*width*/
+ testDefaultCropSize[index][3] / 3.0f, /*height*/
+ };
+ for (int i = 0; i < 4; i++) {
+ EXPECT_LE(std::abs(entry.data.i32[i] - expectedCrop[i]), kMaxAllowedPixelError);
+ }
+
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ if (entry.count == 1) {
+ EXPECT_NEAR(entry.data.f[0], 1.0f, kMaxAllowedRatioError);
+ }
+}
+
+TEST(ZoomRatioTest, ZoomOverMaxDigitalZoomTest) {
+ subZoomOverMaxDigitalZoomTest(false/*usePreCorrectArray*/);
+ subZoomOverMaxDigitalZoomTest(true/*usePreCorrectArray*/);
+}
+
+void subZoomOverZoomRangeTest(bool usePreCorrectArray) {
+ status_t res;
+ ZoomRatioMapper mapper;
+ float zoomRatioRange[2] = {1.0f, 4.0f};
+ res = setupTestMapper(&mapper, 4.0/*maxDigitalZoom*/,
+ testActiveArraySize, testPreCorrActiveArraySize,
+ true/*hasZoomRatioRange*/, zoomRatioRange,
+ usePreCorrectArray);
+ ASSERT_EQ(res, OK);
+
+ CameraMetadata metadata;
+ float zoomRatio = 3.0f;
+ camera_metadata_entry_t entry;
+ size_t index = usePreCorrectArray ? 1 : 0;
+
+ // Full active array crop, zoomRatio is 3.0f
+ metadata.update(ANDROID_SCALER_CROP_REGION, testDefaultCropSize[index], 4);
+ metadata.update(ANDROID_CONTROL_ZOOM_RATIO, &zoomRatio, 1);
+ res = mapper.updateCaptureRequest(&metadata);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.data.f[0], zoomRatio);
+
+ res = mapper.updateCaptureResult(&metadata, false/*requestedZoomRatioIs1*/);
+ ASSERT_EQ(res, OK);
+ entry = metadata.find(ANDROID_SCALER_CROP_REGION);
+ ASSERT_EQ(entry.count, 4U);
+ for (int i = 0; i < 4; i ++) {
+ EXPECT_EQ(entry.data.i32[i], testDefaultCropSize[index][i]);
+ }
+ entry = metadata.find(ANDROID_CONTROL_ZOOM_RATIO);
+ ASSERT_EQ(entry.data.f[0], zoomRatio);
+}
+
+TEST(ZoomRatioTest, ZoomOverZoomRangeTest) {
+ subZoomOverZoomRangeTest(false/*usePreCorrectArray*/);
+ subZoomOverZoomRangeTest(true/*usePreCorrectArray*/);
+}
diff --git a/services/mediacodec/Android.bp b/services/mediacodec/Android.bp
index 3141c31..5811068 100644
--- a/services/mediacodec/Android.bp
+++ b/services/mediacodec/Android.bp
@@ -15,10 +15,6 @@
],
target: {
- vendor: {
- exclude_shared_libs: ["libavservices_minijail"],
- shared_libs: ["libavservices_minijail_vendor"],
- },
android: {
product_variables: {
malloc_not_svelte: {
diff --git a/services/mediacodec/Android.mk b/services/mediacodec/Android.mk
index 1cf0534..88a79e7 100644
--- a/services/mediacodec/Android.mk
+++ b/services/mediacodec/Android.mk
@@ -37,7 +37,7 @@
libutils \
liblog \
libbase \
- libavservices_minijail_vendor \
+ libavservices_minijail \
libcutils \
libhidlbase \
libstagefright_omx \
diff --git a/services/mediaextractor/MediaExtractorService.cpp b/services/mediaextractor/MediaExtractorService.cpp
index 9e814d1..a6cd224 100644
--- a/services/mediaextractor/MediaExtractorService.cpp
+++ b/services/mediaextractor/MediaExtractorService.cpp
@@ -61,13 +61,11 @@
}
::android::binder::Status MediaExtractorService::makeIDataSource(
- const base::unique_fd &fd,
+ base::unique_fd fd,
int64_t offset,
int64_t length,
::android::sp<::android::IDataSource>* _aidl_return) {
- // the caller will close the fd owned by the unique_fd upon return of this function,
- // so we need to dup() it to retain it.
- sp<DataSource> source = DataSourceFactory::getInstance()->CreateFromFd(dup(fd.get()), offset, length);
+ sp<DataSource> source = DataSourceFactory::getInstance()->CreateFromFd(fd.release(), offset, length);
*_aidl_return = CreateIDataSourceFromDataSource(source);
return binder::Status::ok();
}
diff --git a/services/mediaextractor/MediaExtractorService.h b/services/mediaextractor/MediaExtractorService.h
index 1b61c4b..1b40bf9 100644
--- a/services/mediaextractor/MediaExtractorService.h
+++ b/services/mediaextractor/MediaExtractorService.h
@@ -37,7 +37,7 @@
::android::sp<::android::IMediaExtractor>* _aidl_return);
virtual ::android::binder::Status makeIDataSource(
- const base::unique_fd &fd,
+ base::unique_fd fd,
int64_t offset,
int64_t length,
::android::sp<::android::IDataSource>* _aidl_return);
diff --git a/services/mediametrics/AnalyticsActions.h b/services/mediametrics/AnalyticsActions.h
new file mode 100644
index 0000000..5568c91
--- /dev/null
+++ b/services/mediametrics/AnalyticsActions.h
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <media/MediaMetricsItem.h>
+#include <mutex>
+
+namespace android::mediametrics {
+
+/**
+ * AnalyticsActions consists of a map of pairs <trigger, action> which
+ * are evaluated for a given incoming MediaMetrics item.
+ *
+ * A vector of Actions are returned from getActionsForItem() which
+ * should be executed outside of any locks.
+ *
+ * Mediametrics assumes weak consistency, which is fine as the analytics database
+ * is generally strictly increasing in size (until gc removes values that are
+ * supposedly no longer needed).
+ */
+
+class AnalyticsActions {
+public:
+
+ using Elem = mediametrics::Item::Prop::Elem;
+ /**
+ * Trigger: a pair consisting of
+ * std::string: A wildcard url specifying a property in the item,
+ * where '*' indicates 0 or more arbitrary characters
+ * for the item key match.
+ * Elem: A value that needs to match exactly.
+ *
+ * Trigger is used in a map sort; default less with std::string as primary key.
+ * The wildcard accepts a string with '*' as being 0 or more arbitrary
+ * characters for the item key match. A wildcard is preferred over general
+ * regexp for simple fast lookup.
+ *
+ * TODO: incorporate a regexp option.
+ */
+ using Trigger = std::pair<std::string, Elem>;
+
+ /**
+ * Function: The function to be executed.
+ */
+ using Function = std::function<
+ void(const std::shared_ptr<const mediametrics::Item>& item)>;
+
+ /**
+ * Action: An action to execute. This is a shared pointer to Function.
+ */
+ using Action = std::shared_ptr<Function>;
+
+ /**
+ * Adds a new action.
+ *
+ * \param url references a property in the item with wildcards
+ * \param value references a value (cast to Elem automatically)
+ * so be careful of the type. It must be one of
+ * the types acceptable to Elem.
+ * \param action is a function or lambda to execute if the url matches value
+ * in the item.
+ */
+ template <typename T, typename U, typename A>
+ void addAction(T&& url, U&& value, A&& action) {
+ std::lock_guard l(mLock);
+ mFilters[ { std::forward<T>(url), std::forward<U>(value) } ]
+ = std::forward<A>(action);
+ }
+
+ // TODO: remove an action.
+
+ /**
+ * Get all the actions triggered for a particular item.
+ *
+ * \param item to be analyzed for actions.
+ */
+ std::vector<Action>
+ getActionsForItem(const std::shared_ptr<const mediametrics::Item>& item) {
+ std::vector<Action> actions;
+ std::lock_guard l(mLock);
+
+ // Essentially the code looks like this:
+ /*
+ for (auto &[trigger, action] : mFilters) {
+ if (isMatch(trigger, item)) {
+ actions.push_back(action);
+ }
+ }
+ */
+
+ // Optimization: there should only be one match for a non-wildcard url.
+ auto it = mFilters.upper_bound( {item->getKey(), std::monostate{} });
+ if (it != mFilters.end()) {
+ const auto &[trigger, action] = *it;
+ if (isMatch(trigger, item)) {
+ actions.push_back(action);
+ }
+ }
+
+ // Optimization: for wildcard URLs we go backwards until there is no
+ // match with the prefix before the wildcard.
+ while (it != mFilters.begin()) { // this walks backwards, cannot start at begin.
+ const auto &[trigger, action] = *--it; // look backwards
+ int ret = isWildcardMatch(trigger, item);
+ if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_MATCH_FOUND) {
+ actions.push_back(action); // match found.
+ } else if (ret == mediametrics::Item::RECURSIVE_WILDCARD_CHECK_NO_MATCH_NO_WILDCARD) {
+ break; // no match before wildcard.
+ }
+ // a wildcard was encountered when matching prefix, so we should check again.
+ }
+ return actions;
+ }
+
+private:
+
+ static inline bool isMatch(const Trigger& trigger,
+ const std::shared_ptr<const mediametrics::Item>& item) {
+ const auto& [key, elem] = trigger;
+ if (!startsWith(key, item->getKey())) return false;
+ // The trigger key is in format (item key).propName, so + 1 skips '.' delimeter.
+ const char *propName = key.c_str() + item->getKey().size() + 1;
+ return item->hasPropElem(propName, elem);
+ }
+
+ static inline int isWildcardMatch(const Trigger& trigger,
+ const std::shared_ptr<const mediametrics::Item>& item) {
+ const auto& [key, elem] = trigger;
+ return item->recursiveWildcardCheckElem(key.c_str(), elem);
+ }
+
+ mutable std::mutex mLock;
+ std::map<Trigger, Action> mFilters; // GUARDED_BY mLock
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/AnalyticsState.h b/services/mediametrics/AnalyticsState.h
new file mode 100644
index 0000000..4711bb6
--- /dev/null
+++ b/services/mediametrics/AnalyticsState.h
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include "TimeMachine.h"
+#include "TransactionLog.h"
+
+namespace android::mediametrics {
+
+/**
+ * AnalyticsState consists of a TimeMachine and TransactionLog for a set
+ * of MediaMetrics Items.
+ *
+ * One can add new Items with the submit() method.
+ *
+ * The AnalyticsState may be cleared or duplicated to preserve state after crashes
+ * in services are detected.
+ *
+ * As its members may not be moveable due to mutexes, we use this encapsulation
+ * with a shared pointer in order to save it or duplicate it.
+ */
+class AnalyticsState {
+public:
+ /**
+ * Returns success if AnalyticsState accepts the item.
+ *
+ * A trusted source can create a new key, an untrusted source
+ * can only modify the key if the uid will match that authorized
+ * on the existing key.
+ *
+ * \param item the item to be submitted.
+ * \param isTrusted whether the transaction comes from a trusted source.
+ * In this case, a trusted source is verified by binder
+ * UID to be a system service by MediaMetrics service.
+ * Do not use true if you haven't really checked!
+ *
+ * \return NO_ERROR on success or
+ * PERMISSION_DENIED if the item cannot be put into the AnalyticsState.
+ */
+ status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted) {
+ return mTimeMachine.put(item, isTrusted) ?: mTransactionLog.put(item);
+ }
+
+ /**
+ * Returns a pair consisting of the dump string, and the number of lines in the string.
+ *
+ * The number of lines in the returned pair is used as an optimization
+ * for subsequent line limiting.
+ *
+ * The TimeMachine and the TransactionLog are dumped separately under
+ * different locks, so may not be 100% consistent with the last data
+ * delivered.
+ *
+ * \param lines the maximum number of lines in the string returned.
+ */
+ std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const {
+ std::stringstream ss;
+ int32_t ll = lines;
+
+ if (ll > 0) {
+ ss << "TransactionLog:\n";
+ --ll;
+ }
+ if (ll > 0) {
+ auto [s, l] = mTransactionLog.dump(ll);
+ ss << s;
+ ll -= l;
+ }
+ if (ll > 0) {
+ ss << "TimeMachine:\n";
+ --ll;
+ }
+ if (ll > 0) {
+ auto [s, l] = mTimeMachine.dump(ll);
+ ss << s;
+ ll -= l;
+ }
+ return { ss.str(), lines - ll };
+ }
+
+ /**
+ * Clears the AnalyticsState.
+ */
+ void clear() {
+ mTimeMachine.clear();
+ mTransactionLog.clear();
+ }
+
+private:
+ // Note: TimeMachine and TransactionLog are individually locked.
+ // Access to these objects under multiple threads will be weakly synchronized,
+ // which is acceptable as modifications only increase the history (or with GC,
+ // eliminates very old history).
+
+ TimeMachine mTimeMachine;
+ TransactionLog mTransactionLog;
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/Android.bp b/services/mediametrics/Android.bp
index 0840f31..20f346c 100644
--- a/services/mediametrics/Android.bp
+++ b/services/mediametrics/Android.bp
@@ -12,6 +12,7 @@
"libbinder",
"liblog",
"libmediametricsservice",
+ "libmediautils",
"libutils",
],
@@ -52,6 +53,7 @@
"libbinder",
"liblog",
"libmediametrics",
+ "libmediautils",
"libprotobuf-cpp-lite",
"libstatslog",
"libutils",
diff --git a/services/mediametrics/AudioAnalytics.cpp b/services/mediametrics/AudioAnalytics.cpp
index 638c4ab..f7ee051 100644
--- a/services/mediametrics/AudioAnalytics.cpp
+++ b/services/mediametrics/AudioAnalytics.cpp
@@ -27,6 +27,22 @@
AudioAnalytics::AudioAnalytics()
{
ALOGD("%s", __func__);
+
+ // Add action to save AnalyticsState if audioserver is restarted.
+ // This triggers on an item of "audio.flinger"
+ // with a property "event" set to "AudioFlinger" (the constructor).
+ mActions.addAction(
+ "audio.flinger.event",
+ std::string("AudioFlinger"),
+ std::make_shared<AnalyticsActions::Function>(
+ [this](const std::shared_ptr<const android::mediametrics::Item> &){
+ ALOGW("Audioflinger() constructor event detected");
+ mPreviousAnalyticsState.set(std::make_shared<AnalyticsState>(
+ *mAnalyticsState.get()));
+ // Note: get returns shared_ptr temp, whose lifetime is extended
+ // to end of full expression.
+ mAnalyticsState->clear(); // TODO: filter the analytics state.
+ }));
}
AudioAnalytics::~AudioAnalytics()
@@ -35,13 +51,16 @@
}
status_t AudioAnalytics::submit(
- const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted)
+ const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted)
{
- if (startsWith(item->getKey(), "audio.")) {
- return mTimeMachine.put(item, isTrusted)
- ?: mTransactionLog.put(item);
- }
- return BAD_VALUE;
+ if (!startsWith(item->getKey(), "audio.")) return BAD_VALUE;
+ status_t status = mAnalyticsState->submit(item, isTrusted);
+ if (status != NO_ERROR) return status; // may not be permitted.
+
+ // Only if the item was successfully submitted (permission)
+ // do we check triggered actions.
+ checkActions(item);
+ return NO_ERROR;
}
std::pair<std::string, int32_t> AudioAnalytics::dump(int32_t lines) const
@@ -50,24 +69,29 @@
int32_t ll = lines;
if (ll > 0) {
- ss << "TransactionLog:\n";
- --ll;
- }
- if (ll > 0) {
- auto [s, l] = mTransactionLog.dump(ll);
+ auto [s, l] = mAnalyticsState->dump(ll);
ss << s;
ll -= l;
}
if (ll > 0) {
- ss << "TimeMachine:\n";
+ ss << "Prior audioserver state:\n";
--ll;
}
if (ll > 0) {
- auto [s, l] = mTimeMachine.dump(ll);
+ auto [s, l] = mPreviousAnalyticsState->dump(ll);
ss << s;
ll -= l;
}
return { ss.str(), lines - ll };
}
+void AudioAnalytics::checkActions(const std::shared_ptr<const mediametrics::Item>& item)
+{
+ auto actions = mActions.getActionsForItem(item); // internally locked.
+ // Execute actions with no lock held.
+ for (const auto& action : actions) {
+ (*action)(item);
+ }
+}
+
} // namespace android
diff --git a/services/mediametrics/AudioAnalytics.h b/services/mediametrics/AudioAnalytics.h
index 366a809..be885ec 100644
--- a/services/mediametrics/AudioAnalytics.h
+++ b/services/mediametrics/AudioAnalytics.h
@@ -16,8 +16,9 @@
#pragma once
-#include "TimeMachine.h"
-#include "TransactionLog.h"
+#include "AnalyticsActions.h"
+#include "AnalyticsState.h"
+#include "Wrap.h"
namespace android::mediametrics {
@@ -27,7 +28,6 @@
AudioAnalytics();
~AudioAnalytics();
- // TODO: update with conditions for keys.
/**
* Returns success if AudioAnalytics recognizes item.
*
@@ -42,8 +42,12 @@
* In this case, a trusted source is verified by binder
* UID to be a system service by MediaMetrics service.
* Do not use true if you haven't really checked!
+ *
+ * \return NO_ERROR on success,
+ * PERMISSION_DENIED if the item cannot be put into the AnalyticsState,
+ * BAD_VALUE if the item key does not start with "audio.".
*/
- status_t submit(const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted);
+ status_t submit(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted);
/**
* Returns a pair consisting of the dump string, and the number of lines in the string.
@@ -60,9 +64,22 @@
std::pair<std::string, int32_t> dump(int32_t lines = INT32_MAX) const;
private:
- // The following are locked internally
- TimeMachine mTimeMachine;
- TransactionLog mTransactionLog;
+
+ /**
+ * Checks for any pending actions for a particular item.
+ *
+ * \param item to check against the current AnalyticsActions.
+ */
+ void checkActions(const std::shared_ptr<const mediametrics::Item>& item);
+
+ // Actions is individually locked
+ AnalyticsActions mActions;
+
+ // AnalyticsState is individually locked, and we use SharedPtrWrap
+ // to allow safe access even if the shared pointer changes underneath.
+
+ SharedPtrWrap<AnalyticsState> mAnalyticsState;
+ SharedPtrWrap<AnalyticsState> mPreviousAnalyticsState;
};
} // namespace android::mediametrics
diff --git a/services/mediametrics/MediaMetricsService.cpp b/services/mediametrics/MediaMetricsService.cpp
index b735b81..3b95f7a 100644
--- a/services/mediametrics/MediaMetricsService.cpp
+++ b/services/mediametrics/MediaMetricsService.cpp
@@ -15,7 +15,7 @@
*/
//#define LOG_NDEBUG 0
-#define LOG_TAG "MediaAnalyticsService"
+#define LOG_TAG "MediaMetricsService"
#include <utils/Log.h>
#include "MediaMetricsService.h"
@@ -52,21 +52,40 @@
// TODO: need to look at tuning kMaxRecords and friends for low-memory devices
/* static */
-nsecs_t MediaAnalyticsService::roundTime(nsecs_t timeNs)
+nsecs_t MediaMetricsService::roundTime(nsecs_t timeNs)
{
return (timeNs + NANOS_PER_SECOND / 2) / NANOS_PER_SECOND * NANOS_PER_SECOND;
}
-MediaAnalyticsService::MediaAnalyticsService()
+/* static */
+bool MediaMetricsService::useUidForPackage(
+ const std::string& package, const std::string& installer)
+{
+ if (strchr(package.c_str(), '.') == NULL) {
+ return false; // not of form 'com.whatever...'; assume internal and ok
+ } else if (strncmp(package.c_str(), "android.", 8) == 0) {
+ return false; // android.* packages are assumed fine
+ } else if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
+ return false; // from play store
+ } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
+ return false; // some google source
+ } else if (strcmp(installer.c_str(), "preload") == 0) {
+ return false; // preloads
+ } else {
+ return true; // we're not sure where it came from, use uid only.
+ }
+}
+
+MediaMetricsService::MediaMetricsService()
: mMaxRecords(kMaxRecords),
mMaxRecordAgeNs(kMaxRecordAgeNs),
mMaxRecordsExpiredAtOnce(kMaxExpiredAtOnce),
- mDumpProtoDefault(MediaAnalyticsItem::PROTO_V1)
+ mDumpProtoDefault(mediametrics::Item::PROTO_V1)
{
ALOGD("%s", __func__);
}
-MediaAnalyticsService::~MediaAnalyticsService()
+MediaMetricsService::~MediaMetricsService()
{
ALOGD("%s", __func__);
// the class destructor clears anyhow, but we enforce clearing items first.
@@ -74,7 +93,7 @@
mItems.clear();
}
-status_t MediaAnalyticsService::submitInternal(MediaAnalyticsItem *item, bool release)
+status_t MediaMetricsService::submitInternal(mediametrics::Item *item, bool release)
{
// calling PID is 0 for one-way calls.
const pid_t pid = IPCThreadState::self()->getCallingPid();
@@ -112,14 +131,19 @@
break;
}
- // Overwrite package name and version if the caller was untrusted.
- if (!isTrusted) {
- mUidInfo.setPkgInfo(item, item->getUid(), true, true);
- } else if (item->getPkgName().empty()) {
- // empty, so fill out both parts
- mUidInfo.setPkgInfo(item, item->getUid(), true, true);
- } else {
- // trusted, provided a package, do nothing
+ // Overwrite package name and version if the caller was untrusted or empty
+ if (!isTrusted || item->getPkgName().empty()) {
+ const uid_t uid = item->getUid();
+ mediautils::UidInfo::Info info = mUidInfo.getInfo(uid);
+ if (useUidForPackage(info.package, info.installer)) {
+ // remove uid information of unknown installed packages.
+ // TODO: perhaps this can be done just before uploading to Westworld.
+ item->setPkgName(std::to_string(uid));
+ item->setPkgVersionCode(0);
+ } else {
+ item->setPkgName(info.package);
+ item->setPkgVersionCode(info.versionCode);
+ }
}
ALOGV("%s: given uid %d; sanitized uid: %d sanitized pkg: %s "
@@ -157,23 +181,23 @@
}
// now attach either the item or its dup to a const shared pointer
- std::shared_ptr<const MediaAnalyticsItem> sitem(release ? item : item->dup());
+ std::shared_ptr<const mediametrics::Item> sitem(release ? item : item->dup());
(void)mAudioAnalytics.submit(sitem, isTrusted);
- extern bool dump2Statsd(const std::shared_ptr<const MediaAnalyticsItem>& item);
+ extern bool dump2Statsd(const std::shared_ptr<const mediametrics::Item>& item);
(void)dump2Statsd(sitem); // failure should be logged in function.
saveItem(sitem);
return NO_ERROR;
}
-status_t MediaAnalyticsService::dump(int fd, const Vector<String16>& args)
+status_t MediaMetricsService::dump(int fd, const Vector<String16>& args)
{
String8 result;
if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
result.appendFormat("Permission Denial: "
- "can't dump MediaAnalyticsService from pid=%d, uid=%d\n",
+ "can't dump MediaMetricsService from pid=%d, uid=%d\n",
IPCThreadState::self()->getCallingPid(),
IPCThreadState::self()->getCallingUid());
write(fd, result.string(), result.size());
@@ -198,15 +222,15 @@
i++;
if (i < n) {
String8 value(args[i]);
- int proto = MediaAnalyticsItem::PROTO_V0;
+ int proto = mediametrics::Item::PROTO_V0;
char *endp;
const char *p = value.string();
proto = strtol(p, &endp, 10);
if (endp != p || *endp == '\0') {
- if (proto < MediaAnalyticsItem::PROTO_FIRST) {
- proto = MediaAnalyticsItem::PROTO_FIRST;
- } else if (proto > MediaAnalyticsItem::PROTO_LAST) {
- proto = MediaAnalyticsItem::PROTO_LAST;
+ if (proto < mediametrics::Item::PROTO_FIRST) {
+ proto = mediametrics::Item::PROTO_FIRST;
+ } else if (proto > mediametrics::Item::PROTO_LAST) {
+ proto = mediametrics::Item::PROTO_LAST;
}
chosenProto = proto;
} else {
@@ -267,7 +291,11 @@
}
// TODO: maybe consider a better way of dumping audio analytics info.
constexpr int32_t linesToDump = 1000;
- result.append(mAudioAnalytics.dump(linesToDump).first.c_str());
+ auto [ dumpString, lines ] = mAudioAnalytics.dump(linesToDump);
+ result.append(dumpString.c_str());
+ if (lines == linesToDump) {
+ result.append("-- some lines may be truncated --\n");
+ }
}
write(fd, result.string(), result.size());
@@ -275,10 +303,10 @@
}
// dump headers
-void MediaAnalyticsService::dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since)
+void MediaMetricsService::dumpHeaders_l(String8 &result, int dumpProto, nsecs_t ts_since)
{
result.appendFormat("Protocol Version: %d\n", dumpProto);
- if (MediaAnalyticsItem::isEnabled()) {
+ if (mediametrics::Item::isEnabled()) {
result.append("Metrics gathering: enabled\n");
} else {
result.append("Metrics gathering: DISABLED via property\n");
@@ -297,7 +325,7 @@
}
}
-void MediaAnalyticsService::dumpRecent_l(
+void MediaMetricsService::dumpRecent_l(
String8 &result, int dumpProto, nsecs_t ts_since, const char * only)
{
if (only != nullptr && *only == '\0') {
@@ -311,11 +339,11 @@
// talk about # records we discarded, perhaps "discarded w/o reading" too
}
-void MediaAnalyticsService::dumpQueue_l(String8 &result, int dumpProto) {
+void MediaMetricsService::dumpQueue_l(String8 &result, int dumpProto) {
dumpQueue_l(result, dumpProto, (nsecs_t) 0, nullptr /* only */);
}
-void MediaAnalyticsService::dumpQueue_l(
+void MediaMetricsService::dumpQueue_l(
String8 &result, int dumpProto, nsecs_t ts_since, const char * only) {
int slot = 0;
@@ -346,7 +374,7 @@
// if item != NULL, it's the item we just inserted
// true == more items eligible to be recovered
-bool MediaAnalyticsService::expirations_l(const std::shared_ptr<const MediaAnalyticsItem>& item)
+bool MediaMetricsService::expirations_l(const std::shared_ptr<const mediametrics::Item>& item)
{
bool more = false;
@@ -395,7 +423,7 @@
return more;
}
-void MediaAnalyticsService::processExpirations()
+void MediaMetricsService::processExpirations()
{
bool more;
do {
@@ -405,7 +433,7 @@
} while (more);
}
-void MediaAnalyticsService::saveItem(const std::shared_ptr<const MediaAnalyticsItem>& item)
+void MediaMetricsService::saveItem(const std::shared_ptr<const mediametrics::Item>& item)
{
std::lock_guard _l(mLock);
// we assume the items are roughly in time order.
@@ -419,7 +447,7 @@
}
/* static */
-bool MediaAnalyticsService::isContentValid(const MediaAnalyticsItem *item, bool isTrusted)
+bool MediaMetricsService::isContentValid(const mediametrics::Item *item, bool isTrusted)
{
if (isTrusted) return true;
// untrusted uids can only send us a limited set of keys
@@ -445,155 +473,9 @@
}
// are we rate limited, normally false
-bool MediaAnalyticsService::isRateLimited(MediaAnalyticsItem *) const
+bool MediaMetricsService::isRateLimited(mediametrics::Item *) const
{
return false;
}
-// How long we hold package info before we re-fetch it
-constexpr nsecs_t PKG_EXPIRATION_NS = 30 * 60 * NANOS_PER_SECOND; // 30 minutes
-
-// give me the package name, perhaps going to find it
-// manages its own mutex operations internally
-void MediaAnalyticsService::UidInfo::setPkgInfo(
- MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion)
-{
- ALOGV("%s: uid=%d", __func__, uid);
-
- if (!setName && !setVersion) {
- return; // setting nothing? strange
- }
-
- const nsecs_t now = systemTime(SYSTEM_TIME_REALTIME);
- struct UidToPkgInfo mapping;
- {
- std::lock_guard _l(mUidInfoLock);
- auto it = mPkgMappings.find(uid);
- if (it != mPkgMappings.end()) {
- mapping = it->second;
- ALOGV("%s: uid %d expiration %lld now %lld",
- __func__, uid, (long long)mapping.expiration, (long long)now);
- if (mapping.expiration <= now) {
- // purge the stale entry and fall into re-fetching
- ALOGV("%s: entry for uid %d expired, now %lld",
- __func__, uid, (long long)now);
- mPkgMappings.erase(it);
- mapping.uid = (uid_t)-1; // this is always fully overwritten
- }
- }
- }
-
- // if we did not find it
- if (mapping.uid == (uid_t)(-1)) {
- std::string pkg;
- std::string installer;
- int64_t versionCode = 0;
-
- const struct passwd *pw = getpwuid(uid);
- if (pw) {
- pkg = pw->pw_name;
- }
-
- sp<IServiceManager> sm = defaultServiceManager();
- sp<content::pm::IPackageManagerNative> package_mgr;
- if (sm.get() == nullptr) {
- ALOGE("%s: Cannot find service manager", __func__);
- } else {
- sp<IBinder> binder = sm->getService(String16("package_native"));
- if (binder.get() == nullptr) {
- ALOGE("%s: Cannot find package_native", __func__);
- } else {
- package_mgr = interface_cast<content::pm::IPackageManagerNative>(binder);
- }
- }
-
- if (package_mgr != nullptr) {
- std::vector<int> uids;
- std::vector<std::string> names;
- uids.push_back(uid);
- binder::Status status = package_mgr->getNamesForUids(uids, &names);
- if (!status.isOk()) {
- ALOGE("%s: getNamesForUids failed: %s",
- __func__, status.exceptionMessage().c_str());
- } else {
- if (!names[0].empty()) {
- pkg = names[0].c_str();
- }
- }
- }
-
- // strip any leading "shared:" strings that came back
- if (pkg.compare(0, 7, "shared:") == 0) {
- pkg.erase(0, 7);
- }
- // determine how pkg was installed and the versionCode
- if (pkg.empty()) {
- pkg = std::to_string(uid); // no name for us to manage
- } else if (strchr(pkg.c_str(), '.') == NULL) {
- // not of form 'com.whatever...'; assume internal and ok
- } else if (strncmp(pkg.c_str(), "android.", 8) == 0) {
- // android.* packages are assumed fine
- } else if (package_mgr.get() != nullptr) {
- String16 pkgName16(pkg.c_str());
- binder::Status status = package_mgr->getInstallerForPackage(pkgName16, &installer);
- if (!status.isOk()) {
- ALOGE("%s: getInstallerForPackage failed: %s",
- __func__, status.exceptionMessage().c_str());
- }
-
- // skip if we didn't get an installer
- if (status.isOk()) {
- status = package_mgr->getVersionCodeForPackage(pkgName16, &versionCode);
- if (!status.isOk()) {
- ALOGE("%s: getVersionCodeForPackage failed: %s",
- __func__, status.exceptionMessage().c_str());
- }
- }
-
- ALOGV("%s: package '%s' installed by '%s' versioncode %lld",
- __func__, pkg.c_str(), installer.c_str(), (long long)versionCode);
-
- if (strncmp(installer.c_str(), "com.android.", 12) == 0) {
- // from play store, we keep info
- } else if (strncmp(installer.c_str(), "com.google.", 11) == 0) {
- // some google source, we keep info
- } else if (strcmp(installer.c_str(), "preload") == 0) {
- // preloads, we keep the info
- } else if (installer.c_str()[0] == '\0') {
- // sideload (no installer); report UID only
- pkg = std::to_string(uid);
- versionCode = 0;
- } else {
- // unknown installer; report UID only
- pkg = std::to_string(uid);
- versionCode = 0;
- }
- } else {
- // unvalidated by package_mgr just send uid.
- pkg = std::to_string(uid);
- }
-
- // add it to the map, to save a subsequent lookup
- std::lock_guard _l(mUidInfoLock);
- // always overwrite
- mapping.uid = uid;
- mapping.pkg = std::move(pkg);
- mapping.installer = std::move(installer);
- mapping.versionCode = versionCode;
- mapping.expiration = now + PKG_EXPIRATION_NS;
- ALOGV("%s: adding uid %d pkg '%s' expiration: %lld",
- __func__, uid, mapping.pkg.c_str(), (long long)mapping.expiration);
- mPkgMappings[uid] = mapping;
- }
-
- if (mapping.uid != (uid_t)(-1)) {
- if (setName) {
- item->setPkgName(mapping.pkg);
- }
- if (setVersion) {
- item->setPkgVersionCode(mapping.versionCode);
- }
- }
-}
-
} // namespace android
diff --git a/services/mediametrics/MediaMetricsService.h b/services/mediametrics/MediaMetricsService.h
index 5bdc48f..74a114a 100644
--- a/services/mediametrics/MediaMetricsService.h
+++ b/services/mediametrics/MediaMetricsService.h
@@ -22,19 +22,20 @@
#include <mutex>
#include <unordered_map>
-// IMediaAnalyticsService must include Vector, String16, Errors
-#include <media/IMediaAnalyticsService.h>
+// IMediaMetricsService must include Vector, String16, Errors
+#include <media/IMediaMetricsService.h>
+#include <mediautils/ServiceUtilities.h>
#include <utils/String8.h>
#include "AudioAnalytics.h"
namespace android {
-class MediaAnalyticsService : public BnMediaAnalyticsService
+class MediaMetricsService : public BnMediaMetricsService
{
public:
- MediaAnalyticsService();
- ~MediaAnalyticsService() override;
+ MediaMetricsService();
+ ~MediaMetricsService() override;
/**
* Submits the indicated record to the mediaanalytics service.
@@ -43,12 +44,12 @@
* \return status failure, which is negative on binder transaction failure.
* As the transaction is one-way, remote failures will not be reported.
*/
- status_t submit(MediaAnalyticsItem *item) override {
+ status_t submit(mediametrics::Item *item) override {
return submitInternal(item, false /* release */);
}
status_t submitBuffer(const char *buffer, size_t length) override {
- MediaAnalyticsItem *item = new MediaAnalyticsItem();
+ mediametrics::Item *item = new mediametrics::Item();
return item->readFromByteString(buffer, length)
?: submitInternal(item, true /* release */);
}
@@ -62,21 +63,26 @@
*/
static nsecs_t roundTime(nsecs_t timeNs);
+ /**
+ * Returns true if we should use uid for package name when uploading to WestWorld.
+ */
+ static bool useUidForPackage(const std::string& package, const std::string& installer);
+
protected:
// Internal call where release is true if ownership of item is transferred
// to the service (that is, the service will eventually delete the item).
- status_t submitInternal(MediaAnalyticsItem *item, bool release) override;
+ status_t submitInternal(mediametrics::Item *item, bool release) override;
private:
void processExpirations();
// input validation after arrival from client
- static bool isContentValid(const MediaAnalyticsItem *item, bool isTrusted);
- bool isRateLimited(MediaAnalyticsItem *) const;
- void saveItem(const std::shared_ptr<const MediaAnalyticsItem>& item);
+ static bool isContentValid(const mediametrics::Item *item, bool isTrusted);
+ bool isRateLimited(mediametrics::Item *) const;
+ void saveItem(const std::shared_ptr<const mediametrics::Item>& item);
// The following methods are GUARDED_BY(mLock)
- bool expirations_l(const std::shared_ptr<const MediaAnalyticsItem>& item);
+ bool expirations_l(const std::shared_ptr<const mediametrics::Item>& item);
// support for generating output
void dumpQueue_l(String8 &result, int dumpProto);
@@ -96,27 +102,10 @@
const size_t mMaxRecordsExpiredAtOnce;
const int mDumpProtoDefault;
- class UidInfo {
- public:
- void setPkgInfo(MediaAnalyticsItem *item, uid_t uid, bool setName, bool setVersion);
-
- private:
- std::mutex mUidInfoLock;
-
- struct UidToPkgInfo {
- uid_t uid = -1;
- std::string pkg;
- std::string installer;
- int64_t versionCode = 0;
- nsecs_t expiration = 0; // TODO: remove expiration.
- };
-
- // TODO: use concurrent hashmap with striped lock.
- std::unordered_map<uid_t, struct UidToPkgInfo> mPkgMappings; // GUARDED_BY(mUidInfoLock)
- } mUidInfo; // mUidInfo can be accessed without lock (locked internally)
-
std::atomic<int64_t> mItemsSubmitted{}; // accessed outside of lock.
+ mediautils::UidInfo mUidInfo; // mUidInfo can be accessed without lock (locked internally)
+
mediametrics::AudioAnalytics mAudioAnalytics;
std::mutex mLock;
@@ -132,7 +121,7 @@
// Our item queue, generally (oldest at front)
// TODO: Make separate class, use segmented queue, write lock only end.
// Note: Another analytics module might have ownership of an item longer than the log.
- std::deque<std::shared_ptr<const MediaAnalyticsItem>> mItems; // GUARDED_BY(mLock)
+ std::deque<std::shared_ptr<const mediametrics::Item>> mItems; // GUARDED_BY(mLock)
};
} // namespace android
diff --git a/services/mediametrics/TimeMachine.h b/services/mediametrics/TimeMachine.h
index 578b838..4d24ce4 100644
--- a/services/mediametrics/TimeMachine.h
+++ b/services/mediametrics/TimeMachine.h
@@ -23,7 +23,7 @@
#include <variant>
#include <vector>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
#include <utils/Timers.h>
namespace android::mediametrics {
@@ -35,6 +35,14 @@
return s;
}
+// define a way of printing a std::pair.
+template <typename T, typename U>
+std::ostream & operator<< (std::ostream& s,
+ const std::pair<T, U>& v) {
+ s << "{ " << v.first << ", " << v.second << " }";
+ return s;
+}
+
// define a way of printing a variant
// see https://en.cppreference.com/w/cpp/utility/variant/visit
template <typename T0, typename ... Ts>
@@ -52,11 +60,13 @@
*
* The TimeMachine is NOT thread safe.
*/
-class TimeMachine {
-
- using Elem = std::variant<std::monostate, int32_t, int64_t, double, std::string>;
+class TimeMachine final { // made final as we have copy constructor instead of dup() override.
+public:
+ using Elem = Item::Prop::Elem; // use the Item property element.
using PropertyHistory = std::multimap<int64_t /* time */, Elem>;
+private:
+
// KeyHistory contains no lock.
// Access is through the TimeMachine, and a hash-striped lock is used
// before calling into KeyHistory.
@@ -70,10 +80,12 @@
, mCreationTime(time)
, mLastModificationTime(time)
{
- putValue("_pid", (int32_t)pid, time);
- putValue("_uid", (int32_t)uid, time);
+ putValue(BUNDLE_PID, (int32_t)pid, time);
+ putValue(BUNDLE_UID, (int32_t)uid, time);
}
+ KeyHistory(const KeyHistory &other) = default;
+
status_t checkPermission(uid_t uidCheck) const {
return uidCheck != (uid_t)-1 && uidCheck != mUid ? PERMISSION_DENIED : NO_ERROR;
}
@@ -101,8 +113,9 @@
}
void putProp(
- const std::string &name, const MediaAnalyticsItem::Prop &prop, int64_t time = 0) {
- prop.visit([&](auto value) { putValue(name, value, time); });
+ const std::string &name, const mediametrics::Item::Prop &prop, int64_t time = 0) {
+ //alternatively: prop.visit([&](auto value) { putValue(name, value, time); });
+ putValue(name, prop.get(), time);
}
template <typename T>
@@ -119,13 +132,6 @@
}
}
- // Explicitly ignore rate properties - we don't expose them for now.
- void putValue(
- const std::string &property __unused,
- std::pair<int64_t, int64_t>& e __unused,
- int64_t time __unused) {
- }
-
std::pair<std::string, int32_t> dump(int32_t lines, int64_t time) const {
std::stringstream ss;
int32_t ll = lines;
@@ -185,10 +191,38 @@
__func__, keyHighWaterMark, keyLowWaterMark);
}
+ // The TimeMachine copy constructor/assignment uses a deep copy,
+ // though the snapshot is not instantaneous nor isochronous.
+ //
+ // If there are concurrent operations ongoing in the other TimeMachine
+ // then there may be some history more recent than others (a time shear).
+ // This is expected to be a benign addition in history as small number of
+ // future elements are incorporated.
+ TimeMachine(const TimeMachine& other) {
+ *this = other;
+ }
+ TimeMachine& operator=(const TimeMachine& other) {
+ std::lock_guard lock(mLock);
+ mHistory.clear();
+
+ {
+ std::lock_guard lock2(other.mLock);
+ mHistory = other.mHistory;
+ }
+
+ // Now that we safely have our own shared pointers, let's dup them
+ // to ensure they are decoupled. We do this by acquiring the other lock.
+ for (const auto &[lkey, lhist] : mHistory) {
+ std::lock_guard lock2(other.getLockForKey(lkey));
+ mHistory[lkey] = std::make_shared<KeyHistory>(*lhist);
+ }
+ return *this;
+ }
+
/**
* Put all the properties from an item into the Time Machine log.
*/
- status_t put(const std::shared_ptr<const MediaAnalyticsItem>& item, bool isTrusted = false) {
+ status_t put(const std::shared_ptr<const mediametrics::Item>& item, bool isTrusted = false) {
const int64_t time = item->getTimestamp();
const std::string &key = item->getKey();
@@ -214,7 +248,7 @@
}
// deferred contains remote properties (for other keys) to do later.
- std::vector<const MediaAnalyticsItem::Prop *> deferred;
+ std::vector<const mediametrics::Item::Prop *> deferred;
{
// handle local properties
std::lock_guard lock(getLockForKey(key));
@@ -358,10 +392,10 @@
std::stringstream ss;
int32_t ll = lines;
- for (const auto &keyPair : mHistory) {
- std::lock_guard lock(getLockForKey(keyPair.first));
+ for (const auto &[lkey, lhist] : mHistory) {
+ std::lock_guard lock(getLockForKey(lkey));
if (lines <= 0) break;
- auto [s, l] = keyPair.second->dump(ll, time);
+ auto [s, l] = lhist->dump(ll, time);
ss << s;
ll -= l;
}
diff --git a/services/mediametrics/TransactionLog.h b/services/mediametrics/TransactionLog.h
index ca37862..0c5d12b 100644
--- a/services/mediametrics/TransactionLog.h
+++ b/services/mediametrics/TransactionLog.h
@@ -21,12 +21,12 @@
#include <sstream>
#include <string>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
namespace android::mediametrics {
/**
- * The TransactionLog is used to record MediaAnalyticsItems to present
+ * The TransactionLog is used to record mediametrics::Items to present
* different views on the time information (selected by audio, and sorted by key).
*
* The TransactionLog will always present data in timestamp order. (Perhaps we
@@ -36,7 +36,7 @@
*
* The TransactionLog is NOT thread safe.
*/
-class TransactionLog {
+class TransactionLog final { // made final as we have copy constructor instead of dup() override.
public:
// In long term run, the garbage collector aims to keep the
// Transaction Log between the Low Water Mark and the High Water Mark.
@@ -58,10 +58,34 @@
__func__, highWaterMark, lowWaterMark);
}
+ // The TransactionLog copy constructor/assignment is effectively an
+ // instantaneous, isochronous snapshot of the other TransactionLog.
+ //
+ // The contents of the Transaction Log are shared pointers to immutable instances -
+ // std::shared_ptr<const mediametrics::Item>, so we use a shallow copy,
+ // which is more efficient in space and execution time than a deep copy,
+ // and gives the same results.
+
+ TransactionLog(const TransactionLog &other) {
+ *this = other;
+ }
+
+ TransactionLog& operator=(const TransactionLog &other) {
+ std::lock_guard lock(mLock);
+ mLog.clear();
+ mItemMap.clear();
+
+ std::lock_guard lock2(other.mLock);
+ mLog = other.mLog;
+ mItemMap = other.mItemMap;
+
+ return *this;
+ }
+
/**
* Put an item in the TransactionLog.
*/
- status_t put(const std::shared_ptr<const MediaAnalyticsItem>& item) {
+ status_t put(const std::shared_ptr<const mediametrics::Item>& item) {
const std::string& key = item->getKey();
const int64_t time = item->getTimestamp();
@@ -77,7 +101,7 @@
/**
* Returns all records within [startTime, endTime]
*/
- std::vector<std::shared_ptr<const MediaAnalyticsItem>> get(
+ std::vector<std::shared_ptr<const mediametrics::Item>> get(
int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
std::lock_guard lock(mLock);
return getItemsInRange_l(mLog, startTime, endTime);
@@ -86,7 +110,7 @@
/**
* Returns all records for a key within [startTime, endTime]
*/
- std::vector<std::shared_ptr<const MediaAnalyticsItem>> get(
+ std::vector<std::shared_ptr<const mediametrics::Item>> get(
const std::string& key,
int64_t startTime = 0, int64_t endTime = INT64_MAX) const {
std::lock_guard lock(mLock);
@@ -158,7 +182,7 @@
private:
using MapTimeItem =
- std::multimap<int64_t /* time */, std::shared_ptr<const MediaAnalyticsItem>>;
+ std::multimap<int64_t /* time */, std::shared_ptr<const mediametrics::Item>>;
// GUARDED_BY mLock
/**
@@ -179,7 +203,7 @@
// remove at least those elements.
// use a stale vector with precise type to avoid type erasure overhead in garbage
- std::vector<std::shared_ptr<const MediaAnalyticsItem>> stale;
+ std::vector<std::shared_ptr<const mediametrics::Item>> stale;
for (size_t i = 0; i < toRemove; ++i) {
stale.emplace_back(std::move(eraseEnd->second));
@@ -224,7 +248,7 @@
return true;
}
- static std::vector<std::shared_ptr<const MediaAnalyticsItem>> getItemsInRange_l(
+ static std::vector<std::shared_ptr<const mediametrics::Item>> getItemsInRange_l(
const MapTimeItem& map,
int64_t startTime = 0, int64_t endTime = INT64_MAX) {
auto it = map.lower_bound(startTime);
@@ -232,7 +256,7 @@
auto it2 = map.upper_bound(endTime);
- std::vector<std::shared_ptr<const MediaAnalyticsItem>> ret;
+ std::vector<std::shared_ptr<const mediametrics::Item>> ret;
while (it != it2) {
ret.push_back(it->second);
++it;
diff --git a/services/mediametrics/Wrap.h b/services/mediametrics/Wrap.h
new file mode 100644
index 0000000..3584e08
--- /dev/null
+++ b/services/mediametrics/Wrap.h
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+#pragma once
+
+#include <memory>
+#include <mutex>
+
+namespace android::mediametrics {
+
+/**
+ * Wraps a shared-ptr for which member access through operator->() behaves
+ * as if the shared-ptr is atomically copied and then (without a lock) -> called.
+ *
+ * See related C++ 20:
+ * https://en.cppreference.com/w/cpp/memory/shared_ptr/atomic2
+ *
+ * EXAMPLE:
+ *
+ * SharedPtrWrap<T> t{};
+ *
+ * thread1() {
+ * t->func(); // safely executes either the original t or the one created by thread2.
+ * }
+ *
+ * thread2() {
+ * t.set(std::make_shared<T>()); // overwrites the original t.
+ * }
+ */
+template <typename T>
+class SharedPtrWrap {
+ mutable std::mutex mLock;
+ std::shared_ptr<T> mPtr;
+
+public:
+ template <typename... Args>
+ explicit SharedPtrWrap(Args&&... args)
+ : mPtr(std::make_shared<T>(std::forward<Args>(args)...))
+ {}
+
+ /**
+ * Gets the current shared pointer. This must return a value, not a reference.
+ *
+ * For compatibility with existing shared_ptr, we do not pass back a
+ * shared_ptr<const T> for the const getter.
+ */
+ std::shared_ptr<T> get() const {
+ std::lock_guard lock(mLock);
+ return mPtr;
+ }
+
+ /**
+ * Sets the current shared pointer, returning the previous shared pointer.
+ */
+ std::shared_ptr<T> set(std::shared_ptr<T> ptr) { // pass by value as we use swap.
+ std::lock_guard lock(mLock);
+ std::swap(ptr, mPtr);
+ return ptr;
+ }
+
+ /**
+ * Returns a shared pointer value representing T at the instant of time when
+ * the call executes. The lifetime of the shared pointer will
+ * be extended as we are returning an instance of the shared_ptr
+ * not a reference to it. The destructor to the returned shared_ptr
+ * will be called sometime after the expression including the member function or
+ * the member variable is evaluated. Do not change to a reference!
+ */
+
+ // For compatibility with existing shared_ptr, we do not pass back a
+ // shared_ptr<const T> for the const operator pointer access.
+ std::shared_ptr<T> operator->() const {
+ return get();
+ }
+ /**
+ * We do not overload operator*() as the reference is not stable if the
+ * lock is not held.
+ */
+};
+
+/**
+ * Wraps member access to the class T by a lock.
+ *
+ * The object T is constructed within the LockWrap to guarantee
+ * locked access at all times. When T's methods are accessed through ->,
+ * a monitor style lock is obtained to prevent multiple threads from executing
+ * methods in the object T at the same time.
+ * Suggested by Kevin R.
+ *
+ * EXAMPLE:
+ *
+ * // Accumulator class which is very slow, requires locking for multiple threads.
+ *
+ * class Accumulator {
+ * int32_t value_ = 0;
+ * public:
+ * void add(int32_t incr) {
+ * const int32_t temp = value_;
+ * sleep(0); // yield
+ * value_ = temp + incr;
+ * }
+ * int32_t get() { return value_; }
+ * };
+ *
+ * // We use LockWrap on Accumulator to have safe multithread access.
+ * android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
+ *
+ * // Conversely, the following line fails:
+ * // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
+ *
+ * constexpr size_t THREADS = 100;
+ * constexpr size_t ITERATIONS = 10;
+ * constexpr int32_t INCREMENT = 1;
+ *
+ * // Test by generating multiple threads, all adding simultaneously.
+ * std::vector<std::future<void>> threads(THREADS);
+ * for (size_t i = 0; i < THREADS; ++i) {
+ * threads.push_back(std::async(std::launch::async, [&] {
+ * for (size_t j = 0; j < ITERATIONS; ++j) {
+ * a->add(INCREMENT); // add needs locked access here.
+ * }
+ * }));
+ * }
+ * threads.clear();
+ *
+ * // If the add operations are not atomic, value will be smaller than expected.
+ * ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
+ *
+ */
+template <typename T>
+class LockWrap {
+ /**
+ * Holding class that keeps the pointer and the lock.
+ *
+ * We return this holding class from operator->() to keep the lock until the
+ * method function or method variable access is completed.
+ */
+ class LockedPointer {
+ friend LockWrap;
+ LockedPointer(T *t, std::recursive_mutex *lock, std::atomic<size_t> *recursionDepth)
+ : mT(t), mLock(*lock), mRecursionDepth(recursionDepth) { ++*mRecursionDepth; }
+
+ T* const mT;
+ std::lock_guard<std::recursive_mutex> mLock;
+ std::atomic<size_t>* mRecursionDepth;
+ public:
+ ~LockedPointer() {
+ --*mRecursionDepth; // Used for testing, we do not check underflow.
+ }
+
+ const T* operator->() const {
+ return mT;
+ }
+ T* operator->() {
+ return mT;
+ }
+ };
+
+ // We must use a recursive mutex because the end of the full expression may
+ // involve another reference to T->.
+ //
+ // A recursive mutex allows the same thread to recursively acquire,
+ // but different thread would block.
+ //
+ // Example which fails with a normal mutex:
+ //
+ // android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}};
+ // const int sum = v->operator[](0) + v->operator[](1);
+ //
+ mutable std::recursive_mutex mLock;
+ mutable T mT;
+ mutable std::atomic<size_t> mRecursionDepth{}; // Used for testing.
+
+public:
+ template <typename... Args>
+ explicit LockWrap(Args&&... args) : mT(std::forward<Args>(args)...) {}
+
+ const LockedPointer operator->() const {
+ return LockedPointer(&mT, &mLock, &mRecursionDepth);
+ }
+ LockedPointer operator->() {
+ return LockedPointer(&mT, &mLock, &mRecursionDepth);
+ }
+
+ // Returns the lock depth of the recursive mutex.
+ // @TestApi
+ size_t getRecursionDepth() const {
+ return mRecursionDepth;
+ }
+};
+
+} // namespace android::mediametrics
diff --git a/services/mediametrics/iface_statsd.cpp b/services/mediametrics/iface_statsd.cpp
index 962cafe..3a1eea7 100644
--- a/services/mediametrics/iface_statsd.cpp
+++ b/services/mediametrics/iface_statsd.cpp
@@ -38,10 +38,10 @@
namespace android {
-// set of routines that crack a MediaAnalyticsItem
+// set of routines that crack a mediametrics::Item
// and send it off to statsd with the appropriate hooks
//
-// each MediaAnalyticsItem type (extractor, codec, nuplayer, etc)
+// each mediametrics::Item type (extractor, codec, nuplayer, etc)
// has its own routine to handle this.
//
@@ -49,7 +49,7 @@
struct statsd_hooks {
const char *key;
- bool (*handler)(const MediaAnalyticsItem *);
+ bool (*handler)(const mediametrics::Item *);
};
// keep this sorted, so we can do binary searches
@@ -70,7 +70,7 @@
};
// give me a record, i'll look at the type and upload appropriately
-bool dump2Statsd(const std::shared_ptr<const MediaAnalyticsItem>& item) {
+bool dump2Statsd(const std::shared_ptr<const mediametrics::Item>& item) {
if (item == NULL) return false;
// get the key
diff --git a/services/mediametrics/iface_statsd.h b/services/mediametrics/iface_statsd.h
index 1c48118..19505a4 100644
--- a/services/mediametrics/iface_statsd.h
+++ b/services/mediametrics/iface_statsd.h
@@ -19,17 +19,17 @@
extern bool enabled_statsd;
// component specific dumpers
-extern bool statsd_audiopolicy(const MediaAnalyticsItem *);
-extern bool statsd_audiorecord(const MediaAnalyticsItem *);
-extern bool statsd_audiothread(const MediaAnalyticsItem *);
-extern bool statsd_audiotrack(const MediaAnalyticsItem *);
-extern bool statsd_codec(const MediaAnalyticsItem *);
-extern bool statsd_extractor(const MediaAnalyticsItem *);
-extern bool statsd_nuplayer(const MediaAnalyticsItem *);
-extern bool statsd_recorder(const MediaAnalyticsItem *);
+extern bool statsd_audiopolicy(const mediametrics::Item *);
+extern bool statsd_audiorecord(const mediametrics::Item *);
+extern bool statsd_audiothread(const mediametrics::Item *);
+extern bool statsd_audiotrack(const mediametrics::Item *);
+extern bool statsd_codec(const mediametrics::Item *);
+extern bool statsd_extractor(const mediametrics::Item *);
+extern bool statsd_nuplayer(const mediametrics::Item *);
+extern bool statsd_recorder(const mediametrics::Item *);
-extern bool statsd_mediadrm(const MediaAnalyticsItem *);
-extern bool statsd_widevineCDM(const MediaAnalyticsItem *);
-extern bool statsd_drmmanager(const MediaAnalyticsItem *);
+extern bool statsd_mediadrm(const mediametrics::Item *);
+extern bool statsd_widevineCDM(const mediametrics::Item *);
+extern bool statsd_drmmanager(const mediametrics::Item *);
} // namespace android
diff --git a/services/mediametrics/main_mediametrics.cpp b/services/mediametrics/main_mediametrics.cpp
index 4b2a9fa..ec392e2 100644
--- a/services/mediametrics/main_mediametrics.cpp
+++ b/services/mediametrics/main_mediametrics.cpp
@@ -34,10 +34,10 @@
// to match the service name
// we're replacing "/system/bin/mediametrics" with "media.metrics"
// we add a ".", but discard the path components: we finish with a shorter string
- strcpy(argv[0], MediaAnalyticsService::kServiceName);
+ strcpy(argv[0], MediaMetricsService::kServiceName);
defaultServiceManager()->addService(
- String16(MediaAnalyticsService::kServiceName), new MediaAnalyticsService());
+ String16(MediaMetricsService::kServiceName), new MediaMetricsService());
sp<ProcessState> processState(ProcessState::self());
// processState->setThreadPoolMaxThreadCount(8);
diff --git a/services/mediametrics/statsd_audiopolicy.cpp b/services/mediametrics/statsd_audiopolicy.cpp
index 2f934f8..634c801 100644
--- a/services/mediametrics/statsd_audiopolicy.cpp
+++ b/services/mediametrics/statsd_audiopolicy.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_audiopolicy(const MediaAnalyticsItem *item)
+bool statsd_audiopolicy(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_audiorecord.cpp b/services/mediametrics/statsd_audiorecord.cpp
index 4e2829d..69d1661 100644
--- a/services/mediametrics/statsd_audiorecord.cpp
+++ b/services/mediametrics/statsd_audiorecord.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_audiorecord(const MediaAnalyticsItem *item)
+bool statsd_audiorecord(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_audiothread.cpp b/services/mediametrics/statsd_audiothread.cpp
index a5bfcba..300151b 100644
--- a/services/mediametrics/statsd_audiothread.cpp
+++ b/services/mediametrics/statsd_audiothread.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_audiothread(const MediaAnalyticsItem *item)
+bool statsd_audiothread(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_audiotrack.cpp b/services/mediametrics/statsd_audiotrack.cpp
index 9d76d43..397cdf3 100644
--- a/services/mediametrics/statsd_audiotrack.cpp
+++ b/services/mediametrics/statsd_audiotrack.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_audiotrack(const MediaAnalyticsItem *item)
+bool statsd_audiotrack(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_codec.cpp b/services/mediametrics/statsd_codec.cpp
index fde0bfa..214c51a 100644
--- a/services/mediametrics/statsd_codec.cpp
+++ b/services/mediametrics/statsd_codec.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_codec(const MediaAnalyticsItem *item)
+bool statsd_codec(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_drm.cpp b/services/mediametrics/statsd_drm.cpp
index 78d0a22..b12f4f3 100644
--- a/services/mediametrics/statsd_drm.cpp
+++ b/services/mediametrics/statsd_drm.cpp
@@ -38,11 +38,11 @@
namespace android {
// mediadrm
-bool statsd_mediadrm(const MediaAnalyticsItem *item)
+bool statsd_mediadrm(const mediametrics::Item *item)
{
if (item == NULL) return false;
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
@@ -75,11 +75,11 @@
}
// widevineCDM
-bool statsd_widevineCDM(const MediaAnalyticsItem *item)
+bool statsd_widevineCDM(const mediametrics::Item *item)
{
if (item == NULL) return false;
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
@@ -105,11 +105,11 @@
}
// drmmanager
-bool statsd_drmmanager(const MediaAnalyticsItem *item)
+bool statsd_drmmanager(const mediametrics::Item *item)
{
if (item == NULL) return false;
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_extractor.cpp b/services/mediametrics/statsd_extractor.cpp
index cc62241..8574358 100644
--- a/services/mediametrics/statsd_extractor.cpp
+++ b/services/mediametrics/statsd_extractor.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_extractor(const MediaAnalyticsItem *item)
+bool statsd_extractor(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_nuplayer.cpp b/services/mediametrics/statsd_nuplayer.cpp
index 9db1e81..df7e59f 100644
--- a/services/mediametrics/statsd_nuplayer.cpp
+++ b/services/mediametrics/statsd_nuplayer.cpp
@@ -41,12 +41,12 @@
* handles nuplayer AND nuplayer2
* checks for the union of what the two players generate
*/
-bool statsd_nuplayer(const MediaAnalyticsItem *item)
+bool statsd_nuplayer(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/statsd_recorder.cpp b/services/mediametrics/statsd_recorder.cpp
index 972a221..4de1746 100644
--- a/services/mediametrics/statsd_recorder.cpp
+++ b/services/mediametrics/statsd_recorder.cpp
@@ -37,12 +37,12 @@
namespace android {
-bool statsd_recorder(const MediaAnalyticsItem *item)
+bool statsd_recorder(const mediametrics::Item *item)
{
if (item == NULL) return false;
// these go into the statsd wrapper
- const nsecs_t timestamp = MediaAnalyticsService::roundTime(item->getTimestamp());
+ const nsecs_t timestamp = MediaMetricsService::roundTime(item->getTimestamp());
std::string pkgName = item->getPkgName();
int64_t pkgVersionCode = item->getPkgVersionCode();
int64_t mediaApexVersion = 0;
diff --git a/services/mediametrics/tests/Android.bp b/services/mediametrics/tests/Android.bp
index 9eb2d89..bdeda30 100644
--- a/services/mediametrics/tests/Android.bp
+++ b/services/mediametrics/tests/Android.bp
@@ -17,6 +17,7 @@
"liblog",
"libmediametrics",
"libmediametricsservice",
+ "libmediautils",
"libutils",
],
diff --git a/services/mediametrics/tests/build_and_run_all_unit_tests.sh b/services/mediametrics/tests/build_and_run_all_unit_tests.sh
index 2511c30..382be47 100755
--- a/services/mediametrics/tests/build_and_run_all_unit_tests.sh
+++ b/services/mediametrics/tests/build_and_run_all_unit_tests.sh
@@ -20,5 +20,5 @@
echo "========================================"
echo "testing mediametrics"
-adb push $OUT/data/nativetest/mediametrics_tests/mediametrics_tests /system/bin
+adb push $OUT/data/nativetest64/mediametrics_tests/mediametrics_tests /system/bin
adb shell /system/bin/mediametrics_tests
diff --git a/services/mediametrics/tests/mediametrics_tests.cpp b/services/mediametrics/tests/mediametrics_tests.cpp
index 808da7b..27b72eb 100644
--- a/services/mediametrics/tests/mediametrics_tests.cpp
+++ b/services/mediametrics/tests/mediametrics_tests.cpp
@@ -22,7 +22,7 @@
#include <stdio.h>
#include <gtest/gtest.h>
-#include <media/MediaAnalyticsItem.h>
+#include <media/MediaMetricsItem.h>
using namespace android;
@@ -35,12 +35,163 @@
return count;
}
+TEST(mediametrics_tests, startsWith) {
+ std::string s("test");
+ ASSERT_EQ(true, android::mediametrics::startsWith(s, "te"));
+ ASSERT_EQ(true, android::mediametrics::startsWith(s, std::string("tes")));
+ ASSERT_EQ(false, android::mediametrics::startsWith(s, "ts"));
+ ASSERT_EQ(false, android::mediametrics::startsWith(s, std::string("est")));
+}
+
+TEST(mediametrics_tests, defer) {
+ bool check = false;
+ {
+ android::mediametrics::Defer defer([&] { check = true; });
+ ASSERT_EQ(false, check);
+ }
+ ASSERT_EQ(true, check);
+}
+
+TEST(mediametrics_tests, shared_ptr_wrap) {
+ // Test shared pointer wrap with simple access
+ android::mediametrics::SharedPtrWrap<std::string> s("123");
+ ASSERT_EQ('1', s->at(0));
+ ASSERT_EQ('2', s->at(1));
+ s->push_back('4');
+ ASSERT_EQ('4', s->at(3));
+
+ const android::mediametrics::SharedPtrWrap<std::string> s2("345");
+ ASSERT_EQ('3', s2->operator[](0)); // s2[0] == '3'
+ // we allow modification through a const shared pointer wrap
+ // for compatibility with shared_ptr.
+ s2->push_back('6');
+ ASSERT_EQ('6', s2->operator[](3)); // s2[3] == '6'
+
+ android::mediametrics::SharedPtrWrap<std::string> s3("");
+ s3.set(std::make_shared<std::string>("abc"));
+ ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
+
+ // Use Thunk to check whether the destructor was called prematurely
+ // when setting the shared ptr wrap in the middle of a method.
+
+ class Thunk {
+ std::function<void(int)> mF;
+ const int mFinal;
+
+ public:
+ Thunk(decltype(mF) f, int final) : mF(f), mFinal(final) {}
+ ~Thunk() { mF(mFinal); }
+ void thunk(int value) { mF(value); }
+ };
+
+ int counter = 0;
+ android::mediametrics::SharedPtrWrap<Thunk> s4(
+ [&](int value) {
+ s4.set(std::make_shared<Thunk>([](int){}, 0)); // recursively set s4 while in s4.
+ ++counter;
+ ASSERT_EQ(value, counter); // on thunk() value is 1, on destructor this is 2.
+ }, 2);
+
+ // This will fail if the shared ptr wrap doesn't hold a ref count during method access.
+ s4->thunk(1);
+}
+
+TEST(mediametrics_tests, lock_wrap) {
+ // Test lock wrap with simple access
+ android::mediametrics::LockWrap<std::string> s("123");
+ ASSERT_EQ('1', s->at(0));
+ ASSERT_EQ('2', s->at(1));
+ s->push_back('4');
+ ASSERT_EQ('4', s->at(3));
+
+ const android::mediametrics::LockWrap<std::string> s2("345");
+ ASSERT_EQ('3', s2->operator[](0)); // s2[0] == '3'
+ // note: we can't modify s2 due to const, s2->push_back('6');
+
+ android::mediametrics::LockWrap<std::string> s3("");
+ s3->operator=("abc");
+ ASSERT_EQ('b', s3->operator[](1)); // s2[1] = 'b';
+
+ // Check that we can recursively hold lock.
+ android::mediametrics::LockWrap<std::vector<int>> v{std::initializer_list<int>{1, 2}};
+ v->push_back(3);
+ v->push_back(4);
+ ASSERT_EQ(1, v->operator[](0));
+ ASSERT_EQ(2, v->operator[](1));
+ ASSERT_EQ(3, v->operator[](2));
+ ASSERT_EQ(4, v->operator[](3));
+ // The end of the full expression here requires recursive depth of 4.
+ ASSERT_EQ(10, v->operator[](0) + v->operator[](1) + v->operator[](2) + v->operator[](3));
+
+ // Mikhail's note: a non-recursive lock implementation could be used if one obtains
+ // the LockedPointer helper object like this and directly hold the lock through RAII,
+ // though it is trickier in use.
+ //
+ // We include an example here for completeness.
+ {
+ auto l = v.operator->();
+ ASSERT_EQ(10, l->operator[](0) + l->operator[](1) + l->operator[](2) + l->operator[](3));
+ }
+
+ // Use Thunk to check whether we have the lock when calling a method through LockWrap.
+
+ class Thunk {
+ std::function<void()> mF;
+
+ public:
+ Thunk(decltype(mF) f) : mF(f) {}
+ void thunk() { mF(); }
+ };
+
+ android::mediametrics::LockWrap<Thunk> s4([&]{
+ ASSERT_EQ((size_t)1, s4.getRecursionDepth()); // we must be locked when thunk() is called.
+ });
+
+ ASSERT_EQ((size_t)0, s4.getRecursionDepth());
+ // This will fail if we are not locked during method access.
+ s4->thunk();
+ ASSERT_EQ((size_t)0, s4.getRecursionDepth());
+}
+
+TEST(mediametrics_tests, lock_wrap_multithread) {
+ class Accumulator {
+ int32_t value_ = 0;
+ public:
+ void add(int32_t incr) {
+ const int32_t temp = value_;
+ sleep(0); // yield
+ value_ = temp + incr;
+ }
+ int32_t get() { return value_; }
+ };
+
+ android::mediametrics::LockWrap<Accumulator> a{}; // locked accumulator succeeds
+ // auto a = std::make_shared<Accumulator>(); // this fails, only 50% adds atomic.
+
+ constexpr size_t THREADS = 100;
+ constexpr size_t ITERATIONS = 10;
+ constexpr int32_t INCREMENT = 1;
+
+ std::vector<std::future<void>> threads(THREADS);
+ for (size_t i = 0; i < THREADS; ++i) {
+ threads.push_back(std::async(std::launch::async, [&] {
+ for (size_t j = 0; j < ITERATIONS; ++j) {
+ a->add(INCREMENT);
+ }
+ }));
+ }
+ threads.clear();
+
+ // If the add operations are not atomic, value will be smaller than expected.
+ ASSERT_EQ(INCREMENT * THREADS * ITERATIONS, (size_t)a->get());
+}
+
TEST(mediametrics_tests, instantiate) {
- sp mediaMetrics = new MediaAnalyticsService();
+ sp mediaMetrics = new MediaMetricsService();
status_t status;
// random keys ignored when empty
- std::unique_ptr<MediaAnalyticsItem> random_key(MediaAnalyticsItem::create("random_key"));
+ std::unique_ptr<mediametrics::Item> random_key(mediametrics::Item::create("random_key"));
status = mediaMetrics->submit(random_key.get());
ASSERT_EQ(PERMISSION_DENIED, status);
@@ -50,7 +201,7 @@
ASSERT_EQ(PERMISSION_DENIED, status);
// known keys ignored if empty
- std::unique_ptr<MediaAnalyticsItem> audiotrack_key(MediaAnalyticsItem::create("audiotrack"));
+ std::unique_ptr<mediametrics::Item> audiotrack_key(mediametrics::Item::create("audiotrack"));
status = mediaMetrics->submit(audiotrack_key.get());
ASSERT_EQ(BAD_VALUE, status);
@@ -62,7 +213,7 @@
/*
// fluent style that goes directly to mediametrics
- ASSERT_EQ(true, MediaAnalyticsItem("audiorecord")
+ ASSERT_EQ(true, mediametrics::Item("audiorecord")
.setInt32("value", 2)
.addInt32("bar", 1)
.addInt32("value", 3)
@@ -72,8 +223,34 @@
mediaMetrics->dump(fileno(stdout), {} /* args */);
}
+TEST(mediametrics_tests, package_installer_check) {
+ ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+ "abcd", "installer")); // ok, package name has no dot.
+ ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+ "android.com", "installer")); // ok, package name starts with android
+
+ ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+ "abc.def", "com.android.foo")); // ok, installer name starts with com.android
+ ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+ "123.456", "com.google.bar")); // ok, installer name starts with com.google
+ ASSERT_EQ(false, MediaMetricsService::useUidForPackage(
+ "r2.d2", "preload")); // ok, installer name is preload
+
+ ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+ "abc.def", "installer")); // unknown installer
+ ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+ "123.456", "installer")); // unknown installer
+ ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+ "r2.d2", "preload23")); // unknown installer
+
+ ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+ "com.android.foo", "abc.def")); // unknown installer
+ ASSERT_EQ(true, MediaMetricsService::useUidForPackage(
+ "com.google.bar", "123.456")); // unknown installer
+}
+
TEST(mediametrics_tests, item_manipulation) {
- MediaAnalyticsItem item("audiorecord");
+ mediametrics::Item item("audiorecord");
item.setInt32("value", 2).addInt32("bar", 3).addInt32("value", 4);
@@ -128,14 +305,14 @@
printf("item: %s\n", item.toString().c_str());
fflush(stdout);
- sp mediaMetrics = new MediaAnalyticsService();
+ sp mediaMetrics = new MediaMetricsService();
status_t status = mediaMetrics->submit(&item);
ASSERT_EQ(NO_ERROR, status);
mediaMetrics->dump(fileno(stdout), {} /* args */);
}
TEST(mediametrics_tests, superbig_item) {
- MediaAnalyticsItem item("TheBigOne");
+ mediametrics::Item item("TheBigOne");
constexpr size_t count = 10000;
for (size_t i = 0; i < count; ++i) {
@@ -149,7 +326,7 @@
}
TEST(mediametrics_tests, superbig_item_removal) {
- MediaAnalyticsItem item("TheOddBigOne");
+ mediametrics::Item item("TheOddBigOne");
constexpr size_t count = 10000;
for (size_t i = 0; i < count; ++i) {
@@ -170,7 +347,7 @@
}
TEST(mediametrics_tests, superbig_item_removal2) {
- MediaAnalyticsItem item("TheOne");
+ mediametrics::Item item("TheOne");
constexpr size_t count = 10000;
for (size_t i = 0; i < count; ++i) {
@@ -191,7 +368,7 @@
}
TEST(mediametrics_tests, item_transmutation) {
- MediaAnalyticsItem item("Alchemist's Stone");
+ mediametrics::Item item("Alchemist's Stone");
item.setInt64("convert", 123);
int64_t i64;
@@ -207,7 +384,7 @@
}
TEST(mediametrics_tests, item_binderization) {
- MediaAnalyticsItem item;
+ mediametrics::Item item;
item.setInt32("i32", 1)
.setInt64("i64", 2)
.setDouble("double", 3.1)
@@ -218,14 +395,14 @@
item.writeToParcel(&p);
p.setDataPosition(0); // rewind for reading
- MediaAnalyticsItem item2;
+ mediametrics::Item item2;
item2.readFromParcel(p);
ASSERT_EQ(item, item2);
}
TEST(mediametrics_tests, item_byteserialization) {
- MediaAnalyticsItem item;
+ mediametrics::Item item;
item.setInt32("i32", 1)
.setInt64("i64", 2)
.setDouble("double", 3.1)
@@ -237,7 +414,7 @@
ASSERT_EQ(0, item.writeToByteString(&data, &length));
ASSERT_GT(length, (size_t)0);
- MediaAnalyticsItem item2;
+ mediametrics::Item item2;
item2.readFromByteString(data, length);
printf("item: %s\n", item.toString().c_str());
@@ -248,7 +425,7 @@
}
TEST(mediametrics_tests, item_iteration) {
- MediaAnalyticsItem item;
+ mediametrics::Item item;
item.setInt32("i32", 1)
.setInt64("i64", 2)
.setDouble("double", 3.125)
@@ -262,27 +439,32 @@
int32_t i32;
ASSERT_TRUE(prop.get(&i32));
ASSERT_EQ(1, i32);
+ ASSERT_EQ(1, std::get<int32_t>(prop.get()));
mask |= 1;
} else if (!strcmp(name, "i64")) {
int64_t i64;
ASSERT_TRUE(prop.get(&i64));
ASSERT_EQ(2, i64);
+ ASSERT_EQ(2, std::get<int64_t>(prop.get()));
mask |= 2;
} else if (!strcmp(name, "double")) {
double d;
ASSERT_TRUE(prop.get(&d));
ASSERT_EQ(3.125, d);
+ ASSERT_EQ(3.125, std::get<double>(prop.get()));
mask |= 4;
} else if (!strcmp(name, "string")) {
- const char *s;
+ std::string s;
ASSERT_TRUE(prop.get(&s));
- ASSERT_EQ(0, strcmp(s, "abc"));
+ ASSERT_EQ("abc", s);
+ ASSERT_EQ(s, std::get<std::string>(prop.get()));
mask |= 8;
} else if (!strcmp(name, "rate")) {
std::pair<int64_t, int64_t> r;
ASSERT_TRUE(prop.get(&r));
ASSERT_EQ(11, r.first);
ASSERT_EQ(12, r.second);
+ ASSERT_EQ(r, std::get<decltype(r)>(prop.get()));
mask |= 16;
} else {
FAIL();
@@ -292,7 +474,7 @@
}
TEST(mediametrics_tests, item_expansion) {
- mediametrics::Item<1> item("I");
+ mediametrics::LogItem<1> item("I");
item.set("i32", (int32_t)1)
.set("i64", (int64_t)2)
.set("double", (double)3.125)
@@ -300,7 +482,7 @@
.set("rate", std::pair<int64_t, int64_t>(11, 12));
ASSERT_TRUE(item.updateHeader());
- MediaAnalyticsItem item2;
+ mediametrics::Item item2;
item2.readFromByteString(item.getBuffer(), item.getLength());
ASSERT_EQ((pid_t)-1, item2.getPid());
ASSERT_EQ((uid_t)-1, item2.getUid());
@@ -323,9 +505,9 @@
ASSERT_EQ(3.125, d);
mask |= 4;
} else if (!strcmp(name, "string")) {
- const char *s;
+ std::string s;
ASSERT_TRUE(prop.get(&s));
- ASSERT_EQ(0, strcmp(s, "abcdefghijklmnopqrstuvwxyz"));
+ ASSERT_EQ("abcdefghijklmnopqrstuvwxyz", s);
mask |= 8;
} else if (!strcmp(name, "rate")) {
std::pair<int64_t, int64_t> r;
@@ -341,7 +523,7 @@
}
TEST(mediametrics_tests, item_expansion2) {
- mediametrics::Item<1> item("Bigly");
+ mediametrics::LogItem<1> item("Bigly");
item.setPid(123)
.setUid(456);
constexpr size_t count = 10000;
@@ -352,7 +534,7 @@
}
ASSERT_TRUE(item.updateHeader());
- MediaAnalyticsItem item2;
+ mediametrics::Item item2;
printf("begin buffer:%p length:%zu\n", item.getBuffer(), item.getLength());
fflush(stdout);
item2.readFromByteString(item.getBuffer(), item.getLength());
@@ -367,7 +549,7 @@
}
TEST(mediametrics_tests, time_machine_storage) {
- auto item = std::make_shared<MediaAnalyticsItem>("Key");
+ auto item = std::make_shared<mediametrics::Item>("Key");
(*item).set("i32", (int32_t)1)
.set("i64", (int64_t)2)
.set("double", (double)3.125)
@@ -414,21 +596,21 @@
}
TEST(mediametrics_tests, time_machine_remote_key) {
- auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ auto item = std::make_shared<mediametrics::Item>("Key1");
(*item).set("one", (int32_t)1)
.set("two", (int32_t)2);
android::mediametrics::TimeMachine timeMachine;
ASSERT_EQ(NO_ERROR, timeMachine.put(item, true));
- auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ auto item2 = std::make_shared<mediametrics::Item>("Key2");
(*item2).set("three", (int32_t)3)
.set("[Key1]four", (int32_t)4) // affects Key1
.set("[Key1]five", (int32_t)5); // affects key1
ASSERT_EQ(NO_ERROR, timeMachine.put(item2, true));
- auto item3 = std::make_shared<MediaAnalyticsItem>("Key2");
+ auto item3 = std::make_shared<mediametrics::Item>("Key2");
(*item3).set("six", (int32_t)6)
.set("[Key1]seven", (int32_t)7); // affects Key1
@@ -464,7 +646,7 @@
}
TEST(mediametrics_tests, time_machine_gc) {
- auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ auto item = std::make_shared<mediametrics::Item>("Key1");
(*item).set("one", (int32_t)1)
.set("two", (int32_t)2)
.setTimestamp(10);
@@ -477,7 +659,7 @@
ASSERT_EQ((size_t)1, timeMachine.size());
- auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ auto item2 = std::make_shared<mediametrics::Item>("Key2");
(*item2).set("three", (int32_t)3)
.set("[Key1]three", (int32_t)3)
.setTimestamp(11);
@@ -487,7 +669,7 @@
//printf("Before\n%s\n\n", timeMachine.dump().c_str());
- auto item3 = std::make_shared<MediaAnalyticsItem>("Key3");
+ auto item3 = std::make_shared<mediametrics::Item>("Key3");
(*item3).set("six", (int32_t)6)
.set("[Key1]four", (int32_t)4) // affects Key1
.set("[Key1]five", (int32_t)5) // affects key1
@@ -515,7 +697,7 @@
}
TEST(mediametrics_tests, transaction_log_gc) {
- auto item = std::make_shared<MediaAnalyticsItem>("Key1");
+ auto item = std::make_shared<mediametrics::Item>("Key1");
(*item).set("one", (int32_t)1)
.set("two", (int32_t)2)
.setTimestamp(10);
@@ -526,7 +708,7 @@
ASSERT_EQ(NO_ERROR, transactionLog.put(item));
ASSERT_EQ((size_t)1, transactionLog.size());
- auto item2 = std::make_shared<MediaAnalyticsItem>("Key2");
+ auto item2 = std::make_shared<mediametrics::Item>("Key2");
(*item2).set("three", (int32_t)3)
.set("[Key1]three", (int32_t)3)
.setTimestamp(11);
@@ -534,7 +716,7 @@
ASSERT_EQ(NO_ERROR, transactionLog.put(item2));
ASSERT_EQ((size_t)2, transactionLog.size());
- auto item3 = std::make_shared<MediaAnalyticsItem>("Key3");
+ auto item3 = std::make_shared<mediametrics::Item>("Key3");
(*item3).set("six", (int32_t)6)
.set("[Key1]four", (int32_t)4) // affects Key1
.set("[Key1]five", (int32_t)5) // affects key1
@@ -544,17 +726,72 @@
ASSERT_EQ((size_t)2, transactionLog.size());
}
+TEST(mediametrics_tests, analytics_actions) {
+ mediametrics::AnalyticsActions analyticsActions;
+ bool action1 = false;
+ bool action2 = false;
+ bool action3 = false;
+ bool action4 = false;
+
+ // check to see whether various actions have been matched.
+ analyticsActions.addAction(
+ "audio.flinger.event",
+ std::string("AudioFlinger"),
+ std::make_shared<mediametrics::AnalyticsActions::Function>(
+ [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+ action1 = true;
+ }));
+
+ analyticsActions.addAction(
+ "audio.*.event",
+ std::string("AudioFlinger"),
+ std::make_shared<mediametrics::AnalyticsActions::Function>(
+ [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+ action2 = true;
+ }));
+
+ analyticsActions.addAction("audio.fl*n*g*r.event",
+ std::string("AudioFlinger"),
+ std::make_shared<mediametrics::AnalyticsActions::Function>(
+ [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+ action3 = true;
+ }));
+
+ analyticsActions.addAction("audio.fl*gn*r.event",
+ std::string("AudioFlinger"),
+ std::make_shared<mediametrics::AnalyticsActions::Function>(
+ [&](const std::shared_ptr<const android::mediametrics::Item> &) {
+ action4 = true;
+ }));
+
+ // make a test item
+ auto item = std::make_shared<mediametrics::Item>("audio.flinger");
+ (*item).set("event", "AudioFlinger");
+
+ // get the actions and execute them
+ auto actions = analyticsActions.getActionsForItem(item);
+ for (const auto& action : actions) {
+ action->operator()(item);
+ }
+
+ // The following should match.
+ ASSERT_EQ(true, action1);
+ ASSERT_EQ(true, action2);
+ ASSERT_EQ(true, action3);
+ ASSERT_EQ(false, action4); // audio.fl*gn*r != audio.flinger
+}
+
TEST(mediametrics_tests, audio_analytics_permission) {
- auto item = std::make_shared<MediaAnalyticsItem>("audio.1");
+ auto item = std::make_shared<mediametrics::Item>("audio.1");
(*item).set("one", (int32_t)1)
.set("two", (int32_t)2)
.setTimestamp(10);
- auto item2 = std::make_shared<MediaAnalyticsItem>("audio.1");
+ auto item2 = std::make_shared<mediametrics::Item>("audio.1");
(*item2).set("three", (int32_t)3)
.setTimestamp(11);
- auto item3 = std::make_shared<MediaAnalyticsItem>("audio.2");
+ auto item3 = std::make_shared<mediametrics::Item>("audio.2");
(*item3).set("four", (int32_t)4)
.setTimestamp(12);
@@ -566,27 +803,27 @@
// TODO: Verify contents of AudioAnalytics.
// Currently there is no getter API in AudioAnalytics besides dump.
- ASSERT_EQ(4, audioAnalytics.dump(1000).second /* lines */);
+ ASSERT_EQ(9, audioAnalytics.dump(1000).second /* lines */);
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item, true /* isTrusted */));
// untrusted entities can add to an existing key
ASSERT_EQ(NO_ERROR, audioAnalytics.submit(item2, false /* isTrusted */));
// Check that we have some info in the dump.
- ASSERT_LT(4, audioAnalytics.dump(1000).second /* lines */);
+ ASSERT_LT(9, audioAnalytics.dump(1000).second /* lines */);
}
TEST(mediametrics_tests, audio_analytics_dump) {
- auto item = std::make_shared<MediaAnalyticsItem>("audio.1");
+ auto item = std::make_shared<mediametrics::Item>("audio.1");
(*item).set("one", (int32_t)1)
.set("two", (int32_t)2)
.setTimestamp(10);
- auto item2 = std::make_shared<MediaAnalyticsItem>("audio.1");
+ auto item2 = std::make_shared<mediametrics::Item>("audio.1");
(*item2).set("three", (int32_t)3)
.setTimestamp(11);
- auto item3 = std::make_shared<MediaAnalyticsItem>("audio.2");
+ auto item3 = std::make_shared<mediametrics::Item>("audio.2");
(*item3).set("four", (int32_t)4)
.setTimestamp(12);
diff --git a/services/minijail/Android.bp b/services/minijail/Android.bp
index 07a94cc..0713a87 100644
--- a/services/minijail/Android.bp
+++ b/services/minijail/Android.bp
@@ -17,10 +17,14 @@
cc_library_shared {
name: "libavservices_minijail",
defaults: ["libavservices_minijail_defaults"],
+ vendor_available: true,
export_include_dirs: ["."],
}
-// Small library for media.extractor and media.codec sandboxing.
+// By adding "vendor_available: true" to "libavservices_minijail", we don't
+// need to have "libavservices_minijail_vendor" any longer.
+// "libavservices_minijail_vendor" will be removed, once we replace it with
+// "libavservices_minijail" in all vendor modules. (b/146313710)
cc_library_shared {
name: "libavservices_minijail_vendor",
vendor: true,
diff --git a/services/oboeservice/Android.bp b/services/oboeservice/Android.bp
index ca1354d..64d835b 100644
--- a/services/oboeservice/Android.bp
+++ b/services/oboeservice/Android.bp
@@ -55,4 +55,12 @@
"libutils",
],
+ header_libs: [
+ "libaudiohal_headers",
+ ],
+
+ include_dirs: [
+ "frameworks/av/media/libnbaio/include_mono",
+ "frameworks/av/media/libnbaio/include",
+ ],
}
diff --git a/services/soundtrigger/Android.bp b/services/soundtrigger/Android.bp
deleted file mode 100644
index 600f4a3..0000000
--- a/services/soundtrigger/Android.bp
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright 2014 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.
-
-cc_library_shared {
- name: "libsoundtriggerservice",
-
- srcs: [
- "SoundTriggerHwService.cpp",
- "SoundTriggerHalHidl.cpp",
- ],
-
- shared_libs: [
- "liblog",
- "libutils",
- "libbinder",
- "libcutils",
- "libhardware",
- "libsoundtrigger",
- "libaudioclient",
- "libaudioutils",
- "libmediautils",
-
- "libhidlbase",
- "libhidlmemory",
- "libbase",
- "libaudiohal",
- "libaudiohal_deathhandler",
- "android.hardware.soundtrigger@2.0",
- "android.hardware.soundtrigger@2.1",
- "android.hardware.soundtrigger@2.2",
- "android.hardware.soundtrigger@2.3",
- "android.hardware.audio.common@2.0",
- "android.hidl.allocator@1.0",
- "android.hidl.memory@1.0",
- ],
-
- include_dirs: ["frameworks/av/services/audioflinger"],
-
- cflags: [
- "-Wall",
- "-Werror",
- ],
-}
diff --git a/services/soundtrigger/OWNERS b/services/soundtrigger/OWNERS
deleted file mode 100644
index e83f6b9..0000000
--- a/services/soundtrigger/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-elaurent@google.com
-thorntonc@google.com
diff --git a/services/soundtrigger/SoundTriggerHalHidl.cpp b/services/soundtrigger/SoundTriggerHalHidl.cpp
deleted file mode 100644
index b1d34df..0000000
--- a/services/soundtrigger/SoundTriggerHalHidl.cpp
+++ /dev/null
@@ -1,999 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_TAG "SoundTriggerHalHidl"
-//#define LOG_NDEBUG 0
-
-#include <android/hidl/allocator/1.0/IAllocator.h>
-#include <media/audiohal/hidl/HalDeathHandler.h>
-#include <utils/Log.h>
-#include "SoundTriggerHalHidl.h"
-#include <hidlmemory/mapping.h>
-#include <hwbinder/IPCThreadState.h>
-#include <hwbinder/ProcessState.h>
-
-namespace android {
-
-using ::android::hardware::ProcessState;
-using ::android::hardware::Return;
-using ::android::hardware::Status;
-using ::android::hardware::Void;
-using ::android::hardware::audio::common::V2_0::AudioDevice;
-using ::android::hardware::hidl_memory;
-using ::android::hidl::allocator::V1_0::IAllocator;
-using ::android::hidl::memory::V1_0::IMemory;
-
-namespace {
-
-// Backs up by the vector with the contents of shared memory.
-// It is assumed that the passed hidl_vector is empty, so it's
-// not cleared if the memory is a null object.
-// The caller needs to keep the returned sp<IMemory> as long as
-// the data is needed.
-std::pair<bool, sp<IMemory>> memoryAsVector(const hidl_memory& m, hidl_vec<uint8_t>* vec) {
- sp<IMemory> memory;
- if (m.size() == 0) {
- return std::make_pair(true, memory);
- }
- memory = mapMemory(m);
- if (memory != nullptr) {
- memory->read();
- vec->setToExternal(static_cast<uint8_t*>(static_cast<void*>(memory->getPointer())),
- memory->getSize());
- return std::make_pair(true, memory);
- }
- ALOGE("%s: Could not map HIDL memory to IMemory", __func__);
- return std::make_pair(false, memory);
-}
-
-// Moves the data from the vector into allocated shared memory,
-// emptying the vector.
-// It is assumed that the passed hidl_memory is a null object, so it's
-// not reset if the vector is empty.
-// The caller needs to keep the returned sp<IMemory> as long as
-// the data is needed.
-std::pair<bool, sp<IMemory>> moveVectorToMemory(hidl_vec<uint8_t>* v, hidl_memory* mem) {
- sp<IMemory> memory;
- if (v->size() == 0) {
- return std::make_pair(true, memory);
- }
- sp<IAllocator> ashmem = IAllocator::getService("ashmem");
- if (ashmem == 0) {
- ALOGE("Failed to retrieve ashmem allocator service");
- return std::make_pair(false, memory);
- }
- bool success = false;
- Return<void> r = ashmem->allocate(v->size(), [&](bool s, const hidl_memory& m) {
- success = s;
- if (success) *mem = m;
- });
- if (r.isOk() && success) {
- memory = hardware::mapMemory(*mem);
- if (memory != 0) {
- memory->update();
- memcpy(memory->getPointer(), v->data(), v->size());
- memory->commit();
- v->resize(0);
- return std::make_pair(true, memory);
- } else {
- ALOGE("Failed to map allocated ashmem");
- }
- } else {
- ALOGE("Failed to allocate %llu bytes from ashmem", (unsigned long long)v->size());
- }
- return std::make_pair(false, memory);
-}
-
-} // namespace
-
-/* static */
-sp<SoundTriggerHalInterface> SoundTriggerHalInterface::connectModule(const char *moduleName)
-{
- return new SoundTriggerHalHidl(moduleName);
-}
-
-int SoundTriggerHalHidl::getProperties(struct sound_trigger_properties *properties)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- ISoundTriggerHw::Properties halProperties;
- Return<void> hidlReturn;
- int ret;
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->getProperties([&](int rc, auto res) {
- ret = rc;
- halProperties = res;
- ALOGI("getProperties res implementor %s", res.implementor.c_str());
- });
- }
-
- if (hidlReturn.isOk()) {
- if (ret == 0) {
- convertPropertiesFromHal(properties, &halProperties);
- }
- } else {
- ALOGE("getProperties error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
- ALOGI("getProperties ret %d", ret);
- return ret;
-}
-
-int SoundTriggerHalHidl::loadSoundModel(struct sound_trigger_sound_model *sound_model,
- sound_model_callback_t callback,
- void *cookie,
- sound_model_handle_t *handle)
-{
- if (handle == NULL) {
- return -EINVAL;
- }
-
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- uint32_t modelId;
- {
- AutoMutex lock(mLock);
- do {
- modelId = nextUniqueId();
- ALOGI("loadSoundModel modelId %u", modelId);
- sp<SoundModel> model = mSoundModels.valueFor(modelId);
- ALOGI("loadSoundModel model %p", model.get());
- } while (mSoundModels.valueFor(modelId) != 0 && modelId != 0);
- }
- LOG_ALWAYS_FATAL_IF(modelId == 0,
- "loadSoundModel(): wrap around in sound model IDs, num loaded models %zd",
- mSoundModels.size());
-
- Return<void> hidlReturn;
- int ret;
- SoundModelHandle halHandle;
- sp<V2_1_ISoundTriggerHw> soundtrigger_2_1 = toService2_1(soundtrigger);
- sp<V2_2_ISoundTriggerHw> soundtrigger_2_2 = toService2_2(soundtrigger);
- if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- if (soundtrigger_2_2) {
- V2_2_ISoundTriggerHw::PhraseSoundModel halSoundModel;
- auto result = convertPhraseSoundModelToHal(&halSoundModel, sound_model);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_2->loadPhraseSoundModel_2_1(
- halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- } else {
- return NO_MEMORY;
- }
- } else if (soundtrigger_2_1) {
- V2_1_ISoundTriggerHw::PhraseSoundModel halSoundModel;
- auto result = convertPhraseSoundModelToHal(&halSoundModel, sound_model);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_1->loadPhraseSoundModel_2_1(
- halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- } else {
- return NO_MEMORY;
- }
- } else {
- ISoundTriggerHw::PhraseSoundModel halSoundModel;
- convertPhraseSoundModelToHal(&halSoundModel, sound_model);
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->loadPhraseSoundModel(
- halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- }
- } else {
- if (soundtrigger_2_2) {
- V2_2_ISoundTriggerHw::SoundModel halSoundModel;
- auto result = convertSoundModelToHal(&halSoundModel, sound_model);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_2->loadSoundModel_2_1(halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- } else {
- return NO_MEMORY;
- }
- } else if (soundtrigger_2_1) {
- V2_1_ISoundTriggerHw::SoundModel halSoundModel;
- auto result = convertSoundModelToHal(&halSoundModel, sound_model);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_1->loadSoundModel_2_1(halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- } else {
- return NO_MEMORY;
- }
- } else {
- ISoundTriggerHw::SoundModel halSoundModel;
- convertSoundModelToHal(&halSoundModel, sound_model);
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->loadSoundModel(halSoundModel,
- this, modelId, [&](int32_t retval, auto res) {
- ret = retval;
- halHandle = res;
- });
- }
- }
-
- if (hidlReturn.isOk()) {
- if (ret == 0) {
- AutoMutex lock(mLock);
- *handle = (sound_model_handle_t)modelId;
- sp<SoundModel> model = new SoundModel(*handle, callback, cookie, halHandle);
- mSoundModels.add(*handle, model);
- }
- } else {
- ALOGE("loadSoundModel error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
-
- return ret;
-}
-
-int SoundTriggerHalHidl::unloadSoundModel(sound_model_handle_t handle)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- sp<SoundModel> model = removeModel(handle);
- if (model == 0) {
- ALOGE("unloadSoundModel model not found for handle %u", handle);
- return -EINVAL;
- }
-
- Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->unloadSoundModel(model->mHalHandle);
- }
-
- if (!hidlReturn.isOk()) {
- ALOGE("unloadSoundModel error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
-
- return hidlReturn;
-}
-
-int SoundTriggerHalHidl::startRecognition(sound_model_handle_t handle,
- const struct sound_trigger_recognition_config *config,
- recognition_callback_t callback,
- void *cookie)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (model == 0) {
- ALOGE("startRecognition model not found for handle %u", handle);
- return -EINVAL;
- }
-
- model->mRecognitionCallback = callback;
- model->mRecognitionCookie = cookie;
-
- sp<V2_1_ISoundTriggerHw> soundtrigger_2_1 = toService2_1(soundtrigger);
- sp<V2_2_ISoundTriggerHw> soundtrigger_2_2 = toService2_2(soundtrigger);
- Return<int32_t> hidlReturn(0);
-
- if (soundtrigger_2_2) {
- V2_2_ISoundTriggerHw::RecognitionConfig halConfig;
- auto result = convertRecognitionConfigToHal(&halConfig, config);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_2->startRecognition_2_1(
- model->mHalHandle, halConfig, this, handle);
- } else {
- return NO_MEMORY;
- }
- } else if (soundtrigger_2_1) {
- V2_1_ISoundTriggerHw::RecognitionConfig halConfig;
- auto result = convertRecognitionConfigToHal(&halConfig, config);
- if (result.first) {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_1->startRecognition_2_1(
- model->mHalHandle, halConfig, this, handle);
- } else {
- return NO_MEMORY;
- }
- } else {
- ISoundTriggerHw::RecognitionConfig halConfig;
- convertRecognitionConfigToHal(&halConfig, config);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->startRecognition(model->mHalHandle, halConfig, this, handle);
- }
- }
-
- if (!hidlReturn.isOk()) {
- ALOGE("startRecognition error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
- return hidlReturn;
-}
-
-int SoundTriggerHalHidl::stopRecognition(sound_model_handle_t handle)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (model == 0) {
- ALOGE("stopRecognition model not found for handle %u", handle);
- return -EINVAL;
- }
-
- Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->stopRecognition(model->mHalHandle);
- }
-
- if (!hidlReturn.isOk()) {
- ALOGE("stopRecognition error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
- return hidlReturn;
-}
-
-int SoundTriggerHalHidl::stopAllRecognitions()
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger->stopAllRecognitions();
- }
-
- if (!hidlReturn.isOk()) {
- ALOGE("stopAllRecognitions error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
- return hidlReturn;
-}
-
-int SoundTriggerHalHidl::getModelState(sound_model_handle_t handle)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (soundtrigger == 0) {
- return -ENODEV;
- }
-
- sp<V2_2_ISoundTriggerHw> soundtrigger_2_2 = toService2_2(soundtrigger);
- if (soundtrigger_2_2 == 0) {
- ALOGE("getModelState not supported");
- return -ENODEV;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (model == 0) {
- ALOGE("getModelState model not found for handle %u", handle);
- return -EINVAL;
- }
-
- int ret = NO_ERROR;
- Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_2->getModelState(model->mHalHandle);
- }
- if (!hidlReturn.isOk()) {
- ALOGE("getModelState error %s", hidlReturn.description().c_str());
- ret = FAILED_TRANSACTION;
- }
- return ret;
-}
-
-int SoundTriggerHalHidl::setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param, int32_t value)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (!soundtrigger) {
- return -ENODEV;
- }
-
- sp<V2_3_ISoundTriggerHw> soundtrigger_2_3 = toService2_3(soundtrigger);
- if (!soundtrigger_2_3) {
- ALOGE("setParameter not supported");
- return -ENOSYS;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (!model) {
- ALOGE("setParameter model not found for handle %u", handle);
- return -EINVAL;
- }
-
- V2_3_ModelParameter halParam;
- convertModelParameterToHal(&halParam, model_param);
-
- Return<int32_t> hidlReturn(0);
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_3->setParameter(model->mHalHandle, halParam, value);
- }
- if (!hidlReturn.isOk()) {
- ALOGE("getModelState error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
-
- return hidlReturn;
-}
-
-int SoundTriggerHalHidl::getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param, int32_t* value)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (!soundtrigger) {
- return -ENODEV;
- }
-
- sp<V2_3_ISoundTriggerHw> soundtrigger_2_3 = toService2_3(soundtrigger);
- if (!soundtrigger_2_3) {
- ALOGE("getParameter not supported");
- return -ENOSYS;
- }
-
- if (value == NULL) {
- ALOGE("getParameter invalid value pointer");
- return -EINVAL;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (!model) {
- ALOGE("getParameter model not found for handle %u", handle);
- return -EINVAL;
- }
-
- V2_3_ModelParameter halParam;
- convertModelParameterToHal(&halParam, model_param);
-
- Return<void> hidlReturn;
- int32_t hidlStatus;
- int32_t hidlValue;
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_3->getParameter(model->mHalHandle, halParam,
- [&](int32_t retStatus, int32_t retValue) {
- hidlStatus = retStatus;
- hidlValue = retValue;
- });
- }
- if (!hidlReturn.isOk()) {
- ALOGE("getModelState error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
-
- *value = hidlValue;
- return hidlStatus;
-}
-
-int SoundTriggerHalHidl::queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param,
- sound_trigger_model_parameter_range_t* param_range)
-{
- sp<ISoundTriggerHw> soundtrigger = getService();
- if (!soundtrigger) {
- return -ENODEV;
- }
-
- sp<V2_3_ISoundTriggerHw> soundtrigger_2_3 = toService2_3(soundtrigger);
- if (!soundtrigger_2_3) {
- ALOGE("queryParameter not supported");
- return -ENOSYS;
- }
-
- sp<SoundModel> model = getModel(handle);
- if (!model) {
- ALOGE("queryParameter model not found for handle %u", handle);
- return -EINVAL;
- }
-
- V2_3_ModelParameter halParam;
- convertModelParameterToHal(&halParam, model_param);
-
- Return<void> hidlReturn;
- int32_t hidlStatus;
- V2_3_OptionalModelParameterRange hidlValue;
- {
- AutoMutex lock(mHalLock);
- hidlReturn = soundtrigger_2_3->queryParameter(model->mHalHandle, halParam,
- [&](int32_t retStatus, V2_3_OptionalModelParameterRange retValue) {
- hidlStatus = retStatus;
- hidlValue = retValue;
- });
- }
- if (!hidlReturn.isOk()) {
- ALOGE("queryParameter error %s", hidlReturn.description().c_str());
- return FAILED_TRANSACTION;
- }
-
- if (hidlStatus != 0) {
- ALOGE("queryParameter error code: %d", hidlStatus);
- return hidlStatus;
- }
-
- if (hidlValue.getDiscriminator() ==
- V2_3_OptionalModelParameterRange::hidl_discriminator::noinit) {
- return -1;
- }
-
- param_range->start = hidlValue.range().start;
- param_range->end = hidlValue.range().end;
-
- return 0;
-}
-
-SoundTriggerHalHidl::SoundTriggerHalHidl(const char *moduleName)
- : mModuleName(moduleName), mNextUniqueId(1)
-{
- LOG_ALWAYS_FATAL_IF(strcmp(mModuleName, "primary") != 0,
- "Treble soundtrigger only supports primary module");
-}
-
-SoundTriggerHalHidl::~SoundTriggerHalHidl()
-{
-}
-
-sp<ISoundTriggerHw> SoundTriggerHalHidl::getService()
-{
- AutoMutex lock(mLock);
- if (mISoundTrigger == 0) {
- if (mModuleName == NULL) {
- mModuleName = "primary";
- }
- mISoundTrigger = ISoundTriggerHw::getService();
- if (mISoundTrigger != 0) {
- mISoundTrigger->linkToDeath(HalDeathHandler::getInstance(), 0 /*cookie*/);
- }
- }
- return mISoundTrigger;
-}
-
-sp<V2_1_ISoundTriggerHw> SoundTriggerHalHidl::toService2_1(const sp<ISoundTriggerHw>& s)
-{
- auto castResult_2_1 = V2_1_ISoundTriggerHw::castFrom(s);
- return castResult_2_1.isOk() ? static_cast<sp<V2_1_ISoundTriggerHw>>(castResult_2_1) : nullptr;
-}
-
-sp<V2_2_ISoundTriggerHw> SoundTriggerHalHidl::toService2_2(const sp<ISoundTriggerHw>& s)
-{
- auto castResult_2_2 = V2_2_ISoundTriggerHw::castFrom(s);
- return castResult_2_2.isOk() ? static_cast<sp<V2_2_ISoundTriggerHw>>(castResult_2_2) : nullptr;
-}
-
-sp<V2_3_ISoundTriggerHw> SoundTriggerHalHidl::toService2_3(const sp<ISoundTriggerHw>& s)
-{
- auto castResult_3_0 = V2_3_ISoundTriggerHw::castFrom(s);
- return castResult_3_0.isOk() ? static_cast<sp<V2_3_ISoundTriggerHw>>(castResult_3_0) : nullptr;
-}
-
-sp<SoundTriggerHalHidl::SoundModel> SoundTriggerHalHidl::getModel(sound_model_handle_t handle)
-{
- AutoMutex lock(mLock);
- return mSoundModels.valueFor(handle);
-}
-
-sp<SoundTriggerHalHidl::SoundModel> SoundTriggerHalHidl::removeModel(sound_model_handle_t handle)
-{
- AutoMutex lock(mLock);
- sp<SoundModel> model = mSoundModels.valueFor(handle);
- mSoundModels.removeItem(handle);
- return model;
-}
-
-uint32_t SoundTriggerHalHidl::nextUniqueId()
-{
- return (uint32_t) atomic_fetch_add_explicit(&mNextUniqueId,
- (uint_fast32_t) 1, memory_order_acq_rel);
-}
-
-void SoundTriggerHalHidl::convertUuidToHal(Uuid *halUuid,
- const sound_trigger_uuid_t *uuid)
-{
- halUuid->timeLow = uuid->timeLow;
- halUuid->timeMid = uuid->timeMid;
- halUuid->versionAndTimeHigh = uuid->timeHiAndVersion;
- halUuid->variantAndClockSeqHigh = uuid->clockSeq;
- memcpy(halUuid->node.data(), &uuid->node[0], sizeof(uuid->node));
-}
-
-void SoundTriggerHalHidl::convertUuidFromHal(sound_trigger_uuid_t *uuid,
- const Uuid *halUuid)
-{
- uuid->timeLow = halUuid->timeLow;
- uuid->timeMid = halUuid->timeMid;
- uuid->timeHiAndVersion = halUuid->versionAndTimeHigh;
- uuid->clockSeq = halUuid->variantAndClockSeqHigh;
- memcpy(&uuid->node[0], halUuid->node.data(), sizeof(uuid->node));
-}
-
-void SoundTriggerHalHidl::convertPropertiesFromHal(
- struct sound_trigger_properties *properties,
- const ISoundTriggerHw::Properties *halProperties)
-{
- strlcpy(properties->implementor,
- halProperties->implementor.c_str(), SOUND_TRIGGER_MAX_STRING_LEN);
- strlcpy(properties->description,
- halProperties->description.c_str(), SOUND_TRIGGER_MAX_STRING_LEN);
- properties->version = halProperties->version;
- convertUuidFromHal(&properties->uuid, &halProperties->uuid);
- properties->max_sound_models = halProperties->maxSoundModels;
- properties->max_key_phrases = halProperties->maxKeyPhrases;
- properties->max_users = halProperties->maxUsers;
- properties->recognition_modes = halProperties->recognitionModes;
- properties->capture_transition = (bool)halProperties->captureTransition;
- properties->max_buffer_ms = halProperties->maxBufferMs;
- properties->concurrent_capture = (bool)halProperties->concurrentCapture;
- properties->trigger_in_event = (bool)halProperties->triggerInEvent;
- properties->power_consumption_mw = halProperties->powerConsumptionMw;
-}
-
-// static
-void SoundTriggerHalHidl::convertModelParameterToHal(V2_3_ModelParameter* halParam,
- sound_trigger_model_parameter_t param)
-{
- switch (param) {
- case MODEL_PARAMETER_THRESHOLD_FACTOR:
- *halParam = V2_3_ModelParameter::THRESHOLD_FACTOR;
- return;
- case MODEL_PARAMETER_INVALID:
- default:
- *halParam = V2_3_ModelParameter::INVALID;
- }
-}
-
-void SoundTriggerHalHidl::convertTriggerPhraseToHal(
- ISoundTriggerHw::Phrase *halTriggerPhrase,
- const struct sound_trigger_phrase *triggerPhrase)
-{
- halTriggerPhrase->id = triggerPhrase->id;
- halTriggerPhrase->recognitionModes = triggerPhrase->recognition_mode;
- halTriggerPhrase->users.setToExternal((uint32_t *)&triggerPhrase->users[0], triggerPhrase->num_users);
- halTriggerPhrase->locale = triggerPhrase->locale;
- halTriggerPhrase->text = triggerPhrase->text;
-}
-
-
-void SoundTriggerHalHidl::convertTriggerPhrasesToHal(
- hidl_vec<ISoundTriggerHw::Phrase> *halTriggerPhrases,
- struct sound_trigger_phrase_sound_model *keyPhraseModel)
-{
- halTriggerPhrases->resize(keyPhraseModel->num_phrases);
- for (unsigned int i = 0; i < keyPhraseModel->num_phrases; i++) {
- convertTriggerPhraseToHal(&(*halTriggerPhrases)[i], &keyPhraseModel->phrases[i]);
- }
-}
-
-void SoundTriggerHalHidl::convertSoundModelToHal(ISoundTriggerHw::SoundModel *halModel,
- const struct sound_trigger_sound_model *soundModel)
-{
- halModel->type = (SoundModelType)soundModel->type;
- convertUuidToHal(&halModel->uuid, &soundModel->uuid);
- convertUuidToHal(&halModel->vendorUuid, &soundModel->vendor_uuid);
- halModel->data.setToExternal((uint8_t *)soundModel + soundModel->data_offset, soundModel->data_size);
-}
-
-std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertSoundModelToHal(
- V2_1_ISoundTriggerHw::SoundModel *halModel,
- const struct sound_trigger_sound_model *soundModel)
-{
- convertSoundModelToHal(&halModel->header, soundModel);
- return moveVectorToMemory(&halModel->header.data, &halModel->data);
-}
-
-void SoundTriggerHalHidl::convertPhraseSoundModelToHal(
- ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
- const struct sound_trigger_sound_model *soundModel)
-{
- struct sound_trigger_phrase_sound_model *keyPhraseModel =
- (struct sound_trigger_phrase_sound_model *)soundModel;
- convertTriggerPhrasesToHal(&halKeyPhraseModel->phrases, keyPhraseModel);
- convertSoundModelToHal(&halKeyPhraseModel->common, soundModel);
-}
-
-std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertPhraseSoundModelToHal(
- V2_1_ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
- const struct sound_trigger_sound_model *soundModel)
-{
- struct sound_trigger_phrase_sound_model *keyPhraseModel =
- (struct sound_trigger_phrase_sound_model *)soundModel;
- convertTriggerPhrasesToHal(&halKeyPhraseModel->phrases, keyPhraseModel);
- return convertSoundModelToHal(&halKeyPhraseModel->common, soundModel);
-}
-
-void SoundTriggerHalHidl::convertPhraseRecognitionExtraToHal(
- PhraseRecognitionExtra *halExtra,
- const struct sound_trigger_phrase_recognition_extra *extra)
-{
- halExtra->id = extra->id;
- halExtra->recognitionModes = extra->recognition_modes;
- halExtra->confidenceLevel = extra->confidence_level;
- halExtra->levels.resize(extra->num_levels);
- for (unsigned int i = 0; i < extra->num_levels; i++) {
- halExtra->levels[i].userId = extra->levels[i].user_id;
- halExtra->levels[i].levelPercent = extra->levels[i].level;
- }
-}
-
-void SoundTriggerHalHidl::convertRecognitionConfigToHal(
- ISoundTriggerHw::RecognitionConfig *halConfig,
- const struct sound_trigger_recognition_config *config)
-{
- halConfig->captureHandle = config->capture_handle;
- halConfig->captureDevice = (AudioDevice)config->capture_device;
- halConfig->captureRequested = (uint32_t)config->capture_requested;
-
- halConfig->phrases.resize(config->num_phrases);
- for (unsigned int i = 0; i < config->num_phrases; i++) {
- convertPhraseRecognitionExtraToHal(&halConfig->phrases[i],
- &config->phrases[i]);
- }
-
- halConfig->data.setToExternal((uint8_t *)config + config->data_offset, config->data_size);
-}
-
-std::pair<bool, sp<IMemory>> SoundTriggerHalHidl::convertRecognitionConfigToHal(
- V2_1_ISoundTriggerHw::RecognitionConfig *halConfig,
- const struct sound_trigger_recognition_config *config)
-{
- convertRecognitionConfigToHal(&halConfig->header, config);
- return moveVectorToMemory(&halConfig->header.data, &halConfig->data);
-}
-
-
-// ISoundTriggerHwCallback
-::android::hardware::Return<void> SoundTriggerHalHidl::recognitionCallback(
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent& halEvent,
- CallbackCookie cookie)
-{
- sp<SoundModel> model;
- {
- AutoMutex lock(mLock);
- model = mSoundModels.valueFor((SoundModelHandle)cookie);
- if (model == 0) {
- return Return<void>();
- }
- }
- struct sound_trigger_recognition_event *event = convertRecognitionEventFromHal(&halEvent);
- if (event == NULL) {
- return Return<void>();
- }
- event->model = model->mHandle;
- model->mRecognitionCallback(event, model->mRecognitionCookie);
-
- free(event);
-
- return Return<void>();
-}
-
-::android::hardware::Return<void> SoundTriggerHalHidl::phraseRecognitionCallback(
- const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent& halEvent,
- CallbackCookie cookie)
-{
- sp<SoundModel> model;
- {
- AutoMutex lock(mLock);
- model = mSoundModels.valueFor((SoundModelHandle)cookie);
- if (model == 0) {
- return Return<void>();
- }
- }
-
- struct sound_trigger_phrase_recognition_event *event =
- convertPhraseRecognitionEventFromHal(&halEvent);
- if (event == NULL) {
- return Return<void>();
- }
- event->common.model = model->mHandle;
- model->mRecognitionCallback(&event->common, model->mRecognitionCookie);
-
- free(event);
-
- return Return<void>();
-}
-
-::android::hardware::Return<void> SoundTriggerHalHidl::soundModelCallback(
- const V2_0_ISoundTriggerHwCallback::ModelEvent& halEvent,
- CallbackCookie cookie)
-{
- sp<SoundModel> model;
- {
- AutoMutex lock(mLock);
- model = mSoundModels.valueFor((SoundModelHandle)cookie);
- if (model == 0) {
- return Return<void>();
- }
- }
-
- struct sound_trigger_model_event *event = convertSoundModelEventFromHal(&halEvent);
- if (event == NULL) {
- return Return<void>();
- }
-
- event->model = model->mHandle;
- model->mSoundModelCallback(event, model->mSoundModelCookie);
-
- free(event);
-
- return Return<void>();
-}
-
-::android::hardware::Return<void> SoundTriggerHalHidl::recognitionCallback_2_1(
- const ISoundTriggerHwCallback::RecognitionEvent& event, CallbackCookie cookie) {
- // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
- V2_0_ISoundTriggerHwCallback::RecognitionEvent event_2_0 = event.header;
- auto result = memoryAsVector(event.data, &event_2_0.data);
- return result.first ? recognitionCallback(event_2_0, cookie) : Void();
-}
-
-::android::hardware::Return<void> SoundTriggerHalHidl::phraseRecognitionCallback_2_1(
- const ISoundTriggerHwCallback::PhraseRecognitionEvent& event, int32_t cookie) {
- V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent event_2_0;
- // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
- event_2_0.common = event.common.header;
- event_2_0.phraseExtras.setToExternal(
- const_cast<PhraseRecognitionExtra*>(event.phraseExtras.data()),
- event.phraseExtras.size());
- auto result = memoryAsVector(event.common.data, &event_2_0.common.data);
- return result.first ? phraseRecognitionCallback(event_2_0, cookie) : Void();
-}
-
-::android::hardware::Return<void> SoundTriggerHalHidl::soundModelCallback_2_1(
- const ISoundTriggerHwCallback::ModelEvent& event, CallbackCookie cookie) {
- // The data vector in the 'header' part of V2.1 structure is empty, thus copying is cheap.
- V2_0_ISoundTriggerHwCallback::ModelEvent event_2_0 = event.header;
- auto result = memoryAsVector(event.data, &event_2_0.data);
- return result.first ? soundModelCallback(event_2_0, cookie) : Void();
-}
-
-
-struct sound_trigger_model_event *SoundTriggerHalHidl::convertSoundModelEventFromHal(
- const V2_0_ISoundTriggerHwCallback::ModelEvent *halEvent)
-{
- struct sound_trigger_model_event *event = (struct sound_trigger_model_event *)malloc(
- sizeof(struct sound_trigger_model_event) +
- halEvent->data.size());
- if (event == NULL) {
- return NULL;
- }
-
- event->status = (int)halEvent->status;
- // event->model to be set by caller
- event->data_offset = sizeof(struct sound_trigger_model_event);
- event->data_size = halEvent->data.size();
- uint8_t *dst = (uint8_t *)event + event->data_offset;
- uint8_t *src = (uint8_t *)&halEvent->data[0];
- memcpy(dst, src, halEvent->data.size());
-
- return event;
-}
-
-void SoundTriggerHalHidl::convertPhraseRecognitionExtraFromHal(
- struct sound_trigger_phrase_recognition_extra *extra,
- const PhraseRecognitionExtra *halExtra)
-{
- extra->id = halExtra->id;
- extra->recognition_modes = halExtra->recognitionModes;
- extra->confidence_level = halExtra->confidenceLevel;
-
- size_t i;
- for (i = 0; i < halExtra->levels.size() && i < SOUND_TRIGGER_MAX_USERS; i++) {
- extra->levels[i].user_id = halExtra->levels[i].userId;
- extra->levels[i].level = halExtra->levels[i].levelPercent;
- }
- extra->num_levels = (unsigned int)i;
-}
-
-
-struct sound_trigger_phrase_recognition_event* SoundTriggerHalHidl::convertPhraseRecognitionEventFromHal(
- const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent *halPhraseEvent)
-{
- if (halPhraseEvent->common.type != SoundModelType::KEYPHRASE) {
- ALOGE("Received non-keyphrase event type as PhraseRecognitionEvent");
- return NULL;
- }
- struct sound_trigger_phrase_recognition_event *phraseEvent =
- (struct sound_trigger_phrase_recognition_event *)malloc(
- sizeof(struct sound_trigger_phrase_recognition_event) +
- halPhraseEvent->common.data.size());
- if (phraseEvent == NULL) {
- return NULL;
- }
- phraseEvent->common.data_offset = sizeof(sound_trigger_phrase_recognition_event);
-
- for (unsigned int i = 0; i < halPhraseEvent->phraseExtras.size(); i++) {
- convertPhraseRecognitionExtraFromHal(&phraseEvent->phrase_extras[i],
- &halPhraseEvent->phraseExtras[i]);
- }
- phraseEvent->num_phrases = halPhraseEvent->phraseExtras.size();
-
- fillRecognitionEventFromHal(&phraseEvent->common, &halPhraseEvent->common);
- return phraseEvent;
-}
-
-struct sound_trigger_recognition_event *SoundTriggerHalHidl::convertRecognitionEventFromHal(
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent)
-{
- if (halEvent->type == SoundModelType::KEYPHRASE) {
- ALOGE("Received keyphrase event type as RecognitionEvent");
- return NULL;
- }
- struct sound_trigger_recognition_event *event;
- event = (struct sound_trigger_recognition_event *)malloc(
- sizeof(struct sound_trigger_recognition_event) + halEvent->data.size());
- if (event == NULL) {
- return NULL;
- }
- event->data_offset = sizeof(sound_trigger_recognition_event);
-
- fillRecognitionEventFromHal(event, halEvent);
- return event;
-}
-
-void SoundTriggerHalHidl::fillRecognitionEventFromHal(
- struct sound_trigger_recognition_event *event,
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent)
-{
- event->status = (int)halEvent->status;
- event->type = (sound_trigger_sound_model_type_t)halEvent->type;
- // event->model to be set by caller
- event->capture_available = (bool)halEvent->captureAvailable;
- event->capture_session = halEvent->captureSession;
- event->capture_delay_ms = halEvent->captureDelayMs;
- event->capture_preamble_ms = halEvent->capturePreambleMs;
- event->trigger_in_data = (bool)halEvent->triggerInData;
- event->audio_config.sample_rate = halEvent->audioConfig.sampleRateHz;
- event->audio_config.channel_mask = (audio_channel_mask_t)halEvent->audioConfig.channelMask;
- event->audio_config.format = (audio_format_t)halEvent->audioConfig.format;
-
- event->data_size = halEvent->data.size();
- uint8_t *dst = (uint8_t *)event + event->data_offset;
- uint8_t *src = (uint8_t *)&halEvent->data[0];
- memcpy(dst, src, halEvent->data.size());
-}
-
-} // namespace android
diff --git a/services/soundtrigger/SoundTriggerHalHidl.h b/services/soundtrigger/SoundTriggerHalHidl.h
deleted file mode 100644
index 25878d0..0000000
--- a/services/soundtrigger/SoundTriggerHalHidl.h
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_SOUNDTRIGGER_HAL_HIDL_H
-#define ANDROID_HARDWARE_SOUNDTRIGGER_HAL_HIDL_H
-
-#include <utility>
-
-#include <stdatomic.h>
-#include <utils/RefBase.h>
-#include <utils/KeyedVector.h>
-#include <utils/Vector.h>
-#include <utils/threads.h>
-#include "SoundTriggerHalInterface.h"
-#include <android/hardware/soundtrigger/2.0/types.h>
-#include <android/hardware/soundtrigger/2.3/types.h>
-#include <android/hardware/soundtrigger/2.1/ISoundTriggerHw.h>
-#include <android/hardware/soundtrigger/2.2/ISoundTriggerHw.h>
-#include <android/hardware/soundtrigger/2.3/ISoundTriggerHw.h>
-#include <android/hardware/soundtrigger/2.0/ISoundTriggerHwCallback.h>
-#include <android/hardware/soundtrigger/2.1/ISoundTriggerHwCallback.h>
-
-namespace android {
-
-using ::android::hardware::audio::common::V2_0::Uuid;
-using ::android::hardware::hidl_vec;
-using ::android::hardware::soundtrigger::V2_0::ConfidenceLevel;
-using ::android::hardware::soundtrigger::V2_0::PhraseRecognitionExtra;
-using ::android::hardware::soundtrigger::V2_0::SoundModelType;
-using ::android::hardware::soundtrigger::V2_0::SoundModelHandle;
-using ::android::hardware::soundtrigger::V2_0::ISoundTriggerHw;
-using V2_0_ISoundTriggerHwCallback =
- ::android::hardware::soundtrigger::V2_0::ISoundTriggerHwCallback;
-using V2_1_ISoundTriggerHw =
- ::android::hardware::soundtrigger::V2_1::ISoundTriggerHw;
-using V2_1_ISoundTriggerHwCallback =
- ::android::hardware::soundtrigger::V2_1::ISoundTriggerHwCallback;
-using ::android::hidl::memory::V1_0::IMemory;
-using V2_2_ISoundTriggerHw =
- ::android::hardware::soundtrigger::V2_2::ISoundTriggerHw;
-using V2_3_ISoundTriggerHw =
- ::android::hardware::soundtrigger::V2_3::ISoundTriggerHw;
-using V2_3_ModelParameter =
- ::android::hardware::soundtrigger::V2_3::ModelParameter;
-using V2_3_OptionalModelParameterRange =
- ::android::hardware::soundtrigger::V2_3::OptionalModelParameterRange;
-
-class SoundTriggerHalHidl : public SoundTriggerHalInterface,
- public virtual V2_1_ISoundTriggerHwCallback
-
-{
-public:
- virtual int getProperties(struct sound_trigger_properties *properties);
-
- /*
- * Load a sound model. Once loaded, recognition of this model can be started and stopped.
- * Only one active recognition per model at a time. The SoundTrigger service will handle
- * concurrent recognition requests by different users/applications on the same model.
- * The implementation returns a unique handle used by other functions (unload_sound_model(),
- * start_recognition(), etc...
- */
- virtual int loadSoundModel(struct sound_trigger_sound_model *sound_model,
- sound_model_callback_t callback,
- void *cookie,
- sound_model_handle_t *handle);
-
- /*
- * Unload a sound model. A sound model can be unloaded to make room for a new one to overcome
- * implementation limitations.
- */
- virtual int unloadSoundModel(sound_model_handle_t handle);
-
- /* Start recognition on a given model. Only one recognition active at a time per model.
- * Once recognition succeeds of fails, the callback is called.
- * TODO: group recognition configuration parameters into one struct and add key phrase options.
- */
- virtual int startRecognition(sound_model_handle_t handle,
- const struct sound_trigger_recognition_config *config,
- recognition_callback_t callback,
- void *cookie);
-
- /* Stop recognition on a given model.
- * The implementation does not have to call the callback when stopped via this method.
- */
- virtual int stopRecognition(sound_model_handle_t handle);
-
- /* Stop recognition on all models.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_1 or above.
- * If no implementation is provided, stop_recognition will be called for each running model.
- */
- virtual int stopAllRecognitions();
-
- /* Get the current state of a given model.
- * Returns 0 or an error code. If successful the state will be returned asynchronously
- * via a recognition event in the callback method that was registered in the
- * startRecognition() method.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_2 or above.
- */
- virtual int getModelState(sound_model_handle_t handle);
-
- /* Set a model specific ModelParameter with the given value. This parameter
- * will keep its value for the duration the model is loaded regardless of starting and
- * stopping recognition. Once the model is unloaded, the value will be lost.
- * Returns 0 or an error code.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_3 or above.
- */
- int setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param, int32_t value);
-
- /* Get a model specific ModelParameter. This parameter will keep its value
- * for the duration the model is loaded regardless of starting and stopping recognition.
- * Once the model is unloaded, the value will be lost. If the value is not set, a default
- * value is returned. See sound_trigger_model_parameter_t for parameter default values.
- * Returns 0 or an error code. On return 0, value pointer will be set.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_3 or above.
- */
- int getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param, int32_t* value);
-
- /* Get supported parameter attributes with respect to the provided model
- * handle. Along with determining the valid range, this API is also used
- * to determine if a given parameter ID is supported at all by the
- * modelHandle for use with getParameter and setParameter APIs.
- */
- int queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param,
- sound_trigger_model_parameter_range_t* param_range);
-
- // ISoundTriggerHwCallback
- virtual ::android::hardware::Return<void> recognitionCallback(
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent& event, CallbackCookie cookie);
- virtual ::android::hardware::Return<void> phraseRecognitionCallback(
- const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent& event, int32_t cookie);
- virtual ::android::hardware::Return<void> soundModelCallback(
- const V2_0_ISoundTriggerHwCallback::ModelEvent& event, CallbackCookie cookie);
- virtual ::android::hardware::Return<void> recognitionCallback_2_1(
- const RecognitionEvent& event, CallbackCookie cookie);
- virtual ::android::hardware::Return<void> phraseRecognitionCallback_2_1(
- const PhraseRecognitionEvent& event, int32_t cookie);
- virtual ::android::hardware::Return<void> soundModelCallback_2_1(
- const ModelEvent& event, CallbackCookie cookie);
-private:
- class SoundModel : public RefBase {
- public:
- SoundModel(sound_model_handle_t handle, sound_model_callback_t callback,
- void *cookie, android::hardware::soundtrigger::V2_0::SoundModelHandle halHandle)
- : mHandle(handle), mHalHandle(halHandle),
- mSoundModelCallback(callback), mSoundModelCookie(cookie),
- mRecognitionCallback(NULL), mRecognitionCookie(NULL) {}
- ~SoundModel() {}
-
- sound_model_handle_t mHandle;
- android::hardware::soundtrigger::V2_0::SoundModelHandle mHalHandle;
- sound_model_callback_t mSoundModelCallback;
- void * mSoundModelCookie;
- recognition_callback_t mRecognitionCallback;
- void * mRecognitionCookie;
- };
-
- friend class SoundTriggerHalInterface;
-
- explicit SoundTriggerHalHidl(const char *moduleName = NULL);
- virtual ~SoundTriggerHalHidl();
-
- void convertUuidToHal(Uuid *halUuid,
- const sound_trigger_uuid_t *uuid);
- void convertUuidFromHal(sound_trigger_uuid_t *uuid,
- const Uuid *halUuid);
-
- void convertPropertiesFromHal(
- struct sound_trigger_properties *properties,
- const ISoundTriggerHw::Properties *halProperties);
- static void convertModelParameterToHal(V2_3_ModelParameter* halParam,
- sound_trigger_model_parameter_t param);
-
- void convertTriggerPhraseToHal(
- ISoundTriggerHw::Phrase *halTriggerPhrase,
- const struct sound_trigger_phrase *triggerPhrase);
- void convertTriggerPhrasesToHal(
- hidl_vec<ISoundTriggerHw::Phrase> *halTriggerPhrases,
- struct sound_trigger_phrase_sound_model *keyPhraseModel);
- void convertSoundModelToHal(ISoundTriggerHw::SoundModel *halModel,
- const struct sound_trigger_sound_model *soundModel);
- std::pair<bool, sp<IMemory>> convertSoundModelToHal(
- V2_1_ISoundTriggerHw::SoundModel *halModel,
- const struct sound_trigger_sound_model *soundModel)
- __attribute__((warn_unused_result));
- void convertPhraseSoundModelToHal(ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
- const struct sound_trigger_sound_model *soundModel);
- std::pair<bool, sp<IMemory>> convertPhraseSoundModelToHal(
- V2_1_ISoundTriggerHw::PhraseSoundModel *halKeyPhraseModel,
- const struct sound_trigger_sound_model *soundModel)
- __attribute__((warn_unused_result));
-
- void convertPhraseRecognitionExtraToHal(
- PhraseRecognitionExtra *halExtra,
- const struct sound_trigger_phrase_recognition_extra *extra);
- void convertRecognitionConfigToHal(ISoundTriggerHw::RecognitionConfig *halConfig,
- const struct sound_trigger_recognition_config *config);
- std::pair<bool, sp<IMemory>> convertRecognitionConfigToHal(
- V2_1_ISoundTriggerHw::RecognitionConfig *halConfig,
- const struct sound_trigger_recognition_config *config)
- __attribute__((warn_unused_result));
-
- struct sound_trigger_model_event *convertSoundModelEventFromHal(
- const V2_0_ISoundTriggerHwCallback::ModelEvent *halEvent);
- void convertPhraseRecognitionExtraFromHal(
- struct sound_trigger_phrase_recognition_extra *extra,
- const PhraseRecognitionExtra *halExtra);
- struct sound_trigger_phrase_recognition_event* convertPhraseRecognitionEventFromHal(
- const V2_0_ISoundTriggerHwCallback::PhraseRecognitionEvent *halPhraseEvent);
- struct sound_trigger_recognition_event *convertRecognitionEventFromHal(
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent);
- void fillRecognitionEventFromHal(
- struct sound_trigger_recognition_event *event,
- const V2_0_ISoundTriggerHwCallback::RecognitionEvent *halEvent);
-
- uint32_t nextUniqueId();
- sp<ISoundTriggerHw> getService();
- sp<V2_1_ISoundTriggerHw> toService2_1(const sp<ISoundTriggerHw>& s);
- sp<V2_2_ISoundTriggerHw> toService2_2(const sp<ISoundTriggerHw>& s);
- sp<V2_3_ISoundTriggerHw> toService2_3(const sp<ISoundTriggerHw>& s);
- sp<SoundModel> getModel(sound_model_handle_t handle);
- sp<SoundModel> removeModel(sound_model_handle_t handle);
-
- static pthread_once_t sOnceControl;
- static void sOnceInit();
-
- Mutex mLock;
- Mutex mHalLock;
- const char *mModuleName;
- volatile atomic_uint_fast32_t mNextUniqueId;
- // Effect chains without a valid thread
- DefaultKeyedVector< sound_model_handle_t , sp<SoundModel> > mSoundModels;
- sp<::android::hardware::soundtrigger::V2_0::ISoundTriggerHw> mISoundTrigger;
-};
-
-} // namespace android
-
-#endif // ANDROID_HARDWARE_SOUNDTRIGGER_HAL_HIDL_H
diff --git a/services/soundtrigger/SoundTriggerHalInterface.h b/services/soundtrigger/SoundTriggerHalInterface.h
deleted file mode 100644
index e1fffff..0000000
--- a/services/soundtrigger/SoundTriggerHalInterface.h
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_SOUNDTRIGGER_HAL_INTERFACE_H
-#define ANDROID_HARDWARE_SOUNDTRIGGER_HAL_INTERFACE_H
-
-#include <utils/RefBase.h>
-#include <system/sound_trigger.h>
-#include <hardware/sound_trigger.h>
-
-namespace android {
-
-class SoundTriggerHalInterface : public virtual RefBase
-{
-public:
- /* get a sound trigger HAL instance */
- static sp<SoundTriggerHalInterface> connectModule(const char *moduleName);
-
- virtual ~SoundTriggerHalInterface() {}
-
- virtual int getProperties(struct sound_trigger_properties *properties) = 0;
-
- /*
- * Load a sound model. Once loaded, recognition of this model can be started and stopped.
- * Only one active recognition per model at a time. The SoundTrigger service will handle
- * concurrent recognition requests by different users/applications on the same model.
- * The implementation returns a unique handle used by other functions (unload_sound_model(),
- * start_recognition(), etc...
- */
- virtual int loadSoundModel(struct sound_trigger_sound_model *sound_model,
- sound_model_callback_t callback,
- void *cookie,
- sound_model_handle_t *handle) = 0;
-
- /*
- * Unload a sound model. A sound model can be unloaded to make room for a new one to overcome
- * implementation limitations.
- */
- virtual int unloadSoundModel(sound_model_handle_t handle) = 0;
-
- /* Start recognition on a given model. Only one recognition active at a time per model.
- * Once recognition succeeds of fails, the callback is called.
- * TODO: group recognition configuration parameters into one struct and add key phrase options.
- */
- virtual int startRecognition(sound_model_handle_t handle,
- const struct sound_trigger_recognition_config *config,
- recognition_callback_t callback,
- void *cookie) = 0;
-
- /* Stop recognition on a given model.
- * The implementation does not have to call the callback when stopped via this method.
- */
- virtual int stopRecognition(sound_model_handle_t handle) = 0;
-
- /* Stop recognition on all models.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_1 or above.
- * If no implementation is provided, stop_recognition will be called for each running model.
- */
- virtual int stopAllRecognitions() = 0;
-
- /* Get the current state of a given model.
- * Returns 0 or an error code. If successful the state will be returned asynchronously
- * via a recognition event in the callback method that was registered in the
- * startRecognition() method.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_2 or above.
- */
- virtual int getModelState(sound_model_handle_t handle) = 0;
-
- /* Set a model specific ModelParameter with the given value. This parameter
- * will keep its value for the duration the model is loaded regardless of starting and stopping
- * recognition. Once the model is unloaded, the value will be lost.
- * Returns 0 or an error code.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_3 or above.
- */
- virtual int setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t model_param, int32_t value) = 0;
-
- /* Get a model specific ModelParameter. This parameter will keep its value
- * for the duration the model is loaded regardless of starting and stopping recognition.
- * Once the model is unloaded, the value will be lost. If the value is not set, a default
- * value is returned. See sound_trigger_model_parameter_t for parameter default values.
- * Returns 0 or an error code. On return 0, value pointer will be set.
- * Only supported for device api versions SOUND_TRIGGER_DEVICE_API_VERSION_1_3 or above.
- */
- virtual int getParameter(sound_model_handle_t sound_model_handle,
- sound_trigger_model_parameter_t model_param, int32_t* value) = 0;
-
- /* Get supported parameter attributes with respect to the provided model
- * handle. Along with determining the valid range, this API is also used
- * to determine if a given parameter ID is supported at all by the
- * modelHandle for use with getParameter and setParameter APIs.
- */
- virtual int queryParameter(sound_model_handle_t sound_model_handle,
- sound_trigger_model_parameter_t model_param,
- sound_trigger_model_parameter_range_t* param_range) = 0;
-
-protected:
- SoundTriggerHalInterface() {}
-};
-
-} // namespace android
-
-#endif // ANDROID_HARDWARE_SOUNDTRIGGER_HAL_INTERFACE_H
diff --git a/services/soundtrigger/SoundTriggerHwService.cpp b/services/soundtrigger/SoundTriggerHwService.cpp
deleted file mode 100644
index 4d8b0da..0000000
--- a/services/soundtrigger/SoundTriggerHwService.cpp
+++ /dev/null
@@ -1,1249 +0,0 @@
-/*
- * Copyright (C) 2014 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 "SoundTriggerHwService"
-//#define LOG_NDEBUG 0
-
-#include <stdio.h>
-#include <string.h>
-#include <sys/types.h>
-#include <pthread.h>
-
-#include <audio_utils/clock.h>
-#include <system/sound_trigger.h>
-#include <cutils/atomic.h>
-#include <cutils/properties.h>
-#include <hardware/hardware.h>
-#include <media/AudioSystem.h>
-#include <mediautils/ServiceUtilities.h>
-#include <utils/Errors.h>
-#include <utils/Log.h>
-#include <binder/IServiceManager.h>
-#include <binder/MemoryBase.h>
-#include <binder/MemoryHeapBase.h>
-#include <system/sound_trigger.h>
-#include "SoundTriggerHwService.h"
-
-#define HW_MODULE_PREFIX "primary"
-namespace android {
-
-namespace {
-
-// Given an IMemory, returns a copy of its content along with its size.
-// Returns nullptr on failure or if input is nullptr.
-std::pair<std::unique_ptr<uint8_t[]>,
- size_t> CopyToArray(const sp<IMemory>& mem) {
- if (mem == nullptr) {
- return std::make_pair(nullptr, 0);
- }
-
- const size_t size = mem->size();
- if (size == 0) {
- return std::make_pair(nullptr, 0);
- }
-
- std::unique_ptr<uint8_t[]> ar = std::make_unique<uint8_t[]>(size);
- if (ar == nullptr) {
- return std::make_pair(nullptr, 0);
- }
-
- memcpy(ar.get(), mem->unsecurePointer(), size);
- return std::make_pair(std::move(ar), size);
-}
-
-}
-
-SoundTriggerHwService::SoundTriggerHwService()
- : BnSoundTriggerHwService(),
- mNextUniqueId(1),
- mMemoryDealer(new MemoryDealer(1024 * 1024, "SoundTriggerHwService")),
- mCaptureState(false)
-{
-}
-
-void SoundTriggerHwService::onFirstRef()
-{
- int rc;
-
- sp<SoundTriggerHalInterface> halInterface =
- SoundTriggerHalInterface::connectModule(HW_MODULE_PREFIX);
-
- if (halInterface == 0) {
- ALOGW("could not connect to HAL");
- return;
- }
- sound_trigger_module_descriptor descriptor;
- rc = halInterface->getProperties(&descriptor.properties);
- if (rc != 0) {
- ALOGE("could not read implementation properties");
- return;
- }
- descriptor.handle =
- (sound_trigger_module_handle_t)android_atomic_inc(&mNextUniqueId);
- ALOGI("loaded default module %s, handle %d", descriptor.properties.description,
- descriptor.handle);
-
- sp<Module> module = new Module(this, halInterface, descriptor);
- mModules.add(descriptor.handle, module);
- mCallbackThread = new CallbackThread(this);
-}
-
-SoundTriggerHwService::~SoundTriggerHwService()
-{
- if (mCallbackThread != 0) {
- mCallbackThread->exit();
- }
-}
-
-status_t SoundTriggerHwService::listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules)
-{
- ALOGV("listModules");
- if (!captureHotwordAllowed(opPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- AutoMutex lock(mServiceLock);
- if (numModules == NULL || (*numModules != 0 && modules == NULL)) {
- return BAD_VALUE;
- }
- size_t maxModules = *numModules;
- *numModules = mModules.size();
- for (size_t i = 0; i < mModules.size() && i < maxModules; i++) {
- modules[i] = mModules.valueAt(i)->descriptor();
- }
- return NO_ERROR;
-}
-
-status_t SoundTriggerHwService::attach(const String16& opPackageName,
- const sound_trigger_module_handle_t handle,
- const sp<ISoundTriggerClient>& client,
- sp<ISoundTrigger>& moduleInterface)
-{
- ALOGV("attach module %d", handle);
- if (!captureHotwordAllowed(opPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- AutoMutex lock(mServiceLock);
- moduleInterface.clear();
- if (client == 0) {
- return BAD_VALUE;
- }
- ssize_t index = mModules.indexOfKey(handle);
- if (index < 0) {
- return BAD_VALUE;
- }
- sp<Module> module = mModules.valueAt(index);
-
- sp<ModuleClient> moduleClient = module->addClient(client, opPackageName);
- if (moduleClient == 0) {
- return NO_INIT;
- }
-
- moduleClient->setCaptureState_l(mCaptureState);
- moduleInterface = moduleClient;
-
- return NO_ERROR;
-}
-
-status_t SoundTriggerHwService::setCaptureState(bool active)
-{
- ALOGV("setCaptureState %d", active);
- AutoMutex lock(mServiceLock);
- mCaptureState = active;
- for (size_t i = 0; i < mModules.size(); i++) {
- mModules.valueAt(i)->setCaptureState_l(active);
- }
- return NO_ERROR;
-}
-
-
-static const int kDumpLockTimeoutNs = 1 * NANOS_PER_SECOND;
-
-static bool dumpTryLock(Mutex& mutex)
-{
- status_t err = mutex.timedLock(kDumpLockTimeoutNs);
- return err == NO_ERROR;
-}
-
-status_t SoundTriggerHwService::dump(int fd, const Vector<String16>& args __unused) {
- String8 result;
- if (checkCallingPermission(String16("android.permission.DUMP")) == false) {
- result.appendFormat("Permission Denial: can't dump SoundTriggerHwService");
- write(fd, result.string(), result.size());
- } else {
- bool locked = dumpTryLock(mServiceLock);
- // failed to lock - SoundTriggerHwService is probably deadlocked
- if (!locked) {
- result.append("SoundTriggerHwService may be deadlocked\n");
- write(fd, result.string(), result.size());
- }
-
- if (locked) mServiceLock.unlock();
- }
- return NO_ERROR;
-}
-
-status_t SoundTriggerHwService::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) {
- return BnSoundTriggerHwService::onTransact(code, data, reply, flags);
-}
-
-
-// static
-void SoundTriggerHwService::recognitionCallback(struct sound_trigger_recognition_event *event,
- void *cookie)
-{
- Module *module = (Module *)cookie;
- if (module == NULL) {
- return;
- }
- sp<SoundTriggerHwService> service = module->service().promote();
- if (service == 0) {
- return;
- }
-
- service->sendRecognitionEvent(event, module);
-}
-
-sp<IMemory> SoundTriggerHwService::prepareRecognitionEvent(
- struct sound_trigger_recognition_event *event)
-{
- AutoMutex lock(mMemoryDealerLock);
- sp<IMemory> eventMemory;
-
- //sanitize event
- switch (event->type) {
- case SOUND_MODEL_TYPE_KEYPHRASE:
- ALOGW_IF(event->data_size != 0 && event->data_offset !=
- sizeof(struct sound_trigger_phrase_recognition_event),
- "prepareRecognitionEvent(): invalid data offset %u for keyphrase event type",
- event->data_offset);
- event->data_offset = sizeof(struct sound_trigger_phrase_recognition_event);
- break;
- case SOUND_MODEL_TYPE_GENERIC:
- ALOGW_IF(event->data_size != 0 && event->data_offset !=
- sizeof(struct sound_trigger_generic_recognition_event),
- "prepareRecognitionEvent(): invalid data offset %u for generic event type",
- event->data_offset);
- event->data_offset = sizeof(struct sound_trigger_generic_recognition_event);
- break;
- case SOUND_MODEL_TYPE_UNKNOWN:
- ALOGW_IF(event->data_size != 0 && event->data_offset !=
- sizeof(struct sound_trigger_recognition_event),
- "prepareRecognitionEvent(): invalid data offset %u for unknown event type",
- event->data_offset);
- event->data_offset = sizeof(struct sound_trigger_recognition_event);
- break;
- default:
- return eventMemory;
- }
-
- size_t size = event->data_offset + event->data_size;
- eventMemory = mMemoryDealer->allocate(size);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- eventMemory.clear();
- return eventMemory;
- }
- memcpy(eventMemory->unsecurePointer(), event, size);
-
- return eventMemory;
-}
-
-void SoundTriggerHwService::sendRecognitionEvent(struct sound_trigger_recognition_event *event,
- Module *module)
-{
- if (module == NULL) {
- return;
- }
- sp<IMemory> eventMemory = prepareRecognitionEvent(event);
- if (eventMemory == 0) {
- return;
- }
-
- sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_RECOGNITION,
- eventMemory);
- callbackEvent->setModule(module);
- sendCallbackEvent(callbackEvent);
-}
-
-// static
-void SoundTriggerHwService::soundModelCallback(struct sound_trigger_model_event *event,
- void *cookie)
-{
- Module *module = (Module *)cookie;
- if (module == NULL) {
- return;
- }
- sp<SoundTriggerHwService> service = module->service().promote();
- if (service == 0) {
- return;
- }
-
- service->sendSoundModelEvent(event, module);
-}
-
-sp<IMemory> SoundTriggerHwService::prepareSoundModelEvent(struct sound_trigger_model_event *event)
-{
- AutoMutex lock(mMemoryDealerLock);
- sp<IMemory> eventMemory;
-
- size_t size = event->data_offset + event->data_size;
- eventMemory = mMemoryDealer->allocate(size);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- eventMemory.clear();
- return eventMemory;
- }
- memcpy(eventMemory->unsecurePointer(), event, size);
-
- return eventMemory;
-}
-
-void SoundTriggerHwService::sendSoundModelEvent(struct sound_trigger_model_event *event,
- Module *module)
-{
- sp<IMemory> eventMemory = prepareSoundModelEvent(event);
- if (eventMemory == 0) {
- return;
- }
- sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SOUNDMODEL,
- eventMemory);
- callbackEvent->setModule(module);
- sendCallbackEvent(callbackEvent);
-}
-
-
-sp<IMemory> SoundTriggerHwService::prepareServiceStateEvent(sound_trigger_service_state_t state)
-{
- AutoMutex lock(mMemoryDealerLock);
- sp<IMemory> eventMemory;
-
- size_t size = sizeof(sound_trigger_service_state_t);
- eventMemory = mMemoryDealer->allocate(size);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- eventMemory.clear();
- return eventMemory;
- }
- *((sound_trigger_service_state_t *)eventMemory->unsecurePointer()) = state;
- return eventMemory;
-}
-
-void SoundTriggerHwService::sendServiceStateEvent(sound_trigger_service_state_t state,
- Module *module)
-{
- sp<IMemory> eventMemory = prepareServiceStateEvent(state);
- if (eventMemory == 0) {
- return;
- }
- sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SERVICE_STATE,
- eventMemory);
- callbackEvent->setModule(module);
- sendCallbackEvent(callbackEvent);
-}
-
-void SoundTriggerHwService::sendServiceStateEvent(sound_trigger_service_state_t state,
- ModuleClient *moduleClient)
-{
- sp<IMemory> eventMemory = prepareServiceStateEvent(state);
- if (eventMemory == 0) {
- return;
- }
- sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_SERVICE_STATE,
- eventMemory);
- callbackEvent->setModuleClient(moduleClient);
- sendCallbackEvent(callbackEvent);
-}
-
-void SoundTriggerHwService::sendCallbackEvent(const sp<CallbackEvent>& event)
-{
- mCallbackThread->sendCallbackEvent(event);
-}
-
-void SoundTriggerHwService::onCallbackEvent(const sp<CallbackEvent>& event)
-{
- ALOGV("onCallbackEvent");
- sp<Module> module;
- sp<ModuleClient> moduleClient;
- {
- AutoMutex lock(mServiceLock);
- //CallbackEvent is either for Module or ModuleClient
- module = event->mModule.promote();
- if (module == 0) {
- moduleClient = event->mModuleClient.promote();
- if (moduleClient == 0) {
- return;
- }
- } else {
- // Sanity check on this being a Module we know about.
- bool foundModule = false;
- for (size_t i = 0; i < mModules.size(); i++) {
- if (mModules.valueAt(i).get() == module.get()) {
- foundModule = true;
- break;
- }
- }
- if (!foundModule) {
- ALOGE("onCallbackEvent for unknown module");
- return;
- }
- }
- }
- if (module != 0) {
- ALOGV("onCallbackEvent for module");
- module->onCallbackEvent(event);
- } else if (moduleClient != 0) {
- ALOGV("onCallbackEvent for moduleClient");
- moduleClient->onCallbackEvent(event);
- }
- {
- AutoMutex lock(mServiceLock);
- // clear now to execute with mServiceLock locked
- event->mMemory.clear();
- }
-}
-
-#undef LOG_TAG
-#define LOG_TAG "SoundTriggerHwService::CallbackThread"
-
-SoundTriggerHwService::CallbackThread::CallbackThread(const wp<SoundTriggerHwService>& service)
- : mService(service)
-{
-}
-
-SoundTriggerHwService::CallbackThread::~CallbackThread()
-{
- while (!mEventQueue.isEmpty()) {
- mEventQueue[0]->mMemory.clear();
- mEventQueue.removeAt(0);
- }
-}
-
-void SoundTriggerHwService::CallbackThread::onFirstRef()
-{
- run("soundTrigger cbk", ANDROID_PRIORITY_URGENT_AUDIO);
-}
-
-bool SoundTriggerHwService::CallbackThread::threadLoop()
-{
- while (!exitPending()) {
- sp<CallbackEvent> event;
- sp<SoundTriggerHwService> service;
- {
- Mutex::Autolock _l(mCallbackLock);
- while (mEventQueue.isEmpty() && !exitPending()) {
- ALOGV("CallbackThread::threadLoop() sleep");
- mCallbackCond.wait(mCallbackLock);
- ALOGV("CallbackThread::threadLoop() wake up");
- }
- if (exitPending()) {
- break;
- }
- event = mEventQueue[0];
- mEventQueue.removeAt(0);
- service = mService.promote();
- }
- if (service != 0) {
- service->onCallbackEvent(event);
- }
- }
- return false;
-}
-
-void SoundTriggerHwService::CallbackThread::exit()
-{
- Mutex::Autolock _l(mCallbackLock);
- requestExit();
- mCallbackCond.broadcast();
-}
-
-void SoundTriggerHwService::CallbackThread::sendCallbackEvent(
- const sp<SoundTriggerHwService::CallbackEvent>& event)
-{
- AutoMutex lock(mCallbackLock);
- mEventQueue.add(event);
- mCallbackCond.signal();
-}
-
-SoundTriggerHwService::CallbackEvent::CallbackEvent(event_type type, sp<IMemory> memory)
- : mType(type), mMemory(memory)
-{
-}
-
-SoundTriggerHwService::CallbackEvent::~CallbackEvent()
-{
-}
-
-
-#undef LOG_TAG
-#define LOG_TAG "SoundTriggerHwService::Module"
-
-SoundTriggerHwService::Module::Module(const sp<SoundTriggerHwService>& service,
- const sp<SoundTriggerHalInterface>& halInterface,
- sound_trigger_module_descriptor descriptor)
- : mService(service), mHalInterface(halInterface), mDescriptor(descriptor),
- mServiceState(SOUND_TRIGGER_STATE_NO_INIT)
-{
-}
-
-SoundTriggerHwService::Module::~Module() {
- mModuleClients.clear();
-}
-
-sp<SoundTriggerHwService::ModuleClient>
-SoundTriggerHwService::Module::addClient(const sp<ISoundTriggerClient>& client,
- const String16& opPackageName)
-{
- AutoMutex lock(mLock);
- sp<ModuleClient> moduleClient;
-
- for (size_t i = 0; i < mModuleClients.size(); i++) {
- if (mModuleClients[i]->client() == client) {
- // Client already present, reuse client
- return moduleClient;
- }
- }
- moduleClient = new ModuleClient(this, client, opPackageName);
-
- ALOGV("addClient() client %p", moduleClient.get());
- mModuleClients.add(moduleClient);
-
- return moduleClient;
-}
-
-void SoundTriggerHwService::Module::detach(const sp<ModuleClient>& moduleClient)
-{
- ALOGV("Module::detach()");
- Vector<audio_session_t> releasedSessions;
-
- {
- AutoMutex lock(mLock);
- ssize_t index = -1;
-
- for (size_t i = 0; i < mModuleClients.size(); i++) {
- if (mModuleClients[i] == moduleClient) {
- index = i;
- break;
- }
- }
- if (index == -1) {
- return;
- }
-
- ALOGV("remove client %p", moduleClient.get());
- mModuleClients.removeAt(index);
-
- // Iterate in reverse order as models are removed from list inside the loop.
- for (size_t i = mModels.size(); i > 0; i--) {
- sp<Model> model = mModels.valueAt(i - 1);
- if (moduleClient == model->mModuleClient) {
- mModels.removeItemsAt(i - 1);
- ALOGV("detach() unloading model %d", model->mHandle);
- if (mHalInterface != 0) {
- if (model->mState == Model::STATE_ACTIVE) {
- mHalInterface->stopRecognition(model->mHandle);
- }
- mHalInterface->unloadSoundModel(model->mHandle);
- }
- releasedSessions.add(model->mCaptureSession);
- }
- }
- }
-
- for (size_t i = 0; i < releasedSessions.size(); i++) {
- // do not call AudioSystem methods with mLock held
- AudioSystem::releaseSoundTriggerSession(releasedSessions[i]);
- }
-}
-
-status_t SoundTriggerHwService::Module::loadSoundModel(const sp<IMemory>& modelMemory,
- sp<ModuleClient> moduleClient,
- sound_model_handle_t *handle)
-{
- ALOGV("loadSoundModel() handle");
- if (mHalInterface == 0) {
- return NO_INIT;
- }
-
- auto immutableMemory = CopyToArray(modelMemory);
- if (immutableMemory.first == nullptr) {
- return NO_MEMORY;
- }
-
- struct sound_trigger_sound_model* sound_model =
- (struct sound_trigger_sound_model*) immutableMemory.first.get();
-
- size_t structSize;
- if (sound_model->type == SOUND_MODEL_TYPE_KEYPHRASE) {
- structSize = sizeof(struct sound_trigger_phrase_sound_model);
- } else {
- structSize = sizeof(struct sound_trigger_sound_model);
- }
-
- if (sound_model->data_offset < structSize ||
- sound_model->data_size > (UINT_MAX - sound_model->data_offset) ||
- immutableMemory.second < sound_model->data_offset ||
- sound_model->data_size >
- (immutableMemory.second - sound_model->data_offset)) {
- android_errorWriteLog(0x534e4554, "30148546");
- ALOGE("loadSoundModel() data_size is too big");
- return BAD_VALUE;
- }
-
- audio_session_t session;
- audio_io_handle_t ioHandle;
- audio_devices_t device;
- // do not call AudioSystem methods with mLock held
- status_t status = AudioSystem::acquireSoundTriggerSession(&session, &ioHandle, &device);
- if (status != NO_ERROR) {
- return status;
- }
-
- {
- AutoMutex lock(mLock);
-
- if (mModels.size() >= mDescriptor.properties.max_sound_models) {
- ALOGW("loadSoundModel(): Not loading, max number of models (%d) would be exceeded",
- mDescriptor.properties.max_sound_models);
- status = INVALID_OPERATION;
- goto exit;
- }
-
- status = mHalInterface->loadSoundModel(sound_model,
- SoundTriggerHwService::soundModelCallback,
- this, handle);
- if (status != NO_ERROR) {
- goto exit;
- }
-
- sp<Model> model = new Model(*handle, session, ioHandle, device, sound_model->type,
- moduleClient);
- mModels.replaceValueFor(*handle, model);
- }
-exit:
- if (status != NO_ERROR) {
- // do not call AudioSystem methods with mLock held
- AudioSystem::releaseSoundTriggerSession(session);
- }
- return status;
-}
-
-status_t SoundTriggerHwService::Module::unloadSoundModel(sound_model_handle_t handle)
-{
- ALOGV("unloadSoundModel() model handle %d", handle);
- status_t status;
- audio_session_t session;
-
- {
- AutoMutex lock(mLock);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
- ssize_t index = mModels.indexOfKey(handle);
- if (index < 0) {
- return BAD_VALUE;
- }
- sp<Model> model = mModels.valueAt(index);
- mModels.removeItem(handle);
- if (model->mState == Model::STATE_ACTIVE) {
- mHalInterface->stopRecognition(model->mHandle);
- model->mState = Model::STATE_IDLE;
- }
- status = mHalInterface->unloadSoundModel(handle);
- session = model->mCaptureSession;
- }
- // do not call AudioSystem methods with mLock held
- AudioSystem::releaseSoundTriggerSession(session);
- return status;
-}
-
-status_t SoundTriggerHwService::Module::startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory)
-{
- ALOGV("startRecognition() model handle %d", handle);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
-
- auto immutableMemory = CopyToArray(dataMemory);
- if (immutableMemory.first == nullptr) {
- return NO_MEMORY;
- }
-
- struct sound_trigger_recognition_config* config =
- (struct sound_trigger_recognition_config*) immutableMemory.first.get();
-
- if (config->data_offset < sizeof(struct sound_trigger_recognition_config) ||
- config->data_size > (UINT_MAX - config->data_offset) ||
- immutableMemory.second < config->data_offset ||
- config->data_size >
- (immutableMemory.second - config->data_offset)) {
- ALOGE("startRecognition() data_size is too big");
- return BAD_VALUE;
- }
-
- AutoMutex lock(mLock);
- if (mServiceState == SOUND_TRIGGER_STATE_DISABLED) {
- return INVALID_OPERATION;
- }
- sp<Model> model = getModel(handle);
- if (model == 0) {
- return BAD_VALUE;
- }
-
- if (model->mState == Model::STATE_ACTIVE) {
- return INVALID_OPERATION;
- }
-
-
- //TODO: get capture handle and device from audio policy service
- config->capture_handle = model->mCaptureIOHandle;
- config->capture_device = model->mCaptureDevice;
- status_t status = mHalInterface->startRecognition(handle, config,
- SoundTriggerHwService::recognitionCallback,
- this);
-
- if (status == NO_ERROR) {
- model->mState = Model::STATE_ACTIVE;
- model->mConfig = *config;
- }
-
- return status;
-}
-
-status_t SoundTriggerHwService::Module::stopRecognition(sound_model_handle_t handle)
-{
- ALOGV("stopRecognition() model handle %d", handle);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
- AutoMutex lock(mLock);
- sp<Model> model = getModel(handle);
- if (model == 0) {
- return BAD_VALUE;
- }
-
- if (model->mState != Model::STATE_ACTIVE) {
- return INVALID_OPERATION;
- }
- mHalInterface->stopRecognition(handle);
- model->mState = Model::STATE_IDLE;
- return NO_ERROR;
-}
-
-status_t SoundTriggerHwService::Module::getModelState(sound_model_handle_t handle)
-{
- ALOGV("getModelState() model handle %d", handle);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
- AutoMutex lock(mLock);
- sp<Model> model = getModel(handle);
- if (model == 0) {
- return BAD_VALUE;
- }
-
- if (model->mState != Model::STATE_ACTIVE) {
- return INVALID_OPERATION;
- }
-
- return mHalInterface->getModelState(handle);
-}
-
-status_t SoundTriggerHwService::Module::setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t value)
-{
- ALOGV("setParameter() handle=%d, param=%d, value=%d", handle, param, value);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
-
- AutoMutex lock(mLock);
- return mHalInterface->setParameter(handle, param, value);
-}
-
-status_t SoundTriggerHwService::Module::getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t* value)
-{
- ALOGV("getParameter() handle=%d, param=%d", handle, param);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
-
- AutoMutex lock(mLock);
- return mHalInterface->getParameter(handle, param, value);
-}
-
-status_t SoundTriggerHwService::Module::queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range)
-{
- ALOGV("queryParameter() handle=%d, param=%d", handle, param);
- if (mHalInterface == 0) {
- return NO_INIT;
- }
-
- AutoMutex lock(mLock);
- return mHalInterface->queryParameter(handle, param, param_range);
-}
-
-void SoundTriggerHwService::Module::onCallbackEvent(const sp<CallbackEvent>& event)
-{
- ALOGV("onCallbackEvent type %d", event->mType);
-
- // Memory is coming from a trusted process.
- sp<IMemory> eventMemory = event->mMemory;
-
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- return;
- }
- if (mModuleClients.isEmpty()) {
- ALOGI("%s no clients", __func__);
- return;
- }
-
- Vector< sp<ModuleClient> > clients;
-
- switch (event->mType) {
- case CallbackEvent::TYPE_RECOGNITION: {
- struct sound_trigger_recognition_event *recognitionEvent =
- (struct sound_trigger_recognition_event *)eventMemory->unsecurePointer();
- {
- AutoMutex lock(mLock);
- sp<Model> model = getModel(recognitionEvent->model);
- if (model == 0) {
- ALOGW("%s model == 0", __func__);
- return;
- }
- if (model->mState != Model::STATE_ACTIVE) {
- ALOGV("onCallbackEvent model->mState %d != Model::STATE_ACTIVE", model->mState);
- return;
- }
-
- recognitionEvent->capture_session = model->mCaptureSession;
- model->mState = Model::STATE_IDLE;
- clients.add(model->mModuleClient);
- }
- } break;
- case CallbackEvent::TYPE_SOUNDMODEL: {
- struct sound_trigger_model_event *soundmodelEvent =
- (struct sound_trigger_model_event *)eventMemory->unsecurePointer();
- {
- AutoMutex lock(mLock);
- sp<Model> model = getModel(soundmodelEvent->model);
- if (model == 0) {
- ALOGW("%s model == 0", __func__);
- return;
- }
- clients.add(model->mModuleClient);
- }
- } break;
- case CallbackEvent::TYPE_SERVICE_STATE: {
- {
- AutoMutex lock(mLock);
- for (size_t i = 0; i < mModuleClients.size(); i++) {
- if (mModuleClients[i] != 0) {
- clients.add(mModuleClients[i]);
- }
- }
- }
- } break;
- default:
- LOG_ALWAYS_FATAL("onCallbackEvent unknown event type %d", event->mType);
- }
-
- for (size_t i = 0; i < clients.size(); i++) {
- clients[i]->onCallbackEvent(event);
- }
-}
-
-sp<SoundTriggerHwService::Model> SoundTriggerHwService::Module::getModel(
- sound_model_handle_t handle)
-{
- sp<Model> model;
- ssize_t index = mModels.indexOfKey(handle);
- if (index >= 0) {
- model = mModels.valueAt(index);
- }
- return model;
-}
-
-// Called with mServiceLock held
-void SoundTriggerHwService::Module::setCaptureState_l(bool active)
-{
- ALOGV("Module::setCaptureState_l %d", active);
- sp<SoundTriggerHwService> service;
- sound_trigger_service_state_t state;
-
- Vector< sp<IMemory> > events;
- {
- AutoMutex lock(mLock);
- state = (active && !mDescriptor.properties.concurrent_capture) ?
- SOUND_TRIGGER_STATE_DISABLED : SOUND_TRIGGER_STATE_ENABLED;
-
- if (state == mServiceState) {
- return;
- }
-
- mServiceState = state;
-
- service = mService.promote();
- if (service == 0) {
- return;
- }
-
- if (state == SOUND_TRIGGER_STATE_ENABLED) {
- goto exit;
- }
-
- const bool supports_stop_all =
- (mHalInterface != 0) && (mHalInterface->stopAllRecognitions() != -ENOSYS);
-
- for (size_t i = 0; i < mModels.size(); i++) {
- sp<Model> model = mModels.valueAt(i);
- if (model->mState == Model::STATE_ACTIVE) {
- if (mHalInterface != 0 && !supports_stop_all) {
- mHalInterface->stopRecognition(model->mHandle);
- }
- // keep model in ACTIVE state so that event is processed by onCallbackEvent()
- if (model->mType == SOUND_MODEL_TYPE_KEYPHRASE) {
- struct sound_trigger_phrase_recognition_event event;
- memset(&event, 0, sizeof(struct sound_trigger_phrase_recognition_event));
- event.num_phrases = model->mConfig.num_phrases;
- for (size_t i = 0; i < event.num_phrases; i++) {
- event.phrase_extras[i] = model->mConfig.phrases[i];
- }
- event.common.status = RECOGNITION_STATUS_ABORT;
- event.common.type = model->mType;
- event.common.model = model->mHandle;
- event.common.data_size = 0;
- sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
- if (eventMemory != 0) {
- events.add(eventMemory);
- }
- } else if (model->mType == SOUND_MODEL_TYPE_GENERIC) {
- struct sound_trigger_generic_recognition_event event;
- memset(&event, 0, sizeof(struct sound_trigger_generic_recognition_event));
- event.common.status = RECOGNITION_STATUS_ABORT;
- event.common.type = model->mType;
- event.common.model = model->mHandle;
- event.common.data_size = 0;
- sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
- if (eventMemory != 0) {
- events.add(eventMemory);
- }
- } else if (model->mType == SOUND_MODEL_TYPE_UNKNOWN) {
- struct sound_trigger_phrase_recognition_event event;
- memset(&event, 0, sizeof(struct sound_trigger_phrase_recognition_event));
- event.common.status = RECOGNITION_STATUS_ABORT;
- event.common.type = model->mType;
- event.common.model = model->mHandle;
- event.common.data_size = 0;
- sp<IMemory> eventMemory = service->prepareRecognitionEvent(&event.common);
- if (eventMemory != 0) {
- events.add(eventMemory);
- }
- } else {
- goto exit;
- }
- }
- }
- }
-
- for (size_t i = 0; i < events.size(); i++) {
- sp<CallbackEvent> callbackEvent = new CallbackEvent(CallbackEvent::TYPE_RECOGNITION,
- events[i]);
- callbackEvent->setModule(this);
- service->sendCallbackEvent(callbackEvent);
- }
-
-exit:
- service->sendServiceStateEvent(state, this);
-}
-
-
-SoundTriggerHwService::Model::Model(sound_model_handle_t handle, audio_session_t session,
- audio_io_handle_t ioHandle, audio_devices_t device,
- sound_trigger_sound_model_type_t type,
- sp<ModuleClient>& moduleClient) :
- mHandle(handle), mState(STATE_IDLE), mCaptureSession(session),
- mCaptureIOHandle(ioHandle), mCaptureDevice(device), mType(type),
- mModuleClient(moduleClient)
-{
-}
-
-#undef LOG_TAG
-#define LOG_TAG "SoundTriggerHwService::ModuleClient"
-
-SoundTriggerHwService::ModuleClient::ModuleClient(const sp<Module>& module,
- const sp<ISoundTriggerClient>& client,
- const String16& opPackageName)
- : mModule(module), mClient(client), mOpPackageName(opPackageName)
-{
-}
-
-void SoundTriggerHwService::ModuleClient::onFirstRef()
-{
- sp<IBinder> binder = IInterface::asBinder(mClient);
- if (binder != 0) {
- binder->linkToDeath(this);
- }
-}
-
-SoundTriggerHwService::ModuleClient::~ModuleClient()
-{
-}
-
-status_t SoundTriggerHwService::ModuleClient::dump(int fd __unused,
- const Vector<String16>& args __unused) {
- String8 result;
- return NO_ERROR;
-}
-
-void SoundTriggerHwService::ModuleClient::detach() {
- ALOGV("detach()");
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return;
- }
-
- {
- AutoMutex lock(mLock);
- if (mClient != 0) {
- IInterface::asBinder(mClient)->unlinkToDeath(this);
- mClient.clear();
- }
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return;
- }
- module->detach(this);
-}
-
-status_t SoundTriggerHwService::ModuleClient::loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle)
-{
- ALOGV("loadSoundModel() handle");
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
- if (checkIMemory(modelMemory) != NO_ERROR) {
- return BAD_VALUE;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->loadSoundModel(modelMemory, this, handle);
-}
-
-status_t SoundTriggerHwService::ModuleClient::unloadSoundModel(sound_model_handle_t handle)
-{
- ALOGV("unloadSoundModel() model handle %d", handle);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->unloadSoundModel(handle);
-}
-
-status_t SoundTriggerHwService::ModuleClient::startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory)
-{
- ALOGV("startRecognition() model handle %d", handle);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
- if (checkIMemory(dataMemory) != NO_ERROR) {
- return BAD_VALUE;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->startRecognition(handle, dataMemory);
-}
-
-status_t SoundTriggerHwService::ModuleClient::stopRecognition(sound_model_handle_t handle)
-{
- ALOGV("stopRecognition() model handle %d", handle);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->stopRecognition(handle);
-}
-
-status_t SoundTriggerHwService::ModuleClient::getModelState(sound_model_handle_t handle)
-{
- ALOGV("getModelState() model handle %d", handle);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->getModelState(handle);
-}
-
-status_t SoundTriggerHwService::ModuleClient::setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t value)
-{
- ALOGV("setParameter() handle=%d, param=%d, value=%d", handle, param, value);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->setParameter(handle, param, value);
-}
-
-status_t SoundTriggerHwService::ModuleClient::getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t* value)
-{
- ALOGV("getParameter() handle=%d, param=%d", handle, param);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->getParameter(handle, param, value);
-}
-
-status_t SoundTriggerHwService::ModuleClient::queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range)
-{
- ALOGV("isParameterSupported() handle=%d, param=%d", handle, param);
- if (!captureHotwordAllowed(mOpPackageName,
- IPCThreadState::self()->getCallingPid(),
- IPCThreadState::self()->getCallingUid())) {
- return PERMISSION_DENIED;
- }
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return NO_INIT;
- }
- return module->queryParameter(handle, param, param_range);
-}
-
-void SoundTriggerHwService::ModuleClient::setCaptureState_l(bool active)
-{
- ALOGV("ModuleClient::setCaptureState_l %d", active);
- sp<SoundTriggerHwService> service;
- sound_trigger_service_state_t state;
-
- sp<Module> module = mModule.promote();
- if (module == 0) {
- return;
- }
- {
- AutoMutex lock(mLock);
- state = (active && !module->isConcurrentCaptureAllowed()) ?
- SOUND_TRIGGER_STATE_DISABLED : SOUND_TRIGGER_STATE_ENABLED;
-
- service = module->service().promote();
- if (service == 0) {
- return;
- }
- }
- service->sendServiceStateEvent(state, this);
-}
-
-void SoundTriggerHwService::ModuleClient::onCallbackEvent(const sp<CallbackEvent>& event)
-{
- ALOGV("ModuleClient onCallbackEvent type %d", event->mType);
-
- sp<IMemory> eventMemory = event->mMemory;
-
- // Memory is coming from a trusted process.
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- return;
- }
-
- sp<ISoundTriggerClient> client;
- {
- AutoMutex lock(mLock);
- client = mClient;
- }
-
- if (client != 0) {
- switch (event->mType) {
- case CallbackEvent::TYPE_RECOGNITION: {
- client->onRecognitionEvent(eventMemory);
- } break;
- case CallbackEvent::TYPE_SOUNDMODEL: {
- client->onSoundModelEvent(eventMemory);
- } break;
- case CallbackEvent::TYPE_SERVICE_STATE: {
- client->onServiceStateChange(eventMemory);
- } break;
- default:
- LOG_ALWAYS_FATAL("onCallbackEvent unknown event type %d", event->mType);
- }
- }
-}
-
-void SoundTriggerHwService::ModuleClient::binderDied(
- const wp<IBinder> &who __unused) {
- ALOGW("client binder died for client %p", this);
- detach();
-}
-
-}; // namespace android
diff --git a/services/soundtrigger/SoundTriggerHwService.h b/services/soundtrigger/SoundTriggerHwService.h
deleted file mode 100644
index 4057e14..0000000
--- a/services/soundtrigger/SoundTriggerHwService.h
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef ANDROID_HARDWARE_SOUNDTRIGGER_HAL_SERVICE_H
-#define ANDROID_HARDWARE_SOUNDTRIGGER_HAL_SERVICE_H
-
-#include <utils/Vector.h>
-//#include <binder/AppOpsManager.h>
-#include <binder/MemoryDealer.h>
-#include <binder/BinderService.h>
-#include <binder/IAppOpsCallback.h>
-#include <soundtrigger/ISoundTriggerHwService.h>
-#include <soundtrigger/ISoundTrigger.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-#include <system/sound_trigger.h>
-#include "SoundTriggerHalInterface.h"
-
-namespace android {
-
-class MemoryHeapBase;
-
-class SoundTriggerHwService :
- public BinderService<SoundTriggerHwService>,
- public BnSoundTriggerHwService
-{
- friend class BinderService<SoundTriggerHwService>;
-public:
- class Module;
- class ModuleClient;
-
- static char const* getServiceName() { return "media.sound_trigger_hw"; }
-
- SoundTriggerHwService();
- virtual ~SoundTriggerHwService();
-
- // ISoundTriggerHwService
- virtual status_t listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules);
-
- virtual status_t attach(const String16& opPackageName,
- const sound_trigger_module_handle_t handle,
- const sp<ISoundTriggerClient>& client,
- sp<ISoundTrigger>& module);
-
- virtual status_t setCaptureState(bool active);
-
- virtual status_t onTransact(uint32_t code, const Parcel& data,
- Parcel* reply, uint32_t flags);
-
- virtual status_t dump(int fd, const Vector<String16>& args);
-
- class Model : public RefBase {
- public:
-
- enum {
- STATE_IDLE,
- STATE_ACTIVE
- };
-
- Model(sound_model_handle_t handle, audio_session_t session, audio_io_handle_t ioHandle,
- audio_devices_t device, sound_trigger_sound_model_type_t type,
- sp<ModuleClient>& moduleClient);
- ~Model() {}
-
- sound_model_handle_t mHandle;
- int mState;
- audio_session_t mCaptureSession;
- audio_io_handle_t mCaptureIOHandle;
- audio_devices_t mCaptureDevice;
- sound_trigger_sound_model_type_t mType;
- struct sound_trigger_recognition_config mConfig;
- sp<ModuleClient> mModuleClient;
- };
-
- class CallbackEvent : public RefBase {
- public:
- typedef enum {
- TYPE_RECOGNITION,
- TYPE_SOUNDMODEL,
- TYPE_SERVICE_STATE,
- } event_type;
- CallbackEvent(event_type type, sp<IMemory> memory);
-
- virtual ~CallbackEvent();
-
- void setModule(wp<Module> module) { mModule = module; }
- void setModuleClient(wp<ModuleClient> moduleClient) { mModuleClient = moduleClient; }
-
- event_type mType;
- sp<IMemory> mMemory;
- wp<Module> mModule;
- wp<ModuleClient> mModuleClient;
- };
-
- class Module : public RefBase {
- public:
-
- Module(const sp<SoundTriggerHwService>& service,
- const sp<SoundTriggerHalInterface>& halInterface,
- sound_trigger_module_descriptor descriptor);
-
- virtual ~Module();
-
- virtual status_t loadSoundModel(const sp<IMemory>& modelMemory,
- sp<ModuleClient> moduleClient,
- sound_model_handle_t *handle);
-
- virtual status_t unloadSoundModel(sound_model_handle_t handle);
-
- virtual status_t startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory);
- virtual status_t stopRecognition(sound_model_handle_t handle);
- virtual status_t getModelState(sound_model_handle_t handle);
- virtual status_t setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t value);
- virtual status_t getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t* value);
- virtual status_t queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range);
-
- sp<SoundTriggerHalInterface> halInterface() const { return mHalInterface; }
- struct sound_trigger_module_descriptor descriptor() { return mDescriptor; }
- wp<SoundTriggerHwService> service() const { return mService; }
- bool isConcurrentCaptureAllowed() const { return mDescriptor.properties.concurrent_capture; }
-
- sp<Model> getModel(sound_model_handle_t handle);
-
- void setCaptureState_l(bool active);
-
- sp<ModuleClient> addClient(const sp<ISoundTriggerClient>& client,
- const String16& opPackageName);
-
- void detach(const sp<ModuleClient>& moduleClient);
-
- void onCallbackEvent(const sp<CallbackEvent>& event);
-
- private:
-
- Mutex mLock;
- wp<SoundTriggerHwService> mService;
- sp<SoundTriggerHalInterface> mHalInterface;
- struct sound_trigger_module_descriptor mDescriptor;
- Vector< sp<ModuleClient> > mModuleClients;
- DefaultKeyedVector< sound_model_handle_t, sp<Model> > mModels;
- sound_trigger_service_state_t mServiceState;
- }; // class Module
-
- class ModuleClient : public virtual RefBase,
- public BnSoundTrigger,
- public IBinder::DeathRecipient {
- public:
-
- ModuleClient(const sp<Module>& module,
- const sp<ISoundTriggerClient>& client,
- const String16& opPackageName);
-
- virtual ~ModuleClient();
-
- virtual void detach();
-
- virtual status_t loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle);
-
- virtual status_t unloadSoundModel(sound_model_handle_t handle);
-
- virtual status_t startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory);
- virtual status_t stopRecognition(sound_model_handle_t handle);
- virtual status_t getModelState(sound_model_handle_t handle);
- virtual status_t setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t value);
- virtual status_t getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t* value);
- virtual status_t queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range);
-
- virtual status_t dump(int fd, const Vector<String16>& args);
-
- virtual void onFirstRef();
-
- // IBinder::DeathRecipient implementation
- virtual void binderDied(const wp<IBinder> &who);
-
- void onCallbackEvent(const sp<CallbackEvent>& event);
-
- void setCaptureState_l(bool active);
-
- sp<ISoundTriggerClient> client() const { return mClient; }
-
- private:
-
- mutable Mutex mLock;
- wp<Module> mModule;
- sp<ISoundTriggerClient> mClient;
- const String16 mOpPackageName;
- }; // class ModuleClient
-
- class CallbackThread : public Thread {
- public:
-
- explicit CallbackThread(const wp<SoundTriggerHwService>& service);
-
- virtual ~CallbackThread();
-
- // Thread virtuals
- virtual bool threadLoop();
-
- // RefBase
- virtual void onFirstRef();
-
- void exit();
- void sendCallbackEvent(const sp<CallbackEvent>& event);
-
- private:
- wp<SoundTriggerHwService> mService;
- Condition mCallbackCond;
- Mutex mCallbackLock;
- Vector< sp<CallbackEvent> > mEventQueue;
- };
-
- static void recognitionCallback(struct sound_trigger_recognition_event *event, void *cookie);
- sp<IMemory> prepareRecognitionEvent(struct sound_trigger_recognition_event *event);
- void sendRecognitionEvent(struct sound_trigger_recognition_event *event, Module *module);
-
- static void soundModelCallback(struct sound_trigger_model_event *event, void *cookie);
- sp<IMemory> prepareSoundModelEvent(struct sound_trigger_model_event *event);
- void sendSoundModelEvent(struct sound_trigger_model_event *event, Module *module);
-
- sp<IMemory> prepareServiceStateEvent(sound_trigger_service_state_t state);
- void sendServiceStateEvent(sound_trigger_service_state_t state, Module *module);
- void sendServiceStateEvent(sound_trigger_service_state_t state,
- ModuleClient *moduleClient);
-
- void sendCallbackEvent(const sp<CallbackEvent>& event);
- void onCallbackEvent(const sp<CallbackEvent>& event);
-
-private:
-
- virtual void onFirstRef();
-
- Mutex mServiceLock;
- volatile int32_t mNextUniqueId;
- DefaultKeyedVector< sound_trigger_module_handle_t, sp<Module> > mModules;
- sp<CallbackThread> mCallbackThread;
- sp<MemoryDealer> mMemoryDealer;
- Mutex mMemoryDealerLock;
- bool mCaptureState;
-};
-
-} // namespace android
-
-#endif // ANDROID_HARDWARE_SOUNDTRIGGER_HAL_SERVICE_H
diff --git a/soundtrigger/Android.bp b/soundtrigger/Android.bp
deleted file mode 100644
index 6178153..0000000
--- a/soundtrigger/Android.bp
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2014 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.
-
-cc_library_shared {
- name: "libsoundtrigger",
-
- srcs: [
- "SoundTrigger.cpp",
- "ISoundTrigger.cpp",
- "ISoundTriggerClient.cpp",
- "ISoundTriggerHwService.cpp",
- ],
-
- shared_libs: [
- "libcutils",
- "libutils",
- "liblog",
- "libbinder",
- ],
-
- cflags: [
- "-Werror",
- "-Wall",
- ],
-}
diff --git a/soundtrigger/ISoundTrigger.cpp b/soundtrigger/ISoundTrigger.cpp
deleted file mode 100644
index 9b1da13..0000000
--- a/soundtrigger/ISoundTrigger.cpp
+++ /dev/null
@@ -1,319 +0,0 @@
-/*
-**
-** Copyright 2014, 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 "ISoundTrigger"
-#include <utils/Log.h>
-#include <utils/Errors.h>
-#include <binder/IMemory.h>
-#include <soundtrigger/ISoundTrigger.h>
-#include <soundtrigger/ISoundTriggerHwService.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-#include <system/sound_trigger.h>
-
-namespace android {
-
-enum {
- DETACH = IBinder::FIRST_CALL_TRANSACTION,
- LOAD_SOUND_MODEL,
- UNLOAD_SOUND_MODEL,
- START_RECOGNITION,
- STOP_RECOGNITION,
- GET_MODEL_STATE,
- SET_PARAMETER,
- GET_PARAMETER,
- QUERY_PARAMETER,
-};
-
-class BpSoundTrigger: public BpInterface<ISoundTrigger>
-{
-public:
- explicit BpSoundTrigger(const sp<IBinder>& impl)
- : BpInterface<ISoundTrigger>(impl)
- {
- }
-
- void detach()
- {
- ALOGV("detach");
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- remote()->transact(DETACH, data, &reply);
- }
-
- status_t loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle)
- {
- if (modelMemory == 0 || handle == NULL) {
- return BAD_VALUE;
- }
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(modelMemory));
- status_t status = remote()->transact(LOAD_SOUND_MODEL, data, &reply);
- if (status != NO_ERROR) {
- return status;
- }
- status = (status_t)reply.readInt32();
- if (status == NO_ERROR) {
- reply.read(handle, sizeof(sound_model_handle_t));
- }
- return status;
- }
-
- virtual status_t unloadSoundModel(sound_model_handle_t handle)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- status_t status = remote()->transact(UNLOAD_SOUND_MODEL, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- }
- return status;
- }
-
- virtual status_t startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- if (dataMemory == 0) {
- data.writeInt32(0);
- } else {
- data.writeInt32(dataMemory->size());
- }
- data.writeStrongBinder(IInterface::asBinder(dataMemory));
- status_t status = remote()->transact(START_RECOGNITION, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- }
- return status;
- }
-
- virtual status_t stopRecognition(sound_model_handle_t handle)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- status_t status = remote()->transact(STOP_RECOGNITION, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- }
- return status;
- }
-
- virtual status_t getModelState(sound_model_handle_t handle)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- status_t status = remote()->transact(GET_MODEL_STATE, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- }
- return status;
- }
-
- virtual status_t setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t value)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- data.write(¶m, sizeof(sound_trigger_model_parameter_t));
- data.writeInt32(value);
- status_t status = remote()->transact(SET_PARAMETER, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- }
- return status;
- }
-
- virtual status_t getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- int32_t* value)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- data.write(¶m, sizeof(sound_trigger_model_parameter_t));
- status_t status = remote()->transact(GET_PARAMETER, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- *value = reply.readInt32();
- }
- return status;
- }
-
- virtual status_t queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTrigger::getInterfaceDescriptor());
- data.write(&handle, sizeof(sound_model_handle_t));
- data.write(¶m, sizeof(sound_trigger_model_parameter_t));
- status_t status = remote()->transact(QUERY_PARAMETER, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- param_range->start = reply.readInt32();
- param_range->end = reply.readInt32();
- }
- return status;
- }
-
-};
-
-IMPLEMENT_META_INTERFACE(SoundTrigger, "android.hardware.ISoundTrigger");
-
-// ----------------------------------------------------------------------
-
-status_t BnSoundTrigger::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
- switch(code) {
- case DETACH: {
- ALOGV("DETACH");
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- detach();
- return NO_ERROR;
- } break;
- case LOAD_SOUND_MODEL: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sp<IMemory> modelMemory = interface_cast<IMemory>(
- data.readStrongBinder());
- sound_model_handle_t handle;
- status_t status = loadSoundModel(modelMemory, &handle);
- reply->writeInt32(status);
- if (status == NO_ERROR) {
- reply->write(&handle, sizeof(sound_model_handle_t));
- }
- return NO_ERROR;
- }
- case UNLOAD_SOUND_MODEL: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- data.read(&handle, sizeof(sound_model_handle_t));
- status_t status = unloadSoundModel(handle);
- reply->writeInt32(status);
- return NO_ERROR;
- }
- case START_RECOGNITION: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- data.read(&handle, sizeof(sound_model_handle_t));
- sp<IMemory> dataMemory;
- if (data.readInt32() != 0) {
- dataMemory = interface_cast<IMemory>(data.readStrongBinder());
- }
- status_t status = startRecognition(handle, dataMemory);
- reply->writeInt32(status);
- return NO_ERROR;
- }
- case STOP_RECOGNITION: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- data.read(&handle, sizeof(sound_model_handle_t));
- status_t status = stopRecognition(handle);
- reply->writeInt32(status);
- return NO_ERROR;
- }
- case GET_MODEL_STATE: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- status_t status = UNKNOWN_ERROR;
- status_t ret = data.read(&handle, sizeof(sound_model_handle_t));
- if (ret == NO_ERROR) {
- status = getModelState(handle);
- }
- reply->writeInt32(status);
- return ret;
- }
- case SET_PARAMETER: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- sound_trigger_model_parameter_t param;
- int32_t value;
- status_t status = UNKNOWN_ERROR;
- status_t ret;
- ret = data.read(&handle, sizeof(sound_model_handle_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- ret = data.read(¶m, sizeof(sound_trigger_model_parameter_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- ret = data.read(&value, sizeof(int32_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- status = setParameter(handle, param, value);
- reply->writeInt32(status);
- return NO_ERROR;
- }
- case GET_PARAMETER: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- sound_trigger_model_parameter_t param;
- int32_t value;
- status_t status = UNKNOWN_ERROR;
- status_t ret;
- ret = data.read(&handle, sizeof(sound_model_handle_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- ret = data.read(¶m, sizeof(sound_trigger_model_parameter_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- status = getParameter(handle, param, &value);
- reply->writeInt32(status);
- reply->writeInt32(value);
- return NO_ERROR;
- }
- case QUERY_PARAMETER: {
- CHECK_INTERFACE(ISoundTrigger, data, reply);
- sound_model_handle_t handle;
- sound_trigger_model_parameter_t param;
- status_t ret;
- status_t status = UNKNOWN_ERROR;
- sound_trigger_model_parameter_range_t retValue;
- ret = data.read(&handle, sizeof(sound_model_handle_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- ret = data.read(¶m, sizeof(sound_trigger_model_parameter_t));
- if (ret != NO_ERROR) {
- return ret;
- }
- status = queryParameter(handle, param, &retValue);
- reply->writeInt32(status);
- reply->writeInt32(retValue.start);
- reply->writeInt32(retValue.end);
- return NO_ERROR;
- }
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
diff --git a/soundtrigger/ISoundTriggerClient.cpp b/soundtrigger/ISoundTriggerClient.cpp
deleted file mode 100644
index 1385631..0000000
--- a/soundtrigger/ISoundTriggerClient.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
-**
-** Copyright 2014, 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 <sys/types.h>
-#include <binder/IMemory.h>
-#include <binder/Parcel.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-
-namespace android {
-
-enum {
- ON_RECOGNITION_EVENT = IBinder::FIRST_CALL_TRANSACTION,
- ON_SOUNDMODEL_EVENT,
- ON_SERVICE_STATE_CHANGE
-};
-
-class BpSoundTriggerClient: public BpInterface<ISoundTriggerClient>
-{
-
-public:
- explicit BpSoundTriggerClient(const sp<IBinder>& impl)
- : BpInterface<ISoundTriggerClient>(impl)
- {
- }
-
- virtual void onRecognitionEvent(const sp<IMemory>& eventMemory)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerClient::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(eventMemory));
- remote()->transact(ON_RECOGNITION_EVENT,
- data,
- &reply);
- }
-
- virtual void onSoundModelEvent(const sp<IMemory>& eventMemory)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerClient::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(eventMemory));
- remote()->transact(ON_SOUNDMODEL_EVENT,
- data,
- &reply);
- }
- virtual void onServiceStateChange(const sp<IMemory>& eventMemory)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerClient::getInterfaceDescriptor());
- data.writeStrongBinder(IInterface::asBinder(eventMemory));
- remote()->transact(ON_SERVICE_STATE_CHANGE,
- data,
- &reply);
- }
-};
-
-IMPLEMENT_META_INTERFACE(SoundTriggerClient,
- "android.hardware.ISoundTriggerClient");
-
-// ----------------------------------------------------------------------
-
-status_t BnSoundTriggerClient::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
- switch(code) {
- case ON_RECOGNITION_EVENT: {
- CHECK_INTERFACE(ISoundTriggerClient, data, reply);
- sp<IMemory> eventMemory = interface_cast<IMemory>(
- data.readStrongBinder());
- onRecognitionEvent(eventMemory);
- return NO_ERROR;
- } break;
- case ON_SOUNDMODEL_EVENT: {
- CHECK_INTERFACE(ISoundTriggerClient, data, reply);
- sp<IMemory> eventMemory = interface_cast<IMemory>(
- data.readStrongBinder());
- onSoundModelEvent(eventMemory);
- return NO_ERROR;
- } break;
- case ON_SERVICE_STATE_CHANGE: {
- CHECK_INTERFACE(ISoundTriggerClient, data, reply);
- sp<IMemory> eventMemory = interface_cast<IMemory>(
- data.readStrongBinder());
- onServiceStateChange(eventMemory);
- return NO_ERROR;
- } break;
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
diff --git a/soundtrigger/ISoundTriggerHwService.cpp b/soundtrigger/ISoundTriggerHwService.cpp
deleted file mode 100644
index bd107b4..0000000
--- a/soundtrigger/ISoundTriggerHwService.cpp
+++ /dev/null
@@ -1,197 +0,0 @@
-/*
-**
-** Copyright 2014, 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 "BpSoundTriggerHwService"
-//#define LOG_NDEBUG 0
-
-#include <utils/Log.h>
-#include <utils/Errors.h>
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <binder/IMemory.h>
-#include <binder/Parcel.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-
-#include <soundtrigger/ISoundTriggerHwService.h>
-#include <soundtrigger/ISoundTrigger.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-
-namespace android {
-
-enum {
- LIST_MODULES = IBinder::FIRST_CALL_TRANSACTION,
- ATTACH,
- SET_CAPTURE_STATE,
-};
-
-#define MAX_ITEMS_PER_LIST 1024
-
-class BpSoundTriggerHwService: public BpInterface<ISoundTriggerHwService>
-{
-public:
- explicit BpSoundTriggerHwService(const sp<IBinder>& impl)
- : BpInterface<ISoundTriggerHwService>(impl)
- {
- }
-
- virtual status_t listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules)
- {
- if (numModules == NULL || (*numModules != 0 && modules == NULL)) {
- return BAD_VALUE;
- }
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerHwService::getInterfaceDescriptor());
- data.writeString16(opPackageName);
- unsigned int numModulesReq = (modules == NULL) ? 0 : *numModules;
- data.writeInt32(numModulesReq);
- status_t status = remote()->transact(LIST_MODULES, data, &reply);
- if (status == NO_ERROR) {
- status = (status_t)reply.readInt32();
- *numModules = (unsigned int)reply.readInt32();
- }
- ALOGV("listModules() status %d got *numModules %d", status, *numModules);
- if (status == NO_ERROR) {
- if (numModulesReq > *numModules) {
- numModulesReq = *numModules;
- }
- if (numModulesReq > 0) {
- reply.read(modules, numModulesReq * sizeof(struct sound_trigger_module_descriptor));
- }
- }
- return status;
- }
-
- virtual status_t attach(const String16& opPackageName,
- const sound_trigger_module_handle_t handle,
- const sp<ISoundTriggerClient>& client,
- sp<ISoundTrigger>& module)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerHwService::getInterfaceDescriptor());
- data.writeString16(opPackageName);
- data.write(&handle, sizeof(sound_trigger_module_handle_t));
- data.writeStrongBinder(IInterface::asBinder(client));
- status_t status = remote()->transact(ATTACH, data, &reply);
- if (status != NO_ERROR) {
- return status;
- }
- status = reply.readInt32();
- if (reply.readInt32() != 0) {
- module = interface_cast<ISoundTrigger>(reply.readStrongBinder());
- }
- return status;
- }
-
- virtual status_t setCaptureState(bool active)
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISoundTriggerHwService::getInterfaceDescriptor());
- data.writeInt32(active);
- status_t status = remote()->transact(SET_CAPTURE_STATE, data, &reply);
- if (status == NO_ERROR) {
- status = reply.readInt32();
- }
- return status;
- }
-
-};
-
-IMPLEMENT_META_INTERFACE(SoundTriggerHwService, "android.hardware.ISoundTriggerHwService");
-
-// ----------------------------------------------------------------------
-
-status_t BnSoundTriggerHwService::onTransact(
- uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
-{
- switch(code) {
- case LIST_MODULES: {
- CHECK_INTERFACE(ISoundTriggerHwService, data, reply);
- String16 opPackageName;
- status_t status = data.readString16(&opPackageName);
- if (status != NO_ERROR) {
- return status;
- }
- unsigned int numModulesReq = data.readInt32();
- if (numModulesReq > MAX_ITEMS_PER_LIST) {
- numModulesReq = MAX_ITEMS_PER_LIST;
- }
- unsigned int numModules = numModulesReq;
- struct sound_trigger_module_descriptor *modules =
- (struct sound_trigger_module_descriptor *)calloc(numModulesReq,
- sizeof(struct sound_trigger_module_descriptor));
- if (modules == NULL) {
- reply->writeInt32(NO_MEMORY);
- reply->writeInt32(0);
- return NO_ERROR;
- }
- status = listModules(opPackageName, modules, &numModules);
- reply->writeInt32(status);
- reply->writeInt32(numModules);
- ALOGV("LIST_MODULES status %d got numModules %d", status, numModules);
-
- if (status == NO_ERROR) {
- if (numModulesReq > numModules) {
- numModulesReq = numModules;
- }
- reply->write(modules,
- numModulesReq * sizeof(struct sound_trigger_module_descriptor));
- }
- free(modules);
- return NO_ERROR;
- }
-
- case ATTACH: {
- CHECK_INTERFACE(ISoundTriggerHwService, data, reply);
- String16 opPackageName;
- status_t status = data.readString16(&opPackageName);
- if (status != NO_ERROR) {
- return status;
- }
- sound_trigger_module_handle_t handle;
- data.read(&handle, sizeof(sound_trigger_module_handle_t));
- sp<ISoundTriggerClient> client =
- interface_cast<ISoundTriggerClient>(data.readStrongBinder());
- sp<ISoundTrigger> module;
- status = attach(opPackageName, handle, client, module);
- reply->writeInt32(status);
- if (module != 0) {
- reply->writeInt32(1);
- reply->writeStrongBinder(IInterface::asBinder(module));
- } else {
- reply->writeInt32(0);
- }
- return NO_ERROR;
- } break;
-
- case SET_CAPTURE_STATE: {
- CHECK_INTERFACE(ISoundTriggerHwService, data, reply);
- reply->writeInt32(setCaptureState((bool)data.readInt32()));
- return NO_ERROR;
- } break;
-
- default:
- return BBinder::onTransact(code, data, reply, flags);
- }
-}
-
-// ----------------------------------------------------------------------------
-
-}; // namespace android
diff --git a/soundtrigger/OWNERS b/soundtrigger/OWNERS
deleted file mode 100644
index e83f6b9..0000000
--- a/soundtrigger/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-elaurent@google.com
-thorntonc@google.com
diff --git a/soundtrigger/SoundTrigger.cpp b/soundtrigger/SoundTrigger.cpp
deleted file mode 100644
index 021697b..0000000
--- a/soundtrigger/SoundTrigger.cpp
+++ /dev/null
@@ -1,334 +0,0 @@
-/*
-**
-** Copyright (C) 2014, 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 "SoundTrigger"
-//#define LOG_NDEBUG 0
-
-#include <utils/Log.h>
-#include <utils/threads.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/IMemory.h>
-
-#include <soundtrigger/SoundTrigger.h>
-#include <soundtrigger/ISoundTrigger.h>
-#include <soundtrigger/ISoundTriggerHwService.h>
-#include <soundtrigger/ISoundTriggerClient.h>
-#include <soundtrigger/SoundTriggerCallback.h>
-
-namespace android {
-
-namespace {
- sp<ISoundTriggerHwService> gSoundTriggerHwService;
- const int kSoundTriggerHwServicePollDelay = 500000; // 0.5s
- const char* kSoundTriggerHwServiceName = "media.sound_trigger_hw";
- Mutex gLock;
-
- class DeathNotifier : public IBinder::DeathRecipient
- {
- public:
- DeathNotifier() {
- }
-
- virtual void binderDied(const wp<IBinder>& who __unused) {
- ALOGV("binderDied");
- Mutex::Autolock _l(gLock);
- gSoundTriggerHwService.clear();
- ALOGW("Sound trigger service died!");
- }
- };
-
- sp<DeathNotifier> gDeathNotifier;
-}; // namespace anonymous
-
-const sp<ISoundTriggerHwService> SoundTrigger::getSoundTriggerHwService()
-{
- Mutex::Autolock _l(gLock);
- if (gSoundTriggerHwService.get() == 0) {
- sp<IServiceManager> sm = defaultServiceManager();
- sp<IBinder> binder;
- do {
- binder = sm->getService(String16(kSoundTriggerHwServiceName));
- if (binder != 0) {
- break;
- }
- ALOGW("SoundTriggerHwService not published, waiting...");
- usleep(kSoundTriggerHwServicePollDelay);
- } while(true);
- if (gDeathNotifier == NULL) {
- gDeathNotifier = new DeathNotifier();
- }
- binder->linkToDeath(gDeathNotifier);
- gSoundTriggerHwService = interface_cast<ISoundTriggerHwService>(binder);
- }
- ALOGE_IF(gSoundTriggerHwService == 0, "no SoundTriggerHwService!?");
- return gSoundTriggerHwService;
-}
-
-// Static methods
-status_t SoundTrigger::listModules(const String16& opPackageName,
- struct sound_trigger_module_descriptor *modules,
- uint32_t *numModules)
-{
- ALOGV("listModules()");
- const sp<ISoundTriggerHwService> service = getSoundTriggerHwService();
- if (service == 0) {
- return NO_INIT;
- }
- return service->listModules(opPackageName, modules, numModules);
-}
-
-sp<SoundTrigger> SoundTrigger::attach(const String16& opPackageName,
- const sound_trigger_module_handle_t module,
- const sp<SoundTriggerCallback>& callback)
-{
- ALOGV("attach()");
- sp<SoundTrigger> soundTrigger;
- const sp<ISoundTriggerHwService> service = getSoundTriggerHwService();
- if (service == 0) {
- return soundTrigger;
- }
- soundTrigger = new SoundTrigger(module, callback);
- status_t status = service->attach(opPackageName, module, soundTrigger,
- soundTrigger->mISoundTrigger);
-
- if (status == NO_ERROR && soundTrigger->mISoundTrigger != 0) {
- IInterface::asBinder(soundTrigger->mISoundTrigger)->linkToDeath(soundTrigger);
- } else {
- ALOGW("Error %d connecting to sound trigger service", status);
- soundTrigger.clear();
- }
- return soundTrigger;
-}
-
-
-status_t SoundTrigger::setCaptureState(bool active)
-{
- ALOGV("setCaptureState(%d)", active);
- const sp<ISoundTriggerHwService> service = getSoundTriggerHwService();
- if (service == 0) {
- return NO_INIT;
- }
- return service->setCaptureState(active);
-}
-
-// SoundTrigger
-SoundTrigger::SoundTrigger(sound_trigger_module_handle_t /*module*/,
- const sp<SoundTriggerCallback>& callback)
- : mCallback(callback)
-{
-}
-
-SoundTrigger::~SoundTrigger()
-{
- if (mISoundTrigger != 0) {
- mISoundTrigger->detach();
- }
-}
-
-
-void SoundTrigger::detach() {
- ALOGV("detach()");
- Mutex::Autolock _l(mLock);
- mCallback.clear();
- if (mISoundTrigger != 0) {
- mISoundTrigger->detach();
- IInterface::asBinder(mISoundTrigger)->unlinkToDeath(this);
- mISoundTrigger = 0;
- }
-}
-
-status_t SoundTrigger::loadSoundModel(const sp<IMemory>& modelMemory,
- sound_model_handle_t *handle)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
-
- return mISoundTrigger->loadSoundModel(modelMemory, handle);
-}
-
-status_t SoundTrigger::unloadSoundModel(sound_model_handle_t handle)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->unloadSoundModel(handle);
-}
-
-status_t SoundTrigger::startRecognition(sound_model_handle_t handle,
- const sp<IMemory>& dataMemory)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->startRecognition(handle, dataMemory);
-}
-
-status_t SoundTrigger::stopRecognition(sound_model_handle_t handle)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->stopRecognition(handle);
-}
-
-status_t SoundTrigger::getModelState(sound_model_handle_t handle)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->getModelState(handle);
-}
-
-status_t SoundTrigger::setParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t value)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->setParameter(handle, param, value);
-}
-
-status_t SoundTrigger::getParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param, int32_t* value)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->getParameter(handle, param, value);
-}
-
-status_t SoundTrigger::queryParameter(sound_model_handle_t handle,
- sound_trigger_model_parameter_t param,
- sound_trigger_model_parameter_range_t* param_range)
-{
- Mutex::Autolock _l(mLock);
- if (mISoundTrigger == 0) {
- return NO_INIT;
- }
- return mISoundTrigger->queryParameter(handle, param, param_range);
-}
-
-// BpSoundTriggerClient
-void SoundTrigger::onRecognitionEvent(const sp<IMemory>& eventMemory)
-{
- Mutex::Autolock _l(mLock);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- return;
- }
-
- if (mCallback != 0) {
- // Memory is coming from a trusted process.
- mCallback->onRecognitionEvent(
- (struct sound_trigger_recognition_event *)eventMemory->unsecurePointer());
- }
-}
-
-void SoundTrigger::onSoundModelEvent(const sp<IMemory>& eventMemory)
-{
- Mutex::Autolock _l(mLock);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- return;
- }
-
- if (mCallback != 0) {
- // Memory is coming from a trusted process.
- mCallback->onSoundModelEvent(
- (struct sound_trigger_model_event *)eventMemory->unsecurePointer());
- }
-}
-
-void SoundTrigger::onServiceStateChange(const sp<IMemory>& eventMemory)
-{
- Mutex::Autolock _l(mLock);
- if (eventMemory == 0 || eventMemory->unsecurePointer() == NULL) {
- return;
- }
-
- if (mCallback != 0) {
- // Memory is coming from a trusted process.
- mCallback->onServiceStateChange(
- *((sound_trigger_service_state_t *)eventMemory->unsecurePointer()));
- }
-}
-
-//IBinder::DeathRecipient
-void SoundTrigger::binderDied(const wp<IBinder>& who __unused) {
- Mutex::Autolock _l(mLock);
- ALOGW("SoundTrigger server binder Died ");
- mISoundTrigger = 0;
- if (mCallback != 0) {
- mCallback->onServiceDied();
- }
-}
-
-status_t SoundTrigger::stringToGuid(const char *str, sound_trigger_uuid_t *guid)
-{
- if (str == NULL || guid == NULL) {
- return BAD_VALUE;
- }
-
- int tmp[10];
-
- if (sscanf(str, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
- tmp, tmp+1, tmp+2, tmp+3, tmp+4, tmp+5, tmp+6, tmp+7, tmp+8, tmp+9) < 10) {
- return BAD_VALUE;
- }
- guid->timeLow = (uint32_t)tmp[0];
- guid->timeMid = (uint16_t)tmp[1];
- guid->timeHiAndVersion = (uint16_t)tmp[2];
- guid->clockSeq = (uint16_t)tmp[3];
- guid->node[0] = (uint8_t)tmp[4];
- guid->node[1] = (uint8_t)tmp[5];
- guid->node[2] = (uint8_t)tmp[6];
- guid->node[3] = (uint8_t)tmp[7];
- guid->node[4] = (uint8_t)tmp[8];
- guid->node[5] = (uint8_t)tmp[9];
-
- return NO_ERROR;
-}
-
-status_t SoundTrigger::guidToString(const sound_trigger_uuid_t *guid, char *str, size_t maxLen)
-{
- if (guid == NULL || str == NULL) {
- return BAD_VALUE;
- }
-
- snprintf(str, maxLen, "%08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x",
- guid->timeLow,
- guid->timeMid,
- guid->timeHiAndVersion,
- guid->clockSeq,
- guid->node[0],
- guid->node[1],
- guid->node[2],
- guid->node[3],
- guid->node[4],
- guid->node[5]);
-
- return NO_ERROR;
-}
-
-}; // namespace android